Merge branch 'master' into DepauMD
This commit is contained in:
commit
b72b3b48fe
56 changed files with 2760 additions and 2569 deletions
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
lib/ot
|
||||
public/vendor
|
||||
public/build
|
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
module.exports = {
|
||||
"root": true,
|
||||
"extends": "standard",
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
// at some point all of these should return to their default "error" state
|
||||
// but right now, this is not a good choice, because too many places are
|
||||
// wrong.
|
||||
"import/first": ["warn"],
|
||||
"indent": ["warn"],
|
||||
"no-multiple-empty-lines": ["warn"],
|
||||
"no-multi-spaces": ["warn"],
|
||||
"object-curly-spacing": ["warn"],
|
||||
"one-var": ["warn"],
|
||||
"quotes": ["warn"],
|
||||
"semi": ["warn"],
|
||||
"space-infix-ops": ["warn"]
|
||||
}
|
||||
};
|
|
@ -20,6 +20,12 @@ jobs:
|
|||
before_install:
|
||||
- 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
|
||||
|
|
14
README.md
14
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
|
||||
|
|
12
app.js
12
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'))
|
||||
|
|
10
bin/setup
10
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
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
}
|
||||
},
|
||||
"development": {
|
||||
"loglevel": "debug",
|
||||
"hsts": {
|
||||
"enable": false
|
||||
},
|
||||
|
@ -16,6 +17,7 @@
|
|||
},
|
||||
"production": {
|
||||
"domain": "localhost",
|
||||
"loglevel": "info",
|
||||
"hsts": {
|
||||
"enable": true,
|
||||
"maxAgeSeconds": "31536000",
|
||||
|
|
131
docs/guides/migrate-etherpad.md
Normal file
131
docs/guides/migrate-etherpad.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
Pad migration guide from etherpad-lite
|
||||
===
|
||||
|
||||
The goal of this migration is to do a "dumb" import from all the pads in Etherpad, to notes in
|
||||
CodiMD. In particular, the url locations of the pads in Etherpad will be lost. Furthermore, any
|
||||
metadata in Etherpad, such as revisions, author data and also formatted text will not be migrated
|
||||
to CodiMD (only the plain text contents).
|
||||
|
||||
Note that this guide is not really meant as a support guide. I migrated my own Etherpad to CodiMD,
|
||||
and it turned out to be quite easy in my opinion. In this guide I share my experience. Stuff may
|
||||
require some creativity to work properly in your case. When I wrote this guide, I was using
|
||||
[Etherpad 1.7.0] and [CodiMD 1.2.1]. Good luck!
|
||||
|
||||
[Etherpad 1.7.0]: https://github.com/ether/etherpad-lite/tree/1.7.0
|
||||
[CodiMD 1.2.1]: https://github.com/hackmdio/codimd/tree/1.2.1
|
||||
|
||||
## 0. Requirements
|
||||
|
||||
- `curl`
|
||||
- running Etherpad server
|
||||
- running CodiMD server
|
||||
- [codimd-cli]
|
||||
|
||||
[codimd-cli]: https://github.com/hackmdio/codimd-cli/blob/master/bin/codimd
|
||||
|
||||
## 1. Retrieve the list of pads
|
||||
|
||||
First, compose a list of all the pads that you want to have migrated from your Etherpad. Other than
|
||||
the admin interface, Etherpad does not have a dedicated function to dump a list of all the pads.
|
||||
However, the Etherpad wiki explains how to list all the pads by [talking directly to the
|
||||
database][howtolistallpads].
|
||||
|
||||
You will end up with a file containing a pad name on each line:
|
||||
|
||||
```
|
||||
date-ideas
|
||||
groceries
|
||||
london
|
||||
weddingchecklist
|
||||
(...)
|
||||
```
|
||||
|
||||
[howtolistallpads]: https://github.com/ether/etherpad-lite/wiki/How-to-list-all-pads/49701ecdcbe07aea7ad27ffa23aed0d99c2e17db
|
||||
|
||||
## 2. Run the migration
|
||||
|
||||
Download [codimd-cli] and put the script in the same directory as the file containing the pad names.
|
||||
Add to this directory the file listed below, I called it `migrate-etherpad.sh`. Modify at least the
|
||||
configuration settings `ETHERPAD_SERVER` and `CODIMD_SERVER`.
|
||||
|
||||
```shell
|
||||
#!/bin/sh
|
||||
|
||||
# migrate-etherpad.sh
|
||||
#
|
||||
# Description: Migrate pads from etherpad to codimd
|
||||
# Author: Daan Sprenkels <hello@dsprenkels.com>
|
||||
|
||||
# This script uses the codimd command line script[1] to import a list of pads from
|
||||
# [1]: https://github.com/hackmdio/codimd-cli/blob/master/bin/codimd
|
||||
|
||||
# The base url to where etherpad is hosted
|
||||
ETHERPAD_SERVER="https://etherpad.example.com"
|
||||
|
||||
# The base url where codimd is hosted
|
||||
CODIMD_SERVER="https://codimd.example.com"
|
||||
|
||||
# Write a list of pads and the urls which they were migrated to
|
||||
REDIRECTS_FILE="redirects.txt"
|
||||
|
||||
|
||||
# Fail if not called correctly
|
||||
if (( $# != 1 )); then
|
||||
echo "Usage: $0 PAD_NAMES_FILE"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Do the migration
|
||||
for PAD_NAME in $1; do
|
||||
# Download the pad
|
||||
PAD_FILE="$(mktemp)"
|
||||
curl "$ETHERPAD_SERVER/p/$PAD_NAME/export/txt" >"$PAD_FILE"
|
||||
|
||||
# Import the pad into codimd
|
||||
OUTPUT="$(./codimd import "$PAD_FILE")"
|
||||
echo "$PAD_NAME -> $OUTPUT" >>"$REDIRECTS_FILE"
|
||||
done
|
||||
```
|
||||
|
||||
Call this file like this:
|
||||
|
||||
```shell
|
||||
./migrate-etherpad.sh pad_names.txt
|
||||
```
|
||||
|
||||
This will download all the pads in `pad_names.txt` and put them on CodiMD. They will get assigned
|
||||
random ids, so you won't be able to find them. The script will save the mappings to a file though
|
||||
(in my case `redirects.txt`). You can use this file to redirect your users when they visit your
|
||||
etherpad using a `301 Permanent Redirect` status code (see the next section).
|
||||
|
||||
## 3. Setup redirects (optional)
|
||||
|
||||
I got a `redirects.txt` file that looked a bit like this:
|
||||
|
||||
```
|
||||
date-ideas -> Found. Redirecting to https://codimd.example.com/mPt0KfiKSBOTQ3mNcdfn
|
||||
groceries -> Found. Redirecting to https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y
|
||||
london -> Found. Redirecting to https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R
|
||||
weddingchecklist -> Found. Redirecting to https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ
|
||||
(...)
|
||||
```
|
||||
|
||||
Using some `sed` magic, I changed it to an nginx config snippet:
|
||||
|
||||
```
|
||||
location = /p/date-ideas {
|
||||
return 301 https://codimd.example.com/mPt0M1KfiKSBOTQ3mNcdfn;
|
||||
}
|
||||
location = /p/groceries {
|
||||
return 301 https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y;
|
||||
}
|
||||
location = /p/london {
|
||||
return 301 https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R;
|
||||
}
|
||||
location = /p/weddingchecklist {
|
||||
return 301 https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ;
|
||||
}
|
||||
```
|
||||
|
||||
I put this file into my `etherpad.example.com` nginx config, such that all the users would be
|
||||
redirected accordingly.
|
|
@ -1,16 +1,19 @@
|
|||
'use strict'
|
||||
|
||||
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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
"Help us translating on %s": "Help us translating on %s",
|
||||
"Source Code": "Source Code"
|
||||
}
|
|
@ -112,5 +112,6 @@
|
|||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Cela supprimera votre compte, toutes les notes dont vous êtes propriétaire et supprimera toute référence à votre compte dans les autres notes.",
|
||||
"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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -4,16 +4,16 @@
|
|||
"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",
|
||||
"New guest note": "Nieuwe gastnotitie",
|
||||
"Collaborate with URL": "Samenwerken met URL",
|
||||
"Support charts and MathJax": "Ondersteun grafieken en MathJax",
|
||||
"Support slide mode": "Ondersteun slide mode",
|
||||
"Sign In": "Log in",
|
||||
"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": "Log uit",
|
||||
"Sign Out": "Uitloggen",
|
||||
"Explore all features": "Ontdek alle features",
|
||||
"Select tags...": "Selecteer tags...",
|
||||
"Search keyword...": "Zoeken op keyword...",
|
||||
|
@ -38,43 +38,44 @@
|
|||
"New": "Nieuw",
|
||||
"Publish": "Publiceren",
|
||||
"Extra": "Extra",
|
||||
"Revision": "Herzien",
|
||||
"Slide Mode": "Slide Mode",
|
||||
"Revision": "Versie",
|
||||
"Slide Mode": "Presentatiemodus",
|
||||
"Export": "Exporteren",
|
||||
"Import": "Importeren",
|
||||
"Clipboard": "Kladbord",
|
||||
"Download": "Downloaden",
|
||||
"Raw HTML": "Raw HTML",
|
||||
"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 herladen worden",
|
||||
"You have an incompatible client version.": "Je client is niet compatible.",
|
||||
"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": "Zie releases hier",
|
||||
"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": "Afkijkblad",
|
||||
"Cheatsheet": "Spiekbrief",
|
||||
"Example": "Voorbeeld",
|
||||
"Syntax": "Syntax",
|
||||
"Header": "Koptekst",
|
||||
"Unordered List": "Niet gesorteerde Lijst",
|
||||
"Ordered List": "Gesorteerde List",
|
||||
"Todo List": "Todo Lijst",
|
||||
"Blockquote": "Quote",
|
||||
"Blockquote": "Citaat",
|
||||
"Bold font": "Bold tekst",
|
||||
"Italics font": "Italics tekst",
|
||||
"Strikethrough": "Doorstreepte tekst",
|
||||
|
@ -102,5 +103,15 @@
|
|||
"Select From Available Snippets": "Selecteer van beschikbare Snippets",
|
||||
"OR": "OF",
|
||||
"Export to Snippet": "Exporteren naar Snippet",
|
||||
"Select Visibility Level": "Selecteer zichtbaarheids niveau"
|
||||
"Select Visibility Level": "Selecteer zichtbaarheids niveau",
|
||||
"Night Theme": "Nachtweergave",
|
||||
"Follow us on %s and %s.": "Volg ons op %s en %s.",
|
||||
"Privacy": "Privacy",
|
||||
"Terms of Use": "Gebruikersvoorwaarden",
|
||||
"Do you really want to delete your user account?": "Weet je zeker dat je je account wilt verwijderen?",
|
||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Dit zal je account verwijderen. Alle notities waar je eigenaar van bent worden verwijderd, samen met alle verwijzingen naar je account.",
|
||||
"Delete user": "Gebruiker verwijderen",
|
||||
"Export user data": "Gebruikersdata exporteren",
|
||||
"Help us translating on %s": "Help ons vertalen op %s",
|
||||
"Source Code": "Broncode"
|
||||
}
|
|
@ -29,6 +29,8 @@
|
|||
"Import from browser": "从浏览器导入",
|
||||
"Releases": "版本",
|
||||
"Are you sure?": "你确定吗?",
|
||||
"Do you really want to delete this note?": "确定要删除这个文件吗?",
|
||||
"All users will lose their connection.": "所有用户将失去连接",
|
||||
"Cancel": "取消",
|
||||
"Yes, do it!": "没错,就这样办!",
|
||||
"Choose method": "选择方式",
|
||||
|
@ -103,7 +105,13 @@
|
|||
"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上关注我们"
|
||||
"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": "源码"
|
||||
}
|
|
@ -29,6 +29,8 @@
|
|||
"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": "選擇方式",
|
||||
|
@ -103,6 +105,13 @@
|
|||
"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 和我們互動吧!",
|
||||
"Privacy": "隱私權政策",
|
||||
"Terms of Use": "使用條款",
|
||||
"Do you really want to delete your user account?": "你確定真的想要刪除帳戶?",
|
||||
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "我們將會刪除你的帳戶、你所擁有的筆記、以及你在別人筆記裡的作者紀錄。",
|
||||
"Delete user": "刪除使用者",
|
||||
"Export user data": "匯出使用者資料",
|
||||
"Help us translating on %s": "來 %s 幫我們翻譯",
|
||||
"Source Code": "原始碼"
|
||||
}
|
63
package.json
63
package.json
|
@ -5,13 +5,14 @@
|
|||
"main": "app.js",
|
||||
"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"
|
||||
|
|
28
public/.eslintrc.js
Normal file
28
public/.eslintrc.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
// this config file is used in concert with the root .eslintrc.js in the root dir.
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"$": false,
|
||||
"CodeMirror": false,
|
||||
"Cookies": false,
|
||||
"moment": false,
|
||||
"editor": false,
|
||||
"ui": false,
|
||||
"Spinner": false,
|
||||
"modeType": false,
|
||||
"Idle": false,
|
||||
"serverurl": false,
|
||||
"key": false,
|
||||
"gapi": false,
|
||||
"Dropbox": false,
|
||||
"FilePicker": false,
|
||||
"ot": false,
|
||||
"MediaUploader": false,
|
||||
"hex2rgb": false,
|
||||
"num_loaded": false,
|
||||
"Visibility": false,
|
||||
"inlineAttachment": false
|
||||
}
|
||||
};
|
|
@ -72,9 +72,11 @@ Notes can be embedded as follows:
|
|||
|
||||
## [Slide Mode](./slide-example):
|
||||
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.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
type: slide
|
||||
slideOptions:
|
||||
transition: slide
|
||||
---
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -850,9 +850,11 @@ const linkifyAnchors = (level, containingElement) => {
|
|||
const id = slugifyWithUTF8(getHeaderContent(header))
|
||||
header.id = id
|
||||
}
|
||||
if (!(typeof header.id === 'undefined' || header.id === '')) {
|
||||
header.insertBefore(anchorForId(header.id), header.firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function autoLinkify (view) {
|
||||
|
@ -1147,16 +1149,15 @@ 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 emoji = match[1].toLowerCase()
|
||||
const div = $(`<img class="emoji" src="${serverurl}/build/emojify.js/dist/images/basic/${emoji}.png"></img>`)
|
||||
return div[0].outerHTML
|
||||
}
|
||||
return match[0]
|
||||
}
|
||||
)
|
||||
|
||||
// yaml meta, from https://github.com/eugeneware/remarkable-meta
|
||||
|
|
|
@ -218,6 +218,7 @@ export function getStorageHistory (callback) {
|
|||
if (typeof data === 'string') { data = JSON.parse(data) }
|
||||
callback(data)
|
||||
}
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
callback([])
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
8
public/vendor/md-toc.js
vendored
8
public/vendor/md-toc.js
vendored
|
@ -44,7 +44,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
Toc.prototype._createTocContent = function recursiveToc(level = 0, titleElements = [], titleNames = [], ulClass = undefined) {
|
||||
Toc.prototype._createTocContent = function recursiveToc(level = 0, titleElements = [], titleNames = [], ulClass = undefined, index = 0) {
|
||||
// Inititalize our elements from the toc object
|
||||
// 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
|
||||
|
|
|
@ -32,16 +32,16 @@
|
|||
</li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||
</li>
|
||||
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
|
||||
<% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header"><%= __('Export') %></li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
||||
</li>
|
||||
<% if(typeof github !== 'undefined' && github) { %>
|
||||
<% if(enableGitHubGist) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
||||
<% if(enableGitlabSnippets) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||
</li>
|
||||
<% } %>
|
||||
|
@ -52,7 +52,7 @@
|
|||
</li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||
</li>
|
||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
||||
<% if(enableGitlabSnippets) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||
</li>
|
||||
<% } %>
|
||||
|
@ -134,16 +134,16 @@
|
|||
</li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
|
||||
</li>
|
||||
<% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
|
||||
<% if(enableGitHubGist || enableDropBoxSave || enableGitlabSnippets) { %>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header"><%= __('Export') %></li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
|
||||
</li>
|
||||
<% if(typeof github !== 'undefined' && github) { %>
|
||||
<% if(enableGitHubGist) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-gist" tabindex="-1" href="#" target="_blank" rel="noopener"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
||||
<% if(enableGitlabSnippets) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||
</li>
|
||||
<% } %>
|
||||
|
@ -154,7 +154,7 @@
|
|||
</li>
|
||||
<li role="presentation"><a role="menuitem" class="ui-import-gist" href="#" data-toggle="modal" data-target="#gistImportModal"><i class="fa fa-github fa-fw"></i> Gist</a>
|
||||
</li>
|
||||
<% if(typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api')) { %>
|
||||
<% if(enableGitlabSnippets) { %>
|
||||
<li role="presentation"><a role="menuitem" class="ui-import-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
|
||||
</li>
|
||||
<% } %>
|
||||
|
|
|
@ -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>') %>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<% if(useCDN) { %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fork-awesome/1.1.3/css/fork-awesome.min.css" integrity="sha256-ZhApazu+kejqTYhMF+1DzNKjIzP7KXu6AzyXcC1gMus=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css" integrity="sha256-3iu9jgsy9TpTwXKb7bNQzqWekRX7pPK+2OLj3R922fo=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/css/reveal.min.css" integrity="sha256-ol2N5Xr80jdDqxK0lte3orKGb9Ja3sOnpAUV7TTADmg=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/css/reveal.min.css" integrity="sha256-9+Wg2bcNeiOMGXOUNqBdceY2lAH/eCiTDcdzHhHIl48=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/css/basic/emojify.min.css" integrity="sha256-UOrvMOsSDSrW6szVLe8ZDZezBxh5IoIfgTwdNDgTjiU=" crossorigin="anonymous" />
|
||||
<%- include build/slide-header %>
|
||||
<%- include shared/polyfill %>
|
||||
|
@ -88,8 +88,8 @@
|
|||
|
||||
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
||||
<% if(useCDN) { %>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/lib/js/head.min.js" integrity="sha256-+09kLhwACKXFPDvqo4xMMvi4+uXFsRZ2uYGbeN1U8sI=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.6.0/js/reveal.min.js" integrity="sha256-ixSKHrWAL2k0mqVSRju9+to2/uZSEK9+kJRfdNBolG8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/lib/js/head.min.js" integrity="sha256-CTcwyen1cxIrm4hlqdxe0y7Hq6B0rpxAKLiXMD3dJv0=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.7.0/js/reveal.min.js" integrity="sha256-Xr6ZH+/kc7hDVReZLO5khBknteLqu5oen/xnSraXrVk=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue