diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..2c3d6b0 --- /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 0000000..b826a37 --- /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/.travis.yml b/.travis.yml index 887299b..52da97f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,12 @@ jobs: before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION" - 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 script: - shellcheck bin/heroku bin/setup diff --git a/README.md b/README.md index 78c4fc7..c68d6cb 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://.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/` | 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/` | 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) | 1: 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 7795dd8..db93014 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 @@ -83,7 +83,7 @@ app.use(compression()) // use hsts to tell https users stick to this if (config.hsts.enable) { app.use(helmet.hsts({ - maxAge: config.hsts.maxAgeSeconds * 1000, + maxAge: config.hsts.maxAgeSeconds, includeSubdomains: config.hsts.includeSubdomains, preload: config.hsts.preload })) @@ -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 @@ -199,6 +200,11 @@ app.locals.authProviders = { 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/statusRouter')) app.use(require('./lib/web/auth')) diff --git a/bin/setup b/bin/setup index 122cb7a..38aa05a 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 16c9550..fe8c810 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 0000000..c3783c8 --- /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 + +# 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 c3ada98..5a7ae0a 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -1,16 +1,19 @@ '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, hsts: { enable: true, - maxAgeSeconds: 31536000, + maxAgeSeconds: 60 * 60 * 24 * 365, includeSubdomains: true, preload: true }, @@ -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 6737637..a57fe0d 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 f8b68e3..c1005b0 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(',') @@ -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') 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 config.updateI18nFiles = (env === Environment.development) diff --git a/lib/config/utils.js b/lib/config/utils.js index b2406cf..9646f8c 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 f8b3895..5ef1860 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) +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 transports.Console({ + handleExceptions: true + }) + ], + exitOnError: false +}) + +logger.stream = { + write: function (message, encoding) { + logger.info(message) } } -module.exports = new Logger({ - transports: [ - new winston.transports.Console({ - level: 'debug', - handleExceptions: true, - json: false, - colorize: false, - timestamp: true - }) - ], - emitErrs: true, - exitOnError: false -}) +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 eb18211..e1a8866 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 682337c..87e3ff1 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 6cc1337..45d53e6 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 8bf6d11..435ae9c 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 465a09f..547f89b 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 ccdfeb3..f452b1a 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 39e6f7f..56a336a 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 0a8a832..26bc09e 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 0a44ca8..ef70475 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 1bd8c74..648db73 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -1,7 +1,7 @@ 'use strict' // external modules var Sequelize = require('sequelize') -var scrypt = require('scrypt') +var scrypt = require('@mlink/scrypt') // core var logger = require('../logger') @@ -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 7b20453..5014ac8 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 f6c62d4..d04ffdc 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 3cbd320..b94f473 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 f2a3132..b9160f6 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 8c432b0..a2f8700 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 fb2609e..2b9cb65 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 40d5546..57a5e64 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 100f4f5..ead7ce2 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 7d4aa8e..e455502 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/it.json b/locales/it.json index 8a7f9ee..8a2ed58 100644 --- a/locales/it.json +++ b/locales/it.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.": "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", "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" } \ No newline at end of file diff --git a/locales/ko.json b/locales/ko.json index 84dda60..a1df7b9 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -84,7 +84,7 @@ "Link": "링크", "Image": "이미지", "Code": "코드", - "Externals": "Externals", + "Externals": "외부 서비스 연동", "This is a alert area.": "여기는 알림 공간입니다.", "Revert": "되돌리기", "Import from clipboard": "클립보드에서 불러오기", @@ -105,5 +105,13 @@ "Export to Snippet": "Export to Snippet", "Select Visibility Level": "Select Visibility Level", "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" +} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 325c273..4cb6e4a 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 8790718..93e1c86 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 090af9c..eb95e63 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 524f87e..6d2f35f 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": { @@ -19,7 +20,7 @@ "Idle.Js": "git+https://github.com/shawnmclean/Idle.js", "archiver": "^2.1.1", "async": "^2.1.4", - "aws-sdk": "^2.7.20", + "aws-sdk": "^2.345.0", "azure-storage": "^2.7.0", "base64url": "^3.0.0", "blueimp-md5": "^2.6.0", @@ -62,7 +63,7 @@ "jsdom-nogyp": "^0.8.3", "keymaster": "^1.6.2", "list.js": "^1.5.0", - "lodash": "^4.17.4", + "lodash": "^4.17.11", "lz-string": "git+https://github.com/hackmdio/lz-string.git", "markdown-it": "^8.2.2", "markdown-it-abbr": "^1.0.4", @@ -88,7 +89,6 @@ "moment": "^2.17.1", "morgan": "^1.7.0", "mysql": "^2.12.0", - "node-uuid": "^1.4.7", "passport": "^0.4.0", "passport-dropbox-oauth2": "^1.1.0", "passport-facebook": "^2.1.1", @@ -98,7 +98,7 @@ "passport-ldapauth": "^2.0.0", "passport-local": "^1.0.0", "passport-oauth2": "^1.4.0", - "passport-saml": "^0.31.0", + "passport-saml": "^0.35.0", "passport-twitter": "^1.0.4", "passport.socketio": "^3.7.0", "pdfobject": "^2.0.201604172", @@ -108,9 +108,9 @@ "randomcolor": "^0.5.3", "raphael": "git+https://github.com/dmitrybaranovskiy/raphael", "readline-sync": "^1.4.7", - "request": "^2.79.0", - "reveal.js": "~3.6.0", - "scrypt": "^6.0.3", + "request": "^2.88.0", + "reveal.js": "~3.7.0", + "@mlink/scrypt": "^6.1.2", "select2": "^3.5.2-browserify", "sequelize": "^3.28.0", "sequelize-cli": "^2.5.1", @@ -122,19 +122,19 @@ "store": "^2.0.12", "string": "^3.3.3", "tedious": "^1.14.0", - "to-markdown": "^3.0.3", "toobusy-js": "^0.5.1", + "turndown": "^5.0.1", "uuid": "^3.1.0", "validator": "^10.4.0", "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" }, "engines": { - "node": ">=6.x <10.x" + "node": ">=6.x" }, "bugs": "https://github.com/hackmdio/codimd/issues", "keywords": [ @@ -167,8 +167,14 @@ "babel-runtime": "^6.26.0", "copy-webpack-plugin": "^4.5.2", "css-loader": "^1.0.0", - "doctoc": "^1.3.0", + "doctoc": "^1.4.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 +186,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 +195,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 0000000..dc37b3c --- /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 3d79003..a4ffb63 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** to make a presentation. +After that, you can use the **[Slide Mode](./slide-example)** 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 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 5503cbd..4950366 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 888345f..839616a 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/extra.js b/public/js/extra.js index 4db36ff..76e9563 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -846,11 +846,13 @@ const linkifyAnchors = (level, containingElement) => { let header = headers[i] if (header.getElementsByClassName('anchor').length === 0) { if (typeof header.id === 'undefined' || header.id === '') { - // to escape characters not allow in css and humanize + // to escape characters not allow in css and humanize const id = slugifyWithUTF8(getHeaderContent(header)) header.id = id } - header.insertBefore(anchorForId(header.id), header.firstChild) + if (!(typeof header.id === 'undefined' || header.id === '')) { + header.insertBefore(anchorForId(header.id), header.firstChild) + } } } } @@ -1147,15 +1149,14 @@ const pdfPlugin = new Plugin( const emojijsPlugin = new Plugin( // 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) => { - const emoji = match[1] ? match[1].toLowerCase() : undefined - if (window.emojify.emojiNames.includes(emoji)) { - const div = $(``) - return div[0].outerHTML - } - return match[0] + const emoji = match[1].toLowerCase() + const div = $(``) + return div[0].outerHTML } ) diff --git a/public/js/history.js b/public/js/history.js index b4c26b4..6007bef 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 98c3b6d..a845b5d 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -12,7 +12,7 @@ require('../css/site.css') require('highlight.js/styles/github-gist.css') -import toMarkdown from 'to-markdown' +import TurndownService from 'turndown' import { saveAs } from 'file-saver' import randomColor from 'randomcolor' @@ -1498,7 +1498,12 @@ $('#snippetExportModalConfirm').click(function () { }) 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) } } @@ -2511,7 +2516,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')) } } @@ -2786,6 +2793,7 @@ function updateViewInner () { renderTOC(ui.area.markdown) generateToc('ui-toc') generateToc('ui-toc-affix') + autoLinkify(ui.area.markdown) generateScrollspy() updateScrollspy() smoothHashScroll() @@ -3046,7 +3054,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 0537e92..f05d01b 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 ca06d30..29a3778 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 23b8934..ff5e2bf 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 d15b5eb..ad5bfd0 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/vendor/md-toc.js b/public/vendor/md-toc.js index 876978f..59e75ae 100644 --- a/public/vendor/md-toc.js +++ b/public/vendor/md-toc.js @@ -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 // which is only available on level 0 if (level === 0) { @@ -74,8 +74,8 @@ var elementText = (typeof this.process === 'function' ? this.process(element) : element.innerHTML).replace(/<(?:.|\n)*?>/gm, '') var id = element.getAttribute('id') if (!id) { - element.setAttribute('id', 'tip' + i) - id = '#tip' + i + element.setAttribute('id', 'tip' + ++index) + id = '#tip' + index } else { 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. titleElements.unshift(element) // 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 { // 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 diff --git a/public/views/codimd/header.ejs b/public/views/codimd/header.ejs index b994395..42c4d52 100644 --- a/public/views/codimd/header.ejs +++ b/public/views/codimd/header.ejs @@ -32,16 +32,16 @@
  • <%= __('Slide Mode') %>
  • - <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> + <% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
  • Dropbox
  • - <% if(typeof github !== 'undefined' && github) { %> + <% if(enableGitHubGist) { %>
  • Gist
  • <% } %> - <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> + <% if(enableGitlabSnippets) { %>
  • Snippet
  • <% } %> @@ -52,7 +52,7 @@
  • Gist
  • - <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> + <% if(enableGitlabSnippets) { %>
  • Snippet
  • <% } %> @@ -134,16 +134,16 @@
  • <%= __('Slide Mode') %>
  • - <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %> + <% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
  • Dropbox
  • - <% if(typeof github !== 'undefined' && github) { %> + <% if(enableGitHubGist) { %>
  • Gist
  • <% } %> - <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> + <% if(enableGitlabSnippets) { %>
  • Snippet
  • <% } %> @@ -154,7 +154,7 @@
  • Gist
  • - <% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %> + <% if(enableGitlabSnippets) { %>
  • Snippet
  • <% } %> diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index 907cc1a..a5e591e 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -65,19 +65,19 @@
    - +

    <%= __('Support slide mode') %>

    @@ -150,7 +150,7 @@

    - Powered by CodiMD | <%= __('Releases') %><% if(privacyStatement) { %> | <%= __('Privacy') %><% } %><% if(termsOfUse) { %> | <%= __('Terms of Use') %><% } %> + Powered by CodiMD | <%= __('Releases') %>| <%= __('Source Code') %><% if(privacyStatement) { %> | <%= __('Privacy') %><% } %><% if(termsOfUse) { %> | <%= __('Terms of Use') %><% } %>