Merge branch 'master' into DepauMD
This commit is contained in:
commit
d59212ea8b
82 changed files with 2240 additions and 2053 deletions
2
.babelrc
2
.babelrc
|
@ -2,7 +2,7 @@
|
||||||
"presets": [
|
"presets": [
|
||||||
["env", {
|
["env", {
|
||||||
"targets": {
|
"targets": {
|
||||||
"node": "6",
|
"node": "8",
|
||||||
"uglify": true
|
"uglify": true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
||||||
// wrong.
|
// wrong.
|
||||||
"import/first": ["warn"],
|
"import/first": ["warn"],
|
||||||
"indent": ["warn"],
|
"indent": ["warn"],
|
||||||
|
"no-console": ["warn"],
|
||||||
"no-multiple-empty-lines": ["warn"],
|
"no-multiple-empty-lines": ["warn"],
|
||||||
"no-multi-spaces": ["warn"],
|
"no-multi-spaces": ["warn"],
|
||||||
"object-curly-spacing": ["warn"],
|
"object-curly-spacing": ["warn"],
|
||||||
|
|
48
.travis.yml
48
.travis.yml
|
@ -1,40 +1,40 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
dist: trusty
|
dist: xenial
|
||||||
cache: yarn
|
cache: yarn
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CXX=g++-4.8
|
|
||||||
- YARN_VERSION=1.15.2
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- env: task=npm-test
|
- stage: Static Tests
|
||||||
node_js:
|
name: eslint
|
||||||
- 6
|
|
||||||
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:
|
|
||||||
- 8
|
|
||||||
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:
|
node_js:
|
||||||
- 10
|
- 10
|
||||||
before_install:
|
script:
|
||||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
|
- yarn run eslint
|
||||||
- export PATH="$HOME/.yarn/bin:$PATH"
|
- name: ShellCheck
|
||||||
- env: task=ShellCheck
|
|
||||||
script:
|
script:
|
||||||
- shellcheck bin/heroku bin/setup
|
- shellcheck bin/heroku bin/setup
|
||||||
language: generic
|
language: generic
|
||||||
- env: task=json-lint
|
- name: json-lint
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- jq
|
- jq
|
||||||
script:
|
script:
|
||||||
- npm run jsonlint
|
- yarn run jsonlint
|
||||||
language: generic
|
language: generic
|
||||||
|
- stage: Dynamic Tests
|
||||||
|
name: Node.js 8
|
||||||
|
node_js:
|
||||||
|
- 8
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
- name: Node.js 10
|
||||||
|
node_js:
|
||||||
|
- 10
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
- name: Node.js 12
|
||||||
|
node_js:
|
||||||
|
- 12
|
||||||
|
script:
|
||||||
|
- yarn run mocha-suite
|
||||||
|
|
|
@ -5,6 +5,7 @@ CodiMD
|
||||||
[![build status][travis-image]][travis-url]
|
[![build status][travis-image]][travis-url]
|
||||||
[![version][github-version-badge]][github-release-page]
|
[![version][github-version-badge]][github-release-page]
|
||||||
[![POEditor][poeditor-image]][poeditor-url]
|
[![POEditor][poeditor-image]][poeditor-url]
|
||||||
|
[![Mastodon][social-mastodon-image]][social-mastodon]
|
||||||
|
|
||||||
CodiMD lets you create real-time collaborative markdown notes. You can test-drive
|
CodiMD lets you create real-time collaborative markdown notes. You can test-drive
|
||||||
it by visiting our [CodiMD demo server][codimd-demo].
|
it by visiting our [CodiMD demo server][codimd-demo].
|
||||||
|
@ -98,3 +99,5 @@ Licensed under AGPLv3. For our list of contributors, see [AUTHORS](AUTHORS).
|
||||||
[codimd-demo-features]: https://demo.codimd.org/features
|
[codimd-demo-features]: https://demo.codimd.org/features
|
||||||
[codimd-community]: https://community.codimd.org
|
[codimd-community]: https://community.codimd.org
|
||||||
[codimd-community-calls]: https://community.codimd.org/t/codimd-community-call/19
|
[codimd-community-calls]: https://community.codimd.org/t/codimd-community-call/19
|
||||||
|
[social-mastodon]: https://social.codimd.org/mastodon
|
||||||
|
[social-mastodon-image]: https://img.shields.io/badge/social-mastodon-3c99dc.svg
|
||||||
|
|
2
app.js
2
app.js
|
@ -113,7 +113,7 @@ if (config.csp.enable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n.configure({
|
i18n.configure({
|
||||||
locales: ['en', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da', 'ko', 'id', 'sr'],
|
locales: ['en', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da', 'ko', 'id', 'sr', 'vi'],
|
||||||
cookie: 'locale',
|
cookie: 'locale',
|
||||||
indent: ' ', // this is the style poeditor.com exports it, this creates less churn
|
indent: ' ', // this is the style poeditor.com exports it, this creates less churn
|
||||||
directory: path.join(__dirname, '/locales'),
|
directory: path.join(__dirname, '/locales'),
|
||||||
|
|
|
@ -32,6 +32,7 @@ to `config.json` before filling in your own details.
|
||||||
| `imageUploadType` | `imgur`, `s3`, `minio`, `azure`, `lutim` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md)|
|
| `imageUploadType` | `imgur`, `s3`, `minio`, `azure`, `lutim` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md)|
|
||||||
| `sourceURL` | `https://github.com/codimd/server/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) |
|
| `sourceURL` | `https://github.com/codimd/server/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) |
|
||||||
| `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time |
|
| `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time |
|
||||||
|
| `tooBusyLag` | `70` | CPU time for one eventloop tick until node throttles connections. (milliseconds) |
|
||||||
| `heartbeatInterval` | `5000` | socket.io heartbeat interval |
|
| `heartbeatInterval` | `5000` | socket.io heartbeat interval |
|
||||||
| `heartbeatTimeout` | `10000` | socket.io heartbeat timeout |
|
| `heartbeatTimeout` | `10000` | socket.io heartbeat timeout |
|
||||||
| `documentMaxLength` | `100000` | note max length |
|
| `documentMaxLength` | `100000` | note max length |
|
||||||
|
|
|
@ -35,6 +35,7 @@ defaultNotePath can't be set from env-vars
|
||||||
| `CMD_FORBIDDEN_NOTE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` |
|
| `CMD_FORBIDDEN_NOTE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` |
|
||||||
| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio`, `lutim` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md), also there's a whole section on their respective env vars below. |
|
| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio`, `lutim` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md), also there's a whole section on their respective env vars below. |
|
||||||
| `CMD_SOURCE_URL` | `https://github.com/codimd/server/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) |
|
| `CMD_SOURCE_URL` | `https://github.com/codimd/server/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) |
|
||||||
|
| `CMD_TOOBUSY_LAG` | `70` | CPU time for one eventloop tick until node throttles connections. (milliseconds) |
|
||||||
|
|
||||||
|
|
||||||
## CodiMD Location
|
## CodiMD Location
|
||||||
|
|
50
docs/guides/auth/keycloak.md
Normal file
50
docs/guides/auth/keycloak.md
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
Keycloak/Red Hat SSO (self-hosted)
|
||||||
|
===
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
This guide assumes you have run and configured Keycloak. If you'd like to meet this prerequisite quickly, it can be achieved by running a `jboss/keycloak` container and attaching it to your network. Set the environment variables KEYCLOAK_USER and `KEYCLOAK_PASSWORD`, and expose port 8080.
|
||||||
|
|
||||||
|
Where HTTPS is specified throughout, use HTTP instead. You may also have to specify the exposed port, 8080.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Sign in to the administration portal for your Keycloak instance at https://keycloak.example.com/auth/admin/master/console
|
||||||
|
|
||||||
|
You may note that a separate realm is specified throughout this tutorial. It is best practice not to use the master realm, as it normally contains the realm-management client that federates access using the policies and permissions you can create.
|
||||||
|
|
||||||
|
2. Navigate to the client management page at `https://keycloak.example.com/auth/admin/master/console/#/realms/your-realm/clients` (admin permissions required)
|
||||||
|
3. Click **Create** to create a new client and fill out the registration form. You should set the Root URL to the fully qualified public URL of your CodiMD instance.
|
||||||
|
4. Click **Save**
|
||||||
|
5. Set the **Access Type** of the client to `confidential`. This will make your client require a client secret upon authentication.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Additional steps to circumvent generic OAuth2 issue:
|
||||||
|
|
||||||
|
1. Select Client Scopes from the sidebar, and begin to create a new client scope using the Create button.
|
||||||
|
2. Ensure that the **Name** field is set to `id`.
|
||||||
|
3. Create a new mapper under the Mappers tab. This should reference the User Property `id`. `Claim JSON Type` should be String and all switches below should be enabled. Save the mapper.
|
||||||
|
4. Go to the client you set up in the previous steps using the Clients page, then choose the Client Scopes tab. Apply the scope you've created. This should mitigate errors as seen in [codimd/server#56](https://github.com/codimd/server/issues/56), as the `/userinfo` endpoint should now bring back the user's ID under the `id` key as well as `sub`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:`
|
||||||
|
|
||||||
|
```
|
||||||
|
CMD_OAUTH2_USER_PROFILE_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/userinfo
|
||||||
|
CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=preferred_username
|
||||||
|
CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=name
|
||||||
|
CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=email
|
||||||
|
CMD_OAUTH2_TOKEN_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/token
|
||||||
|
CMD_OAUTH2_AUTHORIZATION_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/auth
|
||||||
|
CMD_OAUTH2_CLIENT_ID=<your client ID>
|
||||||
|
CMD_OAUTH2_CLIENT_SECRET=<your client secret, which you can find under the Credentials tab for your client>
|
||||||
|
CMD_OAUTH2_PROVIDERNAME=Keycloak
|
||||||
|
CMD_DOMAIN=<codimd.example.com>
|
||||||
|
CMD_PROTOCOL_USESSL=true
|
||||||
|
CMD_URL_ADDPORT=false
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Run `docker-compose up -d` to apply your settings.
|
||||||
|
8. Sign in to your CodiMD using your Keycloak ID
|
|
@ -1,6 +1,10 @@
|
||||||
Migrations and Notable Changes
|
Migrations and Notable Changes
|
||||||
===
|
===
|
||||||
|
|
||||||
|
## Migrating to 1.4.0
|
||||||
|
|
||||||
|
We dropped support for node 6 with this version. If you have any trouble running this version, please double check that you are running at least node 8!
|
||||||
|
|
||||||
## Migrating to 1.3.2
|
## Migrating to 1.3.2
|
||||||
|
|
||||||
This is not a breaking change, but to stay up to date with the community
|
This is not a breaking change, but to stay up to date with the community
|
||||||
|
|
|
@ -16,7 +16,7 @@ CodiMD by docker container
|
||||||
The easiest way to setup CodiMD using docker are using the following three commands:
|
The easiest way to setup CodiMD using docker are using the following three commands:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/codimd/container.git
|
git clone https://github.com/codimd/container.git codimd-container
|
||||||
cd codimd-container
|
cd codimd-container
|
||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
|
@ -3,11 +3,10 @@ Manual Installation
|
||||||
|
|
||||||
## Requirements on your server
|
## Requirements on your server
|
||||||
|
|
||||||
- Node.js 6.x or up (test up to 7.5.0) and <10.x
|
- Node.js 8.5 or up
|
||||||
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
|
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
|
||||||
- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation))
|
- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation))
|
||||||
- yarn
|
- yarn
|
||||||
- `libssl-dev` for building scrypt (see [here](https://github.com/ml1nk/node-scrypt/blob/master/README.md#installation-instructions) for further information)
|
|
||||||
- Bash (for the setup script)
|
- Bash (for the setup script)
|
||||||
- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM
|
- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ module.exports = {
|
||||||
// socket.io
|
// socket.io
|
||||||
heartbeatInterval: 5000,
|
heartbeatInterval: 5000,
|
||||||
heartbeatTimeout: 10000,
|
heartbeatTimeout: 10000,
|
||||||
|
// too busy timeout
|
||||||
|
tooBusyLag: 70,
|
||||||
// document
|
// document
|
||||||
documentMaxLength: 100000,
|
documentMaxLength: 100000,
|
||||||
// image upload setting, available options are imgur/s3/filesystem/azure/lutim
|
// image upload setting, available options are imgur/s3/filesystem/azure/lutim
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils')
|
const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sourceURL: process.env.CMD_SOURCE_URL,
|
sourceURL: process.env.CMD_SOURCE_URL,
|
||||||
|
@ -33,6 +33,7 @@ module.exports = {
|
||||||
dbURL: process.env.CMD_DB_URL,
|
dbURL: process.env.CMD_DB_URL,
|
||||||
sessionSecret: process.env.CMD_SESSION_SECRET,
|
sessionSecret: process.env.CMD_SESSION_SECRET,
|
||||||
sessionLife: toIntegerConfig(process.env.CMD_SESSION_LIFE),
|
sessionLife: toIntegerConfig(process.env.CMD_SESSION_LIFE),
|
||||||
|
tooBusyLag: toIntegerConfig(process.env.CMD_TOOBUSY_LAG),
|
||||||
imageUploadType: process.env.CMD_IMAGE_UPLOAD_TYPE,
|
imageUploadType: process.env.CMD_IMAGE_UPLOAD_TYPE,
|
||||||
imgur: {
|
imgur: {
|
||||||
clientID: process.env.CMD_IMGUR_CLIENTID
|
clientID: process.env.CMD_IMGUR_CLIENTID
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils')
|
const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
domain: process.env.HMD_DOMAIN,
|
domain: process.env.HMD_DOMAIN,
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const {merge} = require('lodash')
|
const { merge } = require('lodash')
|
||||||
const deepFreeze = require('deep-freeze')
|
const deepFreeze = require('deep-freeze')
|
||||||
const {Environment, Permission} = require('./enum')
|
const { Environment, Permission } = require('./enum')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
const {getGitCommit, getGitHubURL} = require('./utils')
|
const { getGitCommit, getGitHubURL } = require('./utils')
|
||||||
|
|
||||||
const appRootPath = path.resolve(__dirname, '../../')
|
const appRootPath = path.resolve(__dirname, '../../')
|
||||||
const env = process.env.NODE_ENV || Environment.development
|
const env = process.env.NODE_ENV || Environment.development
|
||||||
|
@ -17,7 +17,7 @@ const debugConfig = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get version string from package.json
|
// Get version string from package.json
|
||||||
const {version, repository} = require(path.join(appRootPath, 'package.json'))
|
const { version, repository } = require(path.join(appRootPath, 'package.json'))
|
||||||
|
|
||||||
const commitID = getGitCommit(appRootPath)
|
const commitID = getGitCommit(appRootPath)
|
||||||
const sourceURL = getGitHubURL(repository.url, commitID || version)
|
const sourceURL = getGitHubURL(repository.url, commitID || version)
|
||||||
|
@ -159,8 +159,8 @@ if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) {
|
||||||
if (config.sessionSecret === 'secret') {
|
if (config.sessionSecret === 'secret') {
|
||||||
logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
|
logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
|
||||||
config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
|
config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
|
||||||
.toString('hex') // convert to hexadecimal format
|
.toString('hex') // convert to hexadecimal format
|
||||||
.slice(0, config.sessionSecretLen) // return required number of characters
|
.slice(0, config.sessionSecretLen) // return required number of characters
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate upload upload providers
|
// Validate upload upload providers
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const {toBooleanConfig} = require('./utils')
|
const { toBooleanConfig } = require('./utils')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
debug: toBooleanConfig(process.env.DEBUG),
|
debug: toBooleanConfig(process.env.DEBUG),
|
||||||
|
|
|
@ -30,14 +30,14 @@ exports.generateAvatarURL = function (name, email = '', big = true) {
|
||||||
if (typeof email !== 'string') {
|
if (typeof email !== 'string') {
|
||||||
email = '' + name + '@example.com'
|
email = '' + name + '@example.com'
|
||||||
}
|
}
|
||||||
name=encodeURIComponent(name)
|
name = encodeURIComponent(name)
|
||||||
|
|
||||||
let hash = crypto.createHash('md5')
|
let hash = crypto.createHash('md5')
|
||||||
hash.update(email.toLowerCase())
|
hash.update(email.toLowerCase())
|
||||||
let hexDigest = hash.digest('hex')
|
let hexDigest = hash.digest('hex')
|
||||||
|
|
||||||
if (email !== '' && config.allowGravatar) {
|
if (email !== '' && config.allowGravatar) {
|
||||||
photo = 'https://cdn.libravatar.org/avatar/' + hexDigest;
|
photo = 'https://cdn.libravatar.org/avatar/' + hexDigest
|
||||||
if (big) {
|
if (big) {
|
||||||
photo += '?s=400'
|
photo += '?s=400'
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
const {createLogger, format, transports} = require('winston')
|
const { createLogger, format, transports } = require('winston')
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
|
|
|
@ -22,6 +22,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -9,6 +9,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -18,8 +19,8 @@ module.exports = {
|
||||||
|
|
||||||
down: function (queryInterface, Sequelize) {
|
down: function (queryInterface, Sequelize) {
|
||||||
return queryInterface.removeColumn('Notes', 'lastchangeAt')
|
return queryInterface.removeColumn('Notes', 'lastchangeAt')
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return queryInterface.removeColumn('Notes', 'lastchangeuserId')
|
return queryInterface.removeColumn('Notes', 'lastchangeuserId')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -5,6 +5,7 @@ module.exports = {
|
||||||
return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
|
return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -17,6 +17,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -18,6 +18,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -3,6 +3,7 @@ module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) {
|
return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) {
|
||||||
if (error.message === 'SQLITE_ERROR: duplicate column name: deletedAt' || 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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -4,6 +4,7 @@ module.exports = {
|
||||||
return queryInterface.addColumn('Users', 'email', Sequelize.TEXT).then(function () {
|
return queryInterface.addColumn('Users', 'email', Sequelize.TEXT).then(function () {
|
||||||
return queryInterface.addColumn('Users', 'password', Sequelize.TEXT).catch(function (error) {
|
return queryInterface.addColumn('Users', 'password', Sequelize.TEXT).catch(function (error) {
|
||||||
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'password'" || error.message === 'column "password" of relation "Users" already exists') {
|
if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'password'" || error.message === 'column "password" of relation "Users" already exists') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -11,6 +12,7 @@ module.exports = {
|
||||||
})
|
})
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
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') {
|
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') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log('Migration has already run… ignoring.')
|
console.log('Migration has already run… ignoring.')
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
module.exports = {
|
module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'content', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT('long') })
|
||||||
queryInterface.changeColumn('Revisions', 'patch', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT('long') })
|
||||||
queryInterface.changeColumn('Revisions', 'content', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT('long') })
|
||||||
queryInterface.changeColumn('Revisions', 'lastContent', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT('long') })
|
||||||
},
|
},
|
||||||
|
|
||||||
down: function (queryInterface, Sequelize) {
|
down: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'content', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT })
|
||||||
queryInterface.changeColumn('Revisions', 'patch', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT })
|
||||||
queryInterface.changeColumn('Revisions', 'content', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT })
|
||||||
queryInterface.changeColumn('Revisions', 'lastContent', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'authorship', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT('long') })
|
||||||
queryInterface.changeColumn('Revisions', 'authorship', {type: Sequelize.TEXT('long')})
|
queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT('long') })
|
||||||
},
|
},
|
||||||
|
|
||||||
down: function (queryInterface, Sequelize) {
|
down: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'authorship', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT })
|
||||||
queryInterface.changeColumn('Revisions', 'authorship', {type: Sequelize.TEXT})
|
queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'permission', {type: Sequelize.ENUM('freely', 'editable', 'limited', 'locked', 'protected', 'private')})
|
queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'limited', 'locked', 'protected', 'private') })
|
||||||
},
|
},
|
||||||
|
|
||||||
down: function (queryInterface, Sequelize) {
|
down: function (queryInterface, Sequelize) {
|
||||||
queryInterface.changeColumn('Notes', 'permission', {type: Sequelize.ENUM('freely', 'editable', 'locked', 'private')})
|
queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'locked', 'private') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var Sequelize = require('sequelize')
|
var Sequelize = require('sequelize')
|
||||||
const {cloneDeep} = require('lodash')
|
const { cloneDeep } = require('lodash')
|
||||||
|
|
||||||
// core
|
// core
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
|
@ -39,13 +39,13 @@ sequelize.processData = processData
|
||||||
var db = {}
|
var db = {}
|
||||||
|
|
||||||
fs.readdirSync(__dirname)
|
fs.readdirSync(__dirname)
|
||||||
.filter(function (file) {
|
.filter(function (file) {
|
||||||
return (file.indexOf('.') !== 0) && (file !== 'index.js')
|
return (file.indexOf('.') !== 0) && (file !== 'index.js')
|
||||||
})
|
})
|
||||||
.forEach(function (file) {
|
.forEach(function (file) {
|
||||||
var model = sequelize.import(path.join(__dirname, file))
|
var model = sequelize.import(path.join(__dirname, file))
|
||||||
db[model.name] = model
|
db[model.name] = model
|
||||||
})
|
})
|
||||||
|
|
||||||
Object.keys(db).forEach(function (modelName) {
|
Object.keys(db).forEach(function (modelName) {
|
||||||
if ('associate' in db[modelName]) {
|
if ('associate' in db[modelName]) {
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
// external modules
|
// external modules
|
||||||
var Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
var scrypt = require('scrypt')
|
const crypto = require('crypto')
|
||||||
|
if (!crypto.scrypt) {
|
||||||
|
// polyfill for node.js 8.0, see https://github.com/chrisveness/scrypt-kdf#openssl-implementation
|
||||||
|
const scryptAsync = require('scrypt-async')
|
||||||
|
crypto.scrypt = function (password, salt, keylen, options, callback) {
|
||||||
|
const opt = Object.assign({}, options, { dkLen: keylen })
|
||||||
|
scryptAsync(password, salt, opt, (derivedKey) => callback(null, Buffer.from(derivedKey)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scrypt = require('scrypt-kdf')
|
||||||
|
|
||||||
// core
|
// core
|
||||||
var logger = require('../logger')
|
const logger = require('../logger')
|
||||||
var {generateAvatarURL} = require('../letter-avatars')
|
const { generateAvatarURL } = require('../letter-avatars')
|
||||||
|
|
||||||
module.exports = function (sequelize, DataTypes) {
|
module.exports = function (sequelize, DataTypes) {
|
||||||
var User = sequelize.define('User', {
|
var User = sequelize.define('User', {
|
||||||
|
@ -41,20 +50,12 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: Sequelize.TEXT,
|
type: Sequelize.TEXT
|
||||||
set: function (value) {
|
|
||||||
var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
|
|
||||||
this.setDataValue('password', hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
instanceMethods: {
|
instanceMethods: {
|
||||||
verifyPassword: function (attempt) {
|
verifyPassword: function (attempt) {
|
||||||
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
|
return scrypt.verify(Buffer.from(this.password, 'hex'), attempt)
|
||||||
return this
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
classMethods: {
|
classMethods: {
|
||||||
|
@ -140,6 +141,9 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
case 'saml':
|
case 'saml':
|
||||||
photo = generateAvatarURL(profile.username, profile.emails[0], bigger)
|
photo = generateAvatarURL(profile.username, profile.emails[0], bigger)
|
||||||
break
|
break
|
||||||
|
default:
|
||||||
|
photo = generateAvatarURL(profile.username)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return photo
|
return photo
|
||||||
},
|
},
|
||||||
|
@ -153,5 +157,19 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function updatePasswordHashHook (user, options, done) {
|
||||||
|
// suggested way to hash passwords to be able to do this asynchronously:
|
||||||
|
// @see https://github.com/sequelize/sequelize/issues/1821#issuecomment-44265819
|
||||||
|
if (!user.changed('password')) { return done() }
|
||||||
|
|
||||||
|
scrypt.kdf(user.getDataValue('password'), { logN: 15 }).then(keyBuf => {
|
||||||
|
user.setDataValue('password', keyBuf.toString('hex'))
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
User.beforeCreate(updatePasswordHashHook)
|
||||||
|
User.beforeUpdate(updatePasswordHashHook)
|
||||||
|
|
||||||
return User
|
return User
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ var utils = require('./utils')
|
||||||
// public
|
// public
|
||||||
var response = {
|
var response = {
|
||||||
errorForbidden: function (res) {
|
errorForbidden: function (res) {
|
||||||
const {req} = res
|
const { req } = res
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
responseError(res, '403', 'Forbidden', 'oh no.')
|
responseError(res, '403', 'Forbidden', 'oh no.')
|
||||||
} else {
|
} else {
|
||||||
|
@ -549,16 +549,16 @@ function gitlabActionProjects (req, res, note) {
|
||||||
ret.accesstoken = user.accessToken
|
ret.accesstoken = user.accessToken
|
||||||
ret.profileid = user.profileid
|
ret.profileid = user.profileid
|
||||||
request(
|
request(
|
||||||
config.gitlab.baseURL + '/api/' + config.gitlab.version + '/projects?membership=yes&per_page=100&access_token=' + user.accessToken,
|
config.gitlab.baseURL + '/api/' + config.gitlab.version + '/projects?membership=yes&per_page=100&access_token=' + user.accessToken,
|
||||||
function (error, httpResponse, body) {
|
function (error, httpResponse, body) {
|
||||||
if (!error && httpResponse.statusCode === 200) {
|
if (!error && httpResponse.statusCode === 200) {
|
||||||
ret.projects = JSON.parse(body)
|
ret.projects = JSON.parse(body)
|
||||||
return res.send(ret)
|
return res.send(ret)
|
||||||
} else {
|
} else {
|
||||||
return res.send(ret)
|
return res.send(ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
logger.error('gitlab action projects failed: ' + err)
|
logger.error('gitlab action projects failed: ' + err)
|
||||||
return response.errorInternalError(res)
|
return response.errorInternalError(res)
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Router = require('express').Router
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
const DropboxStrategy = require('passport-dropbox-oauth2').Strategy
|
const DropboxStrategy = require('passport-dropbox-oauth2').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let dropboxAuth = module.exports = Router()
|
let dropboxAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ const LocalStrategy = require('passport-local').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const models = require('../../../models')
|
const models = require('../../../models')
|
||||||
const logger = require('../../../logger')
|
const logger = require('../../../logger')
|
||||||
const {setReturnToFromReferer} = require('../utils')
|
const { setReturnToFromReferer } = require('../utils')
|
||||||
const {urlencodedParser} = require('../../utils')
|
const { urlencodedParser } = require('../../utils')
|
||||||
const response = require('../../../response')
|
const response = require('../../../response')
|
||||||
|
|
||||||
let emailAuth = module.exports = Router()
|
let emailAuth = module.exports = Router()
|
||||||
|
@ -23,8 +23,14 @@ passport.use(new LocalStrategy({
|
||||||
}
|
}
|
||||||
}).then(function (user) {
|
}).then(function (user) {
|
||||||
if (!user) return done(null, false)
|
if (!user) return done(null, false)
|
||||||
if (!user.verifyPassword(password)) return done(null, false)
|
user.verifyPassword(password).then(verified => {
|
||||||
return done(null, user)
|
if (verified) {
|
||||||
|
return done(null, user)
|
||||||
|
} else {
|
||||||
|
logger.warn('invalid password given for %s', user.email)
|
||||||
|
return done(null, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return done(err)
|
return done(err)
|
||||||
|
|
|
@ -5,7 +5,7 @@ const passport = require('passport')
|
||||||
const FacebookStrategy = require('passport-facebook').Strategy
|
const FacebookStrategy = require('passport-facebook').Strategy
|
||||||
|
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let facebookAuth = module.exports = Router()
|
let facebookAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const passport = require('passport')
|
||||||
const GithubStrategy = require('passport-github').Strategy
|
const GithubStrategy = require('passport-github').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const response = require('../../../response')
|
const response = require('../../../response')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let githubAuth = module.exports = Router()
|
let githubAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const passport = require('passport')
|
||||||
const GitlabStrategy = require('passport-gitlab2').Strategy
|
const GitlabStrategy = require('passport-gitlab2').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const response = require('../../../response')
|
const response = require('../../../response')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let gitlabAuth = module.exports = Router()
|
let gitlabAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Router = require('express').Router
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
var GoogleStrategy = require('passport-google-oauth20').Strategy
|
var GoogleStrategy = require('passport-google-oauth20').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let googleAuth = module.exports = Router()
|
let googleAuth = module.exports = Router()
|
||||||
|
|
||||||
|
@ -12,14 +12,14 @@ passport.use(new GoogleStrategy({
|
||||||
clientID: config.google.clientID,
|
clientID: config.google.clientID,
|
||||||
clientSecret: config.google.clientSecret,
|
clientSecret: config.google.clientSecret,
|
||||||
callbackURL: config.serverURL + '/auth/google/callback',
|
callbackURL: config.serverURL + '/auth/google/callback',
|
||||||
userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
|
userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo'
|
||||||
}, passportGeneralCallback))
|
}, passportGeneralCallback))
|
||||||
|
|
||||||
googleAuth.get('/auth/google', function (req, res, next) {
|
googleAuth.get('/auth/google', function (req, res, next) {
|
||||||
setReturnToFromReferer(req)
|
setReturnToFromReferer(req)
|
||||||
passport.authenticate('google', { scope: ['profile'] })(req, res, next)
|
passport.authenticate('google', { scope: ['profile'] })(req, res, next)
|
||||||
})
|
})
|
||||||
// google auth callback
|
// google auth callback
|
||||||
googleAuth.get('/auth/google/callback',
|
googleAuth.get('/auth/google/callback',
|
||||||
passport.authenticate('google', {
|
passport.authenticate('google', {
|
||||||
successReturnToOrRedirect: config.serverURL + '/',
|
successReturnToOrRedirect: config.serverURL + '/',
|
||||||
|
|
|
@ -6,8 +6,8 @@ const LDAPStrategy = require('passport-ldapauth')
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const models = require('../../../models')
|
const models = require('../../../models')
|
||||||
const logger = require('../../../logger')
|
const logger = require('../../../logger')
|
||||||
const {setReturnToFromReferer} = require('../utils')
|
const { setReturnToFromReferer } = require('../utils')
|
||||||
const {urlencodedParser} = require('../../utils')
|
const { urlencodedParser } = require('../../utils')
|
||||||
const response = require('../../../response')
|
const response = require('../../../response')
|
||||||
|
|
||||||
let ldapAuth = module.exports = Router()
|
let ldapAuth = module.exports = Router()
|
||||||
|
|
|
@ -5,7 +5,7 @@ const passport = require('passport')
|
||||||
const Mattermost = require('mattermost')
|
const Mattermost = require('mattermost')
|
||||||
const OAuthStrategy = require('passport-oauth2').Strategy
|
const OAuthStrategy = require('passport-oauth2').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
const mattermost = new Mattermost.Client()
|
const mattermost = new Mattermost.Client()
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ mattermostStrategy.userProfile = (accessToken, done) => {
|
||||||
mattermost.token = accessToken
|
mattermost.token = accessToken
|
||||||
mattermost.useHeaderToken()
|
mattermost.useHeaderToken()
|
||||||
mattermost.getMe(
|
mattermost.getMe(
|
||||||
(data) => {
|
(data) => {
|
||||||
done(null, data)
|
done(null, data)
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
done(err)
|
done(err)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Router = require('express').Router
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
const { Strategy, InternalOAuthError } = require('passport-oauth2')
|
const { Strategy, InternalOAuthError } = require('passport-oauth2')
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let oauth2Auth = module.exports = Router()
|
let oauth2Auth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ const OpenIDStrategy = require('@passport-next/passport-openid').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const models = require('../../../models')
|
const models = require('../../../models')
|
||||||
const logger = require('../../../logger')
|
const logger = require('../../../logger')
|
||||||
const {urlencodedParser} = require('../../utils')
|
const { urlencodedParser } = require('../../utils')
|
||||||
const {setReturnToFromReferer} = require('../utils')
|
const { setReturnToFromReferer } = require('../utils')
|
||||||
|
|
||||||
let openIDAuth = module.exports = Router()
|
let openIDAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ const SamlStrategy = require('passport-saml').Strategy
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const models = require('../../../models')
|
const models = require('../../../models')
|
||||||
const logger = require('../../../logger')
|
const logger = require('../../../logger')
|
||||||
const {urlencodedParser} = require('../../utils')
|
const { urlencodedParser } = require('../../utils')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) }
|
const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) }
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const passport = require('passport')
|
||||||
const TwitterStrategy = require('passport-twitter').Strategy
|
const TwitterStrategy = require('passport-twitter').Strategy
|
||||||
|
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils')
|
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
|
||||||
|
|
||||||
let twitterAuth = module.exports = Router()
|
let twitterAuth = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
const Router = require('express').Router
|
const Router = require('express').Router
|
||||||
|
|
||||||
const {urlencodedParser} = require('./utils')
|
const { urlencodedParser } = require('./utils')
|
||||||
const history = require('../history')
|
const history = require('../history')
|
||||||
const historyRouter = module.exports = Router()
|
const historyRouter = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,12 @@ exports.uploadImage = function (imagePath, callback) {
|
||||||
|
|
||||||
imgur.setClientId(config.imgur.clientID)
|
imgur.setClientId(config.imgur.clientID)
|
||||||
imgur.uploadFile(imagePath)
|
imgur.uploadFile(imagePath)
|
||||||
.then(function (json) {
|
.then(function (json) {
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
logger.info('SERVER uploadimage success: ' + JSON.stringify(json))
|
logger.info('SERVER uploadimage success: ' + JSON.stringify(json))
|
||||||
}
|
}
|
||||||
callback(null, json.data.link.replace(/^http:\/\//i, 'https://'))
|
callback(null, json.data.link.replace(/^http:\/\//i, 'https://'))
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
callback(new Error(err), null)
|
callback(new Error(err), null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const {getImageMimeType} = require('../../utils')
|
const { getImageMimeType } = require('../../utils')
|
||||||
const logger = require('../../logger')
|
const logger = require('../../logger')
|
||||||
|
|
||||||
const Minio = require('minio')
|
const Minio = require('minio')
|
||||||
|
|
|
@ -3,7 +3,7 @@ const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const {getImageMimeType} = require('../../utils')
|
const { getImageMimeType } = require('../../utils')
|
||||||
const logger = require('../../logger')
|
const logger = require('../../logger')
|
||||||
|
|
||||||
const AWS = require('aws-sdk')
|
const AWS = require('aws-sdk')
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
const toobusy = require('toobusy-js')
|
const toobusy = require('toobusy-js')
|
||||||
|
|
||||||
const response = require('../../response')
|
const response = require('../../response')
|
||||||
|
const config = require('../../config')
|
||||||
|
|
||||||
|
toobusy.maxLag(config.tooBusyLag)
|
||||||
|
|
||||||
module.exports = function (req, res, next) {
|
module.exports = function (req, res, next) {
|
||||||
if (toobusy()) {
|
if (toobusy()) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Router = require('express').Router
|
||||||
|
|
||||||
const response = require('../response')
|
const response = require('../response')
|
||||||
|
|
||||||
const {markdownParser} = require('./utils')
|
const { markdownParser } = require('./utils')
|
||||||
|
|
||||||
const noteRouter = module.exports = Router()
|
const noteRouter = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ const config = require('../config')
|
||||||
const models = require('../models')
|
const models = require('../models')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
const {urlencodedParser} = require('./utils')
|
const { urlencodedParser } = require('./utils')
|
||||||
|
|
||||||
const statusRouter = module.exports = Router()
|
const statusRouter = module.exports = Router()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ const response = require('../response')
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
const models = require('../models')
|
const models = require('../models')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
const {generateAvatar} = require('../letter-avatars')
|
const { generateAvatar } = require('../letter-avatars')
|
||||||
|
|
||||||
const UserRouter = module.exports = Router()
|
const UserRouter = module.exports = Router()
|
||||||
|
|
||||||
|
|
223
locales/es.json
223
locales/es.json
|
@ -1,104 +1,121 @@
|
||||||
{
|
{
|
||||||
"Collaborative markdown notes": "Notas colaborativas en Markdown",
|
"Collaborative markdown notes": "Notas colaborativas en Markdown",
|
||||||
"Realtime collaborative markdown notes on all platforms.": "Notas colaborativas en Markdown para todas las plataformas.",
|
"Realtime collaborative markdown notes on all platforms.": "Notas colaborativas en Markdown para todas las plataformas en tiempo real.",
|
||||||
"Best way to write and share your knowledge in markdown.": "La mejor forma de escribir y compartir tu conocimiento en Markdown.",
|
"Best way to write and share your knowledge in markdown.": "La mejor forma de escribir y compartir tu conocimiento en Markdown.",
|
||||||
"Intro": "Introducción",
|
"Intro": "Introducción",
|
||||||
"History": "Historia",
|
"History": "Historia",
|
||||||
"New guest note": "Nueva nota como invitado",
|
"New guest note": "Nueva nota como invitado",
|
||||||
"Collaborate with URL": "Colaborar via URL",
|
"Collaborate with URL": "Colaborar via URL",
|
||||||
"Support charts and MathJax": "Soporte para gráficos y MathJax",
|
"Support charts and MathJax": "Soporte para gráficos y MathJax",
|
||||||
"Support slide mode": "Soporte para diapositivas",
|
"Support slide mode": "Soporte para diapositivas",
|
||||||
"Sign In": "Ingresar",
|
"Sign In": "Ingresar",
|
||||||
"Below is the history from browser": "A continuación se muestra el historial del navegador",
|
"Below is the history from browser": "A continuación se muestra el historial del navegador",
|
||||||
"Welcome!": "¡Bienvenido!",
|
"Welcome!": "¡Bienvenido!",
|
||||||
"New note": "Nueva nota",
|
"New note": "Nueva nota",
|
||||||
"or": "o",
|
"or": "o",
|
||||||
"Sign Out": "Salir",
|
"Sign Out": "Salir",
|
||||||
"Explore all features": "Explorar todas las funciones",
|
"Explore all features": "Explorar todas las funciones",
|
||||||
"Select tags...": "Seleccionar etiquetas...",
|
"Select tags...": "Seleccionar etiquetas...",
|
||||||
"Search keyword...": "Buscar palabras clave...",
|
"Search keyword...": "Buscar palabras clave...",
|
||||||
"Sort by title": "Ordenar por título",
|
"Sort by title": "Ordenar por título",
|
||||||
"Title": "Título",
|
"Title": "Título",
|
||||||
"Sort by time": "Ordenar por fecha",
|
"Sort by time": "Ordenar por fecha",
|
||||||
"Time": "Tiempo",
|
"Time": "Tiempo",
|
||||||
"Export history": "Exportar historial",
|
"Export history": "Exportar historial",
|
||||||
"Import history": "Importar historial",
|
"Import history": "Importar historial",
|
||||||
"Clear history": "Borrar historial",
|
"Clear history": "Borrar historial",
|
||||||
"Refresh history": "Actualizar historial",
|
"Refresh history": "Actualizar historial",
|
||||||
"No history": "Ningún historial",
|
"No history": "Ningún historial",
|
||||||
"Import from browser": "Importar del navegador",
|
"Import from browser": "Importar del navegador",
|
||||||
"Releases": "Versiones",
|
"Releases": "Versiones",
|
||||||
"Are you sure?": "¿Estás seguro?",
|
"Are you sure?": "¿Estás seguro?",
|
||||||
"Cancel": "Cancelar",
|
"Do you really want to delete this note?": "¿Realmente quieres eliminar esta nota?",
|
||||||
"Yes, do it!": "Si, ¡hazlo!",
|
"All users will lose their connection.": "Todos los usuarios perderán su conexión.",
|
||||||
"Choose method": "Elegir método",
|
"Cancel": "Cancelar",
|
||||||
"Sign in via %s": "Ingresar via %s",
|
"Yes, do it!": "Si, ¡hazlo!",
|
||||||
"New": "Nuevo",
|
"Choose method": "Elegir método",
|
||||||
"Publish": "Publicar",
|
"Sign in via %s": "Ingresar via %s",
|
||||||
"Extra": "Extra",
|
"New": "Nuevo",
|
||||||
"Revision": "Revision",
|
"Publish": "Publicar",
|
||||||
"Slide Mode": "Modo presentación",
|
"Extra": "Extra",
|
||||||
"Export": "Exportar",
|
"Revision": "Revision",
|
||||||
"Import": "Importar",
|
"Slide Mode": "Modo presentación",
|
||||||
"Clipboard": "Portapapeles",
|
"Export": "Exportar",
|
||||||
"Download": "Descargar",
|
"Import": "Importar",
|
||||||
"Raw HTML": "HTML puro",
|
"Clipboard": "Portapapeles",
|
||||||
"Edit": "Editar",
|
"Download": "Descargar",
|
||||||
"View": "Ver",
|
"Raw HTML": "HTML puro",
|
||||||
"Both": "Ambos",
|
"Edit": "Editar",
|
||||||
"Help": "Ayuda",
|
"View": "Ver",
|
||||||
"Upload Image": "Subir imagen",
|
"Both": "Ambos",
|
||||||
"Menu": "Menú",
|
"Help": "Ayuda",
|
||||||
"This page need refresh": "Esta página necesita ser cargada de nuevo",
|
"Upload Image": "Subir imagen",
|
||||||
"You have an incompatible client version.": "Tienes una version del cliente incompatible.",
|
"Menu": "Menú",
|
||||||
"Refresh to update.": "Cargar de nuevo para actualizar.",
|
"This page need refresh": "Esta página necesita ser cargada de nuevo",
|
||||||
"New version available!": "¡Nueva versión disponible!",
|
"You have an incompatible client version.": "Tienes una version del cliente incompatible.",
|
||||||
"See releases notes here": "Ver aquí las notas de publicación",
|
"Refresh to update.": "Cargar de nuevo para actualizar.",
|
||||||
"Refresh to enjoy new features.": "Actualizar para usar las nuevas funciones.",
|
"New version available!": "¡Nueva versión disponible!",
|
||||||
"Your user state has changed.": "El estado de tu usuario ha cambiado.",
|
"See releases notes here": "Ver aquí las notas de publicación",
|
||||||
"Refresh to load new user state.": "Recargar para actualizar el estado de tu usuario.",
|
"Refresh to enjoy new features.": "Actualizar para usar las nuevas funciones.",
|
||||||
"Refresh": "Recargar",
|
"Your user state has changed.": "El estado de tu usuario ha cambiado.",
|
||||||
"Contacts": "Contactos",
|
"Refresh to load new user state.": "Recargar para actualizar el estado de tu usuario.",
|
||||||
"Report an issue": "Reportar un problema",
|
"Refresh": "Recargar",
|
||||||
"Send us email": "Enviarnos un email",
|
"Contacts": "Contactos",
|
||||||
"Documents": "Documentos",
|
"Report an issue": "Reportar un problema",
|
||||||
"Features": "Funciones",
|
"Meet us on %s": "Encuéntranos en %s",
|
||||||
"YAML Metadata": "Metadatos en YAML",
|
"Send us email": "Enviarnos un email",
|
||||||
"Slide Example": "Ejemplo de diapositiva",
|
"Documents": "Documentos",
|
||||||
"Cheatsheet": "Ayudamemorias",
|
"Features": "Funciones",
|
||||||
"Example": "Ejemplo",
|
"YAML Metadata": "Metadatos en YAML",
|
||||||
"Syntax": "Sintaxis",
|
"Slide Example": "Ejemplo de diapositiva",
|
||||||
"Header": "Cabecera",
|
"Cheatsheet": "Ayudamemorias",
|
||||||
"Unordered List": "Lista desordenada",
|
"Example": "Ejemplo",
|
||||||
"Ordered List": "Lista ordenada",
|
"Syntax": "Sintaxis",
|
||||||
"Todo List": "Lista de tareas",
|
"Header": "Cabecera",
|
||||||
"Blockquote": "Bloque de cita",
|
"Unordered List": "Lista desordenada",
|
||||||
"Bold font": "Fuente negrita",
|
"Ordered List": "Lista ordenada",
|
||||||
"Italics font": "Fuente itálica",
|
"Todo List": "Lista de tareas",
|
||||||
"Strikethrough": "Tachado",
|
"Blockquote": "Bloque de cita",
|
||||||
"Inserted text": "Texto subrayado",
|
"Bold font": "Fuente negrita",
|
||||||
"Marked text": "Texto marcado",
|
"Italics font": "Fuente itálica",
|
||||||
"Link": "Enlace",
|
"Strikethrough": "Tachado",
|
||||||
"Image": "Imagen",
|
"Inserted text": "Texto subrayado",
|
||||||
"Code": "Código",
|
"Marked text": "Texto marcado",
|
||||||
"Externals": "Externos",
|
"Link": "Enlace",
|
||||||
"This is a alert area.": "Esto es un área de alerta.",
|
"Image": "Imagen",
|
||||||
"Revert": "Revertir",
|
"Code": "Código",
|
||||||
"Import from clipboard": "Importar del portapapeles",
|
"Externals": "Externos",
|
||||||
"Paste your markdown or webpage here...": "Pega tu markdown o página web aquí...",
|
"This is a alert area.": "Esto es un área de alerta.",
|
||||||
"Clear": "Limpiar",
|
"Revert": "Revertir",
|
||||||
"This note is locked": "Esta nota está bloqueada",
|
"Import from clipboard": "Importar del portapapeles",
|
||||||
"Sorry, only owner can edit this note.": "Disculpa, solo el dueño puede editar esta nota.",
|
"Paste your markdown or webpage here...": "Pega tu markdown o página web aquí...",
|
||||||
"OK": "OK",
|
"Clear": "Limpiar",
|
||||||
"Reach the limit": "Haz alcanzado el límite",
|
"This note is locked": "Esta nota está bloqueada",
|
||||||
"Sorry, you've reached the max length this note can be.": "Disculpa, haz alcanzado la longitud máxima que puede tener esta nota.",
|
"Sorry, only owner can edit this note.": "Disculpa, solo el dueño puede editar esta nota.",
|
||||||
"Please reduce the content or divide it to more notes, thank you!": "Por favor, reduce el contenido o dividela en mas notas, ¡gracias!",
|
"OK": "OK",
|
||||||
"Import from Gist": "Importar de un Gist",
|
"Reach the limit": "Haz alcanzado el límite",
|
||||||
"Paste your gist url here...": "Pega el URL de tu Gist aquí...",
|
"Sorry, you've reached the max length this note can be.": "Disculpa, haz alcanzado la longitud máxima que puede tener esta nota.",
|
||||||
"Import from Snippet": "Importar de Snippet",
|
"Please reduce the content or divide it to more notes, thank you!": "Por favor, reduce el contenido o dividela en mas notas, ¡gracias!",
|
||||||
"Select From Available Projects": "Elegir de un proyecto disponible",
|
"Import from Gist": "Importar de un Gist",
|
||||||
"Select From Available Snippets": "Elegir de un Snippet disponible",
|
"Paste your gist url here...": "Pega el URL de tu Gist aquí...",
|
||||||
"OR": "O",
|
"Import from Snippet": "Importar de Snippet",
|
||||||
"Export to Snippet": "Exportar a Snippet",
|
"Select From Available Projects": "Elegir de un proyecto disponible",
|
||||||
"Select Visibility Level": "Elegir el nivel de visibilidad"
|
"Select From Available Snippets": "Elegir de un Snippet disponible",
|
||||||
}
|
"OR": "O",
|
||||||
|
"Export to Snippet": "Exportar a Snippet",
|
||||||
|
"Select Visibility Level": "Elegir el nivel de visibilidad",
|
||||||
|
"Night Theme": "Modo nocturno",
|
||||||
|
"Follow us on %s and %s.": "Síguenos en %s, y %s.",
|
||||||
|
"Privacy": "Privacidad",
|
||||||
|
"Terms of Use": "Términos de uso",
|
||||||
|
"Do you really want to delete your user account?": "¿Estás seguro que quieres eliminar tu cuenta de usuario?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Esta acción eliminará tu cuenta, todas tus notas y las referencias a tu cuenta desde otras notas.",
|
||||||
|
"Delete user": "Eliminar usuario",
|
||||||
|
"Export user data": "Exportar información de usuario",
|
||||||
|
"Help us translating on %s": "Ayúdanos traduciendo en %s",
|
||||||
|
"Source Code": "Código fuente",
|
||||||
|
"Register": "Registrar",
|
||||||
|
"Powered by %s": "Desarrollado por %s.",
|
||||||
|
"Help us translating": "Ayúdanos traduciendo",
|
||||||
|
"Join the community": "Únete a la comunidad"
|
||||||
|
}
|
121
locales/vi.json
Normal file
121
locales/vi.json
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{
|
||||||
|
"Collaborative markdown notes": "Cộng tác ghi chú markdown",
|
||||||
|
"Realtime collaborative markdown notes on all platforms.": "Cộng tác ghi chú markdown đa nền tảng thời gian thực",
|
||||||
|
"Best way to write and share your knowledge in markdown.": "Nền tảng tốt nhất để viết và chia sẻ markdown",
|
||||||
|
"Intro": "Giới thiệu",
|
||||||
|
"History": "Lịch sử",
|
||||||
|
"New guest note": "Khách mới",
|
||||||
|
"Collaborate with URL": "Cộng tác thời gian thực",
|
||||||
|
"Support charts and MathJax": "Làm việc với biểu đồ và MathJax",
|
||||||
|
"Support slide mode": "Hỗ trợ chế độ slide",
|
||||||
|
"Sign In": "Đăng nhập",
|
||||||
|
"Below is the history from browser": "Dưới đây là lịch sử của trình duyệt",
|
||||||
|
"Welcome!": "Chào mừng bạn!",
|
||||||
|
"New note": "Tạo mới ghi chú",
|
||||||
|
"or": "hoặc",
|
||||||
|
"Sign Out": "Đăng xuất",
|
||||||
|
"Explore all features": "Khám phá tất cả tính năng",
|
||||||
|
"Select tags...": "Chọn tag",
|
||||||
|
"Search keyword...": "Tìm kiếm",
|
||||||
|
"Sort by title": "Sắp xếp theo tiêu đề",
|
||||||
|
"Title": "Tiêu đề",
|
||||||
|
"Sort by time": "Sắp xếp theo thời gian",
|
||||||
|
"Time": "Thời gian",
|
||||||
|
"Export history": "Xuất lịch sử",
|
||||||
|
"Import history": "Nhập lịch sử",
|
||||||
|
"Clear history": "Xóa lịch sử",
|
||||||
|
"Refresh history": "Làm mới lịch sử",
|
||||||
|
"No history": "Không có lịch sử",
|
||||||
|
"Import from browser": "Nhập từ trình duyệt",
|
||||||
|
"Releases": "Xuất bản",
|
||||||
|
"Are you sure?": "Bạn có chắc chắn không ?",
|
||||||
|
"Do you really want to delete this note?": "Bạn có thực sự muốn xóa ghi chú này ?",
|
||||||
|
"All users will lose their connection.": "Tất cả người dùng sẽ mất liên kết này.",
|
||||||
|
"Cancel": "Hủy",
|
||||||
|
"Yes, do it!": "Đồng ý",
|
||||||
|
"Choose method": "Chọn phương thức",
|
||||||
|
"Sign in via %s": "Đăng nhấp với %s",
|
||||||
|
"New": "Mới",
|
||||||
|
"Publish": "Xuất bản",
|
||||||
|
"Extra": "Extra",
|
||||||
|
"Revision": "Sửa đổi",
|
||||||
|
"Slide Mode": "Chế độ slide",
|
||||||
|
"Export": "Xuất",
|
||||||
|
"Import": "Nhập",
|
||||||
|
"Clipboard": "Clipboard",
|
||||||
|
"Download": "Tải xuống",
|
||||||
|
"Raw HTML": "Raw HTML",
|
||||||
|
"Edit": "Sửa",
|
||||||
|
"View": "Hiện",
|
||||||
|
"Both": "Cả hai",
|
||||||
|
"Help": "Trợ giúp",
|
||||||
|
"Upload Image": "Tải ảnh lên",
|
||||||
|
"Menu": "Menu",
|
||||||
|
"This page need refresh": "Trang này cần được làm mới",
|
||||||
|
"You have an incompatible client version.": "Phiên bản của client không tương thích.",
|
||||||
|
"Refresh to update.": "Làm mới để cập nhập.",
|
||||||
|
"New version available!": "Phiên bản mới đã có sẵn.",
|
||||||
|
"See releases notes here": "Xem ghi chú xuất bản ở đây.",
|
||||||
|
"Refresh to enjoy new features.": "Làm mới để trải nghiệm tính năng mới.",
|
||||||
|
"Your user state has changed.": "Trạng thái người dùng bị thay đổi.",
|
||||||
|
"Refresh to load new user state.": "Làm mới để cập nhập trạng thái người dùng mới.",
|
||||||
|
"Refresh": "Làm mới",
|
||||||
|
"Contacts": "Liên Lạc",
|
||||||
|
"Report an issue": "Báo cáo vấn đề",
|
||||||
|
"Meet us on %s": "Gặp chúng tôi ở %s",
|
||||||
|
"Send us email": "Gửi email cho chúng tôi",
|
||||||
|
"Documents": "Tài liệu",
|
||||||
|
"Features": "Tính năng",
|
||||||
|
"YAML Metadata": "YAML Metadata",
|
||||||
|
"Slide Example": "Slide ví dụ",
|
||||||
|
"Cheatsheet": "Cheetsheet",
|
||||||
|
"Example": "Ví dụ",
|
||||||
|
"Syntax": "Cú pháp",
|
||||||
|
"Header": "Đầu đề",
|
||||||
|
"Unordered List": "Danh sách chưa sắp xếp",
|
||||||
|
"Ordered List": "Danh sách đã sắp xếp",
|
||||||
|
"Todo List": "Checklist",
|
||||||
|
"Blockquote": "Blockquote",
|
||||||
|
"Bold font": "Bôi đậm",
|
||||||
|
"Italics font": "In nghiêng",
|
||||||
|
"Strikethrough": "Gạch ngang",
|
||||||
|
"Inserted text": "Gạch chân",
|
||||||
|
"Marked text": "Hightlight",
|
||||||
|
"Link": "Liên kết",
|
||||||
|
"Image": "Ảnh",
|
||||||
|
"Code": "Code",
|
||||||
|
"Externals": "Externals",
|
||||||
|
"This is a alert area.": "Đây là khu vực cảnh báo",
|
||||||
|
"Revert": "Trở lại như cũ",
|
||||||
|
"Import from clipboard": "Thêm từ clipboard",
|
||||||
|
"Paste your markdown or webpage here...": "Dán markdown hoặc webpage ở đây ...",
|
||||||
|
"Clear": "Xóa",
|
||||||
|
"This note is locked": "Ghi chú này bị khóa.",
|
||||||
|
"Sorry, only owner can edit this note.": "Xin lỗi, chỉ chủ sở hữu có thể xóa note.",
|
||||||
|
"OK": "Đồng ý",
|
||||||
|
"Reach the limit": "Đạt giới hạn",
|
||||||
|
"Sorry, you've reached the max length this note can be.": "Rất tiếc, bạn đã đạt tới độ dài tối đa ",
|
||||||
|
"Please reduce the content or divide it to more notes, thank you!": "Vui lòng rút ngắn ghi chú",
|
||||||
|
"Import from Gist": "Nhập từ Gist",
|
||||||
|
"Paste your gist url here...": "Dán liên kết gist vào đây ...",
|
||||||
|
"Import from Snippet": "Thêm từ Snippet",
|
||||||
|
"Select From Available Projects": "Chọn từ Project có sẵn",
|
||||||
|
"Select From Available Snippets": "Chọn từ Snippets có sẵn",
|
||||||
|
"OR": "HOẶC",
|
||||||
|
"Export to Snippet": "Xuất ra Snippet",
|
||||||
|
"Select Visibility Level": "Chọn cấp độ hiển thị",
|
||||||
|
"Night Theme": "Giao diện tối",
|
||||||
|
"Follow us on %s and %s.": "Cho phép chúng tôi %s, và %s.",
|
||||||
|
"Privacy": "Quyền riêng tư.",
|
||||||
|
"Terms of Use": "Điều khoản sử dụng.",
|
||||||
|
"Do you really want to delete your user account?": "Bạn có thực sự muốn xóa tài khoản ?",
|
||||||
|
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Điều này sẽ xóa tài khoản của bạn, tất cả các ghi chú thuộc sở hữu của bạn và xóa tất cả các liên kết đến tài khoản của bạn khỏi các ghi chú khác.",
|
||||||
|
"Delete user": "Xóa người dùng",
|
||||||
|
"Export user data": "Xuất dữ liệu người dùng",
|
||||||
|
"Help us translating on %s": "Giúp chúng tôi dịch trên %s",
|
||||||
|
"Source Code": "Mã nguồn",
|
||||||
|
"Register": "Đăng ký",
|
||||||
|
"Powered by %s": "Cung cấp bởi %s",
|
||||||
|
"Help us translating": "Giúp chúng tôi dịch",
|
||||||
|
"Join the community": "Tham gia vào cộng đồng"
|
||||||
|
}
|
20
package.json
20
package.json
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "CodiMD",
|
"name": "CodiMD",
|
||||||
"version": "1.3.2",
|
"version": "1.4.0",
|
||||||
"description": "Realtime collaborative markdown notes on all platforms.",
|
"description": "Realtime collaborative markdown notes on all platforms.",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run-script eslint && npm run-script jsonlint && mocha",
|
"test": "npm run-script eslint && npm run-script jsonlint && npm run-script mocha-suite",
|
||||||
"eslint": "node_modules/.bin/eslint lib public test app.js",
|
"eslint": "node_modules/.bin/eslint --max-warnings 0 lib public test app.js",
|
||||||
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
"jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done",
|
||||||
|
"mocha-suite": "NODE_ENV=test CMD_DB_URL=\"sqlite::memory:\" mocha --exit",
|
||||||
"standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1",
|
"standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1",
|
||||||
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
"dev": "webpack --config webpack.dev.js --progress --colors --watch",
|
||||||
"heroku-prebuild": "bin/heroku",
|
"heroku-prebuild": "bin/heroku",
|
||||||
|
@ -15,7 +16,6 @@
|
||||||
"start": "sequelize db:migrate && node app.js"
|
"start": "sequelize db:migrate && node app.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hackmd/js-sequence-diagrams": "^0.0.1-alpha.2",
|
|
||||||
"@passport-next/passport-openid": "^1.0.0",
|
"@passport-next/passport-openid": "^1.0.0",
|
||||||
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
|
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
|
||||||
"archiver": "^2.1.1",
|
"archiver": "^2.1.1",
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
"i18n": "^0.8.3",
|
"i18n": "^0.8.3",
|
||||||
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
|
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
|
||||||
"ionicons": "~2.0.1",
|
"ionicons": "~2.0.1",
|
||||||
"jquery": "^3.1.1",
|
"jquery": "^3.4.1",
|
||||||
"jquery-mousewheel": "^3.1.13",
|
"jquery-mousewheel": "^3.1.13",
|
||||||
"jquery-ui": "^1.12.1",
|
"jquery-ui": "^1.12.1",
|
||||||
"js-cookie": "^2.1.3",
|
"js-cookie": "^2.1.3",
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
"markdown-pdf": "^9.0.0",
|
"markdown-pdf": "^9.0.0",
|
||||||
"mathjax": "~2.7.0",
|
"mathjax": "~2.7.0",
|
||||||
"mattermost": "^3.4.0",
|
"mattermost": "^3.4.0",
|
||||||
"mermaid": "~8.0.0",
|
"mermaid": "~7.1.0",
|
||||||
"meta-marked": "git+https://github.com/codimd/meta-marked#semver:^0.4.2",
|
"meta-marked": "git+https://github.com/codimd/meta-marked#semver:^0.4.2",
|
||||||
"method-override": "^2.3.7",
|
"method-override": "^2.3.7",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
@ -111,7 +111,8 @@
|
||||||
"readline-sync": "^1.4.7",
|
"readline-sync": "^1.4.7",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"reveal.js": "~3.7.0",
|
"reveal.js": "~3.7.0",
|
||||||
"scrypt": "^6.0.3",
|
"scrypt-async": "^2.0.1",
|
||||||
|
"scrypt-kdf": "^2.0.1",
|
||||||
"select2": "^3.5.2-browserify",
|
"select2": "^3.5.2-browserify",
|
||||||
"sequelize": "^3.28.0",
|
"sequelize": "^3.28.0",
|
||||||
"sequelize-cli": "^2.5.1",
|
"sequelize-cli": "^2.5.1",
|
||||||
|
@ -119,7 +120,7 @@
|
||||||
"socket.io": "~2.1.1",
|
"socket.io": "~2.1.1",
|
||||||
"socket.io-client": "~2.1.1",
|
"socket.io-client": "~2.1.1",
|
||||||
"spin.js": "^2.3.2",
|
"spin.js": "^2.3.2",
|
||||||
"sqlite3": "^4.0.1",
|
"sqlite3": "^4.0.7",
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"string": "^3.3.3",
|
"string": "^3.3.3",
|
||||||
"tedious": "^1.14.0",
|
"tedious": "^1.14.0",
|
||||||
|
@ -132,6 +133,7 @@
|
||||||
"viz.js": "^1.7.0",
|
"viz.js": "^1.7.0",
|
||||||
"winston": "^3.1.0",
|
"winston": "^3.1.0",
|
||||||
"ws": "^6.0.0",
|
"ws": "^6.0.0",
|
||||||
|
"wurl": "^2.5.3",
|
||||||
"xss": "^1.0.3"
|
"xss": "^1.0.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@ -140,7 +142,7 @@
|
||||||
"**/request": "^2.88.0"
|
"**/request": "^2.88.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.x"
|
"node": ">=8.x"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/codimd/server/issues",
|
"bugs": "https://github.com/codimd/server/issues",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -384,6 +384,24 @@ small .dropdown a:focus, small .dropdown a:hover {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Prevent linked heading from being hidden underneath navbar.
|
||||||
|
* Example: http://localhost:3000/features#Editor-Modes would open and
|
||||||
|
* hide the headline "Editor Modes" underneath the navbar without this CSS rule.
|
||||||
|
*/
|
||||||
|
.markdown-body h1[id]:before,
|
||||||
|
.markdown-body h2[id]:before,
|
||||||
|
.markdown-body h3[id]:before,
|
||||||
|
.markdown-body h4[id]:before,
|
||||||
|
.markdown-body h5[id]:before,
|
||||||
|
.markdown-body h6[id]:before {
|
||||||
|
display: block;
|
||||||
|
content: " ";
|
||||||
|
margin-top: -55px;
|
||||||
|
height: 55px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
div, table, img, pre, blockquote {
|
div, table, img, pre, blockquote {
|
||||||
page-break-inside: avoid !important;
|
page-break-inside: avoid !important;
|
||||||
|
|
|
@ -20,24 +20,6 @@ body.night{
|
||||||
background: #333 !important;
|
background: #333 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
background-color: #1c1c1e;
|
|
||||||
border: 1px solid #343434;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar > .btn-toolbar > .btn-group > .btn {
|
|
||||||
background-color: #1c1c1e;
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar > .btn-toolbar > .btn-group > .btn:hover {
|
|
||||||
background-color: #383a3e;
|
|
||||||
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
font-family: "Source Code Pro", Consolas, monaco, monospace;
|
font-family: "Source Code Pro", Consolas, monaco, monospace;
|
||||||
letter-spacing: 0.025em;
|
letter-spacing: 0.025em;
|
||||||
|
@ -124,7 +106,10 @@ body.night{
|
||||||
color: #78B2F2 !important;
|
color: #78B2F2 !important;
|
||||||
}
|
}
|
||||||
.CodeMirror-sizer {
|
.CodeMirror-sizer {
|
||||||
margin-bottom: 0px !important;
|
/* Make sure CodeMirror doesn't hide text under the status bar
|
||||||
|
* 26px is the height of the status bar.
|
||||||
|
*/
|
||||||
|
margin-bottom: 26px !important;
|
||||||
}
|
}
|
||||||
.CodeMirror-insert-match {
|
.CodeMirror-insert-match {
|
||||||
background: lawngreen;
|
background: lawngreen;
|
||||||
|
|
|
@ -56,3 +56,64 @@
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
border: 3px solid #777;
|
border: 3px solid #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides aside.notes {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides ul, .markdown-body.slides ol {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides table {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides table th, .markdown-body.slides table td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||||
|
border:none;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides table tr {
|
||||||
|
border-top: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides table tr:nth-child(2n) {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides table tbody tr:last-child th, .markdown-body.slides table tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body.slides h1, .markdown-body.slides h2 {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night .markdown-body.slides h1,
|
||||||
|
.night .markdown-body.slides h2,
|
||||||
|
.night .markdown-body.slides h3,
|
||||||
|
.night .markdown-body.slides h4,
|
||||||
|
.night .markdown-body.slides h5,
|
||||||
|
.night .markdown-body.slides h6 {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body section > section:last-child {
|
||||||
|
margin-bottom: 1.5em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slides previews get a black background, controlled by js */
|
||||||
|
.ui-view-area.black {
|
||||||
|
background-color: black !important;;
|
||||||
|
}
|
||||||
|
|
33
public/css/ui/toolbar.css
Normal file
33
public/css/ui/toolbar.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.toolbar {
|
||||||
|
background-color: #fafafa;
|
||||||
|
border: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar > .btn-toolbar > .btn-group > .btn {
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar > .btn-toolbar > .btn-group > .btn:hover {
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.night .toolbar {
|
||||||
|
background-color: #1c1c1e;
|
||||||
|
border: 1px solid #353538;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.night .toolbar > .btn-toolbar > .btn-group > .btn {
|
||||||
|
background-color: #1c1c1e;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #5EB7E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.night .toolbar > .btn-toolbar > .btn-group > .btn:hover {
|
||||||
|
background-color: #37373b;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
|
@ -1,6 +1,79 @@
|
||||||
Release Notes
|
Release Notes
|
||||||
===
|
===
|
||||||
|
|
||||||
|
<i class="fa fa-tag"></i> 1.4.0 <i class="fa fa-clock-o"></i> 2019-05-31 00:00
|
||||||
|
---
|
||||||
|
|
||||||
|
### Announcements
|
||||||
|
* CodiMD now has a [Mastodon account](https://social.codimd.org/mastodon)
|
||||||
|
* CodiMD now has a [community forum](https://community.codimd.org)
|
||||||
|
* With CodiMD 1.4.0 we're dropping node 6 support. That version of node.js is discontinued and no longer receives any security updates. We would like to encourage you to upgrade node 8 or later. Node 8 will continue to be supported at least until its end-of-life in January 2020.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
* Use libravatar instead of Gravatar
|
||||||
|
* Fix language description capitalization
|
||||||
|
* Move upload button into the toolbar
|
||||||
|
* Clean up Heroku configurations
|
||||||
|
* Add new screenshot to README and index page
|
||||||
|
* Add link to community call to README
|
||||||
|
* Update languages (pl, sr, zh-CN, fr, it, ja, zh-TW, de, sv, es)
|
||||||
|
* Change edit link to `both` view
|
||||||
|
* Hide minio default ports
|
||||||
|
* Add missing passport-saml configuration
|
||||||
|
* Add lutim support
|
||||||
|
* Update dependencies
|
||||||
|
* Add documentation for keycloak
|
||||||
|
* Add tests for user model
|
||||||
|
* Add Mastodon link
|
||||||
|
* Add config for toobusy middleware
|
||||||
|
* Add vietnamese language
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
* Fix missing space in footer
|
||||||
|
* Fix various possible security vulnerabilities in dependencies
|
||||||
|
* Fix broken dependency js-sequence-diagrams
|
||||||
|
* Fix XSS in graphviz error message rendering
|
||||||
|
* Fix toolbar night mode
|
||||||
|
* Fix hidden header on scroll
|
||||||
|
* Fix missing pictures for OpenID
|
||||||
|
* Fix statusbar hiding text in edit view
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
* Refactor README and documentation
|
||||||
|
* Integrate the old wiki into documentation section
|
||||||
|
* Refactor headers on Features page
|
||||||
|
* Replace js-url with wurl
|
||||||
|
* Refactor scrypt integration
|
||||||
|
|
||||||
|
### Removals
|
||||||
|
* Remove sass-loader
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
* [Amolith](https://github.com/Amolith)
|
||||||
|
* CasperS (translator)
|
||||||
|
* Cedric.couralet (translator)
|
||||||
|
* [Claudius Coenen (ccoenen)](https://github.com/ccoenen)
|
||||||
|
* Daniel (translator)
|
||||||
|
* Deluxghost (translator)
|
||||||
|
* [Dylan Dervaux (Dylanderv)](https://github.com/Dylanderv)
|
||||||
|
* [Emmanuel Ormancey (nopap)](https://github.com/nopap)
|
||||||
|
* Grzegorz (translator)
|
||||||
|
* [Henrik Hüttemann (HerHde)](https://github.com/HerHde)
|
||||||
|
* Hồng (translator)
|
||||||
|
* [Mauricio Robayo (archemiro)](https://github.com/archemiro)
|
||||||
|
* [Max Wu (jackycute)](https://github.com/jackycute)
|
||||||
|
* [naimo](https://github.com/naimo)
|
||||||
|
* [Pedro Ferreira (pferreir)](https://github.com/pferreir)
|
||||||
|
* [Simon Fish (boardfish)](https://github.com/boardfish)
|
||||||
|
* [Stéphane Guillou (stragu)](https://github.com/stragu)
|
||||||
|
* Sylke Vicious (translator)
|
||||||
|
* [Thor77](https://github.com/Thor77)
|
||||||
|
* veracosta (translator)
|
||||||
|
* Vladan (translator)
|
||||||
|
* War (translator)
|
||||||
|
* Zhai233 (translator)
|
||||||
|
|
||||||
|
|
||||||
<i class="fa fa-tag"></i> 1.3.2 <i class="fa fa-clock-o"></i> 2019-03-28 00:00
|
<i class="fa fa-tag"></i> 1.3.2 <i class="fa fa-clock-o"></i> 2019-03-28 00:00
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -101,7 +174,7 @@ Release Notes
|
||||||
* Refactor handling of template variables
|
* Refactor handling of template variables
|
||||||
* Refactor linting to use eslint
|
* Refactor linting to use eslint
|
||||||
|
|
||||||
### Removes
|
### Removals
|
||||||
* Remove no longer working Octicons
|
* Remove no longer working Octicons
|
||||||
* Remove links to our old Gitter channel
|
* Remove links to our old Gitter channel
|
||||||
* Remove unused library node-uuid
|
* Remove unused library node-uuid
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
/* global moment, serverurl */
|
/* global moment, serverurl */
|
||||||
|
|
||||||
require('./locale')
|
|
||||||
|
|
||||||
require('../css/cover.css')
|
|
||||||
require('../css/site.css')
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkIfAuth,
|
checkIfAuth,
|
||||||
clearLoginState,
|
clearLoginState,
|
||||||
getLoginState,
|
getLoginState,
|
||||||
resetCheckAuth,
|
resetCheckAuth,
|
||||||
setloginStateChangeEvent
|
setloginStateChangeEvent
|
||||||
} from './lib/common/login'
|
} from './lib/common/login'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearDuplicatedHistory,
|
clearDuplicatedHistory,
|
||||||
deleteServerHistory,
|
deleteServerHistory,
|
||||||
getHistory,
|
getHistory,
|
||||||
getStorageHistory,
|
getStorageHistory,
|
||||||
parseHistory,
|
parseHistory,
|
||||||
parseServerToHistory,
|
parseServerToHistory,
|
||||||
parseStorageToHistory,
|
parseStorageToHistory,
|
||||||
postHistoryToServer,
|
postHistoryToServer,
|
||||||
removeHistory,
|
removeHistory,
|
||||||
saveHistory,
|
saveHistory,
|
||||||
saveStorageHistoryToServer
|
saveStorageHistoryToServer
|
||||||
} from './history'
|
} from './history'
|
||||||
|
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import List from 'list.js'
|
import List from 'list.js'
|
||||||
import S from 'string'
|
import S from 'string'
|
||||||
|
|
||||||
|
require('./locale')
|
||||||
|
|
||||||
|
require('../css/cover.css')
|
||||||
|
require('../css/site.css')
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
|
valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
|
||||||
item: `<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
item: `<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">
|
||||||
|
@ -67,27 +67,27 @@ pageInit()
|
||||||
|
|
||||||
function pageInit () {
|
function pageInit () {
|
||||||
checkIfAuth(
|
checkIfAuth(
|
||||||
data => {
|
data => {
|
||||||
$('.ui-signin').hide()
|
$('.ui-signin').hide()
|
||||||
$('.ui-or').hide()
|
$('.ui-or').hide()
|
||||||
$('.ui-welcome').show()
|
$('.ui-welcome').show()
|
||||||
if (data.photo) $('.ui-avatar').prop('src', data.photo).show()
|
if (data.photo) $('.ui-avatar').prop('src', data.photo).show()
|
||||||
else $('.ui-avatar').prop('src', '').hide()
|
else $('.ui-avatar').prop('src', '').hide()
|
||||||
$('.ui-name').html(data.name)
|
$('.ui-name').html(data.name)
|
||||||
$('.ui-signout').show()
|
$('.ui-signout').show()
|
||||||
$('.ui-history').click()
|
$('.ui-history').click()
|
||||||
parseServerToHistory(historyList, parseHistoryCallback)
|
parseServerToHistory(historyList, parseHistoryCallback)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
$('.ui-signin').show()
|
$('.ui-signin').show()
|
||||||
$('.ui-or').show()
|
$('.ui-or').show()
|
||||||
$('.ui-welcome').hide()
|
$('.ui-welcome').hide()
|
||||||
$('.ui-avatar').prop('src', '').hide()
|
$('.ui-avatar').prop('src', '').hide()
|
||||||
$('.ui-name').html('')
|
$('.ui-name').html('')
|
||||||
$('.ui-signout').hide()
|
$('.ui-signout').hide()
|
||||||
parseStorageToHistory(historyList, parseHistoryCallback)
|
parseStorageToHistory(historyList, parseHistoryCallback)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
$('.masthead-nav li').click(function () {
|
$('.masthead-nav li').click(function () {
|
||||||
|
@ -132,7 +132,7 @@ function checkHistoryList () {
|
||||||
|
|
||||||
function parseHistoryCallback (list, notehistory) {
|
function parseHistoryCallback (list, notehistory) {
|
||||||
checkHistoryList()
|
checkHistoryList()
|
||||||
// sort by pinned then timestamp
|
// sort by pinned then timestamp
|
||||||
list.sort('', {
|
list.sort('', {
|
||||||
sortFunction (a, b) {
|
sortFunction (a, b) {
|
||||||
const notea = a.values()
|
const notea = a.values()
|
||||||
|
@ -152,13 +152,13 @@ function parseHistoryCallback (list, notehistory) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// parse filter tags
|
// parse filter tags
|
||||||
const filtertags = []
|
const filtertags = []
|
||||||
for (let i = 0, l = list.items.length; i < l; i++) {
|
for (let i = 0, l = list.items.length; i < l; i++) {
|
||||||
const tags = list.items[i]._values.tags
|
const tags = list.items[i]._values.tags
|
||||||
if (tags && tags.length > 0) {
|
if (tags && tags.length > 0) {
|
||||||
for (let j = 0; j < tags.length; j++) {
|
for (let j = 0; j < tags.length; j++) {
|
||||||
// push info filtertags if not found
|
// push info filtertags if not found
|
||||||
let found = false
|
let found = false
|
||||||
if (filtertags.includes(tags[j])) { found = true }
|
if (filtertags.includes(tags[j])) { found = true }
|
||||||
if (!found) { filtertags.push(tags[j]) }
|
if (!found) { filtertags.push(tags[j]) }
|
||||||
|
@ -178,20 +178,20 @@ historyList.on('updated', e => {
|
||||||
const a = itemEl.find('a')
|
const a = itemEl.find('a')
|
||||||
const pin = itemEl.find('.ui-history-pin')
|
const pin = itemEl.find('.ui-history-pin')
|
||||||
const tagsEl = itemEl.find('.tags')
|
const tagsEl = itemEl.find('.tags')
|
||||||
// parse link to element a
|
// parse link to element a
|
||||||
a.attr('href', `${serverurl}/${values.id}`)
|
a.attr('href', `${serverurl}/${values.id}`)
|
||||||
// parse pinned
|
// parse pinned
|
||||||
if (values.pinned) {
|
if (values.pinned) {
|
||||||
pin.addClass('active')
|
pin.addClass('active')
|
||||||
} else {
|
} else {
|
||||||
pin.removeClass('active')
|
pin.removeClass('active')
|
||||||
}
|
}
|
||||||
// parse tags
|
// parse tags
|
||||||
const tags = values.tags
|
const tags = values.tags
|
||||||
if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
|
if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
|
||||||
const labels = []
|
const labels = []
|
||||||
for (let j = 0; j < tags.length; j++) {
|
for (let j = 0; j < tags.length; j++) {
|
||||||
// push into the item label
|
// push into the item label
|
||||||
labels.push(`<span class='label label-default'>${tags[j]}</span>`)
|
labels.push(`<span class='label label-default'>${tags[j]}</span>`)
|
||||||
}
|
}
|
||||||
tagsEl.html(labels.join(' '))
|
tagsEl.html(labels.join(' '))
|
||||||
|
@ -328,7 +328,7 @@ $('.ui-open-history').bind('change', e => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const notehistory = JSON.parse(reader.result)
|
const notehistory = JSON.parse(reader.result)
|
||||||
// console.log(notehistory);
|
// console.log(notehistory);
|
||||||
if (!reader.result) return
|
if (!reader.result) return
|
||||||
getHistory(data => {
|
getHistory(data => {
|
||||||
let mergedata = data.concat(notehistory)
|
let mergedata = data.concat(notehistory)
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
|
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||||
/* global moment, serverurl */
|
/* global moment, serverurl */
|
||||||
|
|
||||||
|
import Prism from 'prismjs'
|
||||||
|
import hljs from 'highlight.js'
|
||||||
|
import PDFObject from 'pdfobject'
|
||||||
|
import S from 'string'
|
||||||
|
import { saveAs } from 'file-saver'
|
||||||
|
import escapeHTML from 'escape-html'
|
||||||
|
|
||||||
|
import getUIElements from './lib/editor/ui-elements'
|
||||||
|
|
||||||
|
import markdownit from 'markdown-it'
|
||||||
|
import markdownitContainer from 'markdown-it-container'
|
||||||
|
|
||||||
|
/* Defined regex markdown it plugins */
|
||||||
|
import Plugin from 'markdown-it-regexp'
|
||||||
|
|
||||||
require('prismjs/themes/prism.css')
|
require('prismjs/themes/prism.css')
|
||||||
require('prismjs/components/prism-wiki')
|
require('prismjs/components/prism-wiki')
|
||||||
require('prismjs/components/prism-haskell')
|
require('prismjs/components/prism-haskell')
|
||||||
|
@ -10,18 +26,9 @@ require('prismjs/components/prism-jsx')
|
||||||
require('prismjs/components/prism-makefile')
|
require('prismjs/components/prism-makefile')
|
||||||
require('prismjs/components/prism-gherkin')
|
require('prismjs/components/prism-gherkin')
|
||||||
|
|
||||||
import Prism from 'prismjs'
|
|
||||||
import hljs from 'highlight.js'
|
|
||||||
import PDFObject from 'pdfobject'
|
|
||||||
import S from 'string'
|
|
||||||
import { saveAs } from 'file-saver'
|
|
||||||
import escapeHTML from 'escape-html'
|
|
||||||
|
|
||||||
require('./lib/common/login')
|
require('./lib/common/login')
|
||||||
require('../vendor/md-toc')
|
require('../vendor/md-toc')
|
||||||
var Viz = require('viz.js')
|
var Viz = require('viz.js')
|
||||||
|
|
||||||
import getUIElements from './lib/editor/ui-elements'
|
|
||||||
const ui = getUIElements()
|
const ui = getUIElements()
|
||||||
|
|
||||||
// auto update last change
|
// auto update last change
|
||||||
|
@ -191,7 +198,7 @@ export function parseMeta (md, edit, view, toc, tocAffix) {
|
||||||
dir = meta.dir
|
dir = meta.dir
|
||||||
breaks = meta.breaks
|
breaks = meta.breaks
|
||||||
}
|
}
|
||||||
// text language
|
// text language
|
||||||
if (lang && typeof lang === 'string') {
|
if (lang && typeof lang === 'string') {
|
||||||
view.attr('lang', lang)
|
view.attr('lang', lang)
|
||||||
toc.attr('lang', lang)
|
toc.attr('lang', lang)
|
||||||
|
@ -203,7 +210,7 @@ export function parseMeta (md, edit, view, toc, tocAffix) {
|
||||||
tocAffix.removeAttr('lang')
|
tocAffix.removeAttr('lang')
|
||||||
if (edit) { edit.removeAttr('lang', lang) }
|
if (edit) { edit.removeAttr('lang', lang) }
|
||||||
}
|
}
|
||||||
// text direction
|
// text direction
|
||||||
if (dir && typeof dir === 'string') {
|
if (dir && typeof dir === 'string') {
|
||||||
view.attr('dir', dir)
|
view.attr('dir', dir)
|
||||||
toc.attr('dir', dir)
|
toc.attr('dir', dir)
|
||||||
|
@ -213,7 +220,7 @@ export function parseMeta (md, edit, view, toc, tocAffix) {
|
||||||
toc.removeAttr('dir')
|
toc.removeAttr('dir')
|
||||||
tocAffix.removeAttr('dir')
|
tocAffix.removeAttr('dir')
|
||||||
}
|
}
|
||||||
// breaks
|
// breaks
|
||||||
if (typeof breaks === 'boolean' && !breaks) {
|
if (typeof breaks === 'boolean' && !breaks) {
|
||||||
md.options.breaks = false
|
md.options.breaks = false
|
||||||
} else {
|
} else {
|
||||||
|
@ -246,7 +253,7 @@ if (typeof window.mermaid !== 'undefined' && window.mermaid) window.mermaid.star
|
||||||
|
|
||||||
// dynamic event or object binding here
|
// dynamic event or object binding here
|
||||||
export function finishView (view) {
|
export function finishView (view) {
|
||||||
// todo list
|
// todo list
|
||||||
const lis = view.find('li.raw').removeClass('raw').sortByDepth().toArray()
|
const lis = view.find('li.raw').removeClass('raw').sortByDepth().toArray()
|
||||||
|
|
||||||
for (let li of lis) {
|
for (let li of lis) {
|
||||||
|
@ -262,7 +269,7 @@ export function finishView (view) {
|
||||||
if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' }
|
if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' }
|
||||||
if (/^\s*\[[x ]\]\s*/.test(html)) {
|
if (/^\s*\[[x ]\]\s*/.test(html)) {
|
||||||
li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`)
|
li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`)
|
||||||
.replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`)
|
.replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`)
|
||||||
if (li.tagName.toLowerCase() !== 'li') {
|
if (li.tagName.toLowerCase() !== 'li') {
|
||||||
li.parentElement.setAttribute('class', 'task-list-item')
|
li.parentElement.setAttribute('class', 'task-list-item')
|
||||||
} else {
|
} else {
|
||||||
|
@ -270,42 +277,42 @@ export function finishView (view) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof editor !== 'undefined' && window.havePermission()) { $(li).find('input').change(toggleTodoEvent) }
|
if (typeof editor !== 'undefined' && window.havePermission()) { $(li).find('input').change(toggleTodoEvent) }
|
||||||
// color tag in list will convert it to tag icon with color
|
// color tag in list will convert it to tag icon with color
|
||||||
const tagColor = $(li).closest('ul').find('.color')
|
const tagColor = $(li).closest('ul').find('.color')
|
||||||
tagColor.each((key, value) => {
|
tagColor.each((key, value) => {
|
||||||
$(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'))
|
$(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// youtube
|
// youtube
|
||||||
view.find('div.youtube.raw').removeClass('raw')
|
view.find('div.youtube.raw').removeClass('raw')
|
||||||
.click(function () {
|
.click(function () {
|
||||||
imgPlayiframe(this, '//www.youtube.com/embed/')
|
imgPlayiframe(this, '//www.youtube.com/embed/')
|
||||||
})
|
})
|
||||||
// vimeo
|
// vimeo
|
||||||
view.find('div.vimeo.raw').removeClass('raw')
|
view.find('div.vimeo.raw').removeClass('raw')
|
||||||
.click(function () {
|
.click(function () {
|
||||||
imgPlayiframe(this, '//player.vimeo.com/video/')
|
imgPlayiframe(this, '//player.vimeo.com/video/')
|
||||||
})
|
})
|
||||||
.each((key, value) => {
|
.each((key, value) => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
|
url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
|
||||||
jsonp: 'callback',
|
jsonp: 'callback',
|
||||||
dataType: 'jsonp',
|
dataType: 'jsonp',
|
||||||
success (data) {
|
success (data) {
|
||||||
const thumbnailSrc = data[0].thumbnail_large
|
const thumbnailSrc = data[0].thumbnail_large
|
||||||
const image = `<img src="${thumbnailSrc}" />`
|
const image = `<img src="${thumbnailSrc}" />`
|
||||||
$(value).prepend(image)
|
$(value).prepend(image)
|
||||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// gist
|
// gist
|
||||||
view.find('code[data-gist-id]').each((key, value) => {
|
view.find('code[data-gist-id]').each((key, value) => {
|
||||||
if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
|
if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
|
||||||
})
|
})
|
||||||
// sequence diagram
|
// sequence diagram
|
||||||
const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
|
const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
|
||||||
sequences.each((key, value) => {
|
sequences.each((key, value) => {
|
||||||
try {
|
try {
|
||||||
|
@ -328,7 +335,7 @@ export function finishView (view) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// flowchart
|
// flowchart
|
||||||
const flow = view.find('div.flow-chart.raw').removeClass('raw')
|
const flow = view.find('div.flow-chart.raw').removeClass('raw')
|
||||||
flow.each((key, value) => {
|
flow.each((key, value) => {
|
||||||
try {
|
try {
|
||||||
|
@ -352,7 +359,7 @@ export function finishView (view) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// graphviz
|
// graphviz
|
||||||
var graphvizs = view.find('div.graphviz.raw').removeClass('raw')
|
var graphvizs = view.find('div.graphviz.raw').removeClass('raw')
|
||||||
graphvizs.each(function (key, value) {
|
graphvizs.each(function (key, value) {
|
||||||
try {
|
try {
|
||||||
|
@ -371,7 +378,7 @@ export function finishView (view) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// mermaid
|
// mermaid
|
||||||
const mermaids = view.find('div.mermaid.raw').removeClass('raw')
|
const mermaids = view.find('div.mermaid.raw').removeClass('raw')
|
||||||
mermaids.each((key, value) => {
|
mermaids.each((key, value) => {
|
||||||
try {
|
try {
|
||||||
|
@ -413,16 +420,16 @@ export function finishView (view) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// image href new window(emoji not included)
|
// image href new window(emoji not included)
|
||||||
const images = view.find('img.raw[src]').removeClass('raw')
|
const images = view.find('img.raw[src]').removeClass('raw')
|
||||||
images.each((key, value) => {
|
images.each((key, value) => {
|
||||||
// if it's already wrapped by link, then ignore
|
// if it's already wrapped by link, then ignore
|
||||||
const $value = $(value)
|
const $value = $(value)
|
||||||
$value[0].onload = e => {
|
$value[0].onload = e => {
|
||||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// blockquote
|
// blockquote
|
||||||
const blockquote = view.find('blockquote.raw').removeClass('raw')
|
const blockquote = view.find('blockquote.raw').removeClass('raw')
|
||||||
const blockquoteP = blockquote.find('p')
|
const blockquoteP = blockquote.find('p')
|
||||||
blockquoteP.each((key, value) => {
|
blockquoteP.each((key, value) => {
|
||||||
|
@ -430,96 +437,96 @@ export function finishView (view) {
|
||||||
html = replaceExtraTags(html)
|
html = replaceExtraTags(html)
|
||||||
$(value).html(html)
|
$(value).html(html)
|
||||||
})
|
})
|
||||||
// color tag in blockquote will change its left border color
|
// color tag in blockquote will change its left border color
|
||||||
const blockquoteColor = blockquote.find('.color')
|
const blockquoteColor = blockquote.find('.color')
|
||||||
blockquoteColor.each((key, value) => {
|
blockquoteColor.each((key, value) => {
|
||||||
$(value).closest('blockquote').css('border-left-color', $(value).attr('data-color'))
|
$(value).closest('blockquote').css('border-left-color', $(value).attr('data-color'))
|
||||||
})
|
})
|
||||||
// slideshare
|
// slideshare
|
||||||
view.find('div.slideshare.raw').removeClass('raw')
|
view.find('div.slideshare.raw').removeClass('raw')
|
||||||
.each((key, value) => {
|
.each((key, value) => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
|
url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
|
||||||
jsonp: 'callback',
|
jsonp: 'callback',
|
||||||
dataType: 'jsonp',
|
dataType: 'jsonp',
|
||||||
success (data) {
|
success (data) {
|
||||||
const $html = $(data.html)
|
const $html = $(data.html)
|
||||||
const iframe = $html.closest('iframe')
|
const iframe = $html.closest('iframe')
|
||||||
const caption = $html.closest('div')
|
const caption = $html.closest('div')
|
||||||
const inner = $('<div class="inner"></div>').append(iframe)
|
const inner = $('<div class="inner"></div>').append(iframe)
|
||||||
const height = iframe.attr('height')
|
const height = iframe.attr('height')
|
||||||
const width = iframe.attr('width')
|
const width = iframe.attr('width')
|
||||||
const ratio = (height / width) * 100
|
const ratio = (height / width) * 100
|
||||||
inner.css('padding-bottom', `${ratio}%`)
|
inner.css('padding-bottom', `${ratio}%`)
|
||||||
$(value).html(inner).append(caption)
|
$(value).html(inner).append(caption)
|
||||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// speakerdeck
|
// speakerdeck
|
||||||
view.find('div.speakerdeck.raw').removeClass('raw')
|
view.find('div.speakerdeck.raw').removeClass('raw')
|
||||||
.each((key, value) => {
|
.each((key, value) => {
|
||||||
const url = `https://speakerdeck.com/${$(value).attr('data-speakerdeckid')}`
|
const url = `https://speakerdeck.com/${$(value).attr('data-speakerdeckid')}`
|
||||||
const inner = $('<a>Speakerdeck</a>')
|
const inner = $('<a>Speakerdeck</a>')
|
||||||
inner.attr('href', url)
|
inner.attr('href', url)
|
||||||
inner.attr('rel', 'noopener noreferrer')
|
inner.attr('rel', 'noopener noreferrer')
|
||||||
inner.attr('target', '_blank')
|
inner.attr('target', '_blank')
|
||||||
$(value).append(inner)
|
$(value).append(inner)
|
||||||
})
|
})
|
||||||
// pdf
|
// pdf
|
||||||
view.find('div.pdf.raw').removeClass('raw')
|
view.find('div.pdf.raw').removeClass('raw')
|
||||||
.each(function (key, value) {
|
.each(function (key, value) {
|
||||||
const url = $(value).attr('data-pdfurl')
|
const url = $(value).attr('data-pdfurl')
|
||||||
const inner = $('<div></div>')
|
const inner = $('<div></div>')
|
||||||
$(this).append(inner)
|
$(this).append(inner)
|
||||||
PDFObject.embed(url, inner, {
|
PDFObject.embed(url, inner, {
|
||||||
height: '400px'
|
height: '400px'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// syntax highlighting
|
// syntax highlighting
|
||||||
view.find('code.raw').removeClass('raw')
|
view.find('code.raw').removeClass('raw')
|
||||||
.each((key, value) => {
|
.each((key, value) => {
|
||||||
const langDiv = $(value)
|
const langDiv = $(value)
|
||||||
if (langDiv.length > 0) {
|
if (langDiv.length > 0) {
|
||||||
const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim()
|
const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim()
|
||||||
const codeDiv = langDiv.find('.code')
|
const codeDiv = langDiv.find('.code')
|
||||||
let code = ''
|
let code = ''
|
||||||
if (codeDiv.length > 0) code = codeDiv.html()
|
if (codeDiv.length > 0) code = codeDiv.html()
|
||||||
else code = langDiv.html()
|
else code = langDiv.html()
|
||||||
var result
|
var result
|
||||||
if (!reallang) {
|
if (!reallang) {
|
||||||
result = {
|
result = {
|
||||||
value: code
|
value: code
|
||||||
}
|
|
||||||
} else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') {
|
|
||||||
code = S(code).unescapeHTML().s
|
|
||||||
result = {
|
|
||||||
value: Prism.highlight(code, Prism.languages[reallang])
|
|
||||||
}
|
|
||||||
} else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
|
|
||||||
code = S(code).unescapeHTML().s
|
|
||||||
result = {
|
|
||||||
value: Prism.highlight(code, Prism.languages.wiki)
|
|
||||||
}
|
|
||||||
} else if (reallang === 'cmake') {
|
|
||||||
code = S(code).unescapeHTML().s
|
|
||||||
result = {
|
|
||||||
value: Prism.highlight(code, Prism.languages.makefile)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
code = S(code).unescapeHTML().s
|
|
||||||
const languages = hljs.listLanguages()
|
|
||||||
if (!languages.includes(reallang)) {
|
|
||||||
result = hljs.highlightAuto(code)
|
|
||||||
} else {
|
|
||||||
result = hljs.highlight(reallang, code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (codeDiv.length > 0) codeDiv.html(result.value)
|
|
||||||
else langDiv.html(result.value)
|
|
||||||
}
|
}
|
||||||
})
|
} else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') {
|
||||||
|
code = S(code).unescapeHTML().s
|
||||||
|
result = {
|
||||||
|
value: Prism.highlight(code, Prism.languages[reallang])
|
||||||
|
}
|
||||||
|
} else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
|
||||||
|
code = S(code).unescapeHTML().s
|
||||||
|
result = {
|
||||||
|
value: Prism.highlight(code, Prism.languages.wiki)
|
||||||
|
}
|
||||||
|
} else if (reallang === 'cmake') {
|
||||||
|
code = S(code).unescapeHTML().s
|
||||||
|
result = {
|
||||||
|
value: Prism.highlight(code, Prism.languages.makefile)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code = S(code).unescapeHTML().s
|
||||||
|
const languages = hljs.listLanguages()
|
||||||
|
if (!languages.includes(reallang)) {
|
||||||
|
result = hljs.highlightAuto(code)
|
||||||
|
} else {
|
||||||
|
result = hljs.highlight(reallang, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (codeDiv.length > 0) codeDiv.html(result.value)
|
||||||
|
else langDiv.html(result.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
// mathjax
|
// mathjax
|
||||||
const mathjaxdivs = view.find('span.mathjax.raw').removeClass('raw').toArray()
|
const mathjaxdivs = view.find('span.mathjax.raw').removeClass('raw').toArray()
|
||||||
try {
|
try {
|
||||||
|
@ -533,7 +540,7 @@ export function finishView (view) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(err)
|
console.warn(err)
|
||||||
}
|
}
|
||||||
// render title
|
// render title
|
||||||
document.title = renderTitle(view)
|
document.title = renderTitle(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,23 +600,23 @@ window.removeDOMEvents = removeDOMEvents
|
||||||
function generateCleanHTML (view) {
|
function generateCleanHTML (view) {
|
||||||
const src = view.clone()
|
const src = view.clone()
|
||||||
const eles = src.find('*')
|
const eles = src.find('*')
|
||||||
// remove syncscroll parts
|
// remove syncscroll parts
|
||||||
eles.removeClass('part')
|
eles.removeClass('part')
|
||||||
src.find('*[class=""]').removeAttr('class')
|
src.find('*[class=""]').removeAttr('class')
|
||||||
eles.removeAttr('data-startline data-endline')
|
eles.removeAttr('data-startline data-endline')
|
||||||
src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
||||||
// remove gist content
|
// remove gist content
|
||||||
src.find('code[data-gist-id]').children().remove()
|
src.find('code[data-gist-id]').children().remove()
|
||||||
// disable todo list
|
// disable todo list
|
||||||
src.find('input.task-list-item-checkbox').attr('disabled', '')
|
src.find('input.task-list-item-checkbox').attr('disabled', '')
|
||||||
// replace emoji image path
|
// replace emoji image path
|
||||||
src.find('img.emoji').each((key, value) => {
|
src.find('img.emoji').each((key, value) => {
|
||||||
let name = $(value).attr('alt')
|
let name = $(value).attr('alt')
|
||||||
name = name.substr(1)
|
name = name.substr(1)
|
||||||
name = name.slice(0, name.length - 1)
|
name = name.slice(0, name.length - 1)
|
||||||
$(value).attr('src', `https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/images/basic/${name}.png`)
|
$(value).attr('src', `https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/images/basic/${name}.png`)
|
||||||
})
|
})
|
||||||
// replace video to iframe
|
// replace video to iframe
|
||||||
src.find('div[data-videoid]').each((key, value) => {
|
src.find('div[data-videoid]').each((key, value) => {
|
||||||
const id = $(value).attr('data-videoid')
|
const id = $(value).attr('data-videoid')
|
||||||
const style = $(value).attr('style')
|
const style = $(value).attr('style')
|
||||||
|
@ -645,12 +652,12 @@ export function exportToHTML (view) {
|
||||||
const title = renderTitle(ui.area.markdown)
|
const title = renderTitle(ui.area.markdown)
|
||||||
const filename = `${renderFilename(ui.area.markdown)}.html`
|
const filename = `${renderFilename(ui.area.markdown)}.html`
|
||||||
const src = generateCleanHTML(view)
|
const src = generateCleanHTML(view)
|
||||||
// generate toc
|
// generate toc
|
||||||
const toc = $('#ui-toc').clone()
|
const toc = $('#ui-toc').clone()
|
||||||
toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
||||||
const tocAffix = $('#ui-toc-affix').clone()
|
const tocAffix = $('#ui-toc-affix').clone()
|
||||||
tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
||||||
// generate html via template
|
// generate html via template
|
||||||
$.get(`${serverurl}/build/html.min.css`, css => {
|
$.get(`${serverurl}/build/html.min.css`, css => {
|
||||||
$.get(`${serverurl}/views/html.hbs`, data => {
|
$.get(`${serverurl}/views/html.hbs`, data => {
|
||||||
const template = window.Handlebars.compile(data)
|
const template = window.Handlebars.compile(data)
|
||||||
|
@ -665,7 +672,6 @@ export function exportToHTML (view) {
|
||||||
dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null
|
dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null
|
||||||
}
|
}
|
||||||
const html = template(context)
|
const html = template(context)
|
||||||
// console.log(html);
|
|
||||||
const blob = new Blob([html], {
|
const blob = new Blob([html], {
|
||||||
type: 'text/html;charset=utf-8'
|
type: 'text/html;charset=utf-8'
|
||||||
})
|
})
|
||||||
|
@ -780,20 +786,20 @@ export function smoothHashScroll () {
|
||||||
const hash = element.hash
|
const hash = element.hash
|
||||||
if (hash) {
|
if (hash) {
|
||||||
$element.on('click', function (e) {
|
$element.on('click', function (e) {
|
||||||
// store hash
|
// store hash
|
||||||
const hash = decodeURIComponent(this.hash)
|
const hash = decodeURIComponent(this.hash)
|
||||||
// escape special characters in jquery selector
|
// escape special characters in jquery selector
|
||||||
const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, '\\$1'))
|
const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, '\\$1'))
|
||||||
// return if no element been selected
|
// return if no element been selected
|
||||||
if ($hash.length <= 0) return
|
if ($hash.length <= 0) return
|
||||||
// prevent default anchor click behavior
|
// prevent default anchor click behavior
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// animate
|
// animate
|
||||||
$('body, html').stop(true, true).animate({
|
$('body, html').stop(true, true).animate({
|
||||||
scrollTop: $hash.offset().top
|
scrollTop: $hash.offset().top
|
||||||
}, 100, 'linear', () => {
|
}, 100, 'linear', () => {
|
||||||
// when done, add hash to url
|
// when done, add hash to url
|
||||||
// (default click behaviour)
|
// (default click behaviour)
|
||||||
window.location.hash = hash
|
window.location.hash = hash
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -935,9 +941,6 @@ function highlightRender (code, lang) {
|
||||||
return result.value
|
return result.value
|
||||||
}
|
}
|
||||||
|
|
||||||
import markdownit from 'markdown-it'
|
|
||||||
import markdownitContainer from 'markdown-it-container'
|
|
||||||
|
|
||||||
export let md = markdownit('default', {
|
export let md = markdownit('default', {
|
||||||
html: true,
|
html: true,
|
||||||
breaks: true,
|
breaks: true,
|
||||||
|
@ -1035,109 +1038,106 @@ md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||||
return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
|
return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Defined regex markdown it plugins */
|
|
||||||
import Plugin from 'markdown-it-regexp'
|
|
||||||
|
|
||||||
// youtube
|
// youtube
|
||||||
const youtubePlugin = new Plugin(
|
const youtubePlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%youtube\s*([\d\D]*?)\s*%}/,
|
/{%youtube\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const videoid = match[1]
|
const videoid = match[1]
|
||||||
if (!videoid) return
|
if (!videoid) return
|
||||||
const div = $('<div class="youtube raw"></div>')
|
const div = $('<div class="youtube raw"></div>')
|
||||||
div.attr('data-videoid', videoid)
|
div.attr('data-videoid', videoid)
|
||||||
const thumbnailSrc = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`
|
const thumbnailSrc = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`
|
||||||
const image = `<img src="${thumbnailSrc}" />`
|
const image = `<img src="${thumbnailSrc}" />`
|
||||||
div.append(image)
|
div.append(image)
|
||||||
const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'
|
const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'
|
||||||
div.append(icon)
|
div.append(icon)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// vimeo
|
// vimeo
|
||||||
const vimeoPlugin = new Plugin(
|
const vimeoPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%vimeo\s*([\d\D]*?)\s*%}/,
|
/{%vimeo\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const videoid = match[1]
|
const videoid = match[1]
|
||||||
if (!videoid) return
|
if (!videoid) return
|
||||||
const div = $('<div class="vimeo raw"></div>')
|
const div = $('<div class="vimeo raw"></div>')
|
||||||
div.attr('data-videoid', videoid)
|
div.attr('data-videoid', videoid)
|
||||||
const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'
|
const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'
|
||||||
div.append(icon)
|
div.append(icon)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// gist
|
// gist
|
||||||
const gistPlugin = new Plugin(
|
const gistPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%gist\s*([\d\D]*?)\s*%}/,
|
/{%gist\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const gistid = match[1]
|
const gistid = match[1]
|
||||||
const code = `<code data-gist-id="${gistid}"></code>`
|
const code = `<code data-gist-id="${gistid}"></code>`
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// TOC
|
// TOC
|
||||||
const tocPlugin = new Plugin(
|
const tocPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/^\[TOC\]$/i,
|
/^\[TOC\]$/i,
|
||||||
|
|
||||||
(match, utils) => '<div class="toc"></div>'
|
(match, utils) => '<div class="toc"></div>'
|
||||||
)
|
)
|
||||||
// slideshare
|
// slideshare
|
||||||
const slidesharePlugin = new Plugin(
|
const slidesharePlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%slideshare\s*([\d\D]*?)\s*%}/,
|
/{%slideshare\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const slideshareid = match[1]
|
const slideshareid = match[1]
|
||||||
const div = $('<div class="slideshare raw"></div>')
|
const div = $('<div class="slideshare raw"></div>')
|
||||||
div.attr('data-slideshareid', slideshareid)
|
div.attr('data-slideshareid', slideshareid)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// speakerdeck
|
// speakerdeck
|
||||||
const speakerdeckPlugin = new Plugin(
|
const speakerdeckPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%speakerdeck\s*([\d\D]*?)\s*%}/,
|
/{%speakerdeck\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const speakerdeckid = match[1]
|
const speakerdeckid = match[1]
|
||||||
const div = $('<div class="speakerdeck raw"></div>')
|
const div = $('<div class="speakerdeck raw"></div>')
|
||||||
div.attr('data-speakerdeckid', speakerdeckid)
|
div.attr('data-speakerdeckid', speakerdeckid)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// pdf
|
// pdf
|
||||||
const pdfPlugin = new Plugin(
|
const pdfPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/{%pdf\s*([\d\D]*?)\s*%}/,
|
/{%pdf\s*([\d\D]*?)\s*%}/,
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const pdfurl = match[1]
|
const pdfurl = match[1]
|
||||||
if (!isValidURL(pdfurl)) return match[0]
|
if (!isValidURL(pdfurl)) return match[0]
|
||||||
const div = $('<div class="pdf raw"></div>')
|
const div = $('<div class="pdf raw"></div>')
|
||||||
div.attr('data-pdfurl', pdfurl)
|
div.attr('data-pdfurl', pdfurl)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const emojijsPlugin = new Plugin(
|
const emojijsPlugin = new Plugin(
|
||||||
// regexp to match emoji shortcodes :something:
|
// regexp to match emoji shortcodes :something:
|
||||||
// We generate an universal regex that guaranteed only contains the
|
// We generate an universal regex that guaranteed only contains the
|
||||||
// emojies we have available. This should prevent all false-positives
|
// emojies we have available. This should prevent all false-positives
|
||||||
new RegExp(':(' + window.emojify.emojiNames.map((item) => { return RegExp.escape(item) }).join('|') + '):', 'i'),
|
new RegExp(':(' + window.emojify.emojiNames.map((item) => { return RegExp.escape(item) }).join('|') + '):', 'i'),
|
||||||
|
|
||||||
(match, utils) => {
|
(match, utils) => {
|
||||||
const emoji = match[1].toLowerCase()
|
const emoji = match[1].toLowerCase()
|
||||||
const div = $(`<img class="emoji" alt=":${emoji}:" src="${serverurl}/build/emojify.js/dist/images/basic/${emoji}.png"></img>`)
|
const div = $(`<img class="emoji" alt=":${emoji}:" src="${serverurl}/build/emojify.js/dist/images/basic/${emoji}.png"></img>`)
|
||||||
return div[0].outerHTML
|
return div[0].outerHTML
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// yaml meta, from https://github.com/eugeneware/remarkable-meta
|
// yaml meta, from https://github.com/eugeneware/remarkable-meta
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
|
/* eslint no-console: ["error", { allow: ["warn", "error", "debug"] }] */
|
||||||
/* global serverurl, moment */
|
/* global serverurl, moment */
|
||||||
|
|
||||||
import store from 'store'
|
import store from 'store'
|
||||||
|
@ -12,11 +13,11 @@ import {
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkIfAuth
|
checkIfAuth
|
||||||
} from './lib/common/login'
|
} from './lib/common/login'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
urlpath
|
urlpath
|
||||||
} from './lib/config'
|
} from './lib/config'
|
||||||
|
|
||||||
window.migrateHistoryFromTempCallback = null
|
window.migrateHistoryFromTempCallback = null
|
||||||
|
@ -28,40 +29,40 @@ function migrateHistoryFromTemp () {
|
||||||
$.get(`${serverurl}/temp`, {
|
$.get(`${serverurl}/temp`, {
|
||||||
tempid: url('#tempid')
|
tempid: url('#tempid')
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done(data => {
|
||||||
if (data && data.temp) {
|
if (data && data.temp) {
|
||||||
getStorageHistory(olddata => {
|
getStorageHistory(olddata => {
|
||||||
if (!olddata || olddata.length === 0) {
|
if (!olddata || olddata.length === 0) {
|
||||||
saveHistoryToStorage(JSON.parse(data.temp))
|
saveHistoryToStorage(JSON.parse(data.temp))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
.always(() => {
|
|
||||||
let hash = location.hash.split('#')[1]
|
|
||||||
hash = hash.split('&')
|
|
||||||
for (let i = 0; i < hash.length; i++) {
|
|
||||||
if (hash[i].indexOf('tempid') === 0) {
|
|
||||||
hash.splice(i, 1)
|
|
||||||
i--
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
hash = hash.join('&')
|
.always(() => {
|
||||||
location.hash = hash
|
let hash = location.hash.split('#')[1]
|
||||||
if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() }
|
hash = hash.split('&')
|
||||||
})
|
for (let i = 0; i < hash.length; i++) {
|
||||||
|
if (hash[i].indexOf('tempid') === 0) {
|
||||||
|
hash.splice(i, 1)
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash = hash.join('&')
|
||||||
|
location.hash = hash
|
||||||
|
if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveHistory (notehistory) {
|
export function saveHistory (notehistory) {
|
||||||
checkIfAuth(
|
checkIfAuth(
|
||||||
() => {
|
() => {
|
||||||
saveHistoryToServer(notehistory)
|
saveHistoryToServer(notehistory)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
saveHistoryToStorage(notehistory)
|
saveHistoryToStorage(notehistory)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveHistoryToStorage (notehistory) {
|
function saveHistoryToStorage (notehistory) {
|
||||||
|
@ -80,9 +81,9 @@ export function saveStorageHistoryToServer (callback) {
|
||||||
$.post(`${serverurl}/history`, {
|
$.post(`${serverurl}/history`, {
|
||||||
history: data
|
history: data
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done(data => {
|
||||||
callback(data)
|
callback(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ export function clearDuplicatedHistory (notehistory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHistory (id, text, time, tags, pinned, notehistory) {
|
function addHistory (id, text, time, tags, pinned, notehistory) {
|
||||||
// only add when note id exists
|
// only add when note id exists
|
||||||
if (id) {
|
if (id) {
|
||||||
notehistory.push({
|
notehistory.push({
|
||||||
id,
|
id,
|
||||||
|
@ -135,14 +136,14 @@ export function removeHistory (id, notehistory) {
|
||||||
// used for inner
|
// used for inner
|
||||||
export function writeHistory (title, tags) {
|
export function writeHistory (title, tags) {
|
||||||
checkIfAuth(
|
checkIfAuth(
|
||||||
() => {
|
() => {
|
||||||
// no need to do this anymore, this will count from server-side
|
// no need to do this anymore, this will count from server-side
|
||||||
// writeHistoryToServer(title, tags);
|
// writeHistoryToServer(title, tags);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
writeHistoryToStorage(title, tags)
|
writeHistoryToStorage(title, tags)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeHistoryToStorage (title, tags) {
|
function writeHistoryToStorage (title, tags) {
|
||||||
|
@ -163,7 +164,7 @@ if (!Array.isArray) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHistory (title, tags) {
|
function renderHistory (title, tags) {
|
||||||
// console.debug(tags);
|
// console.debug(tags);
|
||||||
const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]
|
const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
@ -175,7 +176,7 @@ function renderHistory (title, tags) {
|
||||||
|
|
||||||
function generateHistory (title, tags, notehistory) {
|
function generateHistory (title, tags, notehistory) {
|
||||||
const info = renderHistory(title, tags)
|
const info = renderHistory(title, tags)
|
||||||
// keep any pinned data
|
// keep any pinned data
|
||||||
let pinned = false
|
let pinned = false
|
||||||
for (let i = 0; i < notehistory.length; i++) {
|
for (let i = 0; i < notehistory.length; i++) {
|
||||||
if (notehistory[i].id === info.id && notehistory[i].pinned) {
|
if (notehistory[i].id === info.id && notehistory[i].pinned) {
|
||||||
|
@ -192,25 +193,25 @@ function generateHistory (title, tags, notehistory) {
|
||||||
// used for outer
|
// used for outer
|
||||||
export function getHistory (callback) {
|
export function getHistory (callback) {
|
||||||
checkIfAuth(
|
checkIfAuth(
|
||||||
() => {
|
() => {
|
||||||
getServerHistory(callback)
|
getServerHistory(callback)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
getStorageHistory(callback)
|
getStorageHistory(callback)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServerHistory (callback) {
|
function getServerHistory (callback) {
|
||||||
$.get(`${serverurl}/history`)
|
$.get(`${serverurl}/history`)
|
||||||
.done(data => {
|
.done(data => {
|
||||||
if (data.history) {
|
if (data.history) {
|
||||||
callback(data.history)
|
callback(data.history)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((xhr, status, error) => {
|
.fail((xhr, status, error) => {
|
||||||
console.error(xhr.responseText)
|
console.error(xhr.responseText)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStorageHistory (callback) {
|
export function getStorageHistory (callback) {
|
||||||
|
@ -225,25 +226,25 @@ export function getStorageHistory (callback) {
|
||||||
|
|
||||||
export function parseHistory (list, callback) {
|
export function parseHistory (list, callback) {
|
||||||
checkIfAuth(
|
checkIfAuth(
|
||||||
() => {
|
() => {
|
||||||
parseServerToHistory(list, callback)
|
parseServerToHistory(list, callback)
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
parseStorageToHistory(list, callback)
|
parseStorageToHistory(list, callback)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseServerToHistory (list, callback) {
|
export function parseServerToHistory (list, callback) {
|
||||||
$.get(`${serverurl}/history`)
|
$.get(`${serverurl}/history`)
|
||||||
.done(data => {
|
.done(data => {
|
||||||
if (data.history) {
|
if (data.history) {
|
||||||
parseToHistory(list, data.history, callback)
|
parseToHistory(list, data.history, callback)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((xhr, status, error) => {
|
.fail((xhr, status, error) => {
|
||||||
console.error(xhr.responseText)
|
console.error(xhr.responseText)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseStorageToHistory (list, callback) {
|
export function parseStorageToHistory (list, callback) {
|
||||||
|
@ -269,15 +270,15 @@ function parseToHistory (list, notehistory, callback) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
// parse time to timestamp and fromNow
|
// parse time to timestamp and fromNow
|
||||||
const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
|
const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
|
||||||
notehistory[i].timestamp = timestamp.valueOf()
|
notehistory[i].timestamp = timestamp.valueOf()
|
||||||
notehistory[i].fromNow = timestamp.fromNow()
|
notehistory[i].fromNow = timestamp.fromNow()
|
||||||
notehistory[i].time = timestamp.format('llll')
|
notehistory[i].time = timestamp.format('llll')
|
||||||
// prevent XSS
|
// prevent XSS
|
||||||
notehistory[i].text = S(notehistory[i].text).escapeHTML().s
|
notehistory[i].text = S(notehistory[i].text).escapeHTML().s
|
||||||
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
|
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
|
||||||
// add to list
|
// add to list
|
||||||
if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
|
if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1128
public/js/index.js
1128
public/js/index.js
File diff suppressed because it is too large
Load diff
|
@ -60,22 +60,22 @@ export function checkIfAuth (yesCallback, noCallback) {
|
||||||
if (checkLoginStateChanged()) checkAuth = false
|
if (checkLoginStateChanged()) checkAuth = false
|
||||||
if (!checkAuth || typeof cookieLoginState === 'undefined') {
|
if (!checkAuth || typeof cookieLoginState === 'undefined') {
|
||||||
$.get(`${serverurl}/me`)
|
$.get(`${serverurl}/me`)
|
||||||
.done(data => {
|
.done(data => {
|
||||||
if (data && data.status === 'ok') {
|
if (data && data.status === 'ok') {
|
||||||
profile = data
|
profile = data
|
||||||
yesCallback(profile)
|
yesCallback(profile)
|
||||||
setLoginState(true, data.id)
|
setLoginState(true, data.id)
|
||||||
} else {
|
} else {
|
||||||
noCallback()
|
noCallback()
|
||||||
setLoginState(false)
|
setLoginState(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail(() => {
|
.fail(() => {
|
||||||
noCallback()
|
noCallback()
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
checkAuth = true
|
checkAuth = true
|
||||||
})
|
})
|
||||||
} else if (cookieLoginState) {
|
} else if (cookieLoginState) {
|
||||||
yesCallback(profile)
|
yesCallback(profile)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import config from './config'
|
||||||
import statusBarTemplate from './statusbar.html'
|
import statusBarTemplate from './statusbar.html'
|
||||||
import toolBarTemplate from './toolbar.html'
|
import toolBarTemplate from './toolbar.html'
|
||||||
|
|
||||||
|
import '../../../css/ui/toolbar.css'
|
||||||
|
|
||||||
/* config section */
|
/* config section */
|
||||||
const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
|
const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
|
||||||
const defaultEditorMode = 'gfm'
|
const defaultEditorMode = 'gfm'
|
||||||
|
@ -219,8 +221,8 @@ export default class Editor {
|
||||||
makeComment.click(() => {
|
makeComment.click(() => {
|
||||||
utils.insertText(this.editor, '> []')
|
utils.insertText(this.editor, '> []')
|
||||||
})
|
})
|
||||||
|
|
||||||
uploadImage.bind('change', function (e) {
|
uploadImage.bind('change', function (e) {
|
||||||
console.log("tiggered")
|
|
||||||
var files = e.target.files || e.dataTransfer.files
|
var files = e.target.files || e.dataTransfer.files
|
||||||
e.dataTransfer = {}
|
e.dataTransfer = {}
|
||||||
e.dataTransfer.files = files
|
e.dataTransfer.files = files
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="btn-toolbar" role="toolbar" aria-label="Editor toolbar">
|
<div class="btn-toolbar" role="toolbar" aria-label="Editor toolbar">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a id="makeBold" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Bold">
|
<a id="makeBold" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Bold">
|
||||||
<i class="fa fa-bold fa-fw"></i>
|
<i class="fa fa-bold fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeItalic" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Italic">
|
<a id="makeItalic" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Italic">
|
||||||
<i class="fa fa-italic fa-fw"></i>
|
<i class="fa fa-italic fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeStrike" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Strikethrough">
|
<a id="makeStrike" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Strikethrough">
|
||||||
<i class="fa fa-strikethrough fa-fw"></i>
|
<i class="fa fa-strikethrough fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeHeader" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Heading">
|
<a id="makeHeader" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Heading">
|
||||||
<i class="fa fa-h1 fa-fw">H</i>
|
<i class="fa fa-h1 fa-fw">H</i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeCode" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Code">
|
<a id="makeCode" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Code">
|
||||||
<i class="fa fa-code fa-fw"></i>
|
<i class="fa fa-code fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeQuote" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Quote">
|
<a id="makeQuote" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Quote">
|
||||||
<i class="fa fa-quote-right fa-fw"></i>
|
<i class="fa fa-quote-right fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeGenericList" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="List">
|
<a id="makeGenericList" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="List">
|
||||||
<i class="fa fa-list fa-fw"></i>
|
<i class="fa fa-list fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeOrderedList" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Numbered List">
|
<a id="makeOrderedList" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Numbered List">
|
||||||
<i class="fa fa-list-ol fa-fw"></i>
|
<i class="fa fa-list-ol fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeCheckList" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Check List">
|
<a id="makeCheckList" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Check List">
|
||||||
<i class="fa fa-check-square fa-fw"></i>
|
<i class="fa fa-check-square fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeLink" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Link">
|
<a id="makeLink" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Link">
|
||||||
<i class="fa fa-link fa-fw"></i>
|
<i class="fa fa-link fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeImage" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Image">
|
<a id="makeImage" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Image">
|
||||||
<i class="fa fa-image fa-fw"></i>
|
<i class="fa fa-image fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<span id="uploadImage" class="btn btn-sm btn-dark btn-file ui-upload-image" title="Upload Image">
|
<a id="uploadImage" class="btn btn-sm btn-file ui-upload-image" title="Upload Image">
|
||||||
<i class="fa fa-upload fa-fw"></i><input type="file" accept="image/*" name="upload" multiple>
|
<i class="fa fa-upload fa-fw"></i><input type="file" accept="image/*" name="upload" multiple title="Upload Image">
|
||||||
</span>
|
</a>
|
||||||
<a id="makeTable" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Table">
|
<a id="makeTable" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Table">
|
||||||
<i class="fa fa-table fa-fw"></i>
|
<i class="fa fa-table fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeLine" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Line">
|
<a id="makeLine" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Line">
|
||||||
<i class="fa fa-minus fa-fw"></i>
|
<i class="fa fa-minus fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a id="makeComment" class="btn btn-sm btn-dark text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Comment">
|
<a id="makeComment" class="btn btn-sm text-uppercase" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" title="Comment">
|
||||||
<i class="fa fa-comment fa-fw"></i>
|
<i class="fa fa-comment fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,7 +51,7 @@ export function insertText (cm, text, cursorEnd = 0) {
|
||||||
let cursor = cm.getCursor()
|
let cursor = cm.getCursor()
|
||||||
cm.replaceSelection(text, cursor, cursor)
|
cm.replaceSelection(text, cursor, cursor)
|
||||||
cm.focus()
|
cm.focus()
|
||||||
cm.setCursor({line: cursor.line, ch: cursor.ch + cursorEnd})
|
cm.setCursor({ line: cursor.line, ch: cursor.ch + cursorEnd })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertLink (cm, isImage) {
|
export function insertLink (cm, isImage) {
|
||||||
|
@ -80,7 +80,7 @@ export function insertLink (cm, isImage) {
|
||||||
cm.setSelections(ranges)
|
cm.setSelections(ranges)
|
||||||
} else {
|
} else {
|
||||||
cm.replaceRange(symbol + linkEnd, cursor, cursor)
|
cm.replaceRange(symbol + linkEnd, cursor, cursor)
|
||||||
cm.setCursor({line: cursor.line, ch: cursor.ch + symbol.length + linkEnd.length})
|
cm.setCursor({ line: cursor.line, ch: cursor.ch + symbol.length + linkEnd.length })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cm.focus()
|
cm.focus()
|
||||||
|
@ -88,8 +88,8 @@ export function insertLink (cm, isImage) {
|
||||||
|
|
||||||
export function insertHeader (cm) {
|
export function insertHeader (cm) {
|
||||||
let cursor = cm.getCursor()
|
let cursor = cm.getCursor()
|
||||||
let startOfLine = {line: cursor.line, ch: 0}
|
let startOfLine = { line: cursor.line, ch: 0 }
|
||||||
let startOfLineText = cm.getRange(startOfLine, {line: cursor.line, ch: 1})
|
let startOfLineText = cm.getRange(startOfLine, { line: cursor.line, ch: 1 })
|
||||||
// See if it is already a header
|
// See if it is already a header
|
||||||
if (startOfLineText === '#') {
|
if (startOfLineText === '#') {
|
||||||
cm.replaceRange('#', startOfLine, startOfLine)
|
cm.replaceRange('#', startOfLine, startOfLine)
|
||||||
|
@ -108,14 +108,14 @@ export function insertOnStartOfLines (cm, symbol) {
|
||||||
if (!range.empty()) {
|
if (!range.empty()) {
|
||||||
const from = range.from()
|
const from = range.from()
|
||||||
const to = range.to()
|
const to = range.to()
|
||||||
let selection = cm.getRange({line: from.line, ch: 0}, to)
|
let selection = cm.getRange({ line: from.line, ch: 0 }, to)
|
||||||
selection = selection.replace(/\n/g, '\n' + symbol)
|
selection = selection.replace(/\n/g, '\n' + symbol)
|
||||||
selection = symbol + selection
|
selection = symbol + selection
|
||||||
cm.replaceRange(selection, from, to)
|
cm.replaceRange(selection, from, to)
|
||||||
} else {
|
} else {
|
||||||
cm.replaceRange(symbol, {line: cursor.line, ch: 0}, {line: cursor.line, ch: 0})
|
cm.replaceRange(symbol, { line: cursor.line, ch: 0 }, { line: cursor.line, ch: 0 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cm.setCursor({line: cursor.line, ch: cursor.ch + symbol.length})
|
cm.setCursor({ line: cursor.line, ch: cursor.ch + symbol.length })
|
||||||
cm.focus()
|
cm.focus()
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ function buildMapInner (callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
nonEmptyList.push(0)
|
nonEmptyList.push(0)
|
||||||
// make the first line go top
|
// make the first line go top
|
||||||
_scrollMap[0] = viewTop
|
_scrollMap[0] = viewTop
|
||||||
|
|
||||||
const parts = markdownArea.find('.part').toArray()
|
const parts = markdownArea.find('.part').toArray()
|
||||||
|
@ -336,7 +336,7 @@ export function syncScrollToView (event, preventAnimate) {
|
||||||
const scrollInfo = editor.getScrollInfo()
|
const scrollInfo = editor.getScrollInfo()
|
||||||
const textHeight = editor.defaultTextHeight()
|
const textHeight = editor.defaultTextHeight()
|
||||||
lineNo = Math.floor(scrollInfo.top / textHeight)
|
lineNo = Math.floor(scrollInfo.top / textHeight)
|
||||||
// if reach the last line, will start lerp to the bottom
|
// if reach the last line, will start lerp to the bottom
|
||||||
const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)
|
const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)
|
||||||
if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
|
if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
|
||||||
topDiffPercent = diffToBottom / textHeight
|
topDiffPercent = diffToBottom / textHeight
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
/* global refreshView */
|
/* global refreshView */
|
||||||
|
|
||||||
|
import {
|
||||||
|
autoLinkify,
|
||||||
|
deduplicatedHeaderId,
|
||||||
|
removeDOMEvents,
|
||||||
|
finishView,
|
||||||
|
generateToc,
|
||||||
|
md,
|
||||||
|
parseMeta,
|
||||||
|
postProcess,
|
||||||
|
renderTOC,
|
||||||
|
scrollToHash,
|
||||||
|
smoothHashScroll,
|
||||||
|
updateLastChange
|
||||||
|
} from './extra'
|
||||||
|
|
||||||
|
import { preventXSS } from './render'
|
||||||
|
|
||||||
require('../css/extra.css')
|
require('../css/extra.css')
|
||||||
require('../css/slide-preview.css')
|
require('../css/slide-preview.css')
|
||||||
require('../css/site.css')
|
require('../css/site.css')
|
||||||
|
|
||||||
require('highlight.js/styles/github-gist.css')
|
require('highlight.js/styles/github-gist.css')
|
||||||
|
|
||||||
import {
|
|
||||||
autoLinkify,
|
|
||||||
deduplicatedHeaderId,
|
|
||||||
removeDOMEvents,
|
|
||||||
finishView,
|
|
||||||
generateToc,
|
|
||||||
md,
|
|
||||||
parseMeta,
|
|
||||||
postProcess,
|
|
||||||
renderTOC,
|
|
||||||
scrollToHash,
|
|
||||||
smoothHashScroll,
|
|
||||||
updateLastChange
|
|
||||||
} from './extra'
|
|
||||||
|
|
||||||
import { preventXSS } from './render'
|
|
||||||
|
|
||||||
const markdown = $('#doc.markdown-body')
|
const markdown = $('#doc.markdown-body')
|
||||||
const text = markdown.text()
|
const text = markdown.text()
|
||||||
const lastMeta = md.meta
|
const lastMeta = md.meta
|
||||||
|
@ -38,7 +38,7 @@ if (md.meta.type && md.meta.type === 'slide') {
|
||||||
const slides = window.RevealMarkdown.slidify(text, slideOptions)
|
const slides = window.RevealMarkdown.slidify(text, slideOptions)
|
||||||
markdown.html(slides)
|
markdown.html(slides)
|
||||||
window.RevealMarkdown.initialize()
|
window.RevealMarkdown.initialize()
|
||||||
// prevent XSS
|
// prevent XSS
|
||||||
markdown.html(preventXSS(markdown.html()))
|
markdown.html(preventXSS(markdown.html()))
|
||||||
markdown.addClass('slides')
|
markdown.addClass('slides')
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,12 +46,12 @@ if (md.meta.type && md.meta.type === 'slide') {
|
||||||
refreshView()
|
refreshView()
|
||||||
markdown.removeClass('slides')
|
markdown.removeClass('slides')
|
||||||
}
|
}
|
||||||
// only render again when meta changed
|
// only render again when meta changed
|
||||||
if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
|
if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
|
||||||
parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'))
|
parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'))
|
||||||
rendered = md.render(text)
|
rendered = md.render(text)
|
||||||
}
|
}
|
||||||
// prevent XSS
|
// prevent XSS
|
||||||
rendered = preventXSS(rendered)
|
rendered = preventXSS(rendered)
|
||||||
const result = postProcess(rendered)
|
const result = postProcess(rendered)
|
||||||
markdown.html(result.html())
|
markdown.html(result.html())
|
||||||
|
@ -98,14 +98,14 @@ function generateScrollspy () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function windowResize () {
|
function windowResize () {
|
||||||
// toc right
|
// toc right
|
||||||
const paddingRight = parseFloat(markdown.css('padding-right'))
|
const paddingRight = parseFloat(markdown.css('padding-right'))
|
||||||
const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight))
|
const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight))
|
||||||
toc.css('right', `${right}px`)
|
toc.css('right', `${right}px`)
|
||||||
// affix toc left
|
// affix toc left
|
||||||
let newbool
|
let newbool
|
||||||
const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2
|
const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2
|
||||||
// for ipad or wider device
|
// for ipad or wider device
|
||||||
if (rightMargin >= 133) {
|
if (rightMargin >= 133) {
|
||||||
newbool = true
|
newbool = true
|
||||||
const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2
|
const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2
|
||||||
|
@ -126,7 +126,7 @@ $(document).ready(() => {
|
||||||
windowResize()
|
windowResize()
|
||||||
generateScrollspy()
|
generateScrollspy()
|
||||||
setTimeout(scrollToHash, 0)
|
setTimeout(scrollToHash, 0)
|
||||||
// tooltip
|
// tooltip
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ var filterXSSOptions = {
|
||||||
onIgnoreTag: function (tag, html, options) {
|
onIgnoreTag: function (tag, html, options) {
|
||||||
// allow comment tag
|
// allow comment tag
|
||||||
if (tag === '!--') {
|
if (tag === '!--') {
|
||||||
// do not filter its attributes
|
// do not filter its attributes
|
||||||
return html.replace(/<(?!!--)/g, '<').replace(/-->/g, '__HTML_COMMENT_END__').replace(/>/g, '>').replace(/__HTML_COMMENT_END__/g, '-->')
|
return html.replace(/<(?!!--)/g, '<').replace(/-->/g, '__HTML_COMMENT_END__').replace(/>/g, '>').replace(/__HTML_COMMENT_END__/g, '-->')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/* eslint-env browser, jquery */
|
/* eslint-env browser, jquery */
|
||||||
/* global serverurl, Reveal, RevealMarkdown */
|
/* global serverurl, Reveal, RevealMarkdown */
|
||||||
|
|
||||||
require('../css/extra.css')
|
|
||||||
require('../css/site.css')
|
|
||||||
|
|
||||||
import { preventXSS } from './render'
|
import { preventXSS } from './render'
|
||||||
import { md, updateLastChange, removeDOMEvents, finishView } from './extra'
|
import { md, updateLastChange, removeDOMEvents, finishView } from './extra'
|
||||||
|
|
||||||
|
require('../css/extra.css')
|
||||||
|
require('../css/site.css')
|
||||||
|
|
||||||
const body = preventXSS($('.slides').text())
|
const body = preventXSS($('.slides').text())
|
||||||
|
|
||||||
window.createtime = window.lastchangeui.time.attr('data-createtime')
|
window.createtime = window.lastchangeui.time.attr('data-createtime')
|
||||||
|
@ -17,7 +17,7 @@ $('.ui-edit').attr('href', `${url}/edit`)
|
||||||
$('.ui-print').attr('href', `${url}?print-pdf`)
|
$('.ui-print').attr('href', `${url}?print-pdf`)
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
// tooltip
|
// tooltip
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ function renderSlide (event) {
|
||||||
Reveal.addEventListener('ready', event => {
|
Reveal.addEventListener('ready', event => {
|
||||||
renderSlide(event)
|
renderSlide(event)
|
||||||
const markdown = $(event.currentSlide)
|
const markdown = $(event.currentSlide)
|
||||||
// force browser redraw
|
// force browser redraw
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
markdown.hide().show(0)
|
markdown.hide().show(0)
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
22
public/vendor/showup/showup.css
vendored
22
public/vendor/showup/showup.css
vendored
|
@ -87,26 +87,6 @@
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light theme */
|
|
||||||
.btn-light {
|
|
||||||
color: #555;
|
|
||||||
background-color: rgba(0, 0, 0,.1);
|
|
||||||
}
|
|
||||||
.btn-light:hover {
|
|
||||||
color: #111;
|
|
||||||
background-color: rgba(0, 0, 0,.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme */
|
|
||||||
.btn-dark {
|
|
||||||
color: #fff;
|
|
||||||
background-color: rgba(0, 0, 0,.5);
|
|
||||||
}
|
|
||||||
.btn-dark:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: rgba(0, 0, 0,.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons displayed throughout the content */
|
/* Buttons displayed throughout the content */
|
||||||
.btn-showup {
|
.btn-showup {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -122,4 +102,4 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #39235A;
|
background-color: #39235A;
|
||||||
border-color: #39235A;
|
border-color: #39235A;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js" integrity="sha256-PieqE0QdEDMppwXrTzSZQr6tWFX3W5KkyRVyF1zN3eg=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js" integrity="sha256-PieqE0QdEDMppwXrTzSZQr6tWFX3W5KkyRVyF1zN3eg=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
<div id="ui-toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="top:17px;display:none;" {{{lang}}} {{{dir}}}>
|
<div id="ui-toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="top:17px;display:none;" {{{lang}}} {{{dir}}}>
|
||||||
{{{ui-toc-affix}}}
|
{{{ui-toc-affix}}}
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gist-embed/2.6.0/gist-embed.min.js" integrity="sha256-KyF2D6xPIJUW5sUDSs93vWyZm+1RzIpKCexxElmxl8g=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gist-embed/2.6.0/gist-embed.min.js" integrity="sha256-KyF2D6xPIJUW5sUDSs93vWyZm+1RzIpKCexxElmxl8g=" crossorigin="anonymous" defer></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 inner">
|
<div class="col-md-4 inner">
|
||||||
<a href="<%- serverURL %>/features#Slide-Modee">
|
<a href="<%- serverURL %>/features#Slide-Mode">
|
||||||
<i class="fa fa-tv fa-3x"></i>
|
<i class="fa fa-tv fa-3x"></i>
|
||||||
<h4><%= __('Support slide mode') %></h4>
|
<h4><%= __('Support slide mode') %></h4>
|
||||||
</a>
|
</a>
|
||||||
|
@ -149,12 +149,13 @@
|
||||||
<option value="ko">한국어</option>
|
<option value="ko">한국어</option>
|
||||||
<option value="id">Bahasa Indonesia</option>
|
<option value="id">Bahasa Indonesia</option>
|
||||||
<option value="sr">Cрпски</option>
|
<option value="sr">Cрпски</option>
|
||||||
|
<option value="vi">Tiếng Việt</option>
|
||||||
</select>
|
</select>
|
||||||
<p>
|
<p>
|
||||||
<%- __('Powered by %s', '<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><% } %>
|
<%- __('Powered by %s', '<a href="https://codimd.org">CodiMD</a>') %> | <a href="<%- serverURL %>/s/release-notes" target="_blank" rel="noopener"><%= __('Releases') %></a> | <a href="<%- sourceURL %>" target="_blank" rel="noopener"><%= __('Source Code') %></a><% if(privacyStatement) { %> | <a href="<%- serverURL %>/s/privacy" target="_blank" rel="noopener"><%= __('Privacy') %></a><% } %><% if(termsOfUse) { %> | <a href="<%- serverURL %>/s/terms-of-use" target="_blank" rel="noopener"><%= __('Terms of Use') %></a><% } %>
|
||||||
</p>
|
</p>
|
||||||
<h6 class="social-foot">
|
<h6 class="social-foot">
|
||||||
<%- __('Follow us on %s and %s.', '<a href="https://github.com/codimd/server" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://riot.im/app/#/room/#codimd:matrix.org" target="_blank" rel="noopener"><i class="fa fa-comments"></i> Riot</a>', '<a href="https://translate.codimd.org" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
<%- __('Follow us on %s and %s.', '<a href="https://github.com/codimd/server" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://community.codimd.org" target="_blank" rel="noopener"><i class="fa fa-users" aria-hidden="true"></i> Discourse</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://social.codimd.org/mastodon" target="_blank" rel="noopener"><i class="fa fa-mastodon"></i> Mastodon</a>', '<a href="https://translate.codimd.org" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/list.pagination.js/0.1.1/list.pagination.min.js" integrity="sha256-WwTza96H3BgcQTfEfxX7MFaFc/dZA0QrPRKDRLdFHJo=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/list.pagination.js/0.1.1/list.pagination.min.js" integrity="sha256-WwTza96H3BgcQTfEfxX7MFaFc/dZA0QrPRKDRLdFHJo=" crossorigin="anonymous" defer></script>
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
</html>
|
</html>
|
||||||
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
<script src="<%= serverURL %>/js/mathjax-config-extra.js"></script>
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.0/js/bootstrap.min.js" integrity="sha256-kJrlY+s09+QoWjpkOrXXwhxeaoDz9FW5SaxF8I0DibQ=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
<% if(useCDN) { %>
|
<% if(useCDN) { %>
|
||||||
<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/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/reveal.js/3.7.0/js/reveal.min.js" integrity="sha256-Xr6ZH+/kc7hDVReZLO5khBknteLqu5oen/xnSraXrVk=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.4.0/velocity.min.js" integrity="sha256-bhm0lgEt6ITaZCDzZpkr/VXVrLa5RP4u9v2AYsbzSUk=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js" integrity="sha256-jnOjDTXIPqall8M0MyTSt98JetJuZ7Yu+1Jm7hLTF7U=" crossorigin="anonymous" defer></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.7.0/js-yaml.min.js" integrity="sha256-8PanqYAVOGlOct+i65R+HqibK3KPsXINnrSfxN+Y/J0=" crossorigin="anonymous" defer></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.7.0/js-yaml.min.js" integrity="sha256-8PanqYAVOGlOct+i65R+HqibK3KPsXINnrSfxN+Y/J0=" crossorigin="anonymous" defer></script>
|
||||||
|
|
64
test/user.js
Normal file
64
test/user.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/* eslint-env node, mocha */
|
||||||
|
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const assert = require('assert')
|
||||||
|
|
||||||
|
const models = require('../lib/models')
|
||||||
|
const User = models.User
|
||||||
|
|
||||||
|
describe('User Sequelize model', function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
return models.sequelize.sync({ force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stores a password hash on creation and verifies that password', function () {
|
||||||
|
const userData = {
|
||||||
|
password: 'test123'
|
||||||
|
}
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
return User.create(userData).then(u => {
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(userData.password).then(result => assert.strictEqual(result, true)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword).then(result => assert.strictEqual(result, false))
|
||||||
|
]).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can cope with password stored in standard scrypt header format', function () {
|
||||||
|
const testKey = '736372797074000e00000008000000018c7b8c1ac273fd339badde759b3efc418bc61b776debd02dfe95989383cf9980ad21d2403dce33f4b551f5e98ce84edb792aee62600b1303ab8d4e6f0a53b0746e73193dbf557b888efc83a2d6a055a9'
|
||||||
|
const validPassword = 'test'
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
const u = User.build()
|
||||||
|
u.setDataValue('password', testKey) // this circumvents the setter - which we don't need in this case!
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(validPassword).then(result => assert.strictEqual(result, true)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword).then(result => assert.strictEqual(result, false))
|
||||||
|
]).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deals with various characters correctly', function () {
|
||||||
|
const combinations = [
|
||||||
|
// ['correct password', 'scrypt syle hash']
|
||||||
|
['test', '736372797074000e00000008000000018c7b8c1ac273fd339badde759b3efc418bc61b776debd02dfe95989383cf9980ad21d2403dce33f4b551f5e98ce84edb792aee62600b1303ab8d4e6f0a53b0746e73193dbf557b888efc83a2d6a055a9'],
|
||||||
|
['ohai', '736372797074000e00000008000000010efec4e5ce6a5294491f1b1cccc38d3562f84844b9271aef635f8bc338cf4e0e0bac62ebb11379e85894c1f694e038fc39b087b4fdacd1280b50a7382d7ffbfc82f2190bef70d47708d2a94b75126294'],
|
||||||
|
['my secret pw', '736372797074000f0000000800000001ffb4cd10a1dfe9e64c1e5416fd6d55b390b6822e78b46fd1f963fe9f317a1e05f9c5fee15e1f618286f4e38b55364ae1e7dc295c9dc33ee0f5712e86afe37e5784ff9c7cf84cf0e631dd11f84f3621e7'],
|
||||||
|
['my secret pw', /* different hash! */ '736372797074000f0000000800000001f6083e9593365acd07550f7c72f19973fb7d52c3ef0a78026ff66c48ab14493843c642167b5e6b7f31927e8eeb912bc2639e41955fae15da5099998948cfeacd022f705624931c3b30104e6bb296b805'],
|
||||||
|
['i am so extremely long, it\'s not even funny. Wait, you\'re still reading?', '736372797074000f00000008000000012d205f7bb529bb3a8b8bb25f5ab46197c7e9baf1aad64cf5e7b2584c84748cacf5e60631d58d21cb51fa34ea93b517e2fe2eb722931db5a70ff5a1330d821288ee7380c4136369f064b71b191a785a5b']
|
||||||
|
]
|
||||||
|
const intentionallyInvalidPassword = 'stuff'
|
||||||
|
|
||||||
|
return Promise.all(combinations.map((combination, index) => {
|
||||||
|
const u = User.build()
|
||||||
|
u.setDataValue('password', combination[1])
|
||||||
|
return Promise.all([
|
||||||
|
u.verifyPassword(combination[0])
|
||||||
|
.then(result => assert.strictEqual(result, true, `password #${index} "${combination[0]}" should have been verified`)),
|
||||||
|
u.verifyPassword(intentionallyInvalidPassword)
|
||||||
|
.then(result => assert.strictEqual(result, false, `password #${index} "${combination[0]}" should NOT have been verified`))
|
||||||
|
])
|
||||||
|
})).catch(e => assert.fail(e))
|
||||||
|
})
|
||||||
|
})
|
|
@ -392,18 +392,6 @@ module.exports = {
|
||||||
MiniCssExtractPlugin.loader,
|
MiniCssExtractPlugin.loader,
|
||||||
'css-loader'
|
'css-loader'
|
||||||
]
|
]
|
||||||
}, {
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [
|
|
||||||
MiniCssExtractPlugin.loader,
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
importLoaders: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'sass-loader'
|
|
||||||
]
|
|
||||||
}, {
|
}, {
|
||||||
test: /\.less$/,
|
test: /\.less$/,
|
||||||
use: [
|
use: [
|
||||||
|
|
Loading…
Reference in a new issue