Merge branch 'master' into DepauMD
This commit is contained in:
commit
b72b3b48fe
56 changed files with 2760 additions and 2569 deletions
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
lib/ot
|
||||||
|
public/vendor
|
||||||
|
public/build
|
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
module.exports = {
|
||||||
|
"root": true,
|
||||||
|
"extends": "standard",
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
// at some point all of these should return to their default "error" state
|
||||||
|
// but right now, this is not a good choice, because too many places are
|
||||||
|
// wrong.
|
||||||
|
"import/first": ["warn"],
|
||||||
|
"indent": ["warn"],
|
||||||
|
"no-multiple-empty-lines": ["warn"],
|
||||||
|
"no-multi-spaces": ["warn"],
|
||||||
|
"object-curly-spacing": ["warn"],
|
||||||
|
"one-var": ["warn"],
|
||||||
|
"quotes": ["warn"],
|
||||||
|
"semi": ["warn"],
|
||||||
|
"space-infix-ops": ["warn"]
|
||||||
|
}
|
||||||
|
};
|
|
@ -20,6 +20,12 @@ jobs:
|
||||||
before_install:
|
before_install:
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
|
- env: task=npm-test
|
||||||
|
node_js:
|
||||||
|
- 10
|
||||||
|
before_install:
|
||||||
|
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
||||||
|
- export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
- env: task=ShellCheck
|
- env: task=ShellCheck
|
||||||
script:
|
script:
|
||||||
- shellcheck bin/heroku bin/setup
|
- shellcheck bin/heroku bin/setup
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,8 +1,6 @@
|
||||||
CodiMD
|
CodiMD
|
||||||
===
|
===
|
||||||
|
|
||||||
[![Standard - JavaScript Style Guide][standardjs-image]][standardjs-url]
|
|
||||||
|
|
||||||
[![#CodiMD on matrix.org][matrix.org-image]][matrix.org-url]
|
[![#CodiMD on matrix.org][matrix.org-image]][matrix.org-url]
|
||||||
[![build status][travis-image]][travis-url]
|
[![build status][travis-image]][travis-url]
|
||||||
[![version][github-version-badge]][github-release-page]
|
[![version][github-version-badge]][github-release-page]
|
||||||
|
@ -84,6 +82,8 @@ Just to more confusion: We are still friends with HackMD :heart:
|
||||||
7. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
|
7. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
|
||||||
8. Run the server as you like (node, forever, pm2)
|
8. Run the server as you like (node, forever, pm2)
|
||||||
|
|
||||||
|
To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed].
|
||||||
|
|
||||||
## Heroku Deployment
|
## Heroku Deployment
|
||||||
|
|
||||||
You can quickly setup a sample Heroku CodiMD application by clicking the button below.
|
You can quickly setup a sample Heroku CodiMD application by clicking the button below.
|
||||||
|
@ -141,6 +141,7 @@ If you are upgrading CodiMD from an older version, follow these steps:
|
||||||
6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
|
6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
|
||||||
7. Start your whole new server!
|
7. Start your whole new server!
|
||||||
|
|
||||||
|
To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed].
|
||||||
|
|
||||||
* **migrate-to-1.1.0**
|
* **migrate-to-1.1.0**
|
||||||
|
|
||||||
|
@ -179,6 +180,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `CMD_HOST` | `localhost` | host to listen on |
|
| `CMD_HOST` | `localhost` | host to listen on |
|
||||||
| `CMD_PORT` | `80` | web app port |
|
| `CMD_PORT` | `80` | web app port |
|
||||||
| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) |
|
| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) |
|
||||||
|
| `CMD_LOGLEVEL` | `info` | Defines what kind of logs are provided to stdout. |
|
||||||
| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) |
|
| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) |
|
||||||
| `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) |
|
| `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) |
|
||||||
| `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
|
| `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
|
||||||
|
@ -186,6 +188,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) |
|
| `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) |
|
||||||
| `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
|
| `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
|
||||||
| `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
|
| `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
|
||||||
|
| `CMD_FORBIDDEN_NODE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` |
|
||||||
| `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
|
| `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
|
||||||
| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL |
|
| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL |
|
||||||
| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If non is set, one will randomly generated on startup |
|
| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If non is set, one will randomly generated on startup |
|
||||||
|
@ -260,6 +263,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) |
|
| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) |
|
||||||
| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) |
|
| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) |
|
||||||
| `CMD_CSP_REPORTURI` | `https://<someid>.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations |
|
| `CMD_CSP_REPORTURI` | `https://<someid>.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations |
|
||||||
|
| `CMD_SOURCE_URL` | `https://github.com/hackmdio/codimd/tree/<current commit>` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) |
|
||||||
|
|
||||||
***Note:** Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.*
|
***Note:** Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.*
|
||||||
|
|
||||||
|
@ -273,6 +277,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `host` | `localhost` | host to listen on |
|
| `host` | `localhost` | host to listen on |
|
||||||
| `port` | `80` | web app port |
|
| `port` | `80` | web app port |
|
||||||
| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) |
|
| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) |
|
||||||
|
| `loglevel` | `info` | Defines what kind of logs are provided to stdout. |
|
||||||
| `allowOrigin` | `['localhost']` | domain name whitelist |
|
| `allowOrigin` | `['localhost']` | domain name whitelist |
|
||||||
| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) |
|
| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) |
|
||||||
| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) |
|
| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) |
|
||||||
|
@ -283,6 +288,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) |
|
| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) |
|
||||||
| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
|
| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
|
||||||
| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
|
| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
|
||||||
|
| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` |
|
||||||
| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) |
|
| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) |
|
||||||
| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied |
|
| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied |
|
||||||
| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
|
| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
|
||||||
|
@ -310,6 +316,7 @@ There are some config settings you need to change in the files below.
|
||||||
| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) |
|
| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) |
|
||||||
| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
|
| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
|
||||||
| `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` |
|
| `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` |
|
||||||
|
| `sourceURL` | `https://github.com/hackmdio/codimd/tree/<current commit>` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) |
|
||||||
|
|
||||||
<sup>1</sup>: relative paths are based on CodiMD's base directory
|
<sup>1</sup>: relative paths are based on CodiMD's base directory
|
||||||
|
|
||||||
|
@ -369,7 +376,6 @@ See more at [http://operational-transformation.github.io/](http://operational-tr
|
||||||
[travis-url]: https://travis-ci.org/hackmdio/codimd
|
[travis-url]: https://travis-ci.org/hackmdio/codimd
|
||||||
[github-version-badge]: https://img.shields.io/github/release/hackmdio/codimd.svg
|
[github-version-badge]: https://img.shields.io/github/release/hackmdio/codimd.svg
|
||||||
[github-release-page]: https://github.com/hackmdio/codimd/releases
|
[github-release-page]: https://github.com/hackmdio/codimd/releases
|
||||||
[standardjs-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
|
[github-release-feed]: https://github.com/hackmdio/codimd/releases.atom
|
||||||
[standardjs-url]: https://github.com/feross/standard
|
|
||||||
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg
|
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg
|
||||||
[poeditor-url]: https://poeditor.com/join/project/1OpGjF2Jir
|
[poeditor-url]: https://poeditor.com/join/project/1OpGjF2Jir
|
||||||
|
|
12
app.js
12
app.js
|
@ -53,7 +53,7 @@ if (config.useSSL) {
|
||||||
|
|
||||||
// logger
|
// logger
|
||||||
app.use(morgan('combined', {
|
app.use(morgan('combined', {
|
||||||
'stream': logger
|
'stream': logger.stream
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// socket io
|
// socket io
|
||||||
|
@ -83,7 +83,7 @@ app.use(compression())
|
||||||
// use hsts to tell https users stick to this
|
// use hsts to tell https users stick to this
|
||||||
if (config.hsts.enable) {
|
if (config.hsts.enable) {
|
||||||
app.use(helmet.hsts({
|
app.use(helmet.hsts({
|
||||||
maxAge: config.hsts.maxAgeSeconds * 1000,
|
maxAge: config.hsts.maxAgeSeconds,
|
||||||
includeSubdomains: config.hsts.includeSubdomains,
|
includeSubdomains: config.hsts.includeSubdomains,
|
||||||
preload: config.hsts.preload
|
preload: config.hsts.preload
|
||||||
}))
|
}))
|
||||||
|
@ -125,7 +125,7 @@ app.use(i18n.init)
|
||||||
|
|
||||||
// routes without sessions
|
// routes without sessions
|
||||||
// static files
|
// static files
|
||||||
app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticCacheTime }))
|
app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticCacheTime, index: false }))
|
||||||
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime }))
|
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime }))
|
||||||
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime }))
|
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime }))
|
||||||
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
|
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
|
||||||
|
@ -178,6 +178,7 @@ app.set('view engine', 'ejs')
|
||||||
// set generally available variables for all views
|
// set generally available variables for all views
|
||||||
app.locals.useCDN = config.useCDN
|
app.locals.useCDN = config.useCDN
|
||||||
app.locals.serverURL = config.serverURL
|
app.locals.serverURL = config.serverURL
|
||||||
|
app.locals.sourceURL = config.sourceURL
|
||||||
app.locals.allowAnonymous = config.allowAnonymous
|
app.locals.allowAnonymous = config.allowAnonymous
|
||||||
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
||||||
app.locals.allowPDFExport = config.allowPDFExport
|
app.locals.allowPDFExport = config.allowPDFExport
|
||||||
|
@ -199,6 +200,11 @@ app.locals.authProviders = {
|
||||||
allowEmailRegister: config.allowEmailRegister
|
allowEmailRegister: config.allowEmailRegister
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export/Import menu items
|
||||||
|
app.locals.enableDropBoxSave = config.isDropboxEnable
|
||||||
|
app.locals.enableGitHubGist = config.isGitHubEnable
|
||||||
|
app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable
|
||||||
|
|
||||||
app.use(require('./lib/web/baseRouter'))
|
app.use(require('./lib/web/baseRouter'))
|
||||||
app.use(require('./lib/web/statusRouter'))
|
app.use(require('./lib/web/statusRouter'))
|
||||||
app.use(require('./lib/web/auth'))
|
app.use(require('./lib/web/auth'))
|
||||||
|
|
10
bin/setup
10
bin/setup
|
@ -8,11 +8,12 @@ if [ -d .git ]; then
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! type npm > /dev/null
|
if ! type yarn > /dev/null
|
||||||
then
|
then
|
||||||
cat << EOF
|
cat << EOF
|
||||||
npm is not installed, please install Node.js and npm.
|
yarn is not installed, please install Node.js, npm and yarn.
|
||||||
Read more on Node.js official website: https://nodejs.org
|
Read more on Node.js official website: https://nodejs.org
|
||||||
|
And for yarn package manager at: https://yarnpkg.com/en/
|
||||||
Setup will not be run
|
Setup will not be run
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -27,8 +28,9 @@ if [ ! -f .sequelizerc ]; then
|
||||||
cp .sequelizerc.example .sequelizerc
|
cp .sequelizerc.example .sequelizerc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "install npm packages"
|
echo "install packages"
|
||||||
BUILD_ASSETS=false npm install
|
yarn install --pure-lockfile
|
||||||
|
yarn install --production=false --pure-lockfile
|
||||||
|
|
||||||
cat << EOF
|
cat << EOF
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
|
"loglevel": "debug",
|
||||||
"hsts": {
|
"hsts": {
|
||||||
"enable": false
|
"enable": false
|
||||||
},
|
},
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
|
"loglevel": "info",
|
||||||
"hsts": {
|
"hsts": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"maxAgeSeconds": "31536000",
|
"maxAgeSeconds": "31536000",
|
||||||
|
|
131
docs/guides/migrate-etherpad.md
Normal file
131
docs/guides/migrate-etherpad.md
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
Pad migration guide from etherpad-lite
|
||||||
|
===
|
||||||
|
|
||||||
|
The goal of this migration is to do a "dumb" import from all the pads in Etherpad, to notes in
|
||||||
|
CodiMD. In particular, the url locations of the pads in Etherpad will be lost. Furthermore, any
|
||||||
|
metadata in Etherpad, such as revisions, author data and also formatted text will not be migrated
|
||||||
|
to CodiMD (only the plain text contents).
|
||||||
|
|
||||||
|
Note that this guide is not really meant as a support guide. I migrated my own Etherpad to CodiMD,
|
||||||
|
and it turned out to be quite easy in my opinion. In this guide I share my experience. Stuff may
|
||||||
|
require some creativity to work properly in your case. When I wrote this guide, I was using
|
||||||
|
[Etherpad 1.7.0] and [CodiMD 1.2.1]. Good luck!
|
||||||
|
|
||||||
|
[Etherpad 1.7.0]: https://github.com/ether/etherpad-lite/tree/1.7.0
|
||||||
|
[CodiMD 1.2.1]: https://github.com/hackmdio/codimd/tree/1.2.1
|
||||||
|
|
||||||
|
## 0. Requirements
|
||||||
|
|
||||||
|
- `curl`
|
||||||
|
- running Etherpad server
|
||||||
|
- running CodiMD server
|
||||||
|
- [codimd-cli]
|
||||||
|
|
||||||
|
[codimd-cli]: https://github.com/hackmdio/codimd-cli/blob/master/bin/codimd
|
||||||
|
|
||||||
|
## 1. Retrieve the list of pads
|
||||||
|
|
||||||
|
First, compose a list of all the pads that you want to have migrated from your Etherpad. Other than
|
||||||
|
the admin interface, Etherpad does not have a dedicated function to dump a list of all the pads.
|
||||||
|
However, the Etherpad wiki explains how to list all the pads by [talking directly to the
|
||||||
|
database][howtolistallpads].
|
||||||
|
|
||||||
|
You will end up with a file containing a pad name on each line:
|
||||||
|
|
||||||
|
```
|
||||||
|
date-ideas
|
||||||
|
groceries
|
||||||
|
london
|
||||||
|
weddingchecklist
|
||||||
|
(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
[howtolistallpads]: https://github.com/ether/etherpad-lite/wiki/How-to-list-all-pads/49701ecdcbe07aea7ad27ffa23aed0d99c2e17db
|
||||||
|
|
||||||
|
## 2. Run the migration
|
||||||
|
|
||||||
|
Download [codimd-cli] and put the script in the same directory as the file containing the pad names.
|
||||||
|
Add to this directory the file listed below, I called it `migrate-etherpad.sh`. Modify at least the
|
||||||
|
configuration settings `ETHERPAD_SERVER` and `CODIMD_SERVER`.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# migrate-etherpad.sh
|
||||||
|
#
|
||||||
|
# Description: Migrate pads from etherpad to codimd
|
||||||
|
# Author: Daan Sprenkels <hello@dsprenkels.com>
|
||||||
|
|
||||||
|
# This script uses the codimd command line script[1] to import a list of pads from
|
||||||
|
# [1]: https://github.com/hackmdio/codimd-cli/blob/master/bin/codimd
|
||||||
|
|
||||||
|
# The base url to where etherpad is hosted
|
||||||
|
ETHERPAD_SERVER="https://etherpad.example.com"
|
||||||
|
|
||||||
|
# The base url where codimd is hosted
|
||||||
|
CODIMD_SERVER="https://codimd.example.com"
|
||||||
|
|
||||||
|
# Write a list of pads and the urls which they were migrated to
|
||||||
|
REDIRECTS_FILE="redirects.txt"
|
||||||
|
|
||||||
|
|
||||||
|
# Fail if not called correctly
|
||||||
|
if (( $# != 1 )); then
|
||||||
|
echo "Usage: $0 PAD_NAMES_FILE"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Do the migration
|
||||||
|
for PAD_NAME in $1; do
|
||||||
|
# Download the pad
|
||||||
|
PAD_FILE="$(mktemp)"
|
||||||
|
curl "$ETHERPAD_SERVER/p/$PAD_NAME/export/txt" >"$PAD_FILE"
|
||||||
|
|
||||||
|
# Import the pad into codimd
|
||||||
|
OUTPUT="$(./codimd import "$PAD_FILE")"
|
||||||
|
echo "$PAD_NAME -> $OUTPUT" >>"$REDIRECTS_FILE"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Call this file like this:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./migrate-etherpad.sh pad_names.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download all the pads in `pad_names.txt` and put them on CodiMD. They will get assigned
|
||||||
|
random ids, so you won't be able to find them. The script will save the mappings to a file though
|
||||||
|
(in my case `redirects.txt`). You can use this file to redirect your users when they visit your
|
||||||
|
etherpad using a `301 Permanent Redirect` status code (see the next section).
|
||||||
|
|
||||||
|
## 3. Setup redirects (optional)
|
||||||
|
|
||||||
|
I got a `redirects.txt` file that looked a bit like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
date-ideas -> Found. Redirecting to https://codimd.example.com/mPt0KfiKSBOTQ3mNcdfn
|
||||||
|
groceries -> Found. Redirecting to https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y
|
||||||
|
london -> Found. Redirecting to https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R
|
||||||
|
weddingchecklist -> Found. Redirecting to https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ
|
||||||
|
(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Using some `sed` magic, I changed it to an nginx config snippet:
|
||||||
|
|
||||||
|
```
|
||||||
|
location = /p/date-ideas {
|
||||||
|
return 301 https://codimd.example.com/mPt0M1KfiKSBOTQ3mNcdfn;
|
||||||
|
}
|
||||||
|
location = /p/groceries {
|
||||||
|
return 301 https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y;
|
||||||
|
}
|
||||||
|
location = /p/london {
|
||||||
|
return 301 https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R;
|
||||||
|
}
|
||||||
|
location = /p/weddingchecklist {
|
||||||
|
return 301 https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I put this file into my `etherpad.example.com` nginx config, such that all the users would be
|
||||||
|
redirected accordingly.
|
|
@ -1,16 +1,19 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
domain: '',
|
domain: '',
|
||||||
urlPath: '',
|
urlPath: '',
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
loglevel: 'info',
|
||||||
urlAddPort: false,
|
urlAddPort: false,
|
||||||
allowOrigin: ['localhost'],
|
allowOrigin: ['localhost'],
|
||||||
useSSL: false,
|
useSSL: false,
|
||||||
hsts: {
|
hsts: {
|
||||||
enable: true,
|
enable: true,
|
||||||
maxAgeSeconds: 31536000,
|
maxAgeSeconds: 60 * 60 * 24 * 365,
|
||||||
includeSubdomains: true,
|
includeSubdomains: true,
|
||||||
preload: true
|
preload: true
|
||||||
},
|
},
|
||||||
|
@ -29,6 +32,7 @@ module.exports = {
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
allowAnonymousEdits: false,
|
allowAnonymousEdits: false,
|
||||||
allowFreeURL: false,
|
allowFreeURL: false,
|
||||||
|
forbiddenNoteIDs: ['robots.txt', 'favicon.ico', 'api'],
|
||||||
defaultPermission: 'editable',
|
defaultPermission: 'editable',
|
||||||
dbURL: '',
|
dbURL: '',
|
||||||
db: {},
|
db: {},
|
||||||
|
@ -39,7 +43,7 @@ module.exports = {
|
||||||
dhParamPath: '',
|
dhParamPath: '',
|
||||||
// other path
|
// other path
|
||||||
viewPath: './public/views',
|
viewPath: './public/views',
|
||||||
tmpPath: './tmp',
|
tmpPath: os.tmpdir(),
|
||||||
defaultNotePath: './public/default.md',
|
defaultNotePath: './public/default.md',
|
||||||
docsPath: './public/docs',
|
docsPath: './public/docs',
|
||||||
uploadsPath: './public/uploads',
|
uploadsPath: './public/uploads',
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils')
|
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
sourceURL: process.env.CMD_SOURCE_URL,
|
||||||
domain: process.env.CMD_DOMAIN,
|
domain: process.env.CMD_DOMAIN,
|
||||||
urlPath: process.env.CMD_URL_PATH,
|
urlPath: process.env.CMD_URL_PATH,
|
||||||
host: process.env.CMD_HOST,
|
host: process.env.CMD_HOST,
|
||||||
port: toIntegerConfig(process.env.CMD_PORT),
|
port: toIntegerConfig(process.env.CMD_PORT),
|
||||||
path: process.env.CMD_PATH,
|
path: process.env.CMD_PATH,
|
||||||
|
loglevel: process.env.CMD_LOGLEVEL,
|
||||||
urlAddPort: toBooleanConfig(process.env.CMD_URL_ADDPORT),
|
urlAddPort: toBooleanConfig(process.env.CMD_URL_ADDPORT),
|
||||||
useSSL: toBooleanConfig(process.env.CMD_USESSL),
|
useSSL: toBooleanConfig(process.env.CMD_USESSL),
|
||||||
hsts: {
|
hsts: {
|
||||||
|
@ -26,6 +28,7 @@ module.exports = {
|
||||||
allowAnonymous: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS),
|
allowAnonymous: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS),
|
||||||
allowAnonymousEdits: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_EDITS),
|
allowAnonymousEdits: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_EDITS),
|
||||||
allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL),
|
allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL),
|
||||||
|
forbiddenNoteIDs: toArrayConfig(process.env.CMD_FORBIDDEN_NOTE_IDS),
|
||||||
defaultPermission: process.env.CMD_DEFAULT_PERMISSION,
|
defaultPermission: process.env.CMD_DEFAULT_PERMISSION,
|
||||||
dbURL: process.env.CMD_DB_URL,
|
dbURL: process.env.CMD_DB_URL,
|
||||||
sessionSecret: process.env.CMD_SESSION_SECRET,
|
sessionSecret: process.env.CMD_SESSION_SECRET,
|
||||||
|
|
|
@ -8,6 +8,7 @@ const {merge} = require('lodash')
|
||||||
const deepFreeze = require('deep-freeze')
|
const deepFreeze = require('deep-freeze')
|
||||||
const {Environment, Permission} = require('./enum')
|
const {Environment, Permission} = require('./enum')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
const {getGitCommit, getGitHubURL} = require('./utils')
|
||||||
|
|
||||||
const appRootPath = path.resolve(__dirname, '../../')
|
const appRootPath = path.resolve(__dirname, '../../')
|
||||||
const env = process.env.NODE_ENV || Environment.development
|
const env = process.env.NODE_ENV || Environment.development
|
||||||
|
@ -16,11 +17,17 @@ const debugConfig = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get version string from package.json
|
// Get version string from package.json
|
||||||
const {version} = require(path.join(appRootPath, 'package.json'))
|
const {version, repository} = require(path.join(appRootPath, 'package.json'))
|
||||||
|
|
||||||
|
const commitID = getGitCommit(appRootPath)
|
||||||
|
const sourceURL = getGitHubURL(repository.url, commitID || version)
|
||||||
|
const fullversion = commitID ? `${version}-${commitID}` : version
|
||||||
|
|
||||||
const packageConfig = {
|
const packageConfig = {
|
||||||
version: version,
|
version: version,
|
||||||
minimumCompatibleVersion: '0.5.0'
|
minimumCompatibleVersion: '0.5.0',
|
||||||
|
fullversion: fullversion,
|
||||||
|
sourceURL: sourceURL
|
||||||
}
|
}
|
||||||
|
|
||||||
const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE ||
|
const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE ||
|
||||||
|
@ -38,6 +45,12 @@ merge(config, require('./hackmdEnvironment'))
|
||||||
merge(config, require('./environment'))
|
merge(config, require('./environment'))
|
||||||
merge(config, require('./dockerSecret'))
|
merge(config, require('./dockerSecret'))
|
||||||
|
|
||||||
|
if (['debug', 'verbose', 'info', 'warn', 'error'].includes(config.loglevel)) {
|
||||||
|
logger.level = config.loglevel
|
||||||
|
} else {
|
||||||
|
logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', config.loglevel)
|
||||||
|
}
|
||||||
|
|
||||||
// load LDAP CA
|
// load LDAP CA
|
||||||
if (config.ldap.tlsca) {
|
if (config.ldap.tlsca) {
|
||||||
let ca = config.ldap.tlsca.split(',')
|
let ca = config.ldap.tlsca.split(',')
|
||||||
|
@ -110,6 +123,8 @@ if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !==
|
||||||
logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4')
|
logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4')
|
||||||
config.gitlab.version = 'v4'
|
config.gitlab.version = 'v4'
|
||||||
}
|
}
|
||||||
|
// If gitlab scope is api, enable snippets Export/import
|
||||||
|
config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === 'api')
|
||||||
|
|
||||||
// Only update i18n files in development setups
|
// Only update i18n files in development setups
|
||||||
config.updateI18nFiles = (env === Environment.development)
|
config.updateI18nFiles = (env === Environment.development)
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
exports.toBooleanConfig = function toBooleanConfig (configValue) {
|
exports.toBooleanConfig = function toBooleanConfig (configValue) {
|
||||||
if (configValue && typeof configValue === 'string') {
|
if (configValue && typeof configValue === 'string') {
|
||||||
return (configValue === 'true')
|
return (configValue === 'true')
|
||||||
|
@ -20,3 +23,33 @@ exports.toIntegerConfig = function toIntegerConfig (configValue) {
|
||||||
}
|
}
|
||||||
return configValue
|
return configValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.getGitCommit = function getGitCommit (repodir) {
|
||||||
|
if (!fs.existsSync(repodir + '/.git/HEAD')) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
let reference = fs.readFileSync(repodir + '/.git/HEAD', 'utf8')
|
||||||
|
if (reference.startsWith('ref: ')) {
|
||||||
|
reference = reference.substr(5).replace('\n', '')
|
||||||
|
reference = fs.readFileSync(path.resolve(repodir + '/.git', reference), 'utf8')
|
||||||
|
}
|
||||||
|
reference = reference.replace('\n', '')
|
||||||
|
return reference
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getGitHubURL = function getGitHubURL (repo, reference) {
|
||||||
|
// if it's not a github reference, we handle handle that anyway
|
||||||
|
if (!repo.startsWith('https://github.com') && !repo.startsWith('git@github.com')) {
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
if (repo.startsWith('git@github.com') || repo.startsWith('ssh://git@github.com')) {
|
||||||
|
repo = repo.replace(/^(ssh:\/\/)?git@github.com:/, 'https://github.com/')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo.endsWith('.git')) {
|
||||||
|
repo = repo.replace(/\.git$/, '/')
|
||||||
|
} else if (!repo.endsWith('/')) {
|
||||||
|
repo = repo + '/'
|
||||||
|
}
|
||||||
|
return repo + 'tree/' + reference
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,27 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
const winston = require('winston')
|
const {createLogger, format, transports} = require('winston')
|
||||||
|
|
||||||
class Logger extends winston.Logger {
|
const logger = createLogger({
|
||||||
// Implement stream.writable.write interface
|
|
||||||
write (chunk) {
|
|
||||||
this.info(chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = new Logger({
|
|
||||||
transports: [
|
|
||||||
new winston.transports.Console({
|
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
handleExceptions: true,
|
format: format.combine(
|
||||||
json: false,
|
format.uncolorize(),
|
||||||
colorize: false,
|
format.timestamp(),
|
||||||
timestamp: true
|
format.align(),
|
||||||
|
format.splat(),
|
||||||
|
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new transports.Console({
|
||||||
|
handleExceptions: true
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
emitErrs: true,
|
|
||||||
exitOnError: false
|
exitOnError: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
logger.stream = {
|
||||||
|
write: function (message, encoding) {
|
||||||
|
logger.info(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = logger
|
||||||
|
|
|
@ -21,7 +21,7 @@ module.exports = {
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'shortid'" || error.message === 'column "shortid" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: shortid' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'shortid'" || error.message === 'column "shortid" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -8,7 +8,7 @@ module.exports = {
|
||||||
type: Sequelize.DATE
|
type: Sequelize.DATE
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'lastchangeuserId'" || error.message === 'column "lastchangeuserId" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: lastchangeuserId' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'lastchangeuserId'" || error.message === 'column "lastchangeuserId" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -8,7 +8,7 @@ module.exports = {
|
||||||
indicesType: 'UNIQUE'
|
indicesType: 'UNIQUE'
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'alias'" || error.message === 'column "alias" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: alias' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'alias'" || error.message === 'column "alias" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -4,7 +4,7 @@ module.exports = {
|
||||||
return queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING).then(function () {
|
return queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING).then(function () {
|
||||||
return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
|
return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'accessToken'" || error.message === 'column "accessToken" of relation "Users" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: accessToken' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'accessToken'" || error.message === 'column "accessToken" of relation "Users" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
updatedAt: Sequelize.DATE
|
updatedAt: Sequelize.DATE
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'savedAt'" || error.message === 'column "savedAt" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: savedAt' | error.message === "ER_DUP_FIELDNAME: Duplicate column name 'savedAt'" || error.message === 'column "savedAt" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -17,7 +17,7 @@ module.exports = {
|
||||||
updatedAt: Sequelize.DATE
|
updatedAt: Sequelize.DATE
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'authorship'" || error.message === 'column "authorship" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: authorship' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'authorship'" || error.message === 'column "authorship" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) {
|
return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'deletedAt'" || error.message === 'column "deletedAt" of relation "Notes" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: deletedAt' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'deletedAt'" || error.message === 'column "deletedAt" of relation "Notes" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -10,7 +10,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'email'" || error.message === 'column "email" of relation "Users" already exists') {
|
if (error.message === 'SQLITE_ERROR: duplicate column name: email' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'email'" || error.message === 'column "email" of relation "Users" already exists') {
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -25,6 +25,7 @@ if (config.dbURL) {
|
||||||
// https://github.com/sequelize/sequelize/issues/6485
|
// https://github.com/sequelize/sequelize/issues/6485
|
||||||
function stripNullByte (value) {
|
function stripNullByte (value) {
|
||||||
value = '' + value
|
value = '' + value
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
return value ? value.replace(/\u0000/g, '') : value
|
return value ? value.replace(/\u0000/g, '') : value
|
||||||
}
|
}
|
||||||
sequelize.stripNullByte = stripNullByte
|
sequelize.stripNullByte = stripNullByte
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
// external modules
|
// external modules
|
||||||
var Sequelize = require('sequelize')
|
var Sequelize = require('sequelize')
|
||||||
var scrypt = require('scrypt')
|
var scrypt = require('@mlink/scrypt')
|
||||||
|
|
||||||
// core
|
// core
|
||||||
var logger = require('../logger')
|
var logger = require('../logger')
|
||||||
|
@ -50,7 +50,7 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
}, {
|
}, {
|
||||||
instanceMethods: {
|
instanceMethods: {
|
||||||
verifyPassword: function (attempt) {
|
verifyPassword: function (attempt) {
|
||||||
if (scrypt.verifyKdfSync(new Buffer(this.password, 'hex'), attempt)) {
|
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
|
||||||
return this
|
return this
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -44,7 +44,7 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
|
||||||
socket.origin = 'operation';
|
socket.origin = 'operation';
|
||||||
self.mayWrite(socket, function (mayWrite) {
|
self.mayWrite(socket, function (mayWrite) {
|
||||||
if (!mayWrite) {
|
if (!mayWrite) {
|
||||||
console.log("User doesn't have the right to edit.");
|
logger.info("User doesn't have the right to edit.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -71,14 +71,14 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
|
||||||
socket.origin = 'selection';
|
socket.origin = 'selection';
|
||||||
self.mayWrite(socket, function (mayWrite) {
|
self.mayWrite(socket, function (mayWrite) {
|
||||||
if (!mayWrite) {
|
if (!mayWrite) {
|
||||||
console.log("User doesn't have the right to edit.");
|
logger.info("User doesn't have the right to edit.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.updateSelection(socket, obj && Selection.fromJSON(obj));
|
self.updateSelection(socket, obj && Selection.fromJSON(obj));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
socket.on('disconnect', function () {
|
socket.on('disconnect', function () {
|
||||||
//console.log("Disconnect");
|
logger.debug("Disconnect");
|
||||||
socket.leave(self.docId);
|
socket.leave(self.docId);
|
||||||
self.onDisconnect(socket);
|
self.onDisconnect(socket);
|
||||||
/*
|
/*
|
||||||
|
@ -106,7 +106,7 @@ EditorSocketIOServer.prototype.onOperation = function (socket, revision, operati
|
||||||
var clientId = socket.id;
|
var clientId = socket.id;
|
||||||
var wrappedPrime = this.receiveOperation(revision, wrapped);
|
var wrappedPrime = this.receiveOperation(revision, wrapped);
|
||||||
if(!wrappedPrime) return;
|
if(!wrappedPrime) return;
|
||||||
//console.log("new operation: " + JSON.stringify(wrapped));
|
logger.debug("new operation: " + JSON.stringify(wrapped));
|
||||||
this.getClient(clientId).selection = wrappedPrime.meta;
|
this.getClient(clientId).selection = wrappedPrime.meta;
|
||||||
revision = this.operations.length;
|
revision = this.operations.length;
|
||||||
socket.emit('ack', revision);
|
socket.emit('ack', revision);
|
||||||
|
|
|
@ -242,6 +242,7 @@ function getStatus (callback) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
models.User.count().then(function (regcount) {
|
models.User.count().then(function (regcount) {
|
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
return callback ? callback({
|
return callback ? callback({
|
||||||
onlineNotes: Object.keys(notes).length,
|
onlineNotes: Object.keys(notes).length,
|
||||||
onlineUsers: Object.keys(users).length,
|
onlineUsers: Object.keys(users).length,
|
||||||
|
@ -283,7 +284,7 @@ function extractNoteIdFromSocket (socket) {
|
||||||
if (!referer) {
|
if (!referer) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var hostUrl = url.parse(referer)
|
var hostUrl = url.URL.parse(referer)
|
||||||
var noteId = config.urlPath ? hostUrl.pathname.slice(config.urlPath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]
|
var noteId = config.urlPath ? hostUrl.pathname.slice(config.urlPath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]
|
||||||
return noteId
|
return noteId
|
||||||
} else {
|
} else {
|
||||||
|
@ -887,7 +888,7 @@ function connection (socket) {
|
||||||
// check version
|
// check version
|
||||||
socket.on('version', function () {
|
socket.on('version', function () {
|
||||||
socket.emit('version', {
|
socket.emit('version', {
|
||||||
version: config.version,
|
version: config.fullversion,
|
||||||
minimumCompatibleVersion: config.minimumCompatibleVersion
|
minimumCompatibleVersion: config.minimumCompatibleVersion
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -157,7 +157,7 @@ function findNote (req, res, callback, include) {
|
||||||
include: include || null
|
include: include || null
|
||||||
}).then(function (note) {
|
}).then(function (note) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
if (config.allowFreeURL && noteId) {
|
if (config.allowFreeURL && noteId && !config.forbiddenNoteIDs.includes(noteId)) {
|
||||||
req.alias = noteId
|
req.alias = noteId
|
||||||
return newNote(req, res)
|
return newNote(req, res)
|
||||||
} else {
|
} else {
|
||||||
|
@ -423,6 +423,9 @@ function publishNoteActions (req, res, next) {
|
||||||
findNote(req, res, function (note) {
|
findNote(req, res, function (note) {
|
||||||
var action = req.params.action
|
var action = req.params.action
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case 'download':
|
||||||
|
actionDownload(req, res, note)
|
||||||
|
break
|
||||||
case 'edit':
|
case 'edit':
|
||||||
res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
|
res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
|
||||||
break
|
break
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
const Router = require('express').Router
|
const Router = require('express').Router
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
const OAuth2Strategy = require('passport-oauth2').Strategy
|
const { Strategy, InternalOAuthError } = require('passport-oauth2')
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
||||||
|
|
||||||
let oauth2Auth = module.exports = Router()
|
let oauth2Auth = module.exports = Router()
|
||||||
|
|
||||||
class OAuth2CustomStrategy extends OAuth2Strategy {
|
class OAuth2CustomStrategy extends Strategy {
|
||||||
constructor (options, verify) {
|
constructor (options, verify) {
|
||||||
options.customHeaders = options.customHeaders || {}
|
options.customHeaders = options.customHeaders || {}
|
||||||
super(options, verify)
|
super(options, verify)
|
||||||
|
@ -22,7 +22,7 @@ class OAuth2CustomStrategy extends OAuth2Strategy {
|
||||||
var json
|
var json
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
|
return done(new InternalOAuthError('Failed to fetch user profile', err))
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -67,7 +67,7 @@ OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) {
|
||||||
var json
|
var json
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
|
return done(new InternalOAuthError('Failed to fetch user profile', err))
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,5 +16,5 @@ exports.uploadImage = function (imagePath, callback) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(null, url.resolve(config.serverURL + '/uploads/', path.basename(imagePath)))
|
callback(null, url.URL.resolve(config.serverURL + '/uploads/', path.basename(imagePath)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ statusRouter.get('/config', function (req, res) {
|
||||||
domain: config.domain,
|
domain: config.domain,
|
||||||
urlpath: config.urlPath,
|
urlpath: config.urlPath,
|
||||||
debug: config.debug,
|
debug: config.debug,
|
||||||
version: config.version,
|
version: config.fullversion,
|
||||||
DROPBOX_APP_KEY: config.dropbox.appKey,
|
DROPBOX_APP_KEY: config.dropbox.appKey,
|
||||||
allowedUploadMimeTypes: config.allowedUploadMimeTypes
|
allowedUploadMimeTypes: config.allowedUploadMimeTypes
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,5 +111,7 @@
|
||||||
"Do you really want to delete your user account?": "Möchten Sie wirklich Ihr Nutzeraccount löschen?",
|
"Do you really want to delete your user account?": "Möchten Sie wirklich Ihr Nutzeraccount löschen?",
|
||||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Hiermit löschen Sie Ihren Account, alle Ihre Dokumente und alle Verweise auf Ihren Account aus anderen Dokumenten.",
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Hiermit löschen Sie Ihren Account, alle Ihre Dokumente und alle Verweise auf Ihren Account aus anderen Dokumenten.",
|
||||||
"Delete user": "Benutzer löschen",
|
"Delete user": "Benutzer löschen",
|
||||||
"Export user data": "Exportiere Nutzerdaten"
|
"Export user data": "Exportiere Nutzerdaten",
|
||||||
|
"Help us translating on %s": "Hilf uns übersetzen auf %s",
|
||||||
|
"Source Code": "Quelltext"
|
||||||
}
|
}
|
|
@ -112,5 +112,6 @@
|
||||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "This will delete your account, all notes that are owned by you and remove all references to your account from other notes.",
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "This will delete your account, all notes that are owned by you and remove all references to your account from other notes.",
|
||||||
"Delete user": "Delete user",
|
"Delete user": "Delete user",
|
||||||
"Export user data": "Export user data",
|
"Export user data": "Export user data",
|
||||||
"Help us translating on %s": "Help us translating on %s"
|
"Help us translating on %s": "Help us translating on %s",
|
||||||
|
"Source Code": "Source Code"
|
||||||
}
|
}
|
|
@ -112,5 +112,6 @@
|
||||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Cela supprimera votre compte, toutes les notes dont vous êtes propriétaire et supprimera toute référence à votre compte dans les autres notes.",
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Cela supprimera votre compte, toutes les notes dont vous êtes propriétaire et supprimera toute référence à votre compte dans les autres notes.",
|
||||||
"Delete user": "Suprrimez l'utilisteur",
|
"Delete user": "Suprrimez l'utilisteur",
|
||||||
"Export user data": "Exportez les données utilisateur",
|
"Export user data": "Exportez les données utilisateur",
|
||||||
"Help us translating on %s": ""
|
"Help us translating on %s": "Aidez nous à traduire sur %s",
|
||||||
|
"Source Code": "Code source"
|
||||||
}
|
}
|
|
@ -112,5 +112,6 @@
|
||||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Questo cancellerà il tuo account, tutte le note di cui sei proprietario e rimuoverà i riferimenti al tuo account dalle altre note.",
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Questo cancellerà il tuo account, tutte le note di cui sei proprietario e rimuoverà i riferimenti al tuo account dalle altre note.",
|
||||||
"Delete user": "Elimina utente",
|
"Delete user": "Elimina utente",
|
||||||
"Export user data": "Esporta dati utente",
|
"Export user data": "Esporta dati utente",
|
||||||
"Help us translating on %s": "Aiutaci nella traduzione su %s"
|
"Help us translating on %s": "Aiutaci nella traduzione su %s",
|
||||||
|
"Source Code": "Codice Sorgente"
|
||||||
}
|
}
|
|
@ -84,7 +84,7 @@
|
||||||
"Link": "링크",
|
"Link": "링크",
|
||||||
"Image": "이미지",
|
"Image": "이미지",
|
||||||
"Code": "코드",
|
"Code": "코드",
|
||||||
"Externals": "Externals",
|
"Externals": "외부 서비스 연동",
|
||||||
"This is a alert area.": "여기는 알림 공간입니다.",
|
"This is a alert area.": "여기는 알림 공간입니다.",
|
||||||
"Revert": "되돌리기",
|
"Revert": "되돌리기",
|
||||||
"Import from clipboard": "클립보드에서 불러오기",
|
"Import from clipboard": "클립보드에서 불러오기",
|
||||||
|
@ -105,5 +105,13 @@
|
||||||
"Export to Snippet": "Export to Snippet",
|
"Export to Snippet": "Export to Snippet",
|
||||||
"Select Visibility Level": "Select Visibility Level",
|
"Select Visibility Level": "Select Visibility Level",
|
||||||
"Night Theme": "다크 테마",
|
"Night Theme": "다크 테마",
|
||||||
"Follow us on %s and %s.": "%s과 %s에서 저희를 팔로우해보세요"
|
"Follow us on %s and %s.": "%s과 %s에서 저희를 팔로우해보세요",
|
||||||
|
"Privacy": "Privacy",
|
||||||
|
"Terms of Use": "Terms of Use",
|
||||||
|
"Do you really want to delete your user account?": "Do you really want to delete your user account?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "This will delete your account, all notes that are owned by you and remove all references to your account from other notes.",
|
||||||
|
"Delete user": "Delete user",
|
||||||
|
"Export user data": "Export user data",
|
||||||
|
"Help us translating on %s": "%s에서 번역으로 저희를 도와주세요",
|
||||||
|
"Source Code": "Source Code"
|
||||||
}
|
}
|
|
@ -7,13 +7,13 @@
|
||||||
"New guest note": "Nieuwe gastnotitie",
|
"New guest note": "Nieuwe gastnotitie",
|
||||||
"Collaborate with URL": "Samenwerken met URL",
|
"Collaborate with URL": "Samenwerken met URL",
|
||||||
"Support charts and MathJax": "Ondersteun grafieken en MathJax",
|
"Support charts and MathJax": "Ondersteun grafieken en MathJax",
|
||||||
"Support slide mode": "Ondersteun slide mode",
|
"Support slide mode": "Ondersteun presentatiemodus",
|
||||||
"Sign In": "Log in",
|
"Sign In": "Inloggen",
|
||||||
"Below is the history from browser": "Hier onder staat de browser geschiedenis",
|
"Below is the history from browser": "Hier onder staat de browser geschiedenis",
|
||||||
"Welcome!": "Welkom!",
|
"Welcome!": "Welkom!",
|
||||||
"New note": "Nieuwe notitie",
|
"New note": "Nieuwe notitie",
|
||||||
"or": "of",
|
"or": "of",
|
||||||
"Sign Out": "Log uit",
|
"Sign Out": "Uitloggen",
|
||||||
"Explore all features": "Ontdek alle features",
|
"Explore all features": "Ontdek alle features",
|
||||||
"Select tags...": "Selecteer tags...",
|
"Select tags...": "Selecteer tags...",
|
||||||
"Search keyword...": "Zoeken op keyword...",
|
"Search keyword...": "Zoeken op keyword...",
|
||||||
|
@ -38,43 +38,44 @@
|
||||||
"New": "Nieuw",
|
"New": "Nieuw",
|
||||||
"Publish": "Publiceren",
|
"Publish": "Publiceren",
|
||||||
"Extra": "Extra",
|
"Extra": "Extra",
|
||||||
"Revision": "Herzien",
|
"Revision": "Versie",
|
||||||
"Slide Mode": "Slide Mode",
|
"Slide Mode": "Presentatiemodus",
|
||||||
"Export": "Exporteren",
|
"Export": "Exporteren",
|
||||||
"Import": "Importeren",
|
"Import": "Importeren",
|
||||||
"Clipboard": "Kladbord",
|
"Clipboard": "Kladbord",
|
||||||
"Download": "Downloaden",
|
"Download": "Downloaden",
|
||||||
"Raw HTML": "Raw HTML",
|
"Raw HTML": "Ruwe HTML",
|
||||||
"Edit": "Aanpassen",
|
"Edit": "Aanpassen",
|
||||||
"View": "Bekijken",
|
"View": "Bekijken",
|
||||||
"Both": "Beide",
|
"Both": "Beide",
|
||||||
"Help": "Help",
|
"Help": "Help",
|
||||||
"Upload Image": "Afbeelding uploaden",
|
"Upload Image": "Afbeelding uploaden",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
"This page need refresh": "Deze pagina moet herladen worden",
|
"This page need refresh": "Deze pagina moet vernieuwd worden",
|
||||||
"You have an incompatible client version.": "Je client is niet compatible.",
|
"You have an incompatible client version.": "Je client is niet compatibel.",
|
||||||
"Refresh to update.": "Ververs om te updaten.",
|
"Refresh to update.": "Ververs om te updaten.",
|
||||||
"New version available!": "Nieuwe versie beschikbaar!",
|
"New version available!": "Nieuwe versie beschikbaar!",
|
||||||
"See releases notes here": "Zie releases hier",
|
"See releases notes here": "Bekijk de release notes hier",
|
||||||
"Refresh to enjoy new features.": "Ververs om de nieuwe features te zien.",
|
"Refresh to enjoy new features.": "Ververs om de nieuwe features te zien.",
|
||||||
"Your user state has changed.": "Je gebruikers-status is veranderd.",
|
"Your user state has changed.": "Je gebruikers-status is veranderd.",
|
||||||
"Refresh to load new user state.": "Ververs om je nieuwe gebruikers-status te zien.",
|
"Refresh to load new user state.": "Ververs om je nieuwe gebruikers-status te zien.",
|
||||||
"Refresh": "Ververs",
|
"Refresh": "Ververs",
|
||||||
"Contacts": "Contacten",
|
"Contacts": "Contacten",
|
||||||
"Report an issue": "Probleem rapporteren",
|
"Report an issue": "Probleem rapporteren",
|
||||||
|
"Meet us on %s": "Ontmoet ons op %s",
|
||||||
"Send us email": "Stuur ons een mail",
|
"Send us email": "Stuur ons een mail",
|
||||||
"Documents": "Documenten",
|
"Documents": "Documenten",
|
||||||
"Features": "Features",
|
"Features": "Features",
|
||||||
"YAML Metadata": "YAML Metadata",
|
"YAML Metadata": "YAML Metadata",
|
||||||
"Slide Example": "Slide Voorbeeld",
|
"Slide Example": "Slide Voorbeeld",
|
||||||
"Cheatsheet": "Afkijkblad",
|
"Cheatsheet": "Spiekbrief",
|
||||||
"Example": "Voorbeeld",
|
"Example": "Voorbeeld",
|
||||||
"Syntax": "Syntax",
|
"Syntax": "Syntax",
|
||||||
"Header": "Koptekst",
|
"Header": "Koptekst",
|
||||||
"Unordered List": "Niet gesorteerde Lijst",
|
"Unordered List": "Niet gesorteerde Lijst",
|
||||||
"Ordered List": "Gesorteerde List",
|
"Ordered List": "Gesorteerde List",
|
||||||
"Todo List": "Todo Lijst",
|
"Todo List": "Todo Lijst",
|
||||||
"Blockquote": "Quote",
|
"Blockquote": "Citaat",
|
||||||
"Bold font": "Bold tekst",
|
"Bold font": "Bold tekst",
|
||||||
"Italics font": "Italics tekst",
|
"Italics font": "Italics tekst",
|
||||||
"Strikethrough": "Doorstreepte tekst",
|
"Strikethrough": "Doorstreepte tekst",
|
||||||
|
@ -102,5 +103,15 @@
|
||||||
"Select From Available Snippets": "Selecteer van beschikbare Snippets",
|
"Select From Available Snippets": "Selecteer van beschikbare Snippets",
|
||||||
"OR": "OF",
|
"OR": "OF",
|
||||||
"Export to Snippet": "Exporteren naar Snippet",
|
"Export to Snippet": "Exporteren naar Snippet",
|
||||||
"Select Visibility Level": "Selecteer zichtbaarheids niveau"
|
"Select Visibility Level": "Selecteer zichtbaarheids niveau",
|
||||||
|
"Night Theme": "Nachtweergave",
|
||||||
|
"Follow us on %s and %s.": "Volg ons op %s en %s.",
|
||||||
|
"Privacy": "Privacy",
|
||||||
|
"Terms of Use": "Gebruikersvoorwaarden",
|
||||||
|
"Do you really want to delete your user account?": "Weet je zeker dat je je account wilt verwijderen?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Dit zal je account verwijderen. Alle notities waar je eigenaar van bent worden verwijderd, samen met alle verwijzingen naar je account.",
|
||||||
|
"Delete user": "Gebruiker verwijderen",
|
||||||
|
"Export user data": "Gebruikersdata exporteren",
|
||||||
|
"Help us translating on %s": "Help ons vertalen op %s",
|
||||||
|
"Source Code": "Broncode"
|
||||||
}
|
}
|
|
@ -29,6 +29,8 @@
|
||||||
"Import from browser": "从浏览器导入",
|
"Import from browser": "从浏览器导入",
|
||||||
"Releases": "版本",
|
"Releases": "版本",
|
||||||
"Are you sure?": "你确定吗?",
|
"Are you sure?": "你确定吗?",
|
||||||
|
"Do you really want to delete this note?": "确定要删除这个文件吗?",
|
||||||
|
"All users will lose their connection.": "所有用户将失去连接",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Yes, do it!": "没错,就这样办!",
|
"Yes, do it!": "没错,就这样办!",
|
||||||
"Choose method": "选择方式",
|
"Choose method": "选择方式",
|
||||||
|
@ -103,7 +105,13 @@
|
||||||
"Export to Snippet": "导出到 Snippet",
|
"Export to Snippet": "导出到 Snippet",
|
||||||
"Select Visibility Level": "选择可见层级",
|
"Select Visibility Level": "选择可见层级",
|
||||||
"Night Theme": "夜间主题",
|
"Night Theme": "夜间主题",
|
||||||
"Do you really want to delete this note?": "确定要删除这个文件吗?",
|
"Follow us on %s and %s.": "在%s和%s上关注我们",
|
||||||
"All users will lose their connection.": "所有用户将失去连接",
|
"Privacy": "隐私政策",
|
||||||
"Follow us on %s and %s.": "在%s和%s上关注我们"
|
"Terms of Use": "使用条款",
|
||||||
|
"Do you really want to delete your user account?": "你确定真的想要删除帐户?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "我们将会删除你的帐户、你所拥有的笔记、以及你在别人笔记里的作者纪录。",
|
||||||
|
"Delete user": "删除帐户",
|
||||||
|
"Export user data": "汇出使用者资料",
|
||||||
|
"Help us translating on %s": "来 %s 帮我们翻译",
|
||||||
|
"Source Code": "源码"
|
||||||
}
|
}
|
|
@ -29,6 +29,8 @@
|
||||||
"Import from browser": "從瀏覽器匯入",
|
"Import from browser": "從瀏覽器匯入",
|
||||||
"Releases": "版本",
|
"Releases": "版本",
|
||||||
"Are you sure?": "你確定嗎?",
|
"Are you sure?": "你確定嗎?",
|
||||||
|
"Do you really want to delete this note?": "確定要刪除這個文件嗎?",
|
||||||
|
"All users will lose their connection.": "所有使用者將會失去連線",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Yes, do it!": "沒錯,就這樣辦!",
|
"Yes, do it!": "沒錯,就這樣辦!",
|
||||||
"Choose method": "選擇方式",
|
"Choose method": "選擇方式",
|
||||||
|
@ -103,6 +105,13 @@
|
||||||
"Export to Snippet": "匯出到 Snippet",
|
"Export to Snippet": "匯出到 Snippet",
|
||||||
"Select Visibility Level": "選擇可見層級",
|
"Select Visibility Level": "選擇可見層級",
|
||||||
"Night Theme": "夜間主題",
|
"Night Theme": "夜間主題",
|
||||||
"Do you really want to delete this note?": "確定要刪除這個文件嗎?",
|
"Follow us on %s and %s.": "來 %s 或 %s 和我們互動吧!",
|
||||||
"All users will lose their connection.": "所有使用者將會失去連線"
|
"Privacy": "隱私權政策",
|
||||||
|
"Terms of Use": "使用條款",
|
||||||
|
"Do you really want to delete your user account?": "你確定真的想要刪除帳戶?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "我們將會刪除你的帳戶、你所擁有的筆記、以及你在別人筆記裡的作者紀錄。",
|
||||||
|
"Delete user": "刪除使用者",
|
||||||
|
"Export user data": "匯出使用者資料",
|
||||||
|
"Help us translating on %s": "來 %s 幫我們翻譯",
|
||||||
|
"Source Code": "原始碼"
|
||||||
}
|
}
|
63
package.json
63
package.json
|
@ -5,13 +5,14 @@
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run-script standard && npm run-script jsonlint",
|
"test": "npm run-script eslint && npm run-script jsonlint",
|
||||||
|
"eslint": "node_modules/.bin/eslint lib public app.js",
|
||||||
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
||||||
"standard": "node ./node_modules/standard/bin/cmd.js",
|
"standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1",
|
||||||
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
||||||
"build": "webpack --config webpack.prod.js --progress --colors --bail",
|
"build": "webpack --config webpack.prod.js --progress --colors --bail",
|
||||||
"postinstall": "bin/heroku",
|
"postinstall": "bin/heroku",
|
||||||
"start": "node app.js",
|
"start": "sequelize db:migrate && node app.js",
|
||||||
"doctoc": "doctoc --title='# Table of Contents' README.md"
|
"doctoc": "doctoc --title='# Table of Contents' README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
|
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
|
||||||
"archiver": "^2.1.1",
|
"archiver": "^2.1.1",
|
||||||
"async": "^2.1.4",
|
"async": "^2.1.4",
|
||||||
"aws-sdk": "^2.7.20",
|
"aws-sdk": "^2.345.0",
|
||||||
"azure-storage": "^2.7.0",
|
"azure-storage": "^2.7.0",
|
||||||
"base64url": "^3.0.0",
|
"base64url": "^3.0.0",
|
||||||
"blueimp-md5": "^2.6.0",
|
"blueimp-md5": "^2.6.0",
|
||||||
|
@ -62,7 +63,7 @@
|
||||||
"jsdom-nogyp": "^0.8.3",
|
"jsdom-nogyp": "^0.8.3",
|
||||||
"keymaster": "^1.6.2",
|
"keymaster": "^1.6.2",
|
||||||
"list.js": "^1.5.0",
|
"list.js": "^1.5.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.11",
|
||||||
"lz-string": "git+https://github.com/hackmdio/lz-string.git",
|
"lz-string": "git+https://github.com/hackmdio/lz-string.git",
|
||||||
"markdown-it": "^8.2.2",
|
"markdown-it": "^8.2.2",
|
||||||
"markdown-it-abbr": "^1.0.4",
|
"markdown-it-abbr": "^1.0.4",
|
||||||
|
@ -88,7 +89,6 @@
|
||||||
"moment": "^2.17.1",
|
"moment": "^2.17.1",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"mysql": "^2.12.0",
|
"mysql": "^2.12.0",
|
||||||
"node-uuid": "^1.4.7",
|
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
"passport-dropbox-oauth2": "^1.1.0",
|
"passport-dropbox-oauth2": "^1.1.0",
|
||||||
"passport-facebook": "^2.1.1",
|
"passport-facebook": "^2.1.1",
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
"passport-ldapauth": "^2.0.0",
|
"passport-ldapauth": "^2.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-oauth2": "^1.4.0",
|
"passport-oauth2": "^1.4.0",
|
||||||
"passport-saml": "^0.31.0",
|
"passport-saml": "^0.35.0",
|
||||||
"passport-twitter": "^1.0.4",
|
"passport-twitter": "^1.0.4",
|
||||||
"passport.socketio": "^3.7.0",
|
"passport.socketio": "^3.7.0",
|
||||||
"pdfobject": "^2.0.201604172",
|
"pdfobject": "^2.0.201604172",
|
||||||
|
@ -108,9 +108,9 @@
|
||||||
"randomcolor": "^0.5.3",
|
"randomcolor": "^0.5.3",
|
||||||
"raphael": "git+https://github.com/dmitrybaranovskiy/raphael",
|
"raphael": "git+https://github.com/dmitrybaranovskiy/raphael",
|
||||||
"readline-sync": "^1.4.7",
|
"readline-sync": "^1.4.7",
|
||||||
"request": "^2.79.0",
|
"request": "^2.88.0",
|
||||||
"reveal.js": "~3.6.0",
|
"reveal.js": "~3.7.0",
|
||||||
"scrypt": "^6.0.3",
|
"@mlink/scrypt": "^6.1.2",
|
||||||
"select2": "^3.5.2-browserify",
|
"select2": "^3.5.2-browserify",
|
||||||
"sequelize": "^3.28.0",
|
"sequelize": "^3.28.0",
|
||||||
"sequelize-cli": "^2.5.1",
|
"sequelize-cli": "^2.5.1",
|
||||||
|
@ -122,19 +122,19 @@
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"string": "^3.3.3",
|
"string": "^3.3.3",
|
||||||
"tedious": "^1.14.0",
|
"tedious": "^1.14.0",
|
||||||
"to-markdown": "^3.0.3",
|
|
||||||
"toobusy-js": "^0.5.1",
|
"toobusy-js": "^0.5.1",
|
||||||
|
"turndown": "^5.0.1",
|
||||||
"uuid": "^3.1.0",
|
"uuid": "^3.1.0",
|
||||||
"validator": "^10.4.0",
|
"validator": "^10.4.0",
|
||||||
"velocity-animate": "^1.4.0",
|
"velocity-animate": "^1.4.0",
|
||||||
"visibilityjs": "^1.2.4",
|
"visibilityjs": "^1.2.4",
|
||||||
"viz.js": "^1.7.0",
|
"viz.js": "^1.7.0",
|
||||||
"winston": "^2.3.0",
|
"winston": "^3.1.0",
|
||||||
"ws": "^6.0.0",
|
"ws": "^6.0.0",
|
||||||
"xss": "^1.0.3"
|
"xss": "^1.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.x <10.x"
|
"node": ">=6.x"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/hackmdio/codimd/issues",
|
"bugs": "https://github.com/hackmdio/codimd/issues",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -167,8 +167,14 @@
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"doctoc": "^1.3.0",
|
"doctoc": "^1.4.0",
|
||||||
"ejs-loader": "^0.3.1",
|
"ejs-loader": "^0.3.1",
|
||||||
|
"eslint": "^5.9.0",
|
||||||
|
"eslint-config-standard": "^12.0.0",
|
||||||
|
"eslint-plugin-import": "^2.14.0",
|
||||||
|
"eslint-plugin-node": "^8.0.0",
|
||||||
|
"eslint-plugin-promise": "^4.0.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "^0.7.0",
|
||||||
"expose-loader": "^0.7.5",
|
"expose-loader": "^0.7.5",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
|
@ -180,7 +186,6 @@
|
||||||
"mini-css-extract-plugin": "^0.4.1",
|
"mini-css-extract-plugin": "^0.4.1",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
||||||
"script-loader": "^0.7.2",
|
"script-loader": "^0.7.2",
|
||||||
"standard": "^9.0.1",
|
|
||||||
"string-loader": "^0.0.1",
|
"string-loader": "^0.0.1",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||||
|
@ -190,34 +195,6 @@
|
||||||
"webpack-merge": "^4.1.4",
|
"webpack-merge": "^4.1.4",
|
||||||
"webpack-parallel-uglify-plugin": "^1.1.0"
|
"webpack-parallel-uglify-plugin": "^1.1.0"
|
||||||
},
|
},
|
||||||
"standard": {
|
|
||||||
"globals": [
|
|
||||||
"$",
|
|
||||||
"CodeMirror",
|
|
||||||
"Cookies",
|
|
||||||
"moment",
|
|
||||||
"editor",
|
|
||||||
"ui",
|
|
||||||
"Spinner",
|
|
||||||
"modeType",
|
|
||||||
"Idle",
|
|
||||||
"serverurl",
|
|
||||||
"key",
|
|
||||||
"gapi",
|
|
||||||
"Dropbox",
|
|
||||||
"FilePicker",
|
|
||||||
"ot",
|
|
||||||
"MediaUploader",
|
|
||||||
"hex2rgb",
|
|
||||||
"num_loaded",
|
|
||||||
"Visibility",
|
|
||||||
"inlineAttachment"
|
|
||||||
],
|
|
||||||
"ignore": [
|
|
||||||
"lib/ot",
|
|
||||||
"public/vendor"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.0",
|
"bufferutil": "^4.0.0",
|
||||||
"utf-8-validate": "^5.0.1"
|
"utf-8-validate": "^5.0.1"
|
||||||
|
|
28
public/.eslintrc.js
Normal file
28
public/.eslintrc.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// this config file is used in concert with the root .eslintrc.js in the root dir.
|
||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"$": false,
|
||||||
|
"CodeMirror": false,
|
||||||
|
"Cookies": false,
|
||||||
|
"moment": false,
|
||||||
|
"editor": false,
|
||||||
|
"ui": false,
|
||||||
|
"Spinner": false,
|
||||||
|
"modeType": false,
|
||||||
|
"Idle": false,
|
||||||
|
"serverurl": false,
|
||||||
|
"key": false,
|
||||||
|
"gapi": false,
|
||||||
|
"Dropbox": false,
|
||||||
|
"FilePicker": false,
|
||||||
|
"ot": false,
|
||||||
|
"MediaUploader": false,
|
||||||
|
"hex2rgb": false,
|
||||||
|
"num_loaded": false,
|
||||||
|
"Visibility": false,
|
||||||
|
"inlineAttachment": false
|
||||||
|
}
|
||||||
|
};
|
|
@ -72,9 +72,11 @@ Notes can be embedded as follows:
|
||||||
|
|
||||||
## [Slide Mode](./slide-example):
|
## [Slide Mode](./slide-example):
|
||||||
You can use a special syntax to organize your note into slides.
|
You can use a special syntax to organize your note into slides.
|
||||||
After that, you can use the **Slide Mode** <i class="fa fa-tv"></i> to make a presentation.
|
After that, you can use the **[Slide Mode](./slide-example)** <i class="fa fa-tv"></i> to make a presentation.
|
||||||
Visit the above link for details.
|
Visit the above link for details.
|
||||||
|
|
||||||
|
To switch the editor into slide mode, set the [document type](./yaml-metadata#type) to `slide`.
|
||||||
|
|
||||||
View
|
View
|
||||||
===
|
===
|
||||||
## Table of Contents:
|
## Table of Contents:
|
||||||
|
@ -88,9 +90,23 @@ You can hover and click <i class="fa fa-chain"></i> to anchor on it.
|
||||||
|
|
||||||
Edit:
|
Edit:
|
||||||
===
|
===
|
||||||
|
## Editor Modes:
|
||||||
|
You can look in the bottom right section of the editor area, there you'll find a button with `sublime` on it.
|
||||||
|
When you click it, you can select 3 editor modes:
|
||||||
|
|
||||||
|
- sublime (default)
|
||||||
|
- emacs
|
||||||
|
- vim
|
||||||
|
|
||||||
## Shortcut Keys:
|
## Shortcut Keys:
|
||||||
Just like Sublime text, which is pretty quick and convenient.
|
The shortcut keys depend on your selected editor mode. By default they are just like Sublime text, which is pretty quick and convenient.
|
||||||
> For more infomation, see [here](https://codemirror.net/demo/sublime.html).
|
> For more information, see [here](https://codemirror.net/demo/sublime.html).
|
||||||
|
|
||||||
|
For emacs:
|
||||||
|
> For more information, see [here](https://codemirror.net/demo/emacs.html).
|
||||||
|
|
||||||
|
For vim:
|
||||||
|
> For more information, see [here](https://codemirror.net/demo/vim.html).
|
||||||
|
|
||||||
## Auto-Complete:
|
## Auto-Complete:
|
||||||
This editor provides full auto-complete hints in markdown.
|
This editor provides full auto-complete hints in markdown.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
---
|
---
|
||||||
|
type: slide
|
||||||
slideOptions:
|
slideOptions:
|
||||||
transition: slide
|
transition: slide
|
||||||
---
|
---
|
||||||
|
|
|
@ -25,7 +25,7 @@ This option will set the note title which prior than content title.
|
||||||
> default: not set
|
> default: not set
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
title: meta title
|
title: meta title
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ This option will set the note description.
|
||||||
> default: not set
|
> default: not set
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
description: meta description
|
description: meta description
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ This option will set the tags which prior than content tags.
|
||||||
> default: not set
|
> default: not set
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
tags: features, cool, updated
|
tags: features, cool, updated
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ So you can prevent any search engine index your note by set `noindex, nofollow`.
|
||||||
> default: not set
|
> default: not set
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
robots: noindex, nofollow
|
robots: noindex, nofollow
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -75,13 +75,13 @@ https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||||
> default: not set (which will be en)
|
> default: not set (which will be en)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
langs: ja-jp
|
langs: ja-jp
|
||||||
```
|
```
|
||||||
|
|
||||||
dir
|
dir
|
||||||
---
|
---
|
||||||
This option provide to describe the direction of the text in this note.
|
This option specifies the direction of the text in this note.
|
||||||
You can only use whether `rtl` or `ltr`.
|
You can only use whether `rtl` or `ltr`.
|
||||||
Look more at here:
|
Look more at here:
|
||||||
http://www.w3.org/International/questions/qa-html-dir
|
http://www.w3.org/International/questions/qa-html-dir
|
||||||
|
@ -89,7 +89,7 @@ http://www.w3.org/International/questions/qa-html-dir
|
||||||
> default: not set (which will be ltr)
|
> default: not set (which will be ltr)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
dir: rtl
|
dir: rtl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -102,35 +102,46 @@ You can only use whether `true` or `false`.
|
||||||
> default: not set (which will be true)
|
> default: not set (which will be true)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
breaks: false
|
breaks: false
|
||||||
```
|
```
|
||||||
|
|
||||||
GA
|
GA
|
||||||
---
|
---
|
||||||
This option allow you to enable Google Analytics with your ID.
|
This option allows you to enable Google Analytics with your ID.
|
||||||
|
|
||||||
> default: not set (which won't enable)
|
> default: not set (which won't enable)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
GA: UA-12345667-8
|
GA: UA-12345667-8
|
||||||
```
|
```
|
||||||
|
|
||||||
disqus
|
disqus
|
||||||
---
|
---
|
||||||
This option allow you to enable Disqus with your shortname.
|
This option allows you to enable Disqus with your shortname.
|
||||||
|
|
||||||
> default: not set (which won't enable)
|
> default: not set (which won't enable)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
disqus: codimd
|
disqus: codimd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
type
|
||||||
|
---
|
||||||
|
This option allows you to switch the document view to the slide preview, to simplify live editing of presentations.
|
||||||
|
|
||||||
|
> default: not set
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yml
|
||||||
|
type: slide
|
||||||
|
```
|
||||||
|
|
||||||
slideOptions
|
slideOptions
|
||||||
---
|
---
|
||||||
This option allow you provide custom options to slide mode.
|
This option allows you to provide custom options to slide mode.
|
||||||
Please below document for more details:
|
Please below document for more details:
|
||||||
https://github.com/hakimel/reveal.js/#configuration
|
https://github.com/hakimel/reveal.js/#configuration
|
||||||
|
|
||||||
|
@ -142,7 +153,7 @@ https://github.com/hakimel/reveal.js/tree/master/css/theme
|
||||||
> default: not set (which use default slide options)
|
> default: not set (which use default slide options)
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```xml
|
```yml
|
||||||
slideOptions:
|
slideOptions:
|
||||||
transition: fade
|
transition: fade
|
||||||
theme: white
|
theme: white
|
||||||
|
|
|
@ -850,10 +850,12 @@ const linkifyAnchors = (level, containingElement) => {
|
||||||
const id = slugifyWithUTF8(getHeaderContent(header))
|
const id = slugifyWithUTF8(getHeaderContent(header))
|
||||||
header.id = id
|
header.id = id
|
||||||
}
|
}
|
||||||
|
if (!(typeof header.id === 'undefined' || header.id === '')) {
|
||||||
header.insertBefore(anchorForId(header.id), header.firstChild)
|
header.insertBefore(anchorForId(header.id), header.firstChild)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function autoLinkify (view) {
|
export function autoLinkify (view) {
|
||||||
const contentBlock = view[0]
|
const contentBlock = view[0]
|
||||||
|
@ -1147,16 +1149,15 @@ const pdfPlugin = new Plugin(
|
||||||
|
|
||||||
const emojijsPlugin = new Plugin(
|
const emojijsPlugin = new Plugin(
|
||||||
// regexp to match emoji shortcodes :something:
|
// regexp to match emoji shortcodes :something:
|
||||||
/:([^\s:]+):/,
|
// We generate an universal regex that guaranteed only contains the
|
||||||
|
// emojies we have available. This should prevent all false-positives
|
||||||
|
new RegExp(':(' + window.emojify.emojiNames.map((item) => { return RegExp.escape(item) }).join('|') + '):', 'i'),
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const emoji = match[1] ? match[1].toLowerCase() : undefined
|
const emoji = match[1].toLowerCase()
|
||||||
if (window.emojify.emojiNames.includes(emoji)) {
|
|
||||||
const div = $(`<img class="emoji" src="${serverurl}/build/emojify.js/dist/images/basic/${emoji}.png"></img>`)
|
const div = $(`<img class="emoji" src="${serverurl}/build/emojify.js/dist/images/basic/${emoji}.png"></img>`)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
return match[0]
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// yaml meta, from https://github.com/eugeneware/remarkable-meta
|
// yaml meta, from https://github.com/eugeneware/remarkable-meta
|
||||||
|
|
|
@ -218,6 +218,7 @@ export function getStorageHistory (callback) {
|
||||||
if (typeof data === 'string') { data = JSON.parse(data) }
|
if (typeof data === 'string') { data = JSON.parse(data) }
|
||||||
callback(data)
|
callback(data)
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
callback([])
|
callback([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ require('../css/site.css')
|
||||||
|
|
||||||
require('highlight.js/styles/github-gist.css')
|
require('highlight.js/styles/github-gist.css')
|
||||||
|
|
||||||
import toMarkdown from 'to-markdown'
|
import TurndownService from 'turndown'
|
||||||
|
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import randomColor from 'randomcolor'
|
import randomColor from 'randomcolor'
|
||||||
|
@ -1498,7 +1498,12 @@ $('#snippetExportModalConfirm').click(function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
function parseToEditor (data) {
|
function parseToEditor (data) {
|
||||||
var parsed = toMarkdown(data)
|
var turndownService = new TurndownService({
|
||||||
|
defaultReplacement: function (innerHTML, node) {
|
||||||
|
return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var parsed = turndownService.turndown(data)
|
||||||
if (parsed) { replaceAll(parsed) }
|
if (parsed) { replaceAll(parsed) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2511,7 +2516,9 @@ function buildCursor (user) {
|
||||||
// editor actions
|
// editor actions
|
||||||
function removeNullByte (cm, change) {
|
function removeNullByte (cm, change) {
|
||||||
var str = change.text.join('\n')
|
var str = change.text.join('\n')
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
if (/\u0000/g.test(str) && change.update) {
|
if (/\u0000/g.test(str) && change.update) {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n'))
|
change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2786,6 +2793,7 @@ function updateViewInner () {
|
||||||
renderTOC(ui.area.markdown)
|
renderTOC(ui.area.markdown)
|
||||||
generateToc('ui-toc')
|
generateToc('ui-toc')
|
||||||
generateToc('ui-toc-affix')
|
generateToc('ui-toc-affix')
|
||||||
|
autoLinkify(ui.area.markdown)
|
||||||
generateScrollspy()
|
generateScrollspy()
|
||||||
updateScrollspy()
|
updateScrollspy()
|
||||||
smoothHashScroll()
|
smoothHashScroll()
|
||||||
|
|
|
@ -492,7 +492,7 @@ export default class Editor {
|
||||||
clearInterval(spellcheckTimer)
|
clearInterval(spellcheckTimer)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
100,
|
100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,7 +514,7 @@ export default class Editor {
|
||||||
}
|
}
|
||||||
setOverrideBrowserKeymap () {
|
setOverrideBrowserKeymap () {
|
||||||
var overrideBrowserKeymap = $(
|
var overrideBrowserKeymap = $(
|
||||||
'.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
|
'.ui-preferences-override-browser-keymap label > input[type="checkbox"]'
|
||||||
)
|
)
|
||||||
if (overrideBrowserKeymap.is(':checked')) {
|
if (overrideBrowserKeymap.is(':checked')) {
|
||||||
Cookies.set('preferences-override-browser-keymap', true, {
|
Cookies.set('preferences-override-browser-keymap', true, {
|
||||||
|
@ -529,10 +529,10 @@ export default class Editor {
|
||||||
|
|
||||||
setPreferences () {
|
setPreferences () {
|
||||||
var overrideBrowserKeymap = $(
|
var overrideBrowserKeymap = $(
|
||||||
'.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
|
'.ui-preferences-override-browser-keymap label > input[type="checkbox"]'
|
||||||
)
|
)
|
||||||
var cookieOverrideBrowserKeymap = Cookies.get(
|
var cookieOverrideBrowserKeymap = Cookies.get(
|
||||||
'preferences-override-browser-keymap',
|
'preferences-override-browser-keymap'
|
||||||
)
|
)
|
||||||
if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
|
if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
|
||||||
overrideBrowserKeymap.prop('checked', true)
|
overrideBrowserKeymap.prop('checked', true)
|
||||||
|
|
|
@ -67,7 +67,7 @@ export const getUIElements = () => ({
|
||||||
codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
|
codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
|
||||||
codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
|
codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
|
||||||
codemirrorSizerInner: $(
|
codemirrorSizerInner: $(
|
||||||
'.ui-edit-area .CodeMirror .CodeMirror-sizer > div',
|
'.ui-edit-area .CodeMirror .CodeMirror-sizer > div'
|
||||||
),
|
),
|
||||||
markdown: $('.ui-view-area .markdown-body'),
|
markdown: $('.ui-view-area .markdown-body'),
|
||||||
resize: {
|
resize: {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
/* global filterXSS */
|
|
||||||
// allow some attributes
|
// allow some attributes
|
||||||
|
|
||||||
|
var filterXSS = require('xss')
|
||||||
|
|
||||||
var whiteListAttr = ['id', 'class', 'style']
|
var whiteListAttr = ['id', 'class', 'style']
|
||||||
window.whiteListAttr = whiteListAttr
|
window.whiteListAttr = whiteListAttr
|
||||||
// allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript://
|
// allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript://
|
||||||
|
@ -71,5 +73,6 @@ function preventXSS (html) {
|
||||||
window.preventXSS = preventXSS
|
window.preventXSS = preventXSS
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preventXSS: preventXSS
|
preventXSS: preventXSS,
|
||||||
|
escapeAttrValue: filterXSS.escapeAttrValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
|
|
||||||
import { preventXSS } from './render'
|
import { preventXSS, escapeAttrValue } from './render'
|
||||||
import { md } from './extra'
|
import { md } from './extra'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -259,7 +259,7 @@ import { md } from './extra'
|
||||||
while ((matchesClass = mardownClassRegex.exec(classes))) {
|
while ((matchesClass = mardownClassRegex.exec(classes))) {
|
||||||
var name = matchesClass[1]
|
var name = matchesClass[1]
|
||||||
var value = matchesClass[2]
|
var value = matchesClass[2]
|
||||||
if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, window.filterXSS.escapeAttrValue(value)) }
|
if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, escapeAttrValue(value)) }
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
8
public/vendor/md-toc.js
vendored
8
public/vendor/md-toc.js
vendored
|
@ -44,7 +44,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Toc.prototype._createTocContent = function recursiveToc(level = 0, titleElements = [], titleNames = [], ulClass = undefined) {
|
Toc.prototype._createTocContent = function recursiveToc(level = 0, titleElements = [], titleNames = [], ulClass = undefined, index = 0) {
|
||||||
// Inititalize our elements from the toc object
|
// Inititalize our elements from the toc object
|
||||||
// which is only available on level 0
|
// which is only available on level 0
|
||||||
if (level === 0) {
|
if (level === 0) {
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
var elementText = (typeof this.process === 'function' ? this.process(element) : element.innerHTML).replace(/<(?:.|\n)*?>/gm, '')
|
var elementText = (typeof this.process === 'function' ? this.process(element) : element.innerHTML).replace(/<(?:.|\n)*?>/gm, '')
|
||||||
var id = element.getAttribute('id')
|
var id = element.getAttribute('id')
|
||||||
if (!id) {
|
if (!id) {
|
||||||
element.setAttribute('id', 'tip' + i)
|
element.setAttribute('id', 'tip' + ++index)
|
||||||
id = '#tip' + i
|
id = '#tip' + index
|
||||||
} else {
|
} else {
|
||||||
id = '#' + id
|
id = '#' + id
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
// This element is for the lower lever, we have to re-add it before we send the list down there.
|
// This element is for the lower lever, we have to re-add it before we send the list down there.
|
||||||
titleElements.unshift(element)
|
titleElements.unshift(element)
|
||||||
// Let's call ourself and get to the next level
|
// Let's call ourself and get to the next level
|
||||||
content += recursiveToc(level + 1, titleElements, titleNames, ulClass)
|
content += recursiveToc(level + 1, titleElements, titleNames, ulClass, index)
|
||||||
} else {
|
} else {
|
||||||
// When we end up here, met a higher level element
|
// When we end up here, met a higher level element
|
||||||
// This is not our business so back into the list with the element and let's end this loop
|
// This is not our business so back into the list with the element and let's end this loop
|
||||||
|
|
|
@ -32,16 +32,16 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||||
</li>
|
</li>
|
||||||
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
|
<% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header"><%= __('Export') %></li>
|
<li class="dropdown-header"><%= __('Export') %></li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof github !== 'undefined' && github) { %>
|
<% if(enableGitHubGist) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
<% if(enableGitlabSnippets) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
<% if(enableGitlabSnippets) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -134,16 +134,16 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||||
</li>
|
</li>
|
||||||
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
|
<% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="dropdown-header"><%= __('Export') %></li>
|
<li class="dropdown-header"><%= __('Export') %></li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof github !== 'undefined' && github) { %>
|
<% if(enableGitHubGist) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
<% if(enableGitlabSnippets) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||||
</li>
|
</li>
|
||||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
<% if(enableGitlabSnippets) { %>
|
||||||
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
|
@ -65,19 +65,19 @@
|
||||||
</span>
|
</span>
|
||||||
<div class="lead row" style="width: 90%; margin: 0 auto;">
|
<div class="lead row" style="width: 90%; margin: 0 auto;">
|
||||||
<div class="col-md-4 inner">
|
<div class="col-md-4 inner">
|
||||||
<a href="<%- serverURL %>/features#share-notes">
|
<a href="<%- serverURL %>/features#Share-Notes">
|
||||||
<i class="fa fa-bolt fa-3x"></i>
|
<i class="fa fa-bolt fa-3x"></i>
|
||||||
<h4><%= __('Collaborate with URL') %></h4>
|
<h4><%= __('Collaborate with URL') %></h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 inner">
|
<div class="col-md-4 inner">
|
||||||
<a href="<%- serverURL %>/features#mathjax">
|
<a href="<%- serverURL %>/features#MathJax">
|
||||||
<i class="fa fa-bar-chart fa-3x"></i>
|
<i class="fa fa-bar-chart fa-3x"></i>
|
||||||
<h4><%= __('Support charts and MathJax') %></h4>
|
<h4><%= __('Support charts and MathJax') %></h4>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 inner">
|
<div class="col-md-4 inner">
|
||||||
<a href="<%- serverURL %>/features#slide-mode">
|
<a href="<%- serverURL %>/features#Slide-Modee">
|
||||||
<i class="fa fa-tv fa-3x"></i>
|
<i class="fa fa-tv fa-3x"></i>
|
||||||
<h4><%= __('Support slide mode') %></h4>
|
<h4><%= __('Support slide mode') %></h4>
|
||||||
</a>
|
</a>
|
||||||
|
@ -150,7 +150,7 @@
|
||||||
<option value="id">Bahasa Indonesia</option>
|
<option value="id">Bahasa Indonesia</option>
|
||||||
</select>
|
</select>
|
||||||
<p>
|
<p>
|
||||||
Powered by <a href="https://codimd.org">CodiMD</a> | <a href="<%- serverURL %>/s/release-notes" target="_blank" rel="noopener"><%= __('Releases') %></a><% if(privacyStatement) { %> | <a href="<%- serverURL %>/s/privacy" target="_blank" rel="noopener"><%= __('Privacy') %></a><% } %><% if(termsOfUse) { %> | <a href="<%- serverURL %>/s/terms-of-use" target="_blank" rel="noopener"><%= __('Terms of Use') %></a><% } %>
|
Powered by <a href="https://codimd.org">CodiMD</a> | <a href="<%- serverURL %>/s/release-notes" target="_blank" rel="noopener"><%= __('Releases') %></a>| <a href="<%- sourceURL %>" target="_blank" rel="noopener"><%= __('Source Code') %></a><% if(privacyStatement) { %> | <a href="<%- serverURL %>/s/privacy" target="_blank" rel="noopener"><%= __('Privacy') %></a><% } %><% if(termsOfUse) { %> | <a href="<%- serverURL %>/s/terms-of-use" target="_blank" rel="noopener"><%= __('Terms of Use') %></a><% } %>
|
||||||
</p>
|
</p>
|
||||||
<h6 class="social-foot">
|
<h6 class="social-foot">
|
||||||
<%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://riot.im/app/#/room/#codimd:matrix.org" target="_blank" rel="noopener"><i class="fa fa-comments"></i> Riot</a>', '<a href="https://translate.codimd.org" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
<%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://riot.im/app/#/room/#codimd:matrix.org" target="_blank" rel="noopener"><i class="fa fa-comments"></i> Riot</a>', '<a href="https://translate.codimd.org" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fork-awesome/1.1.3/css/fork-awesome.min.css" integrity="sha256-ZhApazu+kejqTYhMF+1DzNKjIzP7KXu6AzyXcC1gMus=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fork-awesome/1.1.3/css/fork-awesome.min.css" integrity="sha256-ZhApazu+kejqTYhMF+1DzNKjIzP7KXu6AzyXcC1gMus=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css" integrity="sha256-3iu9jgsy9TpTwXKb7bNQzqWekRX7pPK+2OLj3R922fo=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css" integrity="sha256-3iu9jgsy9TpTwXKb7bNQzqWekRX7pPK+2OLj3R922fo=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/reveal.min.css" integrity="sha256-ol2N5Xr80jdDqxK0lte3orKGb9Ja3sOnpAUV7TTADmg=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/css/reveal.min.css" integrity="sha256-9+Wg2bcNeiOMGXOUNqBdceY2lAH/eCiTDcdzHhHIl48=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/css/basic/emojify.min.css" integrity="sha256-UOrvMOsSDSrW6szVLe8ZDZezBxh5IoIfgTwdNDgTjiU=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/css/basic/emojify.min.css" integrity="sha256-UOrvMOsSDSrW6szVLe8ZDZezBxh5IoIfgTwdNDgTjiU=" crossorigin="anonymous" />
|
||||||
<%- include build/slide-header %>
|
<%- include build/slide-header %>
|
||||||
<%- include shared/polyfill %>
|
<%- include shared/polyfill %>
|
||||||
|
@ -88,8 +88,8 @@
|
||||||
|
|
||||||
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/lib/js/head.min.js" integrity="sha256-+09kLhwACKXFPDvqo4xMMvi4+uXFsRZ2uYGbeN1U8sI=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/lib/js/head.min.js" integrity="sha256-CTcwyen1cxIrm4hlqdxe0y7Hq6B0rpxAKLiXMD3dJv0=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/js/reveal.min.js" integrity="sha256-ixSKHrWAL2k0mqVSRju9+to2/uZSEK9+kJRfdNBolG8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/js/reveal.min.js" integrity="sha256-Xr6ZH+/kc7hDVReZLO5khBknteLqu5oen/xnSraXrVk=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
||||||
|
|
|
@ -202,7 +202,6 @@ module.exports = {
|
||||||
'babel-polyfill',
|
'babel-polyfill',
|
||||||
'script-loader!jquery-ui-resizable',
|
'script-loader!jquery-ui-resizable',
|
||||||
'script-loader!js-url',
|
'script-loader!js-url',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'script-loader!Idle.Js',
|
'script-loader!Idle.Js',
|
||||||
'expose-loader?LZString!lz-string',
|
'expose-loader?LZString!lz-string',
|
||||||
'script-loader!codemirror',
|
'script-loader!codemirror',
|
||||||
|
@ -253,7 +252,6 @@ module.exports = {
|
||||||
'script-loader!handlebars',
|
'script-loader!handlebars',
|
||||||
'expose-loader?hljs!highlight.js',
|
'expose-loader?hljs!highlight.js',
|
||||||
'expose-loader?emojify!emojify.js',
|
'expose-loader?emojify!emojify.js',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'script-loader!Idle.Js',
|
'script-loader!Idle.Js',
|
||||||
'script-loader!gist-embed',
|
'script-loader!gist-embed',
|
||||||
'expose-loader?LZString!lz-string',
|
'expose-loader?LZString!lz-string',
|
||||||
|
@ -273,7 +271,6 @@ module.exports = {
|
||||||
],
|
],
|
||||||
pretty: [
|
pretty: [
|
||||||
'babel-polyfill',
|
'babel-polyfill',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'flowchart.js',
|
'flowchart.js',
|
||||||
'js-sequence-diagrams',
|
'js-sequence-diagrams',
|
||||||
'expose-loader?RevealMarkdown!reveal-markdown',
|
'expose-loader?RevealMarkdown!reveal-markdown',
|
||||||
|
@ -298,7 +295,6 @@ module.exports = {
|
||||||
'script-loader!handlebars',
|
'script-loader!handlebars',
|
||||||
'expose-loader?hljs!highlight.js',
|
'expose-loader?hljs!highlight.js',
|
||||||
'expose-loader?emojify!emojify.js',
|
'expose-loader?emojify!emojify.js',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'script-loader!gist-embed',
|
'script-loader!gist-embed',
|
||||||
'flowchart.js',
|
'flowchart.js',
|
||||||
'js-sequence-diagrams',
|
'js-sequence-diagrams',
|
||||||
|
@ -310,7 +306,6 @@ module.exports = {
|
||||||
slide: [
|
slide: [
|
||||||
'babel-polyfill',
|
'babel-polyfill',
|
||||||
'bootstrap-tooltip',
|
'bootstrap-tooltip',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'flowchart.js',
|
'flowchart.js',
|
||||||
'js-sequence-diagrams',
|
'js-sequence-diagrams',
|
||||||
'expose-loader?RevealMarkdown!reveal-markdown',
|
'expose-loader?RevealMarkdown!reveal-markdown',
|
||||||
|
@ -338,7 +333,6 @@ module.exports = {
|
||||||
'script-loader!handlebars',
|
'script-loader!handlebars',
|
||||||
'expose-loader?hljs!highlight.js',
|
'expose-loader?hljs!highlight.js',
|
||||||
'expose-loader?emojify!emojify.js',
|
'expose-loader?emojify!emojify.js',
|
||||||
'expose-loader?filterXSS!xss',
|
|
||||||
'script-loader!gist-embed',
|
'script-loader!gist-embed',
|
||||||
'flowchart.js',
|
'flowchart.js',
|
||||||
'js-sequence-diagrams',
|
'js-sequence-diagrams',
|
||||||
|
|
Loading…
Reference in a new issue