summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintignore3
-rw-r--r--.eslintrc.js21
-rw-r--r--README.md14
-rw-r--r--app.js5
-rwxr-xr-xbin/setup10
-rw-r--r--config.json.example2
-rw-r--r--docs/guides/migrate-etherpad.md131
-rw-r--r--lib/config/default.js6
-rw-r--r--lib/config/environment.js3
-rw-r--r--lib/config/index.js17
-rw-r--r--lib/config/utils.js33
-rw-r--r--lib/logger.js36
-rw-r--r--lib/migrations/20150702001020-update-to-0_3_1.js2
-rw-r--r--lib/migrations/20160112220142-note-add-lastchange.js2
-rw-r--r--lib/migrations/20160420180355-note-add-alias.js2
-rw-r--r--lib/migrations/20160515114000-user-add-tokens.js2
-rw-r--r--lib/migrations/20160607060246-support-revision.js2
-rw-r--r--lib/migrations/20160703062241-support-authorship.js2
-rw-r--r--lib/migrations/20161009040430-support-delete-note.js2
-rw-r--r--lib/migrations/20161201050312-support-email-signin.js2
-rw-r--r--lib/models/index.js1
-rw-r--r--lib/models/user.js2
-rwxr-xr-xlib/ot/editor-socketio-server.js10
-rw-r--r--lib/realtime.js5
-rw-r--r--lib/response.js5
-rw-r--r--lib/web/auth/oauth2/index.js8
-rw-r--r--lib/web/imageRouter/filesystem.js2
-rw-r--r--lib/web/statusRouter.js2
-rw-r--r--locales/de.json4
-rw-r--r--locales/en.json5
-rw-r--r--locales/fr.json3
-rw-r--r--locales/nl.json221
-rw-r--r--locales/zh-CN.json224
-rw-r--r--locales/zh-TW.json223
-rw-r--r--package.json44
-rw-r--r--public/.eslintrc.js28
-rw-r--r--public/docs/features.md22
-rw-r--r--public/docs/slide-example.md1
-rw-r--r--public/docs/yaml-metadata.md39
-rw-r--r--public/js/history.js1
-rw-r--r--public/js/index.js4
-rw-r--r--public/js/lib/editor/index.js8
-rw-r--r--public/js/lib/editor/ui-elements.js2
-rw-r--r--public/js/render.js7
-rw-r--r--public/js/reveal-markdown.js4
-rw-r--r--public/views/index/body.ejs8
-rw-r--r--tmp/.keep0
-rw-r--r--webpack.common.js6
-rw-r--r--yarn.lock38
49 files changed, 752 insertions, 472 deletions
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..2c3d6b04
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+lib/ot
+public/vendor
+public/build
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..b826a37b
--- /dev/null
+++ b/.eslintrc.js
@@ -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"]
+ }
+};
diff --git a/README.md b/README.md
index 78c4fc78..c68d6cb0 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
CodiMD
===
-[![Standard - JavaScript Style Guide][standardjs-image]][standardjs-url]
-
[![#CodiMD on matrix.org][matrix.org-image]][matrix.org-url]
[![build status][travis-image]][travis-url]
[![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
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
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
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**
@@ -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_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_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_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) |
@@ -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_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_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_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 |
@@ -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_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_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.*
@@ -273,6 +277,7 @@ There are some config settings you need to change in the files below.
| `host` | `localhost` | host to listen on |
| `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) |
+| `loglevel` | `info` | Defines what kind of logs are provided to stdout. |
| `allowOrigin` | `['localhost']` | domain name whitelist |
| `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) |
@@ -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`) |
| `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 |
+| `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) |
| `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/) |
@@ -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) |
| `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` |
+| `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
@@ -369,7 +376,6 @@ See more at [http://operational-transformation.github.io/](http://operational-tr
[travis-url]: https://travis-ci.org/hackmdio/codimd
[github-version-badge]: https://img.shields.io/github/release/hackmdio/codimd.svg
[github-release-page]: https://github.com/hackmdio/codimd/releases
-[standardjs-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
-[standardjs-url]: https://github.com/feross/standard
+[github-release-feed]: https://github.com/hackmdio/codimd/releases.atom
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg
[poeditor-url]: https://poeditor.com/join/project/1OpGjF2Jir
diff --git a/app.js b/app.js
index 622d866f..618fba15 100644
--- a/app.js
+++ b/app.js
@@ -53,7 +53,7 @@ if (config.useSSL) {
// logger
app.use(morgan('combined', {
- 'stream': logger
+ 'stream': logger.stream
}))
// socket io
@@ -125,7 +125,7 @@ app.use(i18n.init)
// routes without sessions
// 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('/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 }))
@@ -178,6 +178,7 @@ app.set('view engine', 'ejs')
// set generally available variables for all views
app.locals.useCDN = config.useCDN
app.locals.serverURL = config.serverURL
+app.locals.sourceURL = config.sourceURL
app.locals.allowAnonymous = config.allowAnonymous
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
app.locals.allowPDFExport = config.allowPDFExport
diff --git a/bin/setup b/bin/setup
index 122cb7ae..38aa05a4 100755
--- a/bin/setup
+++ b/bin/setup
@@ -8,11 +8,12 @@ if [ -d .git ]; then
cd "$(git rev-parse --show-toplevel)"
fi
-if ! type npm > /dev/null
+if ! type yarn > /dev/null
then
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
+And for yarn package manager at: https://yarnpkg.com/en/
Setup will not be run
EOF
exit 0
@@ -27,8 +28,9 @@ if [ ! -f .sequelizerc ]; then
cp .sequelizerc.example .sequelizerc
fi
-echo "install npm packages"
-BUILD_ASSETS=false npm install
+echo "install packages"
+yarn install --pure-lockfile
+yarn install --production=false --pure-lockfile
cat << EOF
diff --git a/config.json.example b/config.json.example
index 16c95509..8965add2 100644
--- a/config.json.example
+++ b/config.json.example
@@ -6,6 +6,7 @@
}
},
"development": {
+ "loglevel": "debug",
"hsts": {
"enable": false
},
@@ -16,6 +17,7 @@
},
"production": {
"domain": "localhost",
+ "loglevel": "info"
"hsts": {
"enable": true,
"maxAgeSeconds": "31536000",
diff --git a/docs/guides/migrate-etherpad.md b/docs/guides/migrate-etherpad.md
new file mode 100644
index 00000000..c3783c89
--- /dev/null
+++ b/docs/guides/migrate-etherpad.md
@@ -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.
diff --git a/lib/config/default.js b/lib/config/default.js
index c3ada982..d7a8f471 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -1,10 +1,13 @@
'use strict'
+const os = require('os')
+
module.exports = {
domain: '',
urlPath: '',
host: '0.0.0.0',
port: 3000,
+ loglevel: 'info',
urlAddPort: false,
allowOrigin: ['localhost'],
useSSL: false,
@@ -29,6 +32,7 @@ module.exports = {
allowAnonymous: true,
allowAnonymousEdits: false,
allowFreeURL: false,
+ forbiddenNoteIDs: ['robots.txt', 'favicon.ico', 'api'],
defaultPermission: 'editable',
dbURL: '',
db: {},
@@ -39,7 +43,7 @@ module.exports = {
dhParamPath: '',
// other path
viewPath: './public/views',
- tmpPath: './tmp',
+ tmpPath: os.tmpdir(),
defaultNotePath: './public/default.md',
docsPath: './public/docs',
uploadsPath: './public/uploads',
diff --git a/lib/config/environment.js b/lib/config/environment.js
index 6737637c..a57fe0db 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -3,11 +3,13 @@
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils')
module.exports = {
+ sourceURL: process.env.CMD_SOURCE_URL,
domain: process.env.CMD_DOMAIN,
urlPath: process.env.CMD_URL_PATH,
host: process.env.CMD_HOST,
port: toIntegerConfig(process.env.CMD_PORT),
path: process.env.CMD_PATH,
+ loglevel: process.env.CMD_LOGLEVEL,
urlAddPort: toBooleanConfig(process.env.CMD_URL_ADDPORT),
useSSL: toBooleanConfig(process.env.CMD_USESSL),
hsts: {
@@ -26,6 +28,7 @@ module.exports = {
allowAnonymous: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS),
allowAnonymousEdits: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_EDITS),
allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL),
+ forbiddenNoteIDs: toArrayConfig(process.env.CMD_FORBIDDEN_NOTE_IDS),
defaultPermission: process.env.CMD_DEFAULT_PERMISSION,
dbURL: process.env.CMD_DB_URL,
sessionSecret: process.env.CMD_SESSION_SECRET,
diff --git a/lib/config/index.js b/lib/config/index.js
index 501fdca3..c1005b0b 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -8,6 +8,7 @@ const {merge} = require('lodash')
const deepFreeze = require('deep-freeze')
const {Environment, Permission} = require('./enum')
const logger = require('../logger')
+const {getGitCommit, getGitHubURL} = require('./utils')
const appRootPath = path.resolve(__dirname, '../../')
const env = process.env.NODE_ENV || Environment.development
@@ -16,11 +17,17 @@ const debugConfig = {
}
// 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 = {
version: version,
- minimumCompatibleVersion: '0.5.0'
+ minimumCompatibleVersion: '0.5.0',
+ fullversion: fullversion,
+ sourceURL: sourceURL
}
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('./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
if (config.ldap.tlsca) {
let ca = config.ldap.tlsca.split(',')
diff --git a/lib/config/utils.js b/lib/config/utils.js
index b2406cf1..9646f8c0 100644
--- a/lib/config/utils.js
+++ b/lib/config/utils.js
@@ -1,5 +1,8 @@
'use strict'
+const fs = require('fs')
+const path = require('path')
+
exports.toBooleanConfig = function toBooleanConfig (configValue) {
if (configValue && typeof configValue === 'string') {
return (configValue === 'true')
@@ -20,3 +23,33 @@ exports.toIntegerConfig = function toIntegerConfig (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
+}
diff --git a/lib/logger.js b/lib/logger.js
index f8b3895c..5ef1860a 100644
--- a/lib/logger.js
+++ b/lib/logger.js
@@ -1,23 +1,27 @@
'use strict'
-const winston = require('winston')
+const {createLogger, format, transports} = require('winston')
-class Logger extends winston.Logger {
- // Implement stream.writable.write interface
- write (chunk) {
- this.info(chunk)
- }
-}
-
-module.exports = new Logger({
+const logger = createLogger({
+ level: 'debug',
+ format: format.combine(
+ format.uncolorize(),
+ format.timestamp(),
+ format.align(),
+ format.splat(),
+ format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
+ ),
transports: [
- new winston.transports.Console({
- level: 'debug',
- handleExceptions: true,
- json: false,
- colorize: false,
- timestamp: true
+ new transports.Console({
+ handleExceptions: true
})
],
- emitErrs: true,
exitOnError: false
})
+
+logger.stream = {
+ write: function (message, encoding) {
+ logger.info(message)
+ }
+}
+
+module.exports = logger
diff --git a/lib/migrations/20150702001020-update-to-0_3_1.js b/lib/migrations/20150702001020-update-to-0_3_1.js
index eb18211f..e1a88661 100644
--- a/lib/migrations/20150702001020-update-to-0_3_1.js
+++ b/lib/migrations/20150702001020-update-to-0_3_1.js
@@ -21,7 +21,7 @@ module.exports = {
defaultValue: 0
})
}).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.')
} else {
throw error
diff --git a/lib/migrations/20160112220142-note-add-lastchange.js b/lib/migrations/20160112220142-note-add-lastchange.js
index 682337c9..87e3ff19 100644
--- a/lib/migrations/20160112220142-note-add-lastchange.js
+++ b/lib/migrations/20160112220142-note-add-lastchange.js
@@ -8,7 +8,7 @@ module.exports = {
type: Sequelize.DATE
})
}).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.')
} else {
throw error
diff --git a/lib/migrations/20160420180355-note-add-alias.js b/lib/migrations/20160420180355-note-add-alias.js
index 6cc1337e..45d53e69 100644
--- a/lib/migrations/20160420180355-note-add-alias.js
+++ b/lib/migrations/20160420180355-note-add-alias.js
@@ -8,7 +8,7 @@ module.exports = {
indicesType: 'UNIQUE'
})
}).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.')
} else {
throw error
diff --git a/lib/migrations/20160515114000-user-add-tokens.js b/lib/migrations/20160515114000-user-add-tokens.js
index 8bf6d11f..435ae9cb 100644
--- a/lib/migrations/20160515114000-user-add-tokens.js
+++ b/lib/migrations/20160515114000-user-add-tokens.js
@@ -4,7 +4,7 @@ module.exports = {
return queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING).then(function () {
return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
}).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.')
} else {
throw error
diff --git a/lib/migrations/20160607060246-support-revision.js b/lib/migrations/20160607060246-support-revision.js
index 465a09fa..547f89b8 100644
--- a/lib/migrations/20160607060246-support-revision.js
+++ b/lib/migrations/20160607060246-support-revision.js
@@ -16,7 +16,7 @@ module.exports = {
updatedAt: Sequelize.DATE
})
}).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.')
} else {
throw error
diff --git a/lib/migrations/20160703062241-support-authorship.js b/lib/migrations/20160703062241-support-authorship.js
index ccdfeb3e..f452b1a7 100644
--- a/lib/migrations/20160703062241-support-authorship.js
+++ b/lib/migrations/20160703062241-support-authorship.js
@@ -17,7 +17,7 @@ module.exports = {
updatedAt: Sequelize.DATE
})
}).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.')
} else {
throw error
diff --git a/lib/migrations/20161009040430-support-delete-note.js b/lib/migrations/20161009040430-support-delete-note.js
index 39e6f7fb..56a336ac 100644
--- a/lib/migrations/20161009040430-support-delete-note.js
+++ b/lib/migrations/20161009040430-support-delete-note.js
@@ -2,7 +2,7 @@
module.exports = {
up: function (queryInterface, Sequelize) {
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.')
} else {
throw error
diff --git a/lib/migrations/20161201050312-support-email-signin.js b/lib/migrations/20161201050312-support-email-signin.js
index 0a8a832d..26bc09ea 100644
--- a/lib/migrations/20161201050312-support-email-signin.js
+++ b/lib/migrations/20161201050312-support-email-signin.js
@@ -10,7 +10,7 @@ module.exports = {
}
})
}).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.')
} else {
throw error
diff --git a/lib/models/index.js b/lib/models/index.js
index 0a44ca87..ef70475e 100644
--- a/lib/models/index.js
+++ b/lib/models/index.js
@@ -25,6 +25,7 @@ if (config.dbURL) {
// https://github.com/sequelize/sequelize/issues/6485
function stripNullByte (value) {
value = '' + value
+ // eslint-disable-next-line no-control-regex
return value ? value.replace(/\u0000/g, '') : value
}
sequelize.stripNullByte = stripNullByte
diff --git a/lib/models/user.js b/lib/models/user.js
index 1bd8c745..2ebf6d06 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -50,7 +50,7 @@ module.exports = function (sequelize, DataTypes) {
}, {
instanceMethods: {
verifyPassword: function (attempt) {
- if (scrypt.verifyKdfSync(new Buffer(this.password, 'hex'), attempt)) {
+ if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
return this
} else {
return false
diff --git a/lib/ot/editor-socketio-server.js b/lib/ot/editor-socketio-server.js
index 7b204539..5014ac8d 100755
--- a/lib/ot/editor-socketio-server.js
+++ b/lib/ot/editor-socketio-server.js
@@ -44,7 +44,7 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
socket.origin = 'operation';
self.mayWrite(socket, function (mayWrite) {
if (!mayWrite) {
- console.log("User doesn't have the right to edit.");
+ logger.info("User doesn't have the right to edit.");
return;
}
try {
@@ -71,14 +71,14 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
socket.origin = 'selection';
self.mayWrite(socket, function (mayWrite) {
if (!mayWrite) {
- console.log("User doesn't have the right to edit.");
+ logger.info("User doesn't have the right to edit.");
return;
}
self.updateSelection(socket, obj && Selection.fromJSON(obj));
});
});
socket.on('disconnect', function () {
- //console.log("Disconnect");
+ logger.debug("Disconnect");
socket.leave(self.docId);
self.onDisconnect(socket);
/*
@@ -106,7 +106,7 @@ EditorSocketIOServer.prototype.onOperation = function (socket, revision, operati
var clientId = socket.id;
var wrappedPrime = this.receiveOperation(revision, wrapped);
if(!wrappedPrime) return;
- //console.log("new operation: " + JSON.stringify(wrapped));
+ logger.debug("new operation: " + JSON.stringify(wrapped));
this.getClient(clientId).selection = wrappedPrime.meta;
revision = this.operations.length;
socket.emit('ack', revision);
@@ -161,4 +161,4 @@ EditorSocketIOServer.prototype.onDisconnect = function (socket) {
socket.broadcast.to(this.docId).emit('client_left', clientId);
};
-module.exports = EditorSocketIOServer; \ No newline at end of file
+module.exports = EditorSocketIOServer;
diff --git a/lib/realtime.js b/lib/realtime.js
index f6c62d4e..d04ffdc2 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -242,6 +242,7 @@ function getStatus (callback) {
}
})
models.User.count().then(function (regcount) {
+ // eslint-disable-next-line standard/no-callback-literal
return callback ? callback({
onlineNotes: Object.keys(notes).length,
onlineUsers: Object.keys(users).length,
@@ -283,7 +284,7 @@ function extractNoteIdFromSocket (socket) {
if (!referer) {
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]
return noteId
} else {
@@ -887,7 +888,7 @@ function connection (socket) {
// check version
socket.on('version', function () {
socket.emit('version', {
- version: config.version,
+ version: config.fullversion,
minimumCompatibleVersion: config.minimumCompatibleVersion
})
})
diff --git a/lib/response.js b/lib/response.js
index 3cbd3203..b94f473a 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -157,7 +157,7 @@ function findNote (req, res, callback, include) {
include: include || null
}).then(function (note) {
if (!note) {
- if (config.allowFreeURL && noteId) {
+ if (config.allowFreeURL && noteId && !config.forbiddenNoteIDs.includes(noteId)) {
req.alias = noteId
return newNote(req, res)
} else {
@@ -423,6 +423,9 @@ function publishNoteActions (req, res, next) {
findNote(req, res, function (note) {
var action = req.params.action
switch (action) {
+ case 'download':
+ actionDownload(req, res, note)
+ break
case 'edit':
res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)))
break
diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js
index f2a3132d..b9160f6e 100644
--- a/lib/web/auth/oauth2/index.js
+++ b/lib/web/auth/oauth2/index.js
@@ -2,13 +2,13 @@
const Router = require('express').Router
const passport = require('passport')
-const OAuth2Strategy = require('passport-oauth2').Strategy
+const { Strategy, InternalOAuthError } = require('passport-oauth2')
const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
let oauth2Auth = module.exports = Router()
-class OAuth2CustomStrategy extends OAuth2Strategy {
+class OAuth2CustomStrategy extends Strategy {
constructor (options, verify) {
options.customHeaders = options.customHeaders || {}
super(options, verify)
@@ -22,7 +22,7 @@ class OAuth2CustomStrategy extends OAuth2Strategy {
var json
if (err) {
- return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
+ return done(new InternalOAuthError('Failed to fetch user profile', err))
}
try {
@@ -67,7 +67,7 @@ OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) {
var json
if (err) {
- return done(new passport.InternalOAuthError('Failed to fetch user profile', err))
+ return done(new InternalOAuthError('Failed to fetch user profile', err))
}
try {
diff --git a/lib/web/imageRouter/filesystem.js b/lib/web/imageRouter/filesystem.js
index 8c432b0c..a2f8700d 100644
--- a/lib/web/imageRouter/filesystem.js
+++ b/lib/web/imageRouter/filesystem.js
@@ -16,5 +16,5 @@ exports.uploadImage = function (imagePath, callback) {
return
}
- callback(null, url.resolve(config.serverURL + '/uploads/', path.basename(imagePath)))
+ callback(null, url.URL.resolve(config.serverURL + '/uploads/', path.basename(imagePath)))
}
diff --git a/lib/web/statusRouter.js b/lib/web/statusRouter.js
index fb2609ea..2b9cb65f 100644
--- a/lib/web/statusRouter.js
+++ b/lib/web/statusRouter.js
@@ -96,7 +96,7 @@ statusRouter.get('/config', function (req, res) {
domain: config.domain,
urlpath: config.urlPath,
debug: config.debug,
- version: config.version,
+ version: config.fullversion,
DROPBOX_APP_KEY: config.dropbox.appKey,
allowedUploadMimeTypes: config.allowedUploadMimeTypes
}
diff --git a/locales/de.json b/locales/de.json
index 40d55468..57a5e64d 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -111,5 +111,7 @@
"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.",
"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"
} \ No newline at end of file
diff --git a/locales/en.json b/locales/en.json
index 100f4f54..ead7ce2f 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -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.",
"Delete user": "Delete user",
"Export user data": "Export user data",
- "Help us translating on %s": "Help us translating on %s"
-} \ No newline at end of file
+ "Help us translating on %s": "Help us translating on %s",
+ "Source Code": "Source Code"
+}
diff --git a/locales/fr.json b/locales/fr.json
index 7d4aa8e1..e4555027 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -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.",
"Delete user": "Suprrimez l'utilisteur",
"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"
} \ No newline at end of file
diff --git a/locales/nl.json b/locales/nl.json
index 325c273d..4cb6e4a1 100644
--- a/locales/nl.json
+++ b/locales/nl.json
@@ -1,106 +1,117 @@
{
- "Collaborative markdown notes": "Samenwerkende markdown notities",
- "Realtime collaborative markdown notes on all platforms.": "Realtime samenwerkende markdown notities.",
- "Best way to write and share your knowledge in markdown.": "De beste manier je kennis te delen en schrijven in markdown.",
- "Intro": "Introductie",
- "History": "Geschiedenis",
- "New guest note": "Nieuwe gast notitie",
- "Collaborate with URL": "Samenwerken met URL",
- "Support charts and MathJax": "Ondersteun grafieken en MathJax",
- "Support slide mode": "Ondersteun slide mode",
- "Sign In": "Log in",
- "Below is the history from browser": "Hier onder staat de browser geschiedenis",
- "Welcome!": "Welkom!",
- "New note": "Nieuwe notitie",
- "or": "of",
- "Sign Out": "Log uit",
- "Explore all features": "Ontdek alle features",
- "Select tags...": "Selecteer tags...",
- "Search keyword...": "Zoeken op keyword...",
- "Sort by title": "Sorteren op titel",
- "Title": "Titel",
- "Sort by time": "Sorteren op tijd",
- "Time": "Tijd",
- "Export history": "Exporteer geschiedenis",
- "Import history": "Importeer geschiedenis",
- "Clear history": "Verwijder geschiedenis",
- "Refresh history": "Ververs geschiedenis",
- "No history": "Geen geschidenis gevonden",
- "Import from browser": "Importeer van browser",
- "Releases": "Releases",
- "Are you sure?": "Weet je het zeker?",
- "Do you really want to delete this note?": "Will je deze notitie echt verwijderen?",
- "All users will lose their connection.": "Alle gebruikers zullen hun verbinding verliezen.",
- "Cancel": "Stoppen",
- "Yes, do it!": "Ja, doe het!",
- "Choose method": "Kies methode",
- "Sign in via %s": "Log in via %s",
- "New": "Nieuw",
- "Publish": "Publiceren",
- "Extra": "Extra",
- "Revision": "Herzien",
- "Slide Mode": "Slide Mode",
- "Export": "Exporteren",
- "Import": "Importeren",
- "Clipboard": "Kladbord",
- "Download": "Downloaden",
- "Raw HTML": "Raw HTML",
- "Edit": "Aanpassen",
- "View": "Bekijken",
- "Both": "Beide",
- "Help": "Help",
- "Upload Image": "Afbeelding uploaden",
- "Menu": "Menu",
- "This page need refresh": "Deze pagina moet herladen worden",
- "You have an incompatible client version.": "Je client is niet compatible.",
- "Refresh to update.": "Ververs om te updaten.",
- "New version available!": "Nieuwe versie beschikbaar!",
- "See releases notes here": "Zie releases hier",
- "Refresh to enjoy new features.": "Ververs om de nieuwe features te zien.",
- "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": "Ververs",
- "Contacts": "Contacten",
- "Report an issue": "Probleem rapporteren",
- "Send us email": "Stuur ons een mail",
- "Documents": "Documenten",
- "Features": "Features",
- "YAML Metadata": "YAML Metadata",
- "Slide Example": "Slide Voorbeeld",
- "Cheatsheet": "Afkijkblad",
- "Example": "Voorbeeld",
- "Syntax": "Syntax",
- "Header": "Koptekst",
- "Unordered List": "Niet gesorteerde Lijst",
- "Ordered List": "Gesorteerde List",
- "Todo List": "Todo Lijst",
- "Blockquote": "Quote",
- "Bold font": "Bold tekst",
- "Italics font": "Italics tekst",
- "Strikethrough": "Doorstreepte tekst",
- "Inserted text": "Bijgevoegde tekst",
- "Marked text": "Gemarkeerde tekst",
- "Link": "Link",
- "Image": "Afbeelding",
- "Code": "Code",
- "Externals": "Uiterlijkheden",
- "This is a alert area.": "Dit is een waarschuwingsgebied.",
- "Revert": "Terugzetten",
- "Import from clipboard": "Importeren from kladbord",
- "Paste your markdown or webpage here...": "Plak je markdown of webpagina hier...",
- "Clear": "Legen",
- "This note is locked": "Deze notitie zit op slot",
- "Sorry, only owner can edit this note.": "Sorry, alleen de eigenaar kan deze notitie aanpassen.",
- "OK": "OK",
- "Reach the limit": "Limiet bereikt",
- "Sorry, you've reached the max length this note can be.": "Sorry, je notitie heeft de maximale lengte bereikt.",
- "Please reduce the content or divide it to more notes, thank you!": "Verwijder alsjeblieft wat tekst of verdeel het over meerdere notities!",
- "Import from Gist": "Importeren vanaf een Gist",
- "Paste your gist url here...": "Plak je Gist URL hier...",
- "Import from Snippet": "Imporeren vanaf een Snippet",
- "Select From Available Projects": "Selecteer van beschikbare projecten",
- "Select From Available Snippets": "Selecteer van beschikbare Snippets",
- "OR": "OF",
- "Export to Snippet": "Exporteren naar Snippet",
- "Select Visibility Level": "Selecteer zichtbaarheids niveau"
-}
+ "Collaborative markdown notes": "Samenwerkende markdown notities",
+ "Realtime collaborative markdown notes on all platforms.": "Realtime samenwerkende markdown notities.",
+ "Best way to write and share your knowledge in markdown.": "De beste manier je kennis te delen en schrijven in markdown.",
+ "Intro": "Introductie",
+ "History": "Geschiedenis",
+ "New guest note": "Nieuwe gastnotitie",
+ "Collaborate with URL": "Samenwerken met URL",
+ "Support charts and MathJax": "Ondersteun grafieken en MathJax",
+ "Support slide mode": "Ondersteun presentatiemodus",
+ "Sign In": "Inloggen",
+ "Below is the history from browser": "Hier onder staat de browser geschiedenis",
+ "Welcome!": "Welkom!",
+ "New note": "Nieuwe notitie",
+ "or": "of",
+ "Sign Out": "Uitloggen",
+ "Explore all features": "Ontdek alle features",
+ "Select tags...": "Selecteer tags...",
+ "Search keyword...": "Zoeken op keyword...",
+ "Sort by title": "Sorteren op titel",
+ "Title": "Titel",
+ "Sort by time": "Sorteren op tijd",
+ "Time": "Tijd",
+ "Export history": "Exporteer geschiedenis",
+ "Import history": "Importeer geschiedenis",
+ "Clear history": "Verwijder geschiedenis",
+ "Refresh history": "Ververs geschiedenis",
+ "No history": "Geen geschidenis gevonden",
+ "Import from browser": "Importeer van browser",
+ "Releases": "Releases",
+ "Are you sure?": "Weet je het zeker?",
+ "Do you really want to delete this note?": "Will je deze notitie echt verwijderen?",
+ "All users will lose their connection.": "Alle gebruikers zullen hun verbinding verliezen.",
+ "Cancel": "Stoppen",
+ "Yes, do it!": "Ja, doe het!",
+ "Choose method": "Kies methode",
+ "Sign in via %s": "Log in via %s",
+ "New": "Nieuw",
+ "Publish": "Publiceren",
+ "Extra": "Extra",
+ "Revision": "Versie",
+ "Slide Mode": "Presentatiemodus",
+ "Export": "Exporteren",
+ "Import": "Importeren",
+ "Clipboard": "Kladbord",
+ "Download": "Downloaden",
+ "Raw HTML": "Ruwe HTML",
+ "Edit": "Aanpassen",
+ "View": "Bekijken",
+ "Both": "Beide",
+ "Help": "Help",
+ "Upload Image": "Afbeelding uploaden",
+ "Menu": "Menu",
+ "This page need refresh": "Deze pagina moet vernieuwd worden",
+ "You have an incompatible client version.": "Je client is niet compatibel.",
+ "Refresh to update.": "Ververs om te updaten.",
+ "New version available!": "Nieuwe versie beschikbaar!",
+ "See releases notes here": "Bekijk de release notes hier",
+ "Refresh to enjoy new features.": "Ververs om de nieuwe features te zien.",
+ "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": "Ververs",
+ "Contacts": "Contacten",
+ "Report an issue": "Probleem rapporteren",
+ "Meet us on %s": "Ontmoet ons op %s",
+ "Send us email": "Stuur ons een mail",
+ "Documents": "Documenten",
+ "Features": "Features",
+ "YAML Metadata": "YAML Metadata",
+ "Slide Example": "Slide Voorbeeld",
+ "Cheatsheet": "Spiekbrief",
+ "Example": "Voorbeeld",
+ "Syntax": "Syntax",
+ "Header": "Koptekst",
+ "Unordered List": "Niet gesorteerde Lijst",
+ "Ordered List": "Gesorteerde List",
+ "Todo List": "Todo Lijst",
+ "Blockquote": "Citaat",
+ "Bold font": "Bold tekst",
+ "Italics font": "Italics tekst",
+ "Strikethrough": "Doorstreepte tekst",
+ "Inserted text": "Bijgevoegde tekst",
+ "Marked text": "Gemarkeerde tekst",
+ "Link": "Link",
+ "Image": "Afbeelding",
+ "Code": "Code",
+ "Externals": "Uiterlijkheden",
+ "This is a alert area.": "Dit is een waarschuwingsgebied.",
+ "Revert": "Terugzetten",
+ "Import from clipboard": "Importeren from kladbord",
+ "Paste your markdown or webpage here...": "Plak je markdown of webpagina hier...",
+ "Clear": "Legen",
+ "This note is locked": "Deze notitie zit op slot",
+ "Sorry, only owner can edit this note.": "Sorry, alleen de eigenaar kan deze notitie aanpassen.",
+ "OK": "OK",
+ "Reach the limit": "Limiet bereikt",
+ "Sorry, you've reached the max length this note can be.": "Sorry, je notitie heeft de maximale lengte bereikt.",
+ "Please reduce the content or divide it to more notes, thank you!": "Verwijder alsjeblieft wat tekst of verdeel het over meerdere notities!",
+ "Import from Gist": "Importeren vanaf een Gist",
+ "Paste your gist url here...": "Plak je Gist URL hier...",
+ "Import from Snippet": "Imporeren vanaf een Snippet",
+ "Select From Available Projects": "Selecteer van beschikbare projecten",
+ "Select From Available Snippets": "Selecteer van beschikbare Snippets",
+ "OR": "OF",
+ "Export to Snippet": "Exporteren naar Snippet",
+ "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"
+} \ No newline at end of file
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 87907189..93e1c86f 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -1,109 +1,117 @@
{
- "Collaborative markdown notes": "Markdown 协作笔记",
- "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即时协作笔记",
- "Best way to write and share your knowledge in markdown.": "您使用 Markdown 写作与分享知识的最佳方式",
- "Intro": "简介",
- "History": "历史",
- "New guest note": "建立访客笔记",
- "Collaborate with URL": "使用网址协作",
- "Support charts and MathJax": "支持图表与 MathJax",
- "Support slide mode": "支持简报模式",
- "Sign In": "登录",
- "Below is the history from browser": "以下为来自浏览器的历史",
- "Welcome!": "欢迎!",
- "New note": "建立笔记",
- "or": "或",
- "Sign Out": "登出",
- "Explore all features": "探索所有功能",
- "Select tags...": "选择标签...",
- "Search keyword...": "搜索关键字...",
- "Sort by title": "用标题排序",
- "Title": "标题",
- "Sort by time": "用时间排序",
- "Time": "时间",
- "Export history": "导出历史",
- "Import history": "导入历史",
- "Clear history": "清空历史",
- "Refresh history": "刷新历史",
- "No history": "没有历史",
- "Import from browser": "从浏览器导入",
- "Releases": "版本",
- "Are you sure?": "你确定吗?",
- "Cancel": "取消",
- "Yes, do it!": "没错,就这样办!",
- "Choose method": "选择方式",
- "Sign in via %s": "通过 %s 登录",
- "New": "新增",
- "Publish": "发表",
- "Extra": "增益",
- "Revision": "修订版本",
- "Slide Mode": "简报模式",
- "Export": "导出",
- "Import": "导入",
- "Clipboard": "剪贴板",
- "Download": "下载",
- "Raw HTML": "纯 HTML",
- "Edit": "编辑",
- "View": "检视",
- "Both": "双栏",
- "Help": "帮助",
- "Upload Image": "上传图片",
- "Menu": "菜单",
- "This page need refresh": "此页面需要重新整理",
- "You have an incompatible client version.": "您使用的是不相容的客户端",
- "Refresh to update.": "请重新整理来更新",
- "New version available!": "新版本来了!",
- "See releases notes here": "请由此查阅更新纪录",
- "Refresh to enjoy new features.": "请重新整理来享受最新功能",
- "Your user state has changed.": "您的使用者状态已变更",
- "Refresh to load new user state.": "请重新整理来载入新的使用者状态",
- "Refresh": "重新整理",
- "Contacts": "联络方式",
- "Report an issue": "报告问题",
- "Meet us on %s": "在 %s 上联系我们",
- "Send us email": "寄信给我们",
- "Documents": "文件",
- "Features": "功能简介",
- "YAML Metadata": "YAML Metadata",
- "Slide Example": "简报范例",
- "Cheatsheet": "快速简表",
- "Example": "范例",
- "Syntax": "语法",
- "Header": "标题",
- "Unordered List": "无序清单",
- "Ordered List": "有序清单",
- "Todo List": "待办事项",
- "Blockquote": "引用",
- "Bold font": "粗体",
- "Italics font": "斜体",
- "Strikethrough": "删除线",
- "Inserted text": "插入文字",
- "Marked text": "标记文字",
- "Link": "链接",
- "Image": "图片",
- "Code": "代码",
- "Externals": "外部",
- "This is a alert area.": "这是警告区块",
- "Revert": "还原",
- "Import from clipboard": "从剪贴板导入",
- "Paste your markdown or webpage here...": "在这里贴上 Markdown 或是网页内容...",
- "Clear": "清除",
- "This note is locked": "此份笔记已被锁定",
- "Sorry, only owner can edit this note.": "抱歉,只有拥有者可以编辑此笔记",
- "OK": "好的",
- "Reach the limit": "到达上限",
- "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份笔记可用的最大长度",
- "Please reduce the content or divide it to more notes, thank you!": "请减少内容或是将内容切成更多笔记,谢谢!",
- "Import from Gist": "从 Gist 导入",
- "Paste your gist url here...": "在这里贴上 gist 网址...",
- "Import from Snippet": "从 Snippet 导入",
- "Select From Available Projects": "从可用的项目中选择",
- "Select From Available Snippets": "从可用的 Snippets 中选择",
- "OR": "或是",
- "Export to Snippet": "导出到 Snippet",
- "Select Visibility Level": "选择可见层级",
- "Night Theme": "夜间主题",
- "Do you really want to delete this note?": "确定要删除这个文件吗?",
- "All users will lose their connection.": "所有用户将失去连接",
- "Follow us on %s and %s.": "在%s和%s上关注我们"
-}
+ "Collaborative markdown notes": "Markdown 协作笔记",
+ "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即时协作笔记",
+ "Best way to write and share your knowledge in markdown.": "您使用 Markdown 写作与分享知识的最佳方式",
+ "Intro": "简介",
+ "History": "历史",
+ "New guest note": "建立访客笔记",
+ "Collaborate with URL": "使用网址协作",
+ "Support charts and MathJax": "支持图表与 MathJax",
+ "Support slide mode": "支持简报模式",
+ "Sign In": "登录",
+ "Below is the history from browser": "以下为来自浏览器的历史",
+ "Welcome!": "欢迎!",
+ "New note": "建立笔记",
+ "or": "或",
+ "Sign Out": "登出",
+ "Explore all features": "探索所有功能",
+ "Select tags...": "选择标签...",
+ "Search keyword...": "搜索关键字...",
+ "Sort by title": "用标题排序",
+ "Title": "标题",
+ "Sort by time": "用时间排序",
+ "Time": "时间",
+ "Export history": "导出历史",
+ "Import history": "导入历史",
+ "Clear history": "清空历史",
+ "Refresh history": "刷新历史",
+ "No history": "没有历史",
+ "Import from browser": "从浏览器导入",
+ "Releases": "版本",
+ "Are you sure?": "你确定吗?",
+ "Do you really want to delete this note?": "确定要删除这个文件吗?",
+ "All users will lose their connection.": "所有用户将失去连接",
+ "Cancel": "取消",
+ "Yes, do it!": "没错,就这样办!",
+ "Choose method": "选择方式",
+ "Sign in via %s": "通过 %s 登录",
+ "New": "新增",
+ "Publish": "发表",
+ "Extra": "增益",
+ "Revision": "修订版本",
+ "Slide Mode": "简报模式",
+ "Export": "导出",
+ "Import": "导入",
+ "Clipboard": "剪贴板",
+ "Download": "下载",
+ "Raw HTML": "纯 HTML",
+ "Edit": "编辑",
+ "View": "检视",
+ "Both": "双栏",
+ "Help": "帮助",
+ "Upload Image": "上传图片",
+ "Menu": "菜单",
+ "This page need refresh": "此页面需要重新整理",
+ "You have an incompatible client version.": "您使用的是不相容的客户端",
+ "Refresh to update.": "请重新整理来更新",
+ "New version available!": "新版本来了!",
+ "See releases notes here": "请由此查阅更新纪录",
+ "Refresh to enjoy new features.": "请重新整理来享受最新功能",
+ "Your user state has changed.": "您的使用者状态已变更",
+ "Refresh to load new user state.": "请重新整理来载入新的使用者状态",
+ "Refresh": "重新整理",
+ "Contacts": "联络方式",
+ "Report an issue": "报告问题",
+ "Meet us on %s": "在 %s 上联系我们",
+ "Send us email": "寄信给我们",
+ "Documents": "文件",
+ "Features": "功能简介",
+ "YAML Metadata": "YAML Metadata",
+ "Slide Example": "简报范例",
+ "Cheatsheet": "快速简表",
+ "Example": "范例",
+ "Syntax": "语法",
+ "Header": "标题",
+ "Unordered List": "无序清单",
+ "Ordered List": "有序清单",
+ "Todo List": "待办事项",
+ "Blockquote": "引用",
+ "Bold font": "粗体",
+ "Italics font": "斜体",
+ "Strikethrough": "删除线",
+ "Inserted text": "插入文字",
+ "Marked text": "标记文字",
+ "Link": "链接",
+ "Image": "图片",
+ "Code": "代码",
+ "Externals": "外部",
+ "This is a alert area.": "这是警告区块",
+ "Revert": "还原",
+ "Import from clipboard": "从剪贴板导入",
+ "Paste your markdown or webpage here...": "在这里贴上 Markdown 或是网页内容...",
+ "Clear": "清除",
+ "This note is locked": "此份笔记已被锁定",
+ "Sorry, only owner can edit this note.": "抱歉,只有拥有者可以编辑此笔记",
+ "OK": "好的",
+ "Reach the limit": "到达上限",
+ "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份笔记可用的最大长度",
+ "Please reduce the content or divide it to more notes, thank you!": "请减少内容或是将内容切成更多笔记,谢谢!",
+ "Import from Gist": "从 Gist 导入",
+ "Paste your gist url here...": "在这里贴上 gist 网址...",
+ "Import from Snippet": "从 Snippet 导入",
+ "Select From Available Projects": "从可用的项目中选择",
+ "Select From Available Snippets": "从可用的 Snippets 中选择",
+ "OR": "或是",
+ "Export to Snippet": "导出到 Snippet",
+ "Select Visibility Level": "选择可见层级",
+ "Night Theme": "夜间主题",
+ "Follow us on %s and %s.": "在%s和%s上关注我们",
+ "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": "源码"
+} \ No newline at end of file
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 090af9c0..eb95e630 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -1,108 +1,117 @@
{
- "Collaborative markdown notes": "Markdown 協作筆記",
- "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即時協作筆記",
- "Best way to write and share your knowledge in markdown.": "您使用 Markdown 寫作與分享知識的最佳方式",
- "Intro": "簡介",
- "History": "紀錄",
- "New guest note": "建立訪客筆記",
- "Collaborate with URL": "使用網址協作",
- "Support charts and MathJax": "支援圖表與 MathJax",
- "Support slide mode": "支援簡報模式",
- "Sign In": "登入",
- "Below is the history from browser": "以下為來自瀏覽器的紀錄",
- "Welcome!": "歡迎!",
- "New note": "建立筆記",
- "or": "或",
- "Sign Out": "登出",
- "Explore all features": "探索所有功能",
- "Select tags...": "選擇標籤...",
- "Search keyword...": "搜尋關鍵字...",
- "Sort by title": "用標題排序",
- "Title": "標題",
- "Sort by time": "用時間排序",
- "Time": "時間",
- "Export history": "匯出紀錄",
- "Import history": "匯入紀錄",
- "Clear history": "清空紀錄",
- "Refresh history": "更新紀錄",
- "No history": "沒有紀錄",
- "Import from browser": "從瀏覽器匯入",
- "Releases": "版本",
- "Are you sure?": "你確定嗎?",
- "Cancel": "取消",
- "Yes, do it!": "沒錯,就這樣辦!",
- "Choose method": "選擇方式",
- "Sign in via %s": "透過 %s 登入",
- "New": "新增",
- "Publish": "發表",
- "Extra": "增益",
- "Revision": "修訂版本",
- "Slide Mode": "簡報模式",
- "Export": "匯出",
- "Import": "匯入",
- "Clipboard": "剪貼簿",
- "Download": "下載",
- "Raw HTML": "純 HTML",
- "Edit": "編輯",
- "View": "檢視",
- "Both": "雙欄",
- "Help": "協助",
- "Upload Image": "上傳圖片",
- "Menu": "選單",
- "This page need refresh": "此頁面需要重新整理",
- "You have an incompatible client version.": "您使用的是不相容的客戶端",
- "Refresh to update.": "請重新整理來更新",
- "New version available!": "新版本來了!",
- "See releases notes here": "請由此查閱更新紀錄",
- "Refresh to enjoy new features.": "請重新整理來享受最新功能",
- "Your user state has changed.": "您的使用者狀態已變更",
- "Refresh to load new user state.": "請重新整理來載入新的使用者狀態",
- "Refresh": "重新整理",
- "Contacts": "聯絡方式",
- "Report an issue": "回報問題",
- "Meet us on %s": "透過 %s 聯絡我們",
- "Send us email": "寄信給我們",
- "Documents": "文件",
- "Features": "功能簡介",
- "YAML Metadata": "YAML Metadata",
- "Slide Example": "簡報範例",
- "Cheatsheet": "快速簡表",
- "Example": "範例",
- "Syntax": "語法",
- "Header": "標題",
- "Unordered List": "無序清單",
- "Ordered List": "有序清單",
- "Todo List": "待辦事項",
- "Blockquote": "引用",
- "Bold font": "粗體",
- "Italics font": "斜體",
- "Strikethrough": "刪除線",
- "Inserted text": "插入文字",
- "Marked text": "標記文字",
- "Link": "連結",
- "Image": "圖片",
- "Code": "程式碼",
- "Externals": "外部",
- "This is a alert area.": "這是警告區塊",
- "Revert": "還原",
- "Import from clipboard": "從剪貼簿匯入",
- "Paste your markdown or webpage here...": "在這裡貼上 Markdown 或是網頁內容...",
- "Clear": "清除",
- "This note is locked": "此份筆記已被鎖定",
- "Sorry, only owner can edit this note.": "抱歉,只有擁有者可以編輯此筆記",
- "OK": "好的",
- "Reach the limit": "到達上限",
- "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份筆記可用的最大長度",
- "Please reduce the content or divide it to more notes, thank you!": "請減少內容或是將內容切成更多筆記,謝謝!",
- "Import from Gist": "從 Gist 匯入",
- "Paste your gist url here...": "在這裡貼上 gist 網址...",
- "Import from Snippet": "從 Snippet 匯入",
- "Select From Available Projects": "從可用的專案中選擇",
- "Select From Available Snippets": "從可用的 Snippets 中選擇",
- "OR": "或是",
- "Export to Snippet": "匯出到 Snippet",
- "Select Visibility Level": "選擇可見層級",
- "Night Theme": "夜間主題",
- "Do you really want to delete this note?": "確定要刪除這個文件嗎?",
- "All users will lose their connection.": "所有使用者將會失去連線"
-}
+ "Collaborative markdown notes": "Markdown 協作筆記",
+ "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即時協作筆記",
+ "Best way to write and share your knowledge in markdown.": "您使用 Markdown 寫作與分享知識的最佳方式",
+ "Intro": "簡介",
+ "History": "紀錄",
+ "New guest note": "建立訪客筆記",
+ "Collaborate with URL": "使用網址協作",
+ "Support charts and MathJax": "支援圖表與 MathJax",
+ "Support slide mode": "支援簡報模式",
+ "Sign In": "登入",
+ "Below is the history from browser": "以下為來自瀏覽器的紀錄",
+ "Welcome!": "歡迎!",
+ "New note": "建立筆記",
+ "or": "或",
+ "Sign Out": "登出",
+ "Explore all features": "探索所有功能",
+ "Select tags...": "選擇標籤...",
+ "Search keyword...": "搜尋關鍵字...",
+ "Sort by title": "用標題排序",
+ "Title": "標題",
+ "Sort by time": "用時間排序",
+ "Time": "時間",
+ "Export history": "匯出紀錄",
+ "Import history": "匯入紀錄",
+ "Clear history": "清空紀錄",
+ "Refresh history": "更新紀錄",
+ "No history": "沒有紀錄",
+ "Import from browser": "從瀏覽器匯入",
+ "Releases": "版本",
+ "Are you sure?": "你確定嗎?",
+ "Do you really want to delete this note?": "確定要刪除這個文件嗎?",
+ "All users will lose their connection.": "所有使用者將會失去連線",
+ "Cancel": "取消",
+ "Yes, do it!": "沒錯,就這樣辦!",
+ "Choose method": "選擇方式",
+ "Sign in via %s": "透過 %s 登入",
+ "New": "新增",
+ "Publish": "發表",
+ "Extra": "增益",
+ "Revision": "修訂版本",
+ "Slide Mode": "簡報模式",
+ "Export": "匯出",
+ "Import": "匯入",
+ "Clipboard": "剪貼簿",
+ "Download": "下載",
+ "Raw HTML": "純 HTML",
+ "Edit": "編輯",
+ "View": "檢視",
+ "Both": "雙欄",
+ "Help": "協助",
+ "Upload Image": "上傳圖片",
+ "Menu": "選單",
+ "This page need refresh": "此頁面需要重新整理",
+ "You have an incompatible client version.": "您使用的是不相容的客戶端",
+ "Refresh to update.": "請重新整理來更新",
+ "New version available!": "新版本來了!",
+ "See releases notes here": "請由此查閱更新紀錄",
+ "Refresh to enjoy new features.": "請重新整理來享受最新功能",
+ "Your user state has changed.": "您的使用者狀態已變更",
+ "Refresh to load new user state.": "請重新整理來載入新的使用者狀態",
+ "Refresh": "重新整理",
+ "Contacts": "聯絡方式",
+ "Report an issue": "回報問題",
+ "Meet us on %s": "透過 %s 聯絡我們",
+ "Send us email": "寄信給我們",
+ "Documents": "文件",
+ "Features": "功能簡介",
+ "YAML Metadata": "YAML Metadata",
+ "Slide Example": "簡報範例",
+ "Cheatsheet": "快速簡表",
+ "Example": "範例",
+ "Syntax": "語法",
+ "Header": "標題",
+ "Unordered List": "無序清單",
+ "Ordered List": "有序清單",
+ "Todo List": "待辦事項",
+ "Blockquote": "引用",
+ "Bold font": "粗體",
+ "Italics font": "斜體",
+ "Strikethrough": "刪除線",
+ "Inserted text": "插入文字",
+ "Marked text": "標記文字",
+ "Link": "連結",
+ "Image": "圖片",
+ "Code": "程式碼",
+ "Externals": "外部",
+ "This is a alert area.": "這是警告區塊",
+ "Revert": "還原",
+ "Import from clipboard": "從剪貼簿匯入",
+ "Paste your markdown or webpage here...": "在這裡貼上 Markdown 或是網頁內容...",
+ "Clear": "清除",
+ "This note is locked": "此份筆記已被鎖定",
+ "Sorry, only owner can edit this note.": "抱歉,只有擁有者可以編輯此筆記",
+ "OK": "好的",
+ "Reach the limit": "到達上限",
+ "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份筆記可用的最大長度",
+ "Please reduce the content or divide it to more notes, thank you!": "請減少內容或是將內容切成更多筆記,謝謝!",
+ "Import from Gist": "從 Gist 匯入",
+ "Paste your gist url here...": "在這裡貼上 gist 網址...",
+ "Import from Snippet": "從 Snippet 匯入",
+ "Select From Available Projects": "從可用的專案中選擇",
+ "Select From Available Snippets": "從可用的 Snippets 中選擇",
+ "OR": "或是",
+ "Export to Snippet": "匯出到 Snippet",
+ "Select Visibility Level": "選擇可見層級",
+ "Night Theme": "夜間主題",
+ "Follow us on %s and %s.": "來 %s 或 %s 和我們互動吧!",
+ "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": "原始碼"
+} \ No newline at end of file
diff --git a/package.json b/package.json
index 304a526d..2cc9415a 100644
--- a/package.json
+++ b/package.json
@@ -5,13 +5,14 @@
"main": "app.js",
"license": "AGPL-3.0",
"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",
- "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",
"build": "webpack --config webpack.prod.js --progress --colors --bail",
"postinstall": "bin/heroku",
- "start": "node app.js",
+ "start": "sequelize db:migrate && node app.js",
"doctoc": "doctoc --title='# Table of Contents' README.md"
},
"dependencies": {
@@ -129,7 +130,7 @@
"velocity-animate": "^1.4.0",
"visibilityjs": "^1.2.4",
"viz.js": "^1.7.0",
- "winston": "^2.3.0",
+ "winston": "^3.1.0",
"ws": "^6.0.0",
"xss": "^1.0.3"
},
@@ -169,6 +170,12 @@
"css-loader": "^1.0.0",
"doctoc": "^1.3.0",
"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",
"expose-loader": "^0.7.5",
"file-loader": "^2.0.0",
@@ -180,7 +187,6 @@
"mini-css-extract-plugin": "^0.4.1",
"optimize-css-assets-webpack-plugin": "^5.0.0",
"script-loader": "^0.7.2",
- "standard": "^9.0.1",
"string-loader": "^0.0.1",
"style-loader": "^0.21.0",
"uglifyjs-webpack-plugin": "^1.2.7",
@@ -190,34 +196,6 @@
"webpack-merge": "^4.1.4",
"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": {
"bufferutil": "^4.0.0",
"utf-8-validate": "^5.0.1"
diff --git a/public/.eslintrc.js b/public/.eslintrc.js
new file mode 100644
index 00000000..dc37b3cb
--- /dev/null
+++ b/public/.eslintrc.js
@@ -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
+ }
+};
diff --git a/public/docs/features.md b/public/docs/features.md
index 3d790039..a4ffb633 100644
--- a/public/docs/features.md
+++ b/public/docs/features.md
@@ -72,9 +72,11 @@ Notes can be embedded as follows:
## [Slide Mode](./slide-example):
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.
+To switch the editor into slide mode, set the [document type](./yaml-metadata#type) to `slide`.
+
View
===
## Table of Contents:
@@ -88,9 +90,23 @@ You can hover and click <i class="fa fa-chain"></i> to anchor on it.
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:
-Just like Sublime text, which is pretty quick and convenient.
-> For more infomation, see [here](https://codemirror.net/demo/sublime.html).
+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 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:
This editor provides full auto-complete hints in markdown.
diff --git a/public/docs/slide-example.md b/public/docs/slide-example.md
index 5503cbd7..49503669 100644
--- a/public/docs/slide-example.md
+++ b/public/docs/slide-example.md
@@ -1,4 +1,5 @@
---
+type: slide
slideOptions:
transition: slide
---
diff --git a/public/docs/yaml-metadata.md b/public/docs/yaml-metadata.md
index 888345f7..839616a8 100644
--- a/public/docs/yaml-metadata.md
+++ b/public/docs/yaml-metadata.md
@@ -25,7 +25,7 @@ This option will set the note title which prior than content title.
> default: not set
**Example**
-```xml
+```yml
title: meta title
```
@@ -36,7 +36,7 @@ This option will set the note description.
> default: not set
**Example**
-```xml
+```yml
description: meta description
```
@@ -47,7 +47,7 @@ This option will set the tags which prior than content tags.
> default: not set
**Example**
-```xml
+```yml
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
**Example**
-```xml
+```yml
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)
**Example**
-```xml
+```yml
langs: ja-jp
```
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`.
Look more at here:
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)
**Example**
-```xml
+```yml
dir: rtl
```
@@ -102,35 +102,46 @@ You can only use whether `true` or `false`.
> default: not set (which will be true)
**Example**
-```xml
+```yml
breaks: false
```
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)
**Example**
-```xml
+```yml
GA: UA-12345667-8
```
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)
**Example**
-```xml
+```yml
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
---
-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:
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)
**Example**
-```xml
+```yml
slideOptions:
transition: fade
theme: white
diff --git a/public/js/history.js b/public/js/history.js
index b4c26b42..6007bef4 100644
--- a/public/js/history.js
+++ b/public/js/history.js
@@ -218,6 +218,7 @@ export function getStorageHistory (callback) {
if (typeof data === 'string') { data = JSON.parse(data) }
callback(data)
}
+ // eslint-disable-next-line standard/no-callback-literal
callback([])
}
diff --git a/public/js/index.js b/public/js/index.js
index a7f4df94..c2969e92 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -2511,7 +2511,9 @@ function buildCursor (user) {
// editor actions
function removeNullByte (cm, change) {
var str = change.text.join('\n')
+ // eslint-disable-next-line no-control-regex
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'))
}
}
@@ -3047,7 +3049,7 @@ function checkInCode () {
function checkAbove (method) {
var cursor = editor.getCursor()
var text = []
- for (var i = 0; i < cursor.line; i++) { // contain current line
+ for (var i = 0; i < cursor.line; i++) { // contain current line
text.push(editor.getLine(i))
}
text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch)
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
index 0537e927..f05d01b8 100644
--- a/public/js/lib/editor/index.js
+++ b/public/js/lib/editor/index.js
@@ -492,7 +492,7 @@ export default class Editor {
clearInterval(spellcheckTimer)
}
},
- 100,
+ 100
)
}
}
@@ -514,7 +514,7 @@ export default class Editor {
}
setOverrideBrowserKeymap () {
var overrideBrowserKeymap = $(
- '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+ '.ui-preferences-override-browser-keymap label > input[type="checkbox"]'
)
if (overrideBrowserKeymap.is(':checked')) {
Cookies.set('preferences-override-browser-keymap', true, {
@@ -529,10 +529,10 @@ export default class Editor {
setPreferences () {
var overrideBrowserKeymap = $(
- '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+ '.ui-preferences-override-browser-keymap label > input[type="checkbox"]'
)
var cookieOverrideBrowserKeymap = Cookies.get(
- 'preferences-override-browser-keymap',
+ 'preferences-override-browser-keymap'
)
if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
overrideBrowserKeymap.prop('checked', true)
diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js
index ca06d30c..29a37782 100644
--- a/public/js/lib/editor/ui-elements.js
+++ b/public/js/lib/editor/ui-elements.js
@@ -67,7 +67,7 @@ export const getUIElements = () => ({
codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
codemirrorSizerInner: $(
- '.ui-edit-area .CodeMirror .CodeMirror-sizer > div',
+ '.ui-edit-area .CodeMirror .CodeMirror-sizer > div'
),
markdown: $('.ui-view-area .markdown-body'),
resize: {
diff --git a/public/js/render.js b/public/js/render.js
index 23b8934e..ff5e2bf2 100644
--- a/public/js/render.js
+++ b/public/js/render.js
@@ -1,6 +1,8 @@
/* eslint-env browser, jquery */
-/* global filterXSS */
// allow some attributes
+
+var filterXSS = require('xss')
+
var whiteListAttr = ['id', 'class', 'style']
window.whiteListAttr = whiteListAttr
// allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript://
@@ -71,5 +73,6 @@ function preventXSS (html) {
window.preventXSS = preventXSS
module.exports = {
- preventXSS: preventXSS
+ preventXSS: preventXSS,
+ escapeAttrValue: filterXSS.escapeAttrValue
}
diff --git a/public/js/reveal-markdown.js b/public/js/reveal-markdown.js
index d15b5ebd..ad5bfd04 100644
--- a/public/js/reveal-markdown.js
+++ b/public/js/reveal-markdown.js
@@ -1,6 +1,6 @@
/* eslint-env browser, jquery */
-import { preventXSS } from './render'
+import { preventXSS, escapeAttrValue } from './render'
import { md } from './extra'
/**
@@ -259,7 +259,7 @@ import { md } from './extra'
while ((matchesClass = mardownClassRegex.exec(classes))) {
var name = matchesClass[1]
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
}
diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs
index 907cc1a8..a5e591ef 100644
--- a/public/views/index/body.ejs
+++ b/public/views/index/body.ejs
@@ -65,19 +65,19 @@
</span>
<div class="lead row" style="width: 90%; margin: 0 auto;">
<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>
<h4><%= __('Collaborate with URL') %></h4>
</a>
</div>
<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>
<h4><%= __('Support charts and MathJax') %></h4>
</a>
</div>
<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>
<h4><%= __('Support slide mode') %></h4>
</a>
@@ -150,7 +150,7 @@
<option value="id">Bahasa Indonesia</option>
</select>
<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>
<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>') %>
diff --git a/tmp/.keep b/tmp/.keep
deleted file mode 100644
index e69de29b..00000000
--- a/tmp/.keep
+++ /dev/null
diff --git a/webpack.common.js b/webpack.common.js
index 1fbf247d..1e9c0707 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -202,7 +202,6 @@ module.exports = {
'babel-polyfill',
'script-loader!jquery-ui-resizable',
'script-loader!js-url',
- 'expose-loader?filterXSS!xss',
'script-loader!Idle.Js',
'expose-loader?LZString!lz-string',
'script-loader!codemirror',
@@ -253,7 +252,6 @@ module.exports = {
'script-loader!handlebars',
'expose-loader?hljs!highlight.js',
'expose-loader?emojify!emojify.js',
- 'expose-loader?filterXSS!xss',
'script-loader!Idle.Js',
'script-loader!gist-embed',
'expose-loader?LZString!lz-string',
@@ -273,7 +271,6 @@ module.exports = {
],
pretty: [
'babel-polyfill',
- 'expose-loader?filterXSS!xss',
'flowchart.js',
'js-sequence-diagrams',
'expose-loader?RevealMarkdown!reveal-markdown',
@@ -298,7 +295,6 @@ module.exports = {
'script-loader!handlebars',
'expose-loader?hljs!highlight.js',
'expose-loader?emojify!emojify.js',
- 'expose-loader?filterXSS!xss',
'script-loader!gist-embed',
'flowchart.js',
'js-sequence-diagrams',
@@ -310,7 +306,6 @@ module.exports = {
slide: [
'babel-polyfill',
'bootstrap-tooltip',
- 'expose-loader?filterXSS!xss',
'flowchart.js',
'js-sequence-diagrams',
'expose-loader?RevealMarkdown!reveal-markdown',
@@ -338,7 +333,6 @@ module.exports = {
'script-loader!handlebars',
'expose-loader?hljs!highlight.js',
'expose-loader?emojify!emojify.js',
- 'expose-loader?filterXSS!xss',
'script-loader!gist-embed',
'flowchart.js',
'js-sequence-diagrams',
diff --git a/yarn.lock b/yarn.lock
index bddcab02..504b8129 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -716,10 +716,10 @@ autolinker@~0.15.0:
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832"
integrity sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI=
-aws-sdk@^2.7.20:
- version "2.266.1"
- resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.266.1.tgz#1d0f14cbf82c95cec97752cd5b00df0315a67ff4"
- integrity sha512-b8lisloCETh0Fx0il540i+Hbgf3hyegQ6ezoJFggfc1HIbqzvIjVJYJhOsYl1fL1o+iMUaVU4ZH8cSyoMFR2Tw==
+aws-sdk@^2.345.0:
+ version "2.353.0"
+ resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.353.0.tgz#3c809d2b02834d892a3f5c3f1171273b336e5692"
+ integrity sha512-c5MwJhfcHwA2lC1Wq9csQvP9gz8dVGpZ64s5j9f/sWY6eZiDCQ6OWjxj+VJfpnCmfxyC/pdZO7JDGwems7dqIQ==
dependencies:
buffer "4.9.1"
events "1.1.1"
@@ -729,7 +729,7 @@ aws-sdk@^2.7.20:
sax "1.2.1"
url "0.10.3"
uuid "3.1.0"
- xml2js "0.4.17"
+ xml2js "0.4.19"
aws-sign2@~0.6.0:
version "0.6.0"
@@ -7883,11 +7883,12 @@ passport-oauth@^1.0.0:
passport-oauth1 "1.x.x"
passport-oauth2 "1.x.x"
-passport-saml@^0.31.0:
- version "0.31.0"
- resolved "https://registry.yarnpkg.com/passport-saml/-/passport-saml-0.31.0.tgz#e4d654cab30f018bfd39056efe7bcfa770aab463"
- integrity sha1-5NZUyrMPAYv9OQVu/nvPp3CqtGM=
+passport-saml@^0.35.0:
+ version "0.35.0"
+ resolved "https://registry.yarnpkg.com/passport-saml/-/passport-saml-0.35.0.tgz#06a4952bde9e003923e80efa5c6faffcf7d4f7e0"
+ integrity sha512-WvLhFeMhAy9GaJvuORR2M6NiW0L9KxSlQRbiTajHBJRMziJ/Yg7uZosrwpoDwhztYaB8PpG0tCuMRG43WWYoCQ==
dependencies:
+ debug "^3.1.0"
passport-strategy "*"
q "^1.5.0"
xml-crypto "^0.10.1"
@@ -9184,7 +9185,7 @@ request@2.x, request@^2.40.0, request@^2.79.0, request@^2.81.0, request@^2.86.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
-request@^2.61.0:
+request@^2.61.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -11402,15 +11403,7 @@ xml2js@0.2.8:
dependencies:
sax "0.5.x"
-xml2js@0.4.17:
- version "0.4.17"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868"
- integrity sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "^4.1.0"
-
-xml2js@0.4.x, xml2js@^0.4.15:
+xml2js@0.4.19, xml2js@0.4.x, xml2js@^0.4.15:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
@@ -11428,13 +11421,6 @@ xmlbuilder@0.4.3:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58"
integrity sha1-xGFLp04K0ZbmCcknLNnh3bKKilg=
-xmlbuilder@^4.1.0:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5"
- integrity sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=
- dependencies:
- lodash "^4.0.0"
-
xmlbuilder@^9.0.4, xmlbuilder@~9.0.1:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"