Site (#1049)
by @akellbl4 * create infrastructure for site * wip * fix docker build and add readme * add docker-compose as a build and a run method * rename compose file yaml -> yml * add `src` as volume for watching changes * update configs * update README * add padding at the end of the pages * move demo settings in config * fetch latest release from github * update docs navigation - add sections - redirect from root of the section to first doc - nice styles for navigation - add brand colors * cache github data from first load * add redirects and fix link to docs * fix docs nav styles * add installation page placeholder * fix demo * add 404 * add dark theme, add theme switcher, remove unused files * fix dark theme on main page * fix dark theme background * fix node version * change installation docs * add note block * minor fixes * add code highlighting styles * fixes * fixes * mobile navigation, fix code highlighting colors * fix dev server * fix fetching error * fix path to edit Co-authored-by: Pavel Mineev <pavel@mineev.me> Co-authored-by: Dmitry Verkhoturov <paskal.07@gmail.com>
This commit is contained in:
co-authored by
Pavel Mineev
Dmitry Verkhoturov
parent
5ce89c6c29
commit
dfcf728e6f
@@ -8,9 +8,6 @@ debug
|
||||
debug.test
|
||||
.vscode
|
||||
.idea/
|
||||
/frontend/node_modules/
|
||||
/frontend/public/
|
||||
/frontend/coverage
|
||||
*.prof
|
||||
*.test
|
||||
/rest-client.env.json
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Documentation and FAQ
|
||||
|
||||
- [How to configure remark42 with nginx reverse proxy](nginx-proxy)
|
||||
- [How to configure remark42 without a subdomain](subdomain) with Nginx or Caddy
|
||||
- [Telegram notifications](telegram)
|
||||
- [Setup email authentication and\or email notifications](email)
|
||||
- [How to add new translation to remark42](translation)
|
||||
@@ -1,215 +0,0 @@
|
||||
---
|
||||
title: Email
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This documentation describes how to enable the email-related capabilities of Remark.
|
||||
|
||||
- email authentication for users:
|
||||
|
||||
enabling this will let the user log in using their emails:
|
||||
|
||||

|
||||
|
||||
- email notifications for any users except anonymous:
|
||||
|
||||
GitHub or Google or Twitter or any other kind of user gets the ability to get
|
||||
email notifications about new replies to their comments:
|
||||
|
||||

|
||||
|
||||
## Setup email server connection
|
||||
|
||||
To enable any of email functionality you need to set up email
|
||||
(SMTP) server connection using these variables:
|
||||
|
||||
```
|
||||
SMTP_HOST
|
||||
SMTP_PORT
|
||||
SMTP_TLS
|
||||
SMTP_USERNAME
|
||||
SMTP_PASSWORD
|
||||
SMTP_TIMEOUT
|
||||
```
|
||||
|
||||
### Mailgun
|
||||
|
||||
This is an example of a configuration using [Mailgun](https://www.mailgun.com/) email service:
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=smtp.eu.mailgun.org
|
||||
- SMTP_PORT=465
|
||||
- SMTP_TLS=true
|
||||
- SMTP_USERNAME=postmaster@mg.example.com
|
||||
- SMTP_PASSWORD=secretpassword
|
||||
- AUTH_EMAIL_FROM=notify@example.com
|
||||
- NOTIFY_EMAIL_FROM=notify@example.com
|
||||
```
|
||||
|
||||
#### API
|
||||
|
||||
When you don't want to expose your IP (which is impossible with any SMTP provider)
|
||||
and for situations when connecting to external SMTP server is impossible due to firewall
|
||||
settings is setting up an SMTP-to-API bridge and sending messages trough it.
|
||||
|
||||
To use any of containers below with in remark42 environment set following two `SMTP` variables:
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=mail
|
||||
- SMTP_PORT=25
|
||||
```
|
||||
|
||||
#### stevenolen/mailgun-smtp-server
|
||||
|
||||
Here is `docker-compose.yml` configuration part spinning up a container for
|
||||
[stevenolen/mailgun-smtp-server](https://hub.docker.com/r/stevenolen/mailgun-smtp-server):
|
||||
|
||||
```yaml
|
||||
mailgun:
|
||||
image: stevenolen/mailgun-smtp-server
|
||||
container_name: 'mail'
|
||||
hostname: 'mail'
|
||||
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '5'
|
||||
|
||||
environment:
|
||||
- MG_KEY=key-123456789
|
||||
- MG_DOMAIN=example.com
|
||||
```
|
||||
|
||||
Please note that before
|
||||
[stevenolen/mailgun-smtp-server#5](https://github.com/stevenolen/mailgun-smtp-server/issues/5)
|
||||
is fixed, Europe domain names are not supported by this tool.
|
||||
|
||||
### SendGrid
|
||||
|
||||
This is an example of a configuration using [SendGrid](https://sendgrid.com/) email service:
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=smtp.sendgrid.net
|
||||
- SMTP_PORT=465
|
||||
- SMTP_TLS=true
|
||||
- SMTP_USERNAME=apikey
|
||||
- SMTP_PASSWORD=key-123456789
|
||||
- AUTH_EMAIL_FROM=notify@example.com
|
||||
- NOTIFY_EMAIL_FROM=notify@example.com
|
||||
```
|
||||
|
||||
#### API
|
||||
|
||||
When you don't want to expose your IP (which is impossible with any SMTP provider)
|
||||
and for situations when connecting to external SMTP server is impossible due to firewall
|
||||
settings is setting up an SMTP-to-API bridge and sending messages trough it.
|
||||
|
||||
To use any of containers below with in remark42 environment set following two `SMTP` variables:
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=mail
|
||||
- SMTP_PORT=25
|
||||
```
|
||||
|
||||
#### fgribreau/smtp-to-sendgrid-gateway
|
||||
|
||||
Here is `docker-compose.yml` configuration part spinning up a container for
|
||||
[fgribreau/smtp-to-sendgrid-gateway](https://hub.docker.com/r/fgribreau/smtp-to-sendgrid-gateway):
|
||||
|
||||
```yaml
|
||||
sendgrid:
|
||||
image: fgribreau/smtp-to-sendgrid-gateway
|
||||
container_name: 'mail'
|
||||
hostname: 'mail'
|
||||
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '5'
|
||||
|
||||
environment:
|
||||
- SENDGRID_API=key-123456789
|
||||
```
|
||||
|
||||
### Gmail
|
||||
|
||||
Configuration example for Gmail:
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=smtp.gmail.com
|
||||
- SMTP_PORT=465
|
||||
- SMTP_TLS=true
|
||||
- SMTP_USERNAME=example.user@gmail.com
|
||||
- SMTP_PASSWORD=secretpassword
|
||||
- AUTH_EMAIL_FROM=example.user@gmail.com
|
||||
- NOTIFY_EMAIL_FROM=example.user@gmail.com
|
||||
```
|
||||
|
||||
### Amazon SES
|
||||
|
||||
Configuration example for [Amazon SES](https://aws.amazon.com/ses/) (us-east-1 region):
|
||||
|
||||
```yaml
|
||||
- SMTP_HOST=email-smtp.us-east-1.amazonaws.com
|
||||
- SMTP_PORT=465
|
||||
- SMTP_TLS=true
|
||||
- SMTP_USERNAME=access_key_id
|
||||
- SMTP_PASSWORD=secret_access_key
|
||||
- AUTH_EMAIL_FROM=notify@example.com
|
||||
- NOTIFY_EMAIL_FROM=notify@example.com
|
||||
```
|
||||
|
||||
A domain or an email that will be used in `AUTH_EMAIL_FROM` or `NOTIFY_EMAIL_FROM`
|
||||
must first be [verified](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/verify-domain-procedure.html).
|
||||
|
||||
[SMTP Credentials](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html)
|
||||
must first be obtained from
|
||||
[Amazon SES Console](https://console.aws.amazon.com/ses/home?region=us-east-1#smtp-settings:):
|
||||
|
||||
## Setup email authentication
|
||||
|
||||
Here is the list of variables which affect email authentication:
|
||||
|
||||
```
|
||||
AUTH_EMAIL_ENABLE
|
||||
AUTH_EMAIL_FROM
|
||||
AUTH_EMAIL_SUBJ
|
||||
AUTH_EMAIL_CONTENT_TYPE
|
||||
AUTH_EMAIL_TEMPLATE
|
||||
```
|
||||
|
||||
After you set `SMTP_` variables, you can allow email authentication by setting these two variables:
|
||||
|
||||
```yaml
|
||||
- AUTH_EMAIL_ENABLE=true
|
||||
- AUTH_EMAIL_FROM=notify@example.com
|
||||
```
|
||||
|
||||
Usually, you don't need to change/set anything else. In case if you want to use a different
|
||||
email template set `AUTH_EMAIL_TEMPLATE`, for instance
|
||||
`- AUTH_EMAIL_TEMPLATE="Confirmation email, token: {{.Token}}"`.
|
||||
See [verified-authentication](https://github.com/go-pkgz/auth#verified-authentication) for more details.
|
||||
|
||||
## Setup email notifications
|
||||
|
||||
Here is the list of variables which affect email notifications:
|
||||
|
||||
```yaml
|
||||
NOTIFY_TYPE
|
||||
NOTIFY_EMAIL_FROM
|
||||
NOTIFY_EMAIL_VERIFICATION_SUBJ
|
||||
# for administrator notifications for new comments on their site
|
||||
ADMIN_SHARED_EMAIL
|
||||
NOTIFY_EMAIL_ADMIN
|
||||
```
|
||||
|
||||
After you set `SMTP_` variables, you can allow email notifications by setting these two variables:
|
||||
|
||||
```yaml
|
||||
- NOTIFY_TYPE=email
|
||||
# - NOTIFY_TYPE=email,telegram # this is in case you want to have both email and telegram notifications enabled
|
||||
- NOTIFY_EMAIL_FROM=notify@example.com
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: Nginx
|
||||
---
|
||||
|
||||
## How to configure remark42 with nginx reverse proxy
|
||||
|
||||
Example of nginx configuration (reverse proxy) running remark42 service on remark42.example.com
|
||||
|
||||
```
|
||||
server {
|
||||
listen 443;
|
||||
server_name remark42.example.com;
|
||||
ssl on;
|
||||
ssl_certificate /etc/nginx/ssl/remark42.example.com.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/remark42.example.com.key;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json text/css application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml;
|
||||
gzip_min_length 1000;
|
||||
gzip_proxied any;
|
||||
|
||||
|
||||
location ~ /\.git {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /index.html {
|
||||
proxy_redirect off;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://remark42:8080/web/index.html;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_redirect off;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://remark42:8080/;
|
||||
}
|
||||
|
||||
access_log /var/log/nginx/remark42.log;
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name remark42.example.com;
|
||||
return 301 https://remark42.example.com$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
note: `proxy_pass` points to internal DNS name `remark42` and expected to run from the same compose. If nginx runs outside of compose the real IP (or docker's bridge IP) should be used
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: Telegram
|
||||
---
|
||||
|
||||
## Telegram notifications
|
||||
|
||||
In order to integrate notifications from remark42 with the [telegram](https://telegram.org), you should make [a channel](https://telegram.org/faq_channels) and obtain a token. This token should be used as `NOTIFY_TELEGRAM_TOKEN`. You also need to set `NOTIFY_TYPE=telegram` and set `NOTIFY_TELEGRAM_CHAN` to your channel.
|
||||
|
||||
In order to get token "just talk to [BotFather](https://core.telegram.org/bots#6-botfather)". All you need is to send `/newbot` command, and choose the name for your bot (it must end in `bot`). This is it, you got a token.
|
||||
|
||||
_Example of such a "talk":_
|
||||
|
||||
```
|
||||
Umputun:
|
||||
/newbot
|
||||
|
||||
BotFather:
|
||||
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
|
||||
|
||||
Umputun:
|
||||
example_comments
|
||||
|
||||
BotFather:
|
||||
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
|
||||
|
||||
Umputun:
|
||||
example_comments_bot
|
||||
|
||||
BotFather:
|
||||
Done! Congratulations on your new bot. You will find it at t.me/example_comments_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
|
||||
|
||||
Use this token to access the HTTP API:
|
||||
12345678:xy778Iltzsdr45tg
|
||||
|
||||
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
title: Translation
|
||||
---
|
||||
|
||||
## How to add new language translation to Remark42
|
||||
|
||||
Translation files are stored in [/frontend/app/locales](https://github.com/umputun/remark42/tree/master/frontend/app/locales)
|
||||
directory with `.json` extension and content like following:
|
||||
|
||||
```json
|
||||
{
|
||||
"anonymousLoginForm.length-limit": "Username must be at least 3 characters long",
|
||||
"anonymousLoginForm.log-in": "Log in",
|
||||
"anonymousLoginForm.symbol-limit": "Username must start from the letter and contain only latin letters, numbers, underscores, and spaces",
|
||||
<...>
|
||||
}
|
||||
```
|
||||
|
||||
### How to add a new translation
|
||||
|
||||
We truly appreciate people spending time contributing their translations to remark42. Please go through the steps
|
||||
below in order to have your translation start being available to all remark42 users and included in the next release.
|
||||
|
||||
1. create a fork of [umputun/remark42](https://github.com/umputun/remark42) repo, and if you already have one please
|
||||
pull the latest changes from the upstream master branch. It could be done like that:
|
||||
```shell
|
||||
git remote add upstream https://github.com/umputun/remark42.git
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git push
|
||||
```
|
||||
1. add a new locale with [two-letter code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
|
||||
of the language you want to make the translation into to list in
|
||||
[frontend/tasks/supportedLocales.json](https://github.com/umputun/remark42/blob/master/frontend/tasks/supportedLocales.json)
|
||||
1. run `npm run translation:generate` in `frontend` folder
|
||||
1. translate all values in the newly created json file in
|
||||
[frontend/app/locales/](https://github.com/umputun/remark42/blob/master/frontend/app/locales/)
|
||||
1. commit all changes above in your fork
|
||||
1. test your changes in the interface:
|
||||
|
||||
1. uncomment `locale: "ru"` line in [frontend/index.ejs](https://github.com/umputun/remark42/blob/master/frontend/index.ejs#L133)
|
||||
and replace `ru` with your translation language code
|
||||
1. [run remark42 in Docker](https://github.com/umputun/remark42#development) by issuing following commands
|
||||
from the root directory of your remark42 fork:
|
||||
|
||||
```shell
|
||||
docker-compose -f compose-dev-frontend.yml build
|
||||
docker-compose -f compose-dev-frontend.yml up
|
||||
```
|
||||
|
||||
1. open [http://127.0.0.1:8080](http://127.0.0.1:8080), log in, make a comment, make a reply to a comment,
|
||||
and make sure your translation looks as you expect it to look
|
||||
1. make a screenshot from [http://127.0.0.1:8080](http://127.0.0.1:8080) with your translation in place
|
||||
|
||||
1. after all previous steps are done, create a [Pull Request](https://github.com/umputun/remark42/pulls) to umputun/remark42
|
||||
repo with your changes, attaching a screenshot or two from your local test instance to it
|
||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,30 +0,0 @@
|
||||
---
|
||||
title: Site URL migration
|
||||
---
|
||||
|
||||
Here is an example on how to move your comments after your posts are moved, from `https://example.org/blog/<slug>` to `https://example.org/post/<slug>` in that example.
|
||||
|
||||
### Rules file
|
||||
|
||||
First you have to create a `rules` file in remark's `/var` folder.
|
||||
|
||||
[Here is the test](https://github.com/akosourov/remark/blob/4dc123dbe84f4f248864bcdbbd6cc2b3a4dafe11/backend/app/migrator/mapper_test.go#L13-L17) with an example of rules file syntax, format is simply `old_url new_url` like following:
|
||||
|
||||
```
|
||||
https://example.org/old-url-1/ https://example.org/new-url-1/
|
||||
https://example.org/old-url-2/ https://example.org/new-url-2/
|
||||
```
|
||||
|
||||
### Applying the remap
|
||||
|
||||
After rules file is ready, run the following command:
|
||||
|
||||
```sh
|
||||
remark42 remap --admin-passwd <password> -f var/rules
|
||||
```
|
||||
|
||||
If running in a docker container, the command becomes:
|
||||
```sh
|
||||
docker ps # to find the container name
|
||||
docker exec -it <container> remark42 remap --admin-passwd <password> -f var/rules
|
||||
```
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
title: Subdomain
|
||||
---
|
||||
|
||||
## How to configure remark42 without a subdomain
|
||||
|
||||
All README examples show configurations with remark42 on its own subdomain, i.e. `https://remark42.example.com`. However, it is possible and sometimes desirable to run remark42 without a subdomain, but just under some path, i.e. `https://example.com/remark42`.
|
||||
|
||||
- The frontend URL looks like this: `s.src = 'https://example.com/remark42/web/embed.js;`
|
||||
|
||||
- The backend `REMARK_URL` parameter will be `https://example.com/remark42`
|
||||
|
||||
- And you also need to slightly modify the callback URL for the social media login API's:
|
||||
- Facebook Valid OAuth Redirect URIs: `https://example.com/remark42/auth/facebook/callback`
|
||||
- Google Authorized redirect URIs: `https://example.com/remark42/auth/google/callback`
|
||||
- GitHub Authorised callback URL: `https://example.com/remark42/auth/github/callback`
|
||||
|
||||
### docker-compose configuration
|
||||
|
||||
Both Nginx and Caddy configuration below relies on remark42 available on hostname `remark42`, which is achieved by having `container_name: remark42` in docker-compose.
|
||||
|
||||
Example `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
version: '2'
|
||||
services:
|
||||
remark42:
|
||||
image: umputun/remark42:latest
|
||||
container_name: remark42
|
||||
restart: always
|
||||
environment:
|
||||
- REMARK_URL=https://example.com/remark42/
|
||||
- SITE=<site_ID>
|
||||
- SECRET=<secret>
|
||||
- ADMIN_SHARED_ID=<shared_id>
|
||||
volumes:
|
||||
- ./data:/srv/var
|
||||
logging:
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '1'
|
||||
```
|
||||
|
||||
### Nginx configuration
|
||||
|
||||
The `nginx.conf` would then look something like:
|
||||
|
||||
```
|
||||
location /remark42/ {
|
||||
rewrite /remark42/(.*) /$1 break;
|
||||
proxy_pass http://remark42:8080/; // use internal docker name of remark42 container for proxy
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
```
|
||||
|
||||
### Caddy configuration
|
||||
|
||||
Example of Caddy configuration (`Caddyfile`) running remark42 service on `example.com/remark42/`:
|
||||
|
||||
```caddy
|
||||
example.com {
|
||||
gzip
|
||||
tls mail@example.com
|
||||
|
||||
root /srv/www
|
||||
log /logs/access.log
|
||||
|
||||
# remark42
|
||||
proxy /remark42/ http://remark42:8080/ {
|
||||
without /remark42
|
||||
transparent
|
||||
}
|
||||
}
|
||||
```
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
node_modules
|
||||
extracted-messages
|
||||
/public
|
||||
/*.log
|
||||
public
|
||||
*.log
|
||||
.env
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
coverage
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.gitignore
|
||||
.prettierrc
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
public
|
||||
@@ -0,0 +1,6 @@
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{md,njk}]
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,102 @@
|
||||
const { format } = require('date-fns')
|
||||
const htmlmin = require('html-minifier')
|
||||
const navigationPlugin = require('@11ty/eleventy-navigation')
|
||||
const syntaxHighlightPlugin = require('@11ty/eleventy-plugin-syntaxhighlight')
|
||||
|
||||
function noteContainer() {
|
||||
const { utils } = require('markdown-it')()
|
||||
const elementRegexp = /^note\s+(.*)$/
|
||||
|
||||
return {
|
||||
validate(params) {
|
||||
return params.trim().match(elementRegexp)
|
||||
},
|
||||
|
||||
render(tokens, idx) {
|
||||
const { info, nesting } = tokens[idx]
|
||||
const matches = info.trim().match(elementRegexp)
|
||||
|
||||
if (nesting === 1) {
|
||||
const icon = utils.escapeHtml(matches[1])
|
||||
|
||||
return `<aside class="relative pr-4 pl-12 py-1 bg-gray-50 dark:bg-gray-800"><span class="absolute left-4 top-6 text-xl">${icon}</span>`
|
||||
}
|
||||
|
||||
return `</aside>`
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getMarkdownLib() {
|
||||
const markdownIt = require('markdown-it')
|
||||
const markdownItAnchor = require('markdown-it-anchor')
|
||||
const markdownItContainer = require('markdown-it-container')
|
||||
|
||||
return markdownIt({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
})
|
||||
.use(markdownItAnchor, {
|
||||
permalink: true,
|
||||
permalinkClass: '',
|
||||
permalinkSymbol: '',
|
||||
})
|
||||
.use(markdownItContainer, 'note', noteContainer())
|
||||
}
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
// TODO: create version with commit sha and current version of Remark42
|
||||
eleventyConfig.addShortcode('version', () => `${Date.now()}`)
|
||||
eleventyConfig.setUseGitIgnore(false)
|
||||
eleventyConfig.addWatchTarget('./.tmp/style.css')
|
||||
eleventyConfig.addPassthroughCopy({ './.tmp/style.css': './style.css' })
|
||||
eleventyConfig.addPassthroughCopy({ './public': './' })
|
||||
|
||||
eleventyConfig.addCollection('pages', (collection) =>
|
||||
collection.getFilteredByGlob('pages/*.md')
|
||||
)
|
||||
|
||||
eleventyConfig.addFilter('humanizeDate', (date) =>
|
||||
format(new Date(date), 'LLL dd, yyyy')
|
||||
)
|
||||
|
||||
eleventyConfig.addFilter('robotizeDate', (date) =>
|
||||
format(new Date(date), 'yyyy-MM-dd')
|
||||
)
|
||||
|
||||
eleventyConfig.addFilter(
|
||||
'debug',
|
||||
(content) => `<pre>${JSON.stringify(content, null, 2)}</pre>`
|
||||
)
|
||||
|
||||
// Minify HTML output
|
||||
eleventyConfig.addTransform('htmlmin', function (content, outputPath) {
|
||||
if (!outputPath.endsWith('.html')) {
|
||||
return content
|
||||
}
|
||||
|
||||
return htmlmin.minify(content, {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
})
|
||||
})
|
||||
|
||||
eleventyConfig.setLibrary('md', getMarkdownLib())
|
||||
eleventyConfig.addPlugin(syntaxHighlightPlugin)
|
||||
eleventyConfig.addPlugin(navigationPlugin)
|
||||
|
||||
eleventyConfig.addCollection('docs', (collection) =>
|
||||
collection.getFilteredByGlob('src/docs/**/*.md')
|
||||
)
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: 'src',
|
||||
output: 'build',
|
||||
data: 'data',
|
||||
layouts: 'layouts',
|
||||
includes: 'includes',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
yarn-*.log
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
yarn-*.log
|
||||
.tmp
|
||||
build
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM node:16-alpine
|
||||
|
||||
WORKDIR /site
|
||||
COPY ./ /site
|
||||
RUN yarn --frozen-lockfile
|
||||
|
||||
CMD yarn build
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Reamark42 Website
|
||||
|
||||
## Work on Your Local Environment
|
||||
|
||||
Requirements:
|
||||
|
||||
- [Node.js v14](https://nodejs.org/en/) or higher - Install from package or with Homebrew
|
||||
- Yarn 1.22 or higher - once you have Node.js run `npm i -g yarn`
|
||||
|
||||
### Development
|
||||
|
||||
Install dependencies and start development server:
|
||||
|
||||
```
|
||||
$ yarn
|
||||
$ yarn dev
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
## Work with Docker Compose
|
||||
|
||||
### Build
|
||||
|
||||
Install dependencies and run development server inside Docker:
|
||||
|
||||
```
|
||||
$ docker-compose build
|
||||
$ docker-compose up server
|
||||
```
|
||||
|
||||
Then serve files from `./build` with your favorite server
|
||||
|
||||
### Development
|
||||
|
||||
```
|
||||
$ docker-compose up --build server
|
||||
```
|
||||
|
||||
Then head to http://localhost:8080
|
||||
@@ -0,0 +1,27 @@
|
||||
version: '2'
|
||||
services:
|
||||
build:
|
||||
image: remark42-site
|
||||
build: .
|
||||
|
||||
volumes:
|
||||
- ./build:/site/build
|
||||
|
||||
server:
|
||||
image: remark42-site
|
||||
build: .
|
||||
command: yarn dev
|
||||
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '5'
|
||||
|
||||
ports:
|
||||
- '3001:3001'
|
||||
- '8080:8080'
|
||||
|
||||
volumes:
|
||||
- ./src:/site/src
|
||||
- ./build:/site/build
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "remark42-site",
|
||||
"version": "0.0.0",
|
||||
"repository": "https://github.com/umputun/remark42/site",
|
||||
"homepage": "https://remark42.com",
|
||||
"author": "Pavel Mineev <pavel@mineev.me>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=14.15",
|
||||
"yarn": ">=1.22"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm-run-all clean -p dev:*",
|
||||
"dev:11ty": "eleventy --serve --watch --quiet",
|
||||
"dev:css": "postcss ./src/styles.css -o .tmp/style.css -w",
|
||||
"build": "cross-env NODE_ENV=production run-s clean build:* --print-label",
|
||||
"build:css": "postcss ./src/styles.css -o .tmp/style.css",
|
||||
"build:11ty": "eleventy",
|
||||
"clean": "rm -rf .tmp/* build/*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^0.12.1",
|
||||
"@11ty/eleventy-navigation": "^0.2.0",
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^3.1.1",
|
||||
"@tailwindcss/typography": "^0.4.1",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^2.22.1",
|
||||
"eleventy-plugin-toc": "^1.1.0",
|
||||
"eslint": "^7.28.0",
|
||||
"html-minifier": "^4.0.0",
|
||||
"markdown-it": "^12.0.6",
|
||||
"markdown-it-anchor": "^7.1.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.3.2",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "^2.3.1",
|
||||
"tailwindcss": "^2.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /404/index.html
|
||||
@@ -0,0 +1,28 @@
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
const DEFAULT_DATA = { latestVersion: '' }
|
||||
let currentData = null
|
||||
|
||||
module.exports = async function getLatestReleaseVersion() {
|
||||
if (currentData) {
|
||||
return currentData
|
||||
}
|
||||
try {
|
||||
const res = await fetch(
|
||||
'https://api.github.com/repos/umputun/remark42/releases'
|
||||
)
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`[ERROR] Status: ${res.status}: ${res.statusText}`)
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
currentData = { latestVersion: data[0].tag_name || '' }
|
||||
|
||||
return currentData
|
||||
} catch (e) {
|
||||
console.error(e.message)
|
||||
return DEFAULT_DATA
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = 'docs.njk'
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Remark42",
|
||||
"subtitle": "Privacy focused lightweight commenting system",
|
||||
"description": "Remark42 is a self-hosted, lightweight, and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles or any other place where readers add comments.",
|
||||
"url": "https://remark42.com",
|
||||
"githubUrl": "https://github.com/umputun/remark42",
|
||||
"githubBranch": "master",
|
||||
"remark42Host": "https://demo.remark42.com"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Automatic Backup
|
||||
menuTitle: Automatic
|
||||
parent: Backup
|
||||
order: 400
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
layout: redirect.njk
|
||||
destination: /docs/backup/automatic/
|
||||
title: Backup
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Manual Migration
|
||||
menuTitle: Manual
|
||||
parent: Backup
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Migration
|
||||
parent: Backup
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Restore Backup
|
||||
menuTitle: Restore
|
||||
parent: Backup
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Authorization
|
||||
parent: Configuration
|
||||
order: 200
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Email Settings
|
||||
menuTitle: Email
|
||||
parent: Configuration
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Frontend Configuration
|
||||
menuTitle: Frontend
|
||||
parent: Configuration
|
||||
order: 100
|
||||
---
|
||||
|
||||
## Comments
|
||||
|
||||
## Widgets
|
||||
|
||||
### Counter widget
|
||||
|
||||
### Last comments widget
|
||||
|
||||
## API for Single-Page Applications
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
layout: redirect.njk
|
||||
destination: /docs/configuration/frondend/
|
||||
title: Configuration
|
||||
order: 200
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Deploy with Kubernetes
|
||||
menuTitle: Kubernetes
|
||||
parent: Configuration
|
||||
order: 700
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Configure with Nginx
|
||||
menuTitle: Nginx
|
||||
parent: Configuration
|
||||
order: 500
|
||||
---
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Notification
|
||||
parent: Configuration
|
||||
order: 400
|
||||
---
|
||||
|
||||
## Email
|
||||
|
||||
## Slack
|
||||
|
||||
## Telegram
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Configure Instance on Subdomain
|
||||
menuTitle: Subdomain
|
||||
parent: Configuration
|
||||
order: 600
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: API
|
||||
parent: Contributing
|
||||
order: 400
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Code of Conduct
|
||||
parent: Contributing
|
||||
order: 100
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Development Environmetn
|
||||
menuTitle: Environment
|
||||
parent: Contributing
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Backend Development Guidlines
|
||||
menuTitle: Backend
|
||||
key: Backend Guidelines
|
||||
parent: Guidelines
|
||||
order: 100
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Frontend Development Guidlines
|
||||
menuTitle: Frontend
|
||||
key: Frontend Guidelines
|
||||
parent: Guidelines
|
||||
order: 100
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Guidelines
|
||||
parent: Contributing
|
||||
order: 200
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
layout: redirect.njk
|
||||
destination: /docs/contributing/code-of-conduct/
|
||||
title: Contributing
|
||||
order: 500
|
||||
---
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Technical Details
|
||||
order: 600
|
||||
parent: Contributing
|
||||
---
|
||||
|
||||
Remark42 uses boltdb (embedded key/value database) files under `STORE_BOLT_PATH` for storing data. Each site stored in a separate boltbd file.
|
||||
|
||||
In order to migrate/move Remark42 to another host boltbd files as well as avatars directory `AVATAR_FS_PATH` should be transferred. Optionally, boltdb can be used to store avatars as well.
|
||||
|
||||
Automatic backup process runs every 24 hours and exports all content in json-like format to `backup-remark-YYYYMMDD.gz`.
|
||||
|
||||
Authentication implemented with `go-pkgz/auth` stored in a cookie. It uses HttpOnly, secure cookies.
|
||||
|
||||
All heavy REST calls cached internally in LRU cache limited by `CACHE_MAX_ITEMS` and `CACHE_MAX_SIZE` with `go-pkgz/rest`
|
||||
|
||||
User's activity throttled globally (up to 1000 simultaneous requests) and limited locally (per user, usually up to 10 req/sec). Request timeout set to 60 seconds.
|
||||
|
||||
Admin authentication (`--admin-password` set) allows to hit Remark42 API without social login and with admin privileges. Adds basic-auth for username: admin, password: `${ADMIN_PASSWD}`.
|
||||
|
||||
User can vote for the comment multiple times but only to change the vote. Double-voting not allowed.
|
||||
|
||||
User can edit comments in 5 mins (configurable) window after creation.
|
||||
User ID hashed and prefixed by oauth provider name to avoid collisions and potential abuse.
|
||||
|
||||
All avatars resized and cached locally to prevent rate limiters from oauth providers, part of `go-pkgz/auth` functionality.
|
||||
|
||||
Images can be proxied (`IMAGE_PROXY_HTTP2HTTPS=true`) to prevent mixed `http/https`. All images can be proxied and saved (`IMAGE_PROXY_CACHE_EXTERNAL=true`) instead of serving from original location. Beware, images which are posted with this parameter enabled will be served from proxy even after it will be disabled.
|
||||
Docker build uses publicly available base images.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Interface Translations
|
||||
menuTitle: Translations
|
||||
parent: Contributing
|
||||
order: 300
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
eleventyComputed: {
|
||||
eleventyNavigation: {
|
||||
key: (data) => data.key || data.menuTitle || data.title,
|
||||
title: (data) => data.menuTitle || data.title,
|
||||
parent: (data) => data.parent,
|
||||
order: (data) => data.order,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Getting Started
|
||||
layout: redirect.njk
|
||||
destination: /docs/getting-started/installation/
|
||||
order: 100
|
||||
---
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Installation
|
||||
parent: Getting Started
|
||||
order: 100
|
||||
---
|
||||
|
||||
## Setup Remark42 Instance on Your Server
|
||||
|
||||
### Installation in Docker
|
||||
|
||||
_This is the recommended way to run remark42_
|
||||
|
||||
- copy provided `docker-compose.yml` and customize for your needs
|
||||
- make sure you **don't keep** `ADMIN_PASSWD=something...` for any non-development deployments
|
||||
- pull prepared images from the DockerHub and start - `docker-compose pull && docker-compose up -d`
|
||||
- alternatively compile from the sources - `docker-compose build && docker-compose up -d`
|
||||
|
||||
### Installation with Binary
|
||||
|
||||
- download archive for [stable release](https://github.com/umputun/remark42/releases) or [development version](https://remark42.com/downloads)
|
||||
- unpack with `gunzip` (Linux, macOS) or with `zip` (Windows)
|
||||
- run as `remark42.{os}-{arch} server {parameters...}`, i.e. `remark42.linux-amd64 server --secret=12345 --url=http://127.0.0.1:8080`
|
||||
- alternatively compile from the sources - `make OS=[linux|darwin|windows] ARCH=[amd64,386,arm64,arm]`
|
||||
|
||||
## Setup on Your Website
|
||||
|
||||
Add config for Remark on a page of your site:
|
||||
|
||||
```html
|
||||
<script>
|
||||
const remark_config = {
|
||||
host: 'REMARK_URL',
|
||||
site_id: 'YOUR_SITE_ID',
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
- `REMARK_URL` – the URL where is Remark42 instance is served.
|
||||
- `YOUR_SITE_ID` - the ID that you passed to Remark42 instance on start.
|
||||
|
||||
For example:
|
||||
|
||||
```html
|
||||
<script>
|
||||
const remark_config = {
|
||||
host: 'https://demo.remark42.com',
|
||||
site_id: 'remark',
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
After that place the code snippet right after config.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
```html
|
||||
<script>!function(e,n){for(var o=0;o<e.length;o++){var r=n.createElement("script"),c=".js",d=n.head||n.body;"noModule"in r?(r.type="module",c=".mjs"):r.async=!0,r.defer=!0,r.src=remark_config.host+"/web/"+e[o]+c,d.appendChild(r)}}(remark_config.components||["embed"],document);</script>
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
::: note 💡
|
||||
**Note that:** You can place the config with the snippet in any place of the HTML code of your site. If it closer to start of the HTML (for example in `<head>`) it will start loading sooner and show comments faster.
|
||||
:::
|
||||
|
||||
Put the next code snippet on a page of your site where you want to have comments:
|
||||
|
||||
```html
|
||||
<div id="remark42"></div>
|
||||
```
|
||||
|
||||
After that widget will be rendered inside this node.
|
||||
|
||||
If you want to set this up on a Single Page App, see [appropriate doc page](https://github.com/umputun/remark42/blob/master/docs/latest/spa.md).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: System Requirements
|
||||
parent: Getting Started
|
||||
order: 200
|
||||
---
|
||||
@@ -0,0 +1,40 @@
|
||||
{% macro renderList(entry, level) %}
|
||||
{% if level === 1 %}
|
||||
<h5 class="uppercase font-semibold text-sm text-gray-400 mb-2">{{ entry.title }}</h5>
|
||||
{% else %}
|
||||
<li class="my-1">
|
||||
<a class="py-1 px-2 -mx-2 block rounded text-gray-600 dark:text-gray-300 {% if entry.url === page.url %} bg-brand-100 dark:bg-brand-900 text-brand-600 dark:text-brand-400 hover:text-brand-600{% else %} hover:text-gray-900 dark:hover:text-gray-400{% endif %}" href="{{ entry.url | url }}">{{ entry.title }}</a>
|
||||
{% endif %}
|
||||
{% if entry.children and level < 2 %}
|
||||
<ul class="mb-8 font-medium">
|
||||
{% for child in entry.children %}
|
||||
{{renderList(child, level + 1)}}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if entry.children and level >= 2 and page.url.startsWith(entry.url) %}
|
||||
<ul class="ml-4 font-normal">
|
||||
{% for child in entry.children %}
|
||||
{{renderList(child, level + 1)}}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
<aside class="flex-shrink-0 top-24 md:top-0 md:relative w-full md:w-1/4">
|
||||
<input id="menu-toggle" type="checkbox" class="menu-state absolute invisible h-0 w-0 md:hidden"/>
|
||||
<label for="menu-toggle" aria-title="Open Menu" class="flex items-center md:hidden py-2 border-t border-b border-gray-100 dark:border-gray-800 text-gray-500 dark:text-gray-300 mb-8 font-medium">
|
||||
<div class="burger-icon" role="icon">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
Open Menu
|
||||
</label>
|
||||
<nav class="hidden md:block mr-10">
|
||||
{% for entry in collections.docs | eleventyNavigation %}
|
||||
{{renderList(entry, 1)}}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -0,0 +1 @@
|
||||
<footer class="pt-12"></footer>
|
||||
@@ -0,0 +1,16 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% if title %}
|
||||
<title>{{ title }} | {{ site.name }}</title>
|
||||
{% else %}
|
||||
<title>{{ renderData.title or site.name }}</title>
|
||||
{% endif %}
|
||||
<meta name="description" content="{{ metaDescription or renderData.metaDescription or site.description }}">
|
||||
<link rel="stylesheet" href="/style.css?v={% version %}"/>
|
||||
<script>{% include "inline.js" %}</script>
|
||||
{% set js %}
|
||||
{% include "script.js" %}
|
||||
{% endset %}
|
||||
<script defer>{{ js | safe }}</script>
|
||||
</head>
|
||||
@@ -0,0 +1,24 @@
|
||||
<header class="md:py-2 bg-white dark:bg-trueGray-900 bg-opacity-80 dark:bg-opacity-60 backdrop-filter backdrop-blur-sm z-20 fixed top-0 left-0 right-0 text-gray-600 dark:text-gray-300">
|
||||
<div class="container flex flex-wrap items-center">
|
||||
<a class="order-1 py-1 mt-1 mr-4 no-underline text-2xl font-extrabold transition-colors text-brand-500 hover:text-brand-600 dark:text-brand-400" href="/">
|
||||
{{ site.name }}
|
||||
</a>
|
||||
<nav class="order-3 md:order-2 py-1 mt-1 w-full md:w-auto flex-shrink-0 space-x-2">
|
||||
<a class="inline-block leading-5 font-medium border-b dark:border-gray-600 transition hover:text-brand-600 dark:hover:text-brand-300 hover:border-brand-300 dark:hover:border-brand-500" href="/demo">Demo</a>
|
||||
<a class="inline-block leading-5 font-medium border-b dark:border-gray-600 transition hover:text-brand-600 dark:hover:text-brand-300 hover:border-brand-300 dark:hover:border-brand-500" href="/docs/getting-started/installation/">Docs</a>
|
||||
</nav>
|
||||
<div class="order-2 md:order-3 flex space-x-2 ml-auto py-1 px-4">
|
||||
<a class="hover:text-gray-800 dark:hover:text-gray-300 flex items-center" aria-label="Remark42's GitHub Repository" rel="noopener noreferrer" href="{{ site.githubUrl}}" target="_blank">
|
||||
<span class="mr-2">{{ github.latestVersion }}</span>
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
|
||||
<path fill="currentColor" d="M12 3C7.0275 3 3 7.12937 3 12.2276C3 16.3109 5.57625 19.7597 9.15374 20.9824C9.60374 21.0631 9.77249 20.7863 9.77249 20.5441C9.77249 20.3249 9.76125 19.5982 9.76125 18.8254C7.5 19.2522 6.915 18.2602 6.735 17.7412C6.63375 17.4759 6.19499 16.6569 5.8125 16.4378C5.4975 16.2647 5.0475 15.838 5.80124 15.8264C6.51 15.8149 7.01625 16.4954 7.18499 16.7723C7.99499 18.1679 9.28875 17.7758 9.80625 17.5335C9.885 16.9337 10.1212 16.53 10.38 16.2993C8.3775 16.0687 6.285 15.2728 6.285 11.7432C6.285 10.7397 6.63375 9.9092 7.20749 9.26326C7.1175 9.03257 6.8025 8.08674 7.2975 6.81794C7.2975 6.81794 8.05125 6.57571 9.77249 7.76377C10.4925 7.55615 11.2575 7.45234 12.0225 7.45234C12.7875 7.45234 13.5525 7.55615 14.2725 7.76377C15.9937 6.56418 16.7475 6.81794 16.7475 6.81794C17.2424 8.08674 16.9275 9.03257 16.8375 9.26326C17.4113 9.9092 17.76 10.7281 17.76 11.7432C17.76 15.2843 15.6563 16.0687 13.6537 16.2993C13.98 16.5877 14.2613 17.1414 14.2613 18.0065C14.2613 19.2407 14.25 20.2326 14.25 20.5441C14.25 20.7863 14.4188 21.0746 14.8688 20.9824C16.6554 20.364 18.2079 19.1866 19.3078 17.6162C20.4077 16.0457 20.9995 14.1611 21 12.2276C21 7.12937 16.9725 3 12 3Z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<button class="hover:text-yellow-400" title="Toggle Site Theme" onclick="toggleTheme()">
|
||||
<svg width="28" height="28" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.67 8.658a3.661 3.661 0 0 0-.781 1.114 3.28 3.28 0 0 0-.268 1.329v1.6a1.304 1.304 0 0 1-.794 1.197 1.282 1.282 0 0 1-.509.102H7.712a1.285 1.285 0 0 1-.922-.379 1.303 1.303 0 0 1-.38-.92v-1.6c0-.479-.092-.921-.274-1.329a3.556 3.556 0 0 0-.776-1.114 4.689 4.689 0 0 1-1.006-1.437A4.187 4.187 0 0 1 4 5.5a4.432 4.432 0 0 1 .616-2.27c.197-.336.432-.64.705-.914a4.6 4.6 0 0 1 .911-.702c.338-.196.7-.348 1.084-.454a4.45 4.45 0 0 1 1.2-.16 4.476 4.476 0 0 1 2.276.614 4.475 4.475 0 0 1 1.622 1.616 4.438 4.438 0 0 1 .616 2.27c0 .617-.117 1.191-.353 1.721a4.69 4.69 0 0 1-1.006 1.437zM9.623 10.5H7.409v2.201c0 .081.028.15.09.212a.29.29 0 0 0 .213.09h1.606a.289.289 0 0 0 .213-.09.286.286 0 0 0 .09-.212V10.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1,5 @@
|
||||
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
if (mq.matches) {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
function toggleTheme() {
|
||||
const root = document.documentElement
|
||||
|
||||
root.classList.toggle('dark')
|
||||
|
||||
if (window.REMARK42) {
|
||||
const isDark = root.classList.contains('dark')
|
||||
|
||||
window.REMARK42.changeTheme(isDark ? 'dark' : 'light')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
<h1 class="m-auto text-center relative">
|
||||
<div class="text-9xl md:text-[200px] font-black mb-4 text-gray-100">404</div>
|
||||
<div class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 whitespace-nowrap text-4xl font-bold text-gray-800">Page Not Found</div>
|
||||
</h2>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html {% if section %} data-current="{{ section }}"{% endif %} lang="en">
|
||||
{% include "components/head.njk" %}
|
||||
<body class="h-screen bg-white dark:bg-trueGray-900 dark:text-gray-100">
|
||||
{% include "components/header.njk" %}
|
||||
<main id="main" class="relative mt-24 md:mt-20 container flex-grow min-h-full flex flex-wrap">
|
||||
{{ layoutContent | safe }}
|
||||
</main>
|
||||
{% include "components/footer.njk" %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
layout: page.njk
|
||||
---
|
||||
|
||||
{{ content | safe }}
|
||||
|
||||
<div id="remark42" class="max-w-2xl m-auto"></div>
|
||||
<noscript>You use your browser with disabled JavaScript. Please enable JavaScript for comments.</noscript>
|
||||
<script>
|
||||
window.remark_config = {
|
||||
host: '{{ site.remark42Host }}',
|
||||
site_id: 'remark',
|
||||
components: ['embed'],
|
||||
url: '{{ site.url }}/{{ permalink }}',
|
||||
max_shown_comments: 20,
|
||||
theme: document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
||||
}
|
||||
</script>
|
||||
<script>!function(e,n){for(var o=0;o<e.length;o++){var r=n.createElement("script"),c=".js",d=n.head||n.body;"noModule"in r?(r.type="module",c=".mjs"):r.async=!0,r.defer=!0,r.src=remark_config.host+"/web/"+e[o]+c,d.appendChild(r)}}(remark_config.components||["embed"],document);</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
{% include "components/docs-aside.njk" %}
|
||||
<section class="md:w-3/4 overflow-hidden">
|
||||
<article class="flex flex-row flex-wrap">
|
||||
{# TODO: implement table of content
|
||||
<aside class="order-2 mt-4">
|
||||
<nav aria-labelledby="table-of-content">
|
||||
<h3 class="uppercase font-semibold text-gray-600 text-sm" id="table-of-content">On this page</h3>
|
||||
<ol></ol>
|
||||
</nav>
|
||||
</aside>
|
||||
#}
|
||||
<div class="order-1 max-w-full flex-grow">
|
||||
<h1 class="text-2xl md:text-4xl font-bold mb-6 md:mb-8 text-brand-900 dark:text-brand-100">{{ title }}</h1>
|
||||
<div class="prose dark:prose-dark">{{ layoutContent | safe }}</div>
|
||||
</div>
|
||||
<footer class="order-3 min-w-full flex mt-4 justify-end mr-1 text-sm text-gray-500 dark:text-gray-300 space-x-3 leading-5">
|
||||
<span>
|
||||
Updated <time datetime="{{ page.date | robotizeDate }}">{{ page.date | humanizeDate }}</time>
|
||||
</span>
|
||||
<a class="border-b border-transparent transition-colors hover:border-current" target="_blank" rel="noopener noreferrer" href="{{ site.githubUrl }}/edit/{{ site.githubBranch }}/site/{{ page.inputPath }}">Edit</a>
|
||||
</footer>
|
||||
</article>
|
||||
{# TODO: implement navigation to next page of the docs
|
||||
<nav class="flex justify-between mt-10 mb-8 p-2 border-t dark:border-gray-700 text-gray-800 dark:text-gray-200">
|
||||
<a>Prev page</a>
|
||||
<a>Next page</a>
|
||||
</nav> #}
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
<article class="prose dark:prose-dark mx-auto mt-10">
|
||||
{{ content |safe }}
|
||||
</article>
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: base.njk
|
||||
---
|
||||
|
||||
<article class="mx-auto max-w-3xl flex-grow">
|
||||
<header class="pt-12 pb-10 mb-10 border-b border-gray-100 dark:border-gray-600 text-center">
|
||||
<h1 class="text-2xl md:text-3xl font-bold mb-2">{{ title }}</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">{{ description }}</p>
|
||||
</header>
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
@@ -0,0 +1,13 @@
|
||||
{% if not destination %}
|
||||
{% set destination = redirect.destination %}
|
||||
{% endif %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="refresh" content="0; url='{{ destination }}'" />
|
||||
</head>
|
||||
<body>
|
||||
you should be redirecting to <a href="{{ destination }}">{{ destination }}</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
layout: 404.njk
|
||||
permalink: 404/index.html
|
||||
title: Page Not Found
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
layout: demo.njk
|
||||
permalink: demo/index.html
|
||||
title: Demo
|
||||
description: This is a page with working Remark42. Feel free to sign in and try its features.
|
||||
---
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
layout: home.njk
|
||||
permalink: index.html
|
||||
title: Remark42 – Privacy focused lightweight commenting engine
|
||||
---
|
||||
|
||||
<h1 class="text-center !text-4xl !md:text-5xl">Privacy focused lightweight commenting engine</h1>
|
||||
|
||||
Remark42 gives you opportunity to have self-hosted, lightweight, and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles or any other place where readers add comments.
|
||||
|
||||
- Social login via Google, Twitter, Facebook, Microsoft, GitHub and Yandex
|
||||
- Login via email
|
||||
- Optional anonymous access
|
||||
- Multi-level nested comments with both tree and plain presentations
|
||||
- Import from Disqus and WordPress
|
||||
- Markdown support with friendly formatter toolbar
|
||||
- Moderator can remove comments and block users
|
||||
- Voting, pinning and verification system
|
||||
- Sortable comments
|
||||
- Images upload with drag-and-drop
|
||||
- Extractor for recent comments, cross-post
|
||||
- RSS for all comments and each post
|
||||
- Telegram, Slack and email notifications for Admins (get notified for each new comment)
|
||||
- Email notifications for users (get notified when someone responds to your comment)
|
||||
- Export data to json with automatic backups
|
||||
- No external databases, everything embedded in a single data file
|
||||
- Fully dockerized and can be deployed in a single command
|
||||
- Self-contained executable can be deployed directly to Linux, Windows and MacOS
|
||||
- Clean, lightweight and customizable UI with white and dark themes
|
||||
- Multi-site mode from a single instance
|
||||
- Integration with automatic SSL
|
||||
- [Privacy focused](/privacy/)
|
||||
|
||||
<div class="text-right italic">— The Remark42 Team</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
layout: page.njk
|
||||
permalink: privacy/index.html
|
||||
---
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# This file does hijinx with the "pagingation" system to generate many small pages from one set of data...
|
||||
# and uses that do to redirects from some URLs to others.
|
||||
# We use this to try to keep old links working.
|
||||
#
|
||||
# There's limited power to this approach (it only works for specific pages listed; it can't glob),
|
||||
# but those are limitations inherent to an approach that works via static site gen, rather than via server configuration.
|
||||
# The related upside of an approach that works via static site gen is the portability.
|
||||
pagination:
|
||||
data: redirects
|
||||
size: 1
|
||||
alias: redirect
|
||||
# Add your redirection tuples to this list!
|
||||
redirects:
|
||||
- { 'from': '/docs/', 'destination': '/docs/getting-started/installation/' }
|
||||
# The "permalink" attribute determines where the output page will be located.
|
||||
permalink: '{{ redirect.from }}'
|
||||
# The "redirect" layout just has a small html header with the meta tags that do redirection.
|
||||
layout: redirect
|
||||
---
|
||||
@@ -0,0 +1,188 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.burger-icon {
|
||||
@apply relative inline-block w-4 h-4 mr-3;
|
||||
}
|
||||
|
||||
.burger-icon div {
|
||||
@apply absolute bg-gray-400 rounded transition-all transform w-full h-[2px];
|
||||
}
|
||||
|
||||
.burger-icon div:nth-child(1) {
|
||||
@apply left-0 top-0;
|
||||
}
|
||||
|
||||
.burger-icon div:nth-child(2) {
|
||||
@apply left-0 top-[7px];
|
||||
}
|
||||
|
||||
.burger-icon div:nth-child(3) {
|
||||
@apply left-0 bottom-0;
|
||||
}
|
||||
|
||||
.menu-state:checked ~ nav {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.menu-state:checked ~ label .burger-icon div:nth-child(1) {
|
||||
@apply -rotate-45 scale-x-[0.7] top-[7px] left-[-3px];
|
||||
}
|
||||
.menu-state:checked ~ label .burger-icon div:nth-child(2) {
|
||||
@apply scale-0 duration-75;
|
||||
}
|
||||
.menu-state:checked ~ label .burger-icon div:nth-child(3) {
|
||||
@apply rotate-45 scale-x-[0.7] top-[7px] left-[4px];
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata,
|
||||
.token.punctuation {
|
||||
@apply text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
@apply text-brand-800 dark:text-brand-200;
|
||||
}
|
||||
|
||||
.token.delimiter.important,
|
||||
.token.selector .parent,
|
||||
.token.tag,
|
||||
.token.tag .token.punctuation {
|
||||
@apply text-brand-600 dark:text-brand-300 font-semibold;
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.boolean,
|
||||
.token.boolean.important,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.selector .token.attribute {
|
||||
@apply font-normal text-yellow-600 dark:text-yellow-500;
|
||||
}
|
||||
|
||||
.token.class-name,
|
||||
.token.key,
|
||||
.token.parameter,
|
||||
.token.property,
|
||||
.token.property-access,
|
||||
.token.variable {
|
||||
@apply text-yellow-600 dark:text-yellow-400;
|
||||
}
|
||||
|
||||
.token.attr-value,
|
||||
.token.inserted,
|
||||
.token.color,
|
||||
.token.selector .token.value,
|
||||
.token.string,
|
||||
.token.string .token.url-link {
|
||||
@apply text-brand-800 dark:text-brand-300 font-normal;
|
||||
}
|
||||
|
||||
.token.builtin,
|
||||
.token.keyword-array,
|
||||
.token.package,
|
||||
.token.regex {
|
||||
color: #af00af;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.selector .token.class,
|
||||
.token.selector .token.id {
|
||||
color: #7c00aa;
|
||||
}
|
||||
|
||||
.token.atrule .token.rule,
|
||||
.token.combinator,
|
||||
.token.keyword,
|
||||
.token.operator,
|
||||
.token.pseudo-class,
|
||||
.token.pseudo-element,
|
||||
.token.selector,
|
||||
.token.unit {
|
||||
@apply text-pink-700 dark:text-pink-300;
|
||||
}
|
||||
|
||||
.token.deleted,
|
||||
.token.important {
|
||||
color: #c22f2e;
|
||||
}
|
||||
|
||||
.token.keyword-this,
|
||||
.token.this {
|
||||
color: #005a8e;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.keyword-this,
|
||||
.token.this,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.delimiter.important {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.language-markdown .token.title,
|
||||
.language-markdown .token.title .token.punctuation {
|
||||
color: #005a8e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.language-markdown .token.blockquote.punctuation {
|
||||
color: #af00af;
|
||||
}
|
||||
|
||||
.language-markdown .token.code {
|
||||
color: #006d6d;
|
||||
}
|
||||
|
||||
.language-markdown .token.hr.punctuation {
|
||||
color: #005a8e;
|
||||
}
|
||||
|
||||
.language-markdown .token.url > .token.content {
|
||||
color: #116b00;
|
||||
}
|
||||
|
||||
.language-markdown .token.url-link {
|
||||
color: #755f00;
|
||||
}
|
||||
|
||||
.language-markdown .token.list.punctuation {
|
||||
color: #af00af;
|
||||
}
|
||||
|
||||
.language-markdown .token.table-header {
|
||||
color: #111b27;
|
||||
}
|
||||
|
||||
.language-json .token.operator {
|
||||
color: #111b27;
|
||||
}
|
||||
|
||||
.language-scss .token.variable {
|
||||
color: #006d6d;
|
||||
}
|
||||
|
||||
/* overrides color-values for the Show Invisibles plugin
|
||||
* https://prismjs.com/plugins/show-invisibles/
|
||||
*/
|
||||
.token.tab:not(:empty):before,
|
||||
.token.cr:before,
|
||||
.token.lf:before,
|
||||
.token.space:before {
|
||||
color: #3c526d;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
const colors = require('tailwindcss/colors')
|
||||
const { spacing } = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
purge: ['.eleventy.js', 'src/**/*.{njk,md,html,js}'],
|
||||
mode: 'jit',
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
trueGray: colors.trueGray,
|
||||
brand: {
|
||||
50: '#edfdfb',
|
||||
100: '#e0fbf8',
|
||||
200: '#aef4ee',
|
||||
300: '#4be7dc',
|
||||
400: '#1ccac1',
|
||||
500: '#16a29f',
|
||||
600: '#157f7f',
|
||||
700: '#126263',
|
||||
800: '#125254',
|
||||
900: '#134b4e',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
padding: spacing[4],
|
||||
screens: {
|
||||
sm: '100%',
|
||||
md: '860px',
|
||||
lg: '940px',
|
||||
xl: false,
|
||||
},
|
||||
},
|
||||
typography: (theme) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'h1, h2': { color: theme('colors.brand.900') },
|
||||
'h3,h4,h5,h6': { color: theme('colors.gray.700') },
|
||||
pre: {
|
||||
color: theme('colors.gray.500'),
|
||||
backgroundColor: theme('colors.gray.100'),
|
||||
},
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
css: [
|
||||
{
|
||||
color: theme('colors.gray.300'),
|
||||
a: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
strong: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
'ol > li::before': {
|
||||
color: theme('colors.gray.400'),
|
||||
},
|
||||
'ul > li::before': {
|
||||
backgroundColor: theme('colors.gray.600'),
|
||||
},
|
||||
hr: {
|
||||
borderColor: theme('colors.gray.300'),
|
||||
},
|
||||
blockquote: {
|
||||
color: theme('colors.gray.300'),
|
||||
borderLeftColor: theme('colors.gray.600'),
|
||||
},
|
||||
h1: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
h2: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
h3: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
h4: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
'figure figcaption': {
|
||||
color: theme('colors.gray.400'),
|
||||
},
|
||||
code: {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
'a code': {
|
||||
color: theme('colors.gray.200'),
|
||||
},
|
||||
pre: {
|
||||
backgroundColor: theme('colors.gray.800'),
|
||||
},
|
||||
thead: {
|
||||
color: theme('colors.gray.200'),
|
||||
borderBottomColor: theme('colors.gray.400'),
|
||||
},
|
||||
'tbody tr': {
|
||||
borderBottomColor: theme('colors.gray.600'),
|
||||
},
|
||||
hr: {
|
||||
borderColor: theme('colors.gray.500'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
}
|
||||
+4087
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user