Merge branch 'master' into feature/addSecrets

This commit is contained in:
Raccoon 2017-03-03 09:22:35 +08:00 committed by GitHub
commit 48592d692c
62 changed files with 1374 additions and 1397 deletions

8
.babelrc Normal file
View file

@ -0,0 +1,8 @@
{
"presets": [
"es2015"
],
"plugins": [
"transform-runtime"
]
}

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
*.min.js

View file

@ -16,7 +16,6 @@
], ],
"array-callback-return": "error", "array-callback-return": "error",
"arrow-body-style": "error", "arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error", "arrow-spacing": "error",
"block-scoped-var": "off", "block-scoped-var": "off",
"block-spacing": "error", "block-spacing": "error",
@ -123,7 +122,7 @@
"no-extend-native": "error", "no-extend-native": "error",
"no-extra-bind": "error", "no-extra-bind": "error",
"no-extra-label": "error", "no-extra-label": "error",
"no-extra-parens": "error", "no-extra-parens": "warn",
"no-floating-decimal": "error", "no-floating-decimal": "error",
"no-global-assign": "error", "no-global-assign": "error",
"no-implicit-coercion": "error", "no-implicit-coercion": "error",
@ -195,7 +194,7 @@
"no-unneeded-ternary": "error", "no-unneeded-ternary": "error",
"no-unsafe-negation": "error", "no-unsafe-negation": "error",
"no-unused-expressions": "error", "no-unused-expressions": "error",
"no-use-before-define": "error", "no-use-before-define": "warn",
"no-useless-call": "error", "no-useless-call": "error",
"no-useless-computed-key": "error", "no-useless-computed-key": "error",
"no-useless-concat": "error", "no-useless-concat": "error",

1
.gitignore vendored
View file

@ -18,7 +18,6 @@ backups/
# ignore config files # ignore config files
config.json config.json
public/js/config.js
.sequelizerc .sequelizerc
# ignore webpack build # ignore webpack build

13
.travis.yml Normal file
View file

@ -0,0 +1,13 @@
language: node_js
node_js:
- 6
- 7
- stable
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

View file

@ -1,7 +1,9 @@
HackMD HackMD
=== ===
[![Join the chat at https://gitter.im/hackmdio/hackmd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hackmdio/hackmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/hackmdio/hackmd][gitter-image]][gitter-url]
[![build status][travis-image]][travis-url]
HackMD lets you create realtime collaborative markdown notes on all platforms. HackMD lets you create realtime collaborative markdown notes on all platforms.
Inspired by Hackpad, with more focus on speed and flexibility. Inspired by Hackpad, with more focus on speed and flexibility.
@ -48,9 +50,9 @@ Browsers Requirement
Prerequisite Prerequisite
--- ---
- Node.js 4.x or up (test up to 6.7.0) - Node.js 6.x or up (test up to 7.5.0)
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8` - Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
- npm - npm (and its dependencies, especially [uWebSockets](https://github.com/uWebSockets/uWebSockets#nodejs-developers), [node-gyp](https://github.com/nodejs/node-gyp#installation))
Get started Get started
--- ---
@ -59,7 +61,7 @@ Get started
2. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite. 2. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite.
3. Setup the configs, see more below 3. Setup the configs, see more below
4. Setup environment variables which will overwrite the configs 4. Setup environment variables which will overwrite the configs
5. Build front-end bundle by `npm run build:prod` (use `npm run build:dev` if you are in development) 5. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development)
6. Run the server as you like (node, forever, pm2) 6. Run the server as you like (node, forever, pm2)
Upgrade guide Upgrade guide
@ -70,7 +72,7 @@ If you are upgrading HackMD from an older version, follow these steps:
1. Fully stop your old server first (important) 1. Fully stop your old server first (important)
2. `git pull` or do whatever that updates the files 2. `git pull` or do whatever that updates the files
3. `npm install` to update dependencies 3. `npm install` to update dependencies
4. Build front-end bundle by `npm run build:prod` (use `npm run build:dev` if you are in development) 4. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development)
5. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string 5. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string
For example: `postgres://username:password@localhost:5432/hackmd` For example: `postgres://username:password@localhost:5432/hackmd`
6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema 6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
@ -97,19 +99,9 @@ Configuration files
There are some configs you need to change in the files below There are some configs you need to change in the files below
``` ```
./config.json --- for server settings ./config.json ----application settings
./public/js/config.js --- for client settings
``` ```
Client settings `config.js`
---
| variables | example values | description |
| --------- | ------ | ----------- |
| debug | `true` or `false` | set debug mode, show more logs |
| domain | `localhost` | domain name |
| urlpath | `hackmd` | sub url path, like: `www.example.com/<urlpath>` |
Environment variables (will overwrite other server configs) Environment variables (will overwrite other server configs)
--- ---
@ -126,6 +118,7 @@ Environment variables (will overwrite other server configs)
| HMD_USECDN | `true` or `false` | set to use CDN resources or not (default is `true`) | | HMD_USECDN | `true` or `false` | set to use CDN resources or not (default is `true`) |
| HMD_ALLOW_ANONYMOUS | `true` or `false` | set to allow anonymous usage (default is `true`) | | HMD_ALLOW_ANONYMOUS | `true` or `false` | set to allow anonymous usage (default is `true`) |
| HMD_ALLOW_FREEURL | `true` or `false` | set to allow new note by accessing not exist note url | | HMD_ALLOW_FREEURL | `true` or `false` | set to allow new note by accessing not exist note url |
| HMD_DEFAULT_PERMISSION | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
| HMD_DB_URL | `mysql://localhost:3306/database` | set the db url | | HMD_DB_URL | `mysql://localhost:3306/database` | set the db url |
| HMD_FACEBOOK_CLIENTID | no example | Facebook API client id | | HMD_FACEBOOK_CLIENTID | no example | Facebook API client id |
| HMD_FACEBOOK_CLIENTSECRET | no example | Facebook API client secret | | HMD_FACEBOOK_CLIENTSECRET | no example | Facebook API client secret |
@ -140,15 +133,15 @@ Environment variables (will overwrite other server configs)
| HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret | | HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret |
| HMD_GOOGLE_CLIENTID | no example | Google API client id | | HMD_GOOGLE_CLIENTID | no example | Google API client id |
| HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret | | HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret |
| HMD_LDAP_URL | ldap://example.com | url of LDAP server | | HMD_LDAP_URL | `ldap://example.com` | url of LDAP server |
| HMD_LDAP_BINDDN | no example | bindDn for LDAP access | | HMD_LDAP_BINDDN | no example | bindDn for LDAP access |
| HMD_LDAP_BINDCREDENTIALS | no example | bindCredentials for LDAP access | | HMD_LDAP_BINDCREDENTIALS | no example | bindCredentials for LDAP access |
| HMD_LDAP_TOKENSECRET | supersecretkey | secret used for generating access/refresh tokens | | HMD_LDAP_TOKENSECRET | `supersecretkey` | secret used for generating access/refresh tokens |
| HMD_LDAP_SEARCHBASE | o=users,dc=example,dc=com | LDAP directory to begin search from | | HMD_LDAP_SEARCHBASE | `o=users,dc=example,dc=com` | LDAP directory to begin search from |
| HMD_LDAP_SEARCHFILTER | (uid={{username}}) | LDAP filter to search with | | HMD_LDAP_SEARCHFILTER | `(uid={{username}})` | LDAP filter to search with |
| HMD_LDAP_SEARCHATTRIBUTES | no example | LDAP attributes to search with | | HMD_LDAP_SEARCHATTRIBUTES | no example | LDAP attributes to search with |
| HMD_LDAP_TLS_CA | no example | Root CA for LDAP TLS in PEM format | | HMD_LDAP_TLS_CA | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) |
| HMD_LDAP_PROVIDERNAME | My institution | Optional name to be displayed at login form indicating the LDAP provider | | HMD_LDAP_PROVIDERNAME | `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
| HMD_IMGUR_CLIENTID | no example | Imgur API client id | | HMD_IMGUR_CLIENTID | no example | Imgur API client id |
| HMD_EMAIL | `true` or `false` | set to allow email signin | | HMD_EMAIL | `true` or `false` | set to allow email signin |
| HMD_ALLOW_EMAIL_REGISTER | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) | | HMD_ALLOW_EMAIL_REGISTER | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) |
@ -158,7 +151,7 @@ Environment variables (will overwrite other server configs)
| HMD_S3_REGION | `ap-northeast-1` | AWS S3 region | | HMD_S3_REGION | `ap-northeast-1` | AWS S3 region |
| HMD_S3_BUCKET | no example | AWS S3 bucket name | | HMD_S3_BUCKET | no example | AWS S3 bucket name |
Server settings `config.json` Application settings `config.json`
--- ---
| variables | example values | description | | variables | example values | description |
@ -174,6 +167,7 @@ Server settings `config.json`
| usecdn | `true` or `false` | set to use CDN resources or not (default is `true`) | | usecdn | `true` or `false` | set to use CDN resources or not (default is `true`) |
| allowanonymous | `true` or `false` | set to allow anonymous usage (default is `true`) | | allowanonymous | `true` or `false` | set to allow anonymous usage (default is `true`) |
| allowfreeurl | `true` or `false` | set to allow new note by accessing not exist note url | | allowfreeurl | `true` or `false` | set to allow new note by accessing not exist note url |
| defaultpermission | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
| dburl | `mysql://localhost:3306/database` | set the db url, if set this variable then below db config won't be applied | | dburl | `mysql://localhost:3306/database` | set the db url, if set this variable then below db config won't be applied |
| db | `{ "dialect": "sqlite", "storage": "./db.hackmd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) | | db | `{ "dialect": "sqlite", "storage": "./db.hackmd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
| sslkeypath | `./cert/client.key` | ssl key path (only need when you set usessl) | | sslkeypath | `./cert/client.key` | ssl key path (only need when you set usessl) |
@ -207,7 +201,7 @@ Third-party integration api key settings
| ------- | --------- | ----------- | | ------- | --------- | ----------- |
| facebook, twitter, github, gitlab, dropbox, google, ldap | environment variables or `config.json` | for signin | | facebook, twitter, github, gitlab, dropbox, google, ldap | environment variables or `config.json` | for signin |
| imgur | environment variables or `config.json` | for image upload | | imgur | environment variables or `config.json` | for image upload |
| google drive, dropbox | `public/js/config.js` | for export and import | | google drive(`google/apiKey`, `google/clientID`), dropbox(`dropbox/appKey`) | `config.json` | for export and import |
Third-party integration oauth callback urls Third-party integration oauth callback urls
--- ---
@ -230,3 +224,7 @@ Additionally, now can show other clients' selections.
See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/)
**License under MIT.** **License under MIT.**
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[gitter-url]: https://gitter.im/hackmdio/hackmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[travis-image]: https://travis-ci.org/hackmdio/hackmd.svg?branch=master
[travis-url]: https://travis-ci.org/hackmdio/hackmd

8
app.js
View file

@ -26,7 +26,6 @@ var validator = require('validator');
var config = require("./lib/config.js"); var config = require("./lib/config.js");
var logger = require("./lib/logger.js"); var logger = require("./lib/logger.js");
var auth = require("./lib/auth.js"); var auth = require("./lib/auth.js");
var history = require("./lib/history.js");
var response = require("./lib/response.js"); var response = require("./lib/response.js");
var models = require("./lib/models"); var models = require("./lib/models");
@ -443,6 +442,7 @@ app.get('/logout', function (req, res) {
req.logout(); req.logout();
res.redirect(config.serverurl + '/'); res.redirect(config.serverurl + '/');
}); });
var history = require("./lib/history.js");
//get history //get history
app.get('/history', history.historyGet); app.get('/history', history.historyGet);
//post history //post history
@ -502,7 +502,7 @@ app.post('/uploadimage', function (req, res) {
switch (config.imageUploadType) { switch (config.imageUploadType) {
case 'filesystem': case 'filesystem':
res.send({ res.send({
link: url.resolve(config.serverurl, files.image.path.match(/^public(.+$)/)[1]) link: url.resolve(config.serverurl + '/', files.image.path.match(/^public\/(.+$)/)[1])
}); });
break; break;
@ -608,7 +608,7 @@ function startListen() {
// sync db then start listen // sync db then start listen
models.sequelize.sync().then(function () { models.sequelize.sync().then(function () {
// check if realtime is ready // check if realtime is ready
if (history.isReady() && realtime.isReady()) { if (realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) throw new Error(err); if (err) throw new Error(err);
if (!notes || notes.length <= 0) return startListen(); if (!notes || notes.length <= 0) return startListen();
@ -639,7 +639,7 @@ function handleTermSignals() {
}, 0); }, 0);
}); });
var checkCleanTimer = setInterval(function () { var checkCleanTimer = setInterval(function () {
if (history.isReady() && realtime.isReady()) { if (realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) return logger.error(err); if (err) return logger.error(err);
if (!notes || notes.length <= 0) { if (!notes || notes.length <= 0) {

View file

@ -28,8 +28,6 @@ EOF
EOF EOF
cp public/js/config.js.example public/js/config.js
# build app # build app
npm run build:prod npm run build
fi fi

View file

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
set -e
# run command at repo root # run command at repo root
CURRENT_PATH=$PWD CURRENT_PATH=$PWD
if [ -d .git ]; then if [ -d .git ]; then
@ -21,10 +23,6 @@ if [ ! -f config.json ]; then
cp config.json.example config.json cp config.json.example config.json
fi fi
if [ ! -f publis/js/config.js ]; then
cp public/js/config.js.example public/js/config.js
fi
if [ ! -f .sequelizerc ]; then if [ ! -f .sequelizerc ]; then
cp .sequelizerc.example .sequelizerc cp .sequelizerc.example .sequelizerc
fi fi

View file

@ -2,18 +2,13 @@
"test": { "test": {
"db": { "db": {
"dialect": "sqlite", "dialect": "sqlite",
"storage": "./db.hackmd.sqlite" "storage": ":memory:"
} }
}, },
"development": { "development": {
"domain": "localhost",
"db": { "db": {
"username": "", "dialect": "sqlite",
"password": "", "storage": "./db.hackmd.sqlite"
"database": "hackmd",
"host": "localhost",
"port": "3306",
"dialect": "mysql"
} }
}, },
"production": { "production": {
@ -45,11 +40,13 @@
}, },
"dropbox": { "dropbox": {
"clientID": "change this", "clientID": "change this",
"clientSecret": "change this" "clientSecret": "change this",
"appKey": "change this"
}, },
"google": { "google": {
"clientID": "change this", "clientID": "change this",
"clientSecret": "change this" "clientSecret": "change this",
"apiKey": "change this"
}, },
"ldap": { "ldap": {
"url": "ldap://change_this", "url": "ldap://change_this",

View file

@ -1,4 +1,5 @@
// external modules // external modules
var fs = require('fs');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
@ -27,8 +28,16 @@ var allowanonymous = process.env.HMD_ALLOW_ANONYMOUS ? (process.env.HMD_ALLOW_AN
var allowfreeurl = process.env.HMD_ALLOW_FREEURL ? (process.env.HMD_ALLOW_FREEURL === 'true') : !!config.allowfreeurl; var allowfreeurl = process.env.HMD_ALLOW_FREEURL ? (process.env.HMD_ALLOW_FREEURL === 'true') : !!config.allowfreeurl;
var permissions = ['editable', 'limited', 'locked', 'protected', 'private'];
if (allowanonymous) {
permissions.unshift('freely');
}
var defaultpermission = process.env.HMD_DEFAULT_PERMISSION || config.defaultpermission;
defaultpermission = permissions.indexOf(defaultpermission) != -1 ? defaultpermission : 'editable';
// db // db
var dburl = config.dburl || process.env.HMD_DB_URL || process.env.DATABASE_URL; var dburl = process.env.HMD_DB_URL || process.env.DATABASE_URL || config.dburl;
var db = config.db || {}; var db = config.db || {};
// ssl path // ssl path
@ -91,15 +100,16 @@ var gitlab = (process.env.HMD_GITLAB_CLIENTID && process.env.HMD_GITLAB_CLIENTSE
clientID: handleDockerSecret('gitlab_clientID') || process.env.HMD_GITLAB_CLIENTID, clientID: handleDockerSecret('gitlab_clientID') || process.env.HMD_GITLAB_CLIENTID,
clientSecret: handleDockerSecret('gitlab_clientSecret') || process.env.HMD_GITLAB_CLIENTSECRET clientSecret: handleDockerSecret('gitlab_clientSecret') || process.env.HMD_GITLAB_CLIENTSECRET
} : config.gitlab || false; } : config.gitlab || false;
var dropbox = (process.env.HMD_DROPBOX_CLIENTID && process.env.HMD_DROPBOX_CLIENTSECRET || fs.existsSync('/run/secrets/dropbox_clientID') && fs.existsSync('/run/secrets/dropbox_clientSecret')) ? { var dropbox = ((process.env.HMD_DROPBOX_CLIENTID && process.env.HMD_DROPBOX_CLIENTSECRET) || (fs.existsSync('/run/secrets/dropbox_clientID') && fs.existsSync('/run/secrets/dropbox_clientSecret'))) ? {
clientID: handleDockerSecret('dropbox_clientID') || process.env.HMD_DROPBOX_CLIENTID, clientID: handleDockerSecret('dropbox_clientID') || process.env.HMD_DROPBOX_CLIENTID,
clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_DROPBOX_CLIENTSECRET clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_DROPBOX_CLIENTSECRET
} : config.dropbox || false; } : (config.dropbox && config.dropbox.clientID && config.dropbox.clientSecret && config.dropbox) || false;
var google = (process.env.HMD_GOOGLE_CLIENTID && process.env.HMD_GOOGLE_CLIENTSECRET || fs.existsSync('/run/secrets/google_clientID') && fs.existsSync('/run/secrets/google_clientSecret')) ? { var google = ((process.env.HMD_GOOGLE_CLIENTID && process.env.HMD_GOOGLE_CLIENTSECRET)
clientID: process.env.HMD_GOOGLE_CLIENTID, || (fs.existsSync('/run/secrets/google_clientID') && fs.existsSync('/run/secrets/google_clientSecret'))) ? {
clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET clientID: handleDockerSecret('google_clientID') || process.env.HMD_GOOGLE_CLIENTID,
} : config.google || false; clientSecret: handleDockerSecret('google_clientSecret') || process.env.HMD_GOOGLE_CLIENTSECRET
var ldap = config.ldap || ( } : (config.google && config.google.clientID && config.google.clientSecret && config.google) || false;
var ldap = config.ldap || ((
process.env.HMD_LDAP_URL || process.env.HMD_LDAP_URL ||
process.env.HMD_LDAP_BINDDN || process.env.HMD_LDAP_BINDDN ||
process.env.HMD_LDAP_BINDCREDENTIALS || process.env.HMD_LDAP_BINDCREDENTIALS ||
@ -107,10 +117,9 @@ var ldap = config.ldap || (
process.env.HMD_LDAP_SEARCHBASE || process.env.HMD_LDAP_SEARCHBASE ||
process.env.HMD_LDAP_SEARCHFILTER || process.env.HMD_LDAP_SEARCHFILTER ||
process.env.HMD_LDAP_SEARCHATTRIBUTES || process.env.HMD_LDAP_SEARCHATTRIBUTES ||
process.env.HMD_LDAP_TLS_CA ||
process.env.HMD_LDAP_PROVIDERNAME process.env.HMD_LDAP_PROVIDERNAME
) || false; ) ? {} : false);
if (ldap == true)
ldap = {};
if (process.env.HMD_LDAP_URL) if (process.env.HMD_LDAP_URL)
ldap.url = process.env.HMD_LDAP_URL; ldap.url = process.env.HMD_LDAP_URL;
if (process.env.HMD_LDAP_BINDDN) if (process.env.HMD_LDAP_BINDDN)
@ -127,9 +136,17 @@ if (process.env.HMD_LDAP_SEARCHATTRIBUTES)
ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES; ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES;
if (process.env.HMD_LDAP_TLS_CA) { if (process.env.HMD_LDAP_TLS_CA) {
var ca = { var ca = {
ca: process.env.HMD_LDAP_TLS_CA ca: process.env.HMD_LDAP_TLS_CA.split(',')
}
ldap.tlsOptions = ldap.tlsOptions ? Object.assign(ldap.tlsOptions, ca) : ca;
if (Array.isArray(ldap.tlsOptions.ca) && ldap.tlsOptions.ca.length > 0) {
var i, len, results;
results = [];
for (i = 0, len = ldap.tlsOptions.ca.length; i < len; i++) {
results.push(fs.readFileSync(ldap.tlsOptions.ca[i], 'utf8'));
}
ldap.tlsOptions.ca = results;
} }
ldap.tlsOptions = ldap.tlsOptions ? Object.assign(ldap.tlsOptions, ca) : ca
} }
if (process.env.HMD_LDAP_PROVIDERNAME) { if (process.env.HMD_LDAP_PROVIDERNAME) {
ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME; ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME;
@ -169,6 +186,7 @@ module.exports = {
usecdn: usecdn, usecdn: usecdn,
allowanonymous: allowanonymous, allowanonymous: allowanonymous,
allowfreeurl: allowfreeurl, allowfreeurl: allowfreeurl,
defaultpermission: defaultpermission,
dburl: dburl, dburl: dburl,
db: db, db: db,
sslkeypath: path.join(cwd, sslkeypath), sslkeypath: path.join(cwd, sslkeypath),

View file

@ -1,7 +1,6 @@
//history //history
//external modules //external modules
var async = require('async'); var async = require('async');
var moment = require('moment');
//core //core
var config = require("./config.js"); var config = require("./config.js");
@ -14,45 +13,32 @@ var History = {
historyGet: historyGet, historyGet: historyGet,
historyPost: historyPost, historyPost: historyPost,
historyDelete: historyDelete, historyDelete: historyDelete,
isReady: isReady,
updateHistory: updateHistory updateHistory: updateHistory
}; };
var caches = {}; function getHistory(userid, callback) {
//update when the history is dirty models.User.findOne({
var updater = setInterval(function () { where: {
var deleted = []; id: userid
async.each(Object.keys(caches), function (key, callback) {
var cache = caches[key];
if (cache.isDirty) {
if (config.debug) logger.info("history updater found dirty history: " + key);
var history = parseHistoryToArray(cache.history);
cache.isDirty = false;
finishUpdateHistory(key, history, function (err, count) {
if (err) return callback(err, null);
if (!count) return callback(null, null);
cache.updateAt = Date.now();
return callback(null, null);
});
} else {
if (moment().isAfter(moment(cache.updateAt).add(5, 'minutes'))) {
deleted.push(key);
}
return callback(null, null);
} }
}, function (err) { }).then(function (user) {
if (err) return logger.error('history updater error', err); if (!user)
return callback(null, null);
var history = {};
if (user.history)
history = parseHistoryToObject(JSON.parse(user.history));
if (config.debug)
logger.info('read history success: ' + user.id);
return callback(null, history);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return callback(err, null);
}); });
// delete specified caches }
for (var i = 0, l = deleted.length; i < l; i++) {
caches[deleted[i]].history = {};
delete caches[deleted[i]];
}
}, 1000);
function finishUpdateHistory(userid, history, callback) { function setHistory(userid, history, callback) {
models.User.update({ models.User.update({
history: JSON.stringify(history) history: JSON.stringify(parseHistoryToArray(history))
}, { }, {
where: { where: {
id: userid id: userid
@ -60,72 +46,27 @@ function finishUpdateHistory(userid, history, callback) {
}).then(function (count) { }).then(function (count) {
return callback(null, count); return callback(null, count);
}).catch(function (err) { }).catch(function (err) {
logger.error('set history failed: ' + err);
return callback(err, null); return callback(err, null);
}); });
} }
function isReady() { function updateHistory(userid, noteId, document, time) {
var dirtyCount = 0;
async.each(Object.keys(caches), function (key, callback) {
if (caches[key].isDirty) dirtyCount++;
return callback(null, null);
}, function (err) {
if (err) return logger.error('history ready check error', err);
});
return dirtyCount > 0 ? false : true;
}
function getHistory(userid, callback) {
if (caches[userid]) {
return callback(null, caches[userid].history);
} else {
models.User.findOne({
where: {
id: userid
}
}).then(function (user) {
if (!user)
return callback(null, null);
var history = [];
if (user.history)
history = JSON.parse(user.history);
if (config.debug)
logger.info('read history success: ' + user.id);
setHistory(userid, history);
return callback(null, history);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return callback(err, null);
});
}
}
function setHistory(userid, history) {
if (Array.isArray(history)) history = parseHistoryToObject(history);
if (!caches[userid]) {
caches[userid] = {
history: {},
isDirty: false,
updateAt: Date.now()
};
}
caches[userid].history = history;
}
function updateHistory(userid, noteId, document) {
if (userid && noteId && typeof document !== 'undefined') { if (userid && noteId && typeof document !== 'undefined') {
getHistory(userid, function (err, history) { getHistory(userid, function (err, history) {
if (err || !history) return; if (err || !history) return;
if (!caches[userid].history[noteId]) { if (!history[noteId]) {
caches[userid].history[noteId] = {}; history[noteId] = {};
} }
var noteHistory = caches[userid].history[noteId]; var noteHistory = history[noteId];
var noteInfo = models.Note.parseNoteInfo(document); var noteInfo = models.Note.parseNoteInfo(document);
noteHistory.id = noteId; noteHistory.id = noteId;
noteHistory.text = noteInfo.title; noteHistory.text = noteInfo.title;
noteHistory.time = moment().valueOf(); noteHistory.time = time || Date.now();
noteHistory.tags = noteInfo.tags; noteHistory.tags = noteInfo.tags;
caches[userid].isDirty = true; setHistory(userid, history, function (err, count) {
return;
});
}); });
} }
} }
@ -175,9 +116,10 @@ function historyPost(req, res) {
return response.errorBadRequest(res); return response.errorBadRequest(res);
} }
if (Array.isArray(history)) { if (Array.isArray(history)) {
setHistory(req.user.id, history); setHistory(req.user.id, history, function (err, count) {
caches[req.user.id].isDirty = true; if (err) return response.errorInternalError(res);
res.end(); res.end();
});
} else { } else {
return response.errorBadRequest(res); return response.errorBadRequest(res);
} }
@ -186,11 +128,13 @@ function historyPost(req, res) {
getHistory(req.user.id, function (err, history) { getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res); if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res); if (!history) return response.errorNotFound(res);
if (!caches[req.user.id].history[noteId]) return response.errorNotFound(res); if (!history[noteId]) return response.errorNotFound(res);
if (req.body.pinned === 'true' || req.body.pinned === 'false') { if (req.body.pinned === 'true' || req.body.pinned === 'false') {
caches[req.user.id].history[noteId].pinned = (req.body.pinned === 'true'); history[noteId].pinned = (req.body.pinned === 'true');
caches[req.user.id].isDirty = true; setHistory(req.user.id, history, function (err, count) {
res.end(); if (err) return response.errorInternalError(res);
res.end();
});
} else { } else {
return response.errorBadRequest(res); return response.errorBadRequest(res);
} }
@ -205,16 +149,19 @@ function historyDelete(req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
var noteId = req.params.noteId; var noteId = req.params.noteId;
if (!noteId) { if (!noteId) {
setHistory(req.user.id, []); setHistory(req.user.id, [], function (err, count) {
caches[req.user.id].isDirty = true; if (err) return response.errorInternalError(res);
res.end(); res.end();
});
} else { } else {
getHistory(req.user.id, function (err, history) { getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res); if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res); if (!history) return response.errorNotFound(res);
delete caches[req.user.id].history[noteId]; delete history[noteId];
caches[req.user.id].isDirty = true; setHistory(req.user.id, history, function (err, count) {
res.end(); if (err) return response.errorInternalError(res);
res.end();
});
}); });
} }
} else { } else {

View file

@ -6,6 +6,6 @@ module.exports = {
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
queryInterface.removeColumn('Notes', 'deletedAt', Sequelize.DATE); queryInterface.removeColumn('Notes', 'deletedAt');
} }
}; };

View file

@ -7,7 +7,7 @@ module.exports = {
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
queryInterface.removeColumn('Users', 'email', Sequelize.TEXT); queryInterface.removeColumn('Users', 'email');
queryInterface.removeColumn('Users', 'password', Sequelize.TEXT); queryInterface.removeColumn('Users', 'password');
} }
}; };

View file

@ -513,10 +513,10 @@ module.exports = function (sequelize, DataTypes) {
} }
} }
} }
// if no permission specified and have owner then give editable permission, else default permission is freely // if no permission specified and have owner then give default permission in config, else default permission is freely
if (!note.permission) { if (!note.permission) {
if (note.ownerId) { if (note.ownerId) {
note.permission = "editable"; note.permission = config.defaultpermission;
} else { } else {
note.permission = "freely"; note.permission = "freely";
} }

View file

@ -79,39 +79,54 @@ module.exports = function (sequelize, DataTypes) {
if (profile) { if (profile) {
profile = { profile = {
name: profile.displayName || profile.username, name: profile.displayName || profile.username,
photo: User.parsePhotoByProfile(profile) photo: User.parsePhotoByProfile(profile),
biggerphoto: User.parsePhotoByProfile(profile, true)
} }
} }
return profile; return profile;
}, },
parsePhotoByProfile: function (profile) { parsePhotoByProfile: function (profile, bigger) {
var photo = null; var photo = null;
switch (profile.provider) { switch (profile.provider) {
case "facebook": case "facebook":
photo = 'https://graph.facebook.com/' + profile.id + '/picture?width=96'; photo = 'https://graph.facebook.com/' + profile.id + '/picture';
if (bigger) photo += '?width=400';
else photo += '?width=96';
break; break;
case "twitter": case "twitter":
photo = 'https://twitter.com/' + profile.username + '/profile_image?size=bigger'; photo = 'https://twitter.com/' + profile.username + '/profile_image';
if (bigger) photo += '?size=original';
else photo += '?size=bigger';
break; break;
case "github": case "github":
photo = 'https://avatars.githubusercontent.com/u/' + profile.id + '?s=96'; photo = 'https://avatars.githubusercontent.com/u/' + profile.id;
if (bigger) photo += '?s=400';
else photo += '?s=96';
break; break;
case "gitlab": case "gitlab":
photo = profile.avatarUrl.replace(/(\?s=)\d*$/i, '$196'); photo = profile.avatarUrl;
if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400');
else photo = photo.replace(/(\?s=)\d*$/i, '$196');
break; break;
case "dropbox": case "dropbox":
//no image api provided, use gravatar //no image api provided, use gravatar
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value) + '?s=96'; photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value);
if (bigger) photo += '?s=400';
else photo += '?s=96';
break; break;
case "google": case "google":
photo = profile.photos[0].value.replace(/(\?sz=)\d*$/i, '$196'); photo = profile.photos[0].value;
if (bigger) photo = photo.replace(/(\?sz=)\d*$/i, '$1400');
else photo = photo.replace(/(\?sz=)\d*$/i, '$196');
break; break;
case "ldap": case "ldap":
//no image api provided, //no image api provided,
//use gravatar if email exists, //use gravatar if email exists,
//otherwise generate a letter avatar //otherwise generate a letter avatar
if (profile.emails[0]) { if (profile.emails[0]) {
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0]) + '?s=96'; photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0]);
if (bigger) photo += '?s=400';
else photo += '?s=96';
} else { } else {
photo = letterAvatars(profile.username); photo = letterAvatars(profile.username);
} }
@ -123,7 +138,8 @@ module.exports = function (sequelize, DataTypes) {
var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email); var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email);
return { return {
name: email.substring(0, email.lastIndexOf("@")), name: email.substring(0, email.lastIndexOf("@")),
photo: photoUrl += '?s=96' photo: photoUrl += '?s=96',
biggerphoto: photoUrl += '?s=400'
}; };
} }
} }

View file

@ -122,6 +122,12 @@ function updateNote(note, callback) {
} }
}).then(function (_note) { }).then(function (_note) {
if (!_note) return callback(null, null); if (!_note) return callback(null, null);
// update user note history
var tempUsers = Object.assign({}, note.tempUsers);
note.tempUsers = {};
Object.keys(tempUsers).forEach(function (key) {
updateHistory(key, note, tempUsers[key]);
});
if (note.lastchangeuser) { if (note.lastchangeuser) {
if (_note.lastchangeuserId != note.lastchangeuser) { if (_note.lastchangeuserId != note.lastchangeuser) {
models.User.findOne({ models.User.findOne({
@ -348,9 +354,12 @@ function clearSocketQueue(queue, socket) {
} }
function connectNextSocket() { function connectNextSocket() {
isConnectionBusy = false; setTimeout(function () {
if (connectionSocketQueue.length > 0) isConnectionBusy = false;
startConnection(connectionSocketQueue[0]); if (connectionSocketQueue.length > 0) {
startConnection(connectionSocketQueue[0]);
}
}, 1);
} }
function interruptConnection(socket, note, user) { function interruptConnection(socket, note, user) {
@ -405,10 +414,7 @@ function finishConnection(socket, note, user) {
note.server.setColor(socket, user.color); note.server.setColor(socket, user.color);
// update user note history // update user note history
setTimeout(function () { updateHistory(user.userid, note);
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id);
if (note.server) history.updateHistory(user.userid, noteId, note.server.document);
}, 0);
emitOnlineUsers(socket); emitOnlineUsers(socket);
emitRefresh(socket); emitRefresh(socket);
@ -497,6 +503,7 @@ function startConnection(socket) {
lastchangeuserprofile: lastchangeuserprofile, lastchangeuserprofile: lastchangeuserprofile,
socks: [], socks: [],
users: {}, users: {},
tempUsers: {},
createtime: moment(createtime).valueOf(), createtime: moment(createtime).valueOf(),
updatetime: moment(updatetime).valueOf(), updatetime: moment(updatetime).valueOf(),
server: server, server: server,
@ -687,15 +694,17 @@ function operationCallback(socket, operation) {
return logger.error('operation callback failed: ' + err); return logger.error('operation callback failed: ' + err);
}); });
} }
// update user note history note.tempUsers[userId] = Date.now();
setTimeout(function() {
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id);
if (note.server) history.updateHistory(userId, noteId, note.server.document);
}, 0);
} }
// save authorship // save authorship - use timer here because it's an O(n) complexity algorithm
note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship); setImmediate(function () {
note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
});
}
function updateHistory(userId, note, time) {
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id);
if (note.server) history.updateHistory(userId, noteId, note.server.document, time);
} }
function connection(socket) { function connection(socket) {
@ -925,4 +934,4 @@ function connection(socket) {
}); });
} }
module.exports = realtime; module.exports = realtime;

View file

@ -47,7 +47,7 @@
"View": "Anzeigen", "View": "Anzeigen",
"Both": "Beides", "Both": "Beides",
"Help": "Hilfe", "Help": "Hilfe",
"Upload Image": "Foto hochloaden", "Upload Image": "Foto hochladen",
"Menu": "Menü", "Menu": "Menü",
"This page need refresh": "Bitte laden Sie die Seite neu", "This page need refresh": "Bitte laden Sie die Seite neu",
"You have an incompatible client version.": "Ihre Client Version ist nicht mit dem Server kompatibel", "You have an incompatible client version.": "Ihre Client Version ist nicht mit dem Server kompatibel",

View file

@ -1,7 +1,7 @@
{ {
"Collaborative markdown notes": "Συνεργατικές σημειώσεις markdown", "Collaborative markdown notes": "Συνεργατικές σημειώσεις markdown",
"Realtime collaborative markdown notes on all platforms.": "Συνεργατική σημειώσεις markdown σε όλες τις πλατφόρμες σε πραγματικό χρόνο.", "Realtime collaborative markdown notes on all platforms.": "Συνεργατική σημειώσεις markdown σε όλες τις πλατφόρμες σε πραγματικό χρόνο.",
"Best way to write and share your knowledge in markdown.": "Ο καλύτερος τρόπς να γράψεις και να μοιρατσείς την γνώση σου in markdown.", "Best way to write and share your knowledge in markdown.": "Ο καλύτερος τρόπος να γράψεις και να μοιραστείς την γνώση σου σε markdown.",
"Intro": "Εισαγωγή", "Intro": "Εισαγωγή",
"History": "Ιστορία", "History": "Ιστορία",
"New guest note": "Νέα σημείωση επισκέπτη", "New guest note": "Νέα σημείωση επισκέπτη",
@ -54,9 +54,9 @@
"Refresh to update.": "Ανανεώστε για ενημέρωση", "Refresh to update.": "Ανανεώστε για ενημέρωση",
"New version available!": "Νέα διαθέσιμη έκδοση ", "New version available!": "Νέα διαθέσιμη έκδοση ",
"See releases notes here": "Δείτε τις κυκλοφορίες της σημείωσης εδώ", "See releases notes here": "Δείτε τις κυκλοφορίες της σημείωσης εδώ",
"Refresh to enjoy new features.": "Ανανεώστε για να δείτε τις κανούργίες λειτουργίες", "Refresh to enjoy new features.": "Ανανεώστε για να δείτε τις κανούργιες λειτουργίες",
"Your user state has changed.": "Η κατάσταση χρήστη έχει αλλάξει.", "Your user state has changed.": "Η κατάσταση χρήστη έχει αλλάξει.",
"Refresh to load new user state.": "Ανανεώστε για να φορτωσετε την νέα κατάσταση χρήστη.", "Refresh to load new user state.": "Ανανεώστε για να φορτώσετε την νέα κατάσταση χρήστη.",
"Refresh": "Ανανέωση", "Refresh": "Ανανέωση",
"Contacts": "Επαφές", "Contacts": "Επαφές",
"Report an issue": "Αναφέρετε ένα θέμα", "Report an issue": "Αναφέρετε ένα θέμα",
@ -73,32 +73,32 @@
"Ordered List": "Αριθμημένη λίστα", "Ordered List": "Αριθμημένη λίστα",
"Todo List": "Todo List", "Todo List": "Todo List",
"Blockquote": "Παράγραφος", "Blockquote": "Παράγραφος",
"Bold font": "Εντονη γραμματόσειρά", "Bold font": "Εντονη γραμματοσειρά",
"Italics font": "Italics γραμματόσειρά", "Italics font": "Πλάγια γραμματοσειρά",
"Strikethrough": "Διακριτή διαγραφή", "Strikethrough": "Διαγραμένη γραμματοσειρά",
"Inserted text": "Εισαγμένο κείμενο", "Inserted text": "Εισαγμένο κείμενο",
"Marked text": "Επιλεγμένο κέιμενο", "Marked text": "Επιλεγμένο κείμενο",
"Link": "Σύνδεσμος", "Link": "Σύνδεσμος",
"Image": "Εικόνα", "Image": "Εικόνα",
"Code": "Κώδικας", "Code": "Κώδικας",
"Externals": "Εξωτερικά", "Externals": "Εξωτερικά",
"This is a alert area.": "Αυτή είνια μια περιοχή ειδοποίησης", "This is a alert area.": "Αυτή είναι μια περιοχή ειδοποίησης",
"Revert": "Επαναστροφή", "Revert": "Επαναστροφή",
"Import from clipboard": "Εισαγωγή από πρόχειρο", "Import from clipboard": "Εισαγωγή από πρόχειρο",
"Paste your markdown or webpage here...": "Επικολλήστε το markdown ή την ιστοσελίδα σας εδώ...", "Paste your markdown or webpage here...": "Επικολλήστε markdown ή την ιστοσελίδα σας εδώ...",
"Clear": "Καθαρισμός", "Clear": "Καθαρισμός",
"This note is locked": "Η σημείωση είναι κλειδωμένη", "This note is locked": "Η σημείωση είναι κλειδωμένη",
"Sorry, only owner can edit this note.": "Συγνώμη, μόνο ο ιδικτήτης μπορεί να επεξεργαστεί αυτη την σημείωση.", "Sorry, only owner can edit this note.": "Συγνώμη, μόνο ο ιδιοκτήτης μπορεί να επεξεργαστεί αυτη την σημείωση.",
"OK": "Εντάξει", "OK": "Εντάξει",
"Reach the limit": "Φτάσατε το όριο", "Reach the limit": "Φτάσατε το όριο",
"Sorry, you've reached the max length this note can be.": "Συγνώμη, φτάσατε το μέγιστο μέγεθος αυτής της σημείωσης.", "Sorry, you've reached the max length this note can be.": "Συγνώμη, φτάσατε το μέγιστο μέγεθος αυτής της σημείωσης.",
"Please reduce the content or divide it to more notes, thank you!": "Παρακαλώ μειώστε το περιεχόμενο η διαιρεστε το σε περισσότερες σημειώσεις, ευχαριστώ!", "Please reduce the content or divide it to more notes, thank you!": "Παρακαλώ μειώστε το περιεχόμενο η διαιρέστε το σε περισσότερες σημειώσεις, ευχαριστώ!",
"Import from Gist": "Εισαγωγή από Gist", "Import from Gist": "Εισαγωγή από Gist",
"Paste your gist url here...": "Κάντε επικκόληση του gist url εδώ...", "Paste your gist url here...": "Κάντε επικκόληση του gist url εδώ...",
"Import from Snippet": "Εισαγωγή απο Snippet", "Import from Snippet": "Εισαγωγή από Snippet",
"Select From Available Projects": "Eπιλογή από διαθέσιμα Projects", "Select From Available Projects": "Eπιλογή από διαθέσιμα Projects",
"Select From Available Snippets": "Eπιλογή από διαθέσιμα Snippets", "Select From Available Snippets": "Eπιλογή από διαθέσιμα Snippets",
"OR": "Ή", "OR": "Ή",
"Export to Snippet": "Eξαγωγή σε Snippet", "Export to Snippet": "Eξαγωγή σε Snippet",
"Select Visibility Level": "Επιλέξτε επίπεδο ορατότητας" "Select Visibility Level": "Επιλέξτε επίπεδο ορατότητας"
} }

View file

@ -5,8 +5,10 @@
"main": "app.js", "main": "app.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build:dev": "webpack --config webpack.config.js --progress --colors --watch", "test": "npm run-script lint",
"build:prod": "webpack --config webpack.production.js --progress --colors", "lint": "eslint .",
"dev": "webpack --config webpack.config.js --progress --colors --watch",
"build": "webpack --config webpack.production.js --progress --colors",
"postinstall": "bin/heroku", "postinstall": "bin/heroku",
"start": "node app.js" "start": "node app.js"
}, },
@ -71,7 +73,7 @@
"markdown-it-sup": "^1.0.0", "markdown-it-sup": "^1.0.0",
"markdown-pdf": "^7.0.0", "markdown-pdf": "^7.0.0",
"mathjax": "~2.7.0", "mathjax": "~2.7.0",
"mermaid": "~6.0.0", "mermaid": "~7.0.0",
"meta-marked": "^0.4.2", "meta-marked": "^0.4.2",
"method-override": "^2.3.7", "method-override": "^2.3.7",
"moment": "^2.17.1", "moment": "^2.17.1",
@ -115,12 +117,12 @@
"validator": "^6.2.0", "validator": "^6.2.0",
"velocity-animate": "^1.4.0", "velocity-animate": "^1.4.0",
"visibilityjs": "^1.2.4", "visibilityjs": "^1.2.4",
"viz.js": "^1.4.1", "viz.js": "^1.7.0",
"winston": "^2.3.0", "winston": "^2.3.0",
"xss": "^0.3.3" "xss": "^0.3.3"
}, },
"engines": { "engines": {
"node": ">=4.x" "node": ">=6.x"
}, },
"bugs": "https://github.com/hackmdio/hackmd/issues", "bugs": "https://github.com/hackmdio/hackmd/issues",
"keywords": [ "keywords": [
@ -140,9 +142,17 @@
"url": "https://github.com/hackmdio/hackmd.git" "url": "https://github.com/hackmdio/hackmd.git"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.22.0",
"babel-preset-es2015": "^6.18.0",
"babel-runtime": "^6.20.0",
"copy-webpack-plugin": "^4.0.1", "copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.26.1", "css-loader": "^0.26.1",
"ejs-loader": "^0.3.0", "ejs-loader": "^0.3.0",
"eslint": "^3.15.0",
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",
"expose-loader": "^0.7.1", "expose-loader": "^0.7.1",
"extract-text-webpack-plugin": "^1.0.1", "extract-text-webpack-plugin": "^1.0.1",
@ -156,6 +166,7 @@
"script-loader": "^0.7.0", "script-loader": "^0.7.0",
"style-loader": "^0.13.1", "style-loader": "^0.13.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.14.0" "webpack": "^1.14.0",
"webpack-parallel-uglify-plugin": "^0.2.0"
} }
} }

View file

@ -354,6 +354,12 @@ input {
color: white; color: white;
} }
.screenshot {
margin: 30px auto;
width: 100%;
border-radius: 3px;
}
select { select {
color: black; color: black;
} }

View file

@ -49,7 +49,9 @@ There are four possible options:
<i class="fa fa-leaf fa-fw"></i> **Freely**: Anyone can edit this note. <i class="fa fa-leaf fa-fw"></i> **Freely**: Anyone can edit this note.
<i class="fa fa-pencil fa-fw"></i> **Editable**: A signed-in user can edit this note. <i class="fa fa-pencil fa-fw"></i> **Editable**: A signed-in user can edit this note.
<i class="fa fa-lock fa-fw"></i> **Locked**: Only the owner can edit this note. <i class="fa fa-id-card fa-fw"></i> **Limited**: People have to sign-in to view and edit this note.
<i class="fa fa-lock fa-fw"></i> **Locked**: Anyone can view this note but only the owner can edit it.
<i class="fa fa-umbrella fa-fw"></i> **Protected**: People have to sign-in to view this note but only owner can edit.
<i class="fa fa-hand-stop-o fa-fw"></i> **Private**: Only the owner can view and edit this note. <i class="fa fa-hand-stop-o fa-fw"></i> **Private**: Only the owner can view and edit this note.
**Only the owner of the note can change the note's permissions.** **Only the owner of the note can change the note's permissions.**

View file

@ -1,118 +0,0 @@
var config = require('./config');
var domain = config.domain; // domain name
var urlpath = config.urlpath; // sub url path, like: www.example.com/<urlpath>
var debug = config.debug;
var GOOGLE_API_KEY = config.GOOGLE_API_KEY;
var GOOGLE_CLIENT_ID = config.GOOGLE_CLIENT_ID;
var DROPBOX_APP_KEY = config.DROPBOX_APP_KEY;
//common
var port = window.location.port;
window.serverurl = window.location.protocol + '//' + (domain ? domain : window.location.hostname) + (port ? ':' + port : '') + (urlpath ? '/' + urlpath : '');
var noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
var noteurl = serverurl + '/' + noteid;
var version = '0.5.0';
var checkAuth = false;
var profile = null;
var lastLoginState = getLoginState();
var lastUserId = getUserId();
window.loginStateChangeEvent = null;
function resetCheckAuth() {
checkAuth = false;
}
function setLoginState(bool, id) {
Cookies.set('loginstate', bool, {
expires: 365
});
if (id) {
Cookies.set('userid', id, {
expires: 365
});
} else {
Cookies.remove('userid');
}
lastLoginState = bool;
lastUserId = id;
checkLoginStateChanged();
}
function checkLoginStateChanged() {
if (getLoginState() != lastLoginState || getUserId() != lastUserId) {
if(loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100);
return true;
} else {
return false;
}
}
function getLoginState() {
var state = Cookies.get('loginstate');
return state === "true" || state === true;
}
function getUserId() {
return Cookies.get('userid');
}
function clearLoginState() {
Cookies.remove('loginstate');
}
function checkIfAuth(yesCallback, noCallback) {
var cookieLoginState = getLoginState();
if (checkLoginStateChanged()) checkAuth = false;
if (!checkAuth || typeof cookieLoginState == 'undefined') {
$.get(serverurl + '/me')
.done(function (data) {
if (data && data.status == 'ok') {
profile = data;
yesCallback(profile);
setLoginState(true, data.id);
} else {
noCallback();
setLoginState(false);
}
})
.fail(function () {
noCallback();
})
.always(function () {
checkAuth = true;
});
} else if (cookieLoginState) {
yesCallback(profile);
} else {
noCallback();
}
}
module.exports = {
domain: domain,
urlpath: urlpath,
debug: debug,
GOOGLE_API_KEY: GOOGLE_API_KEY,
GOOGLE_CLIENT_ID: GOOGLE_CLIENT_ID,
DROPBOX_APP_KEY: DROPBOX_APP_KEY,
port: port,
noteid: noteid,
noteurl: noteurl,
version: version,
checkAuth: checkAuth,
profile: profile,
lastLoginState: lastLoginState,
lastUserId: lastUserId,
/* export functions */
resetCheckAuth: resetCheckAuth,
setLoginState: setLoginState,
checkLoginStateChanged: checkLoginStateChanged,
getLoginState: getLoginState,
getUserId: getUserId,
clearLoginState: clearLoginState,
checkIfAuth: checkIfAuth
};

View file

@ -1,11 +0,0 @@
module.exports = {
domain: '', // domain name
urlpath: '', // sub url path, like: www.example.com/<urlpath>
// settings
debug: false,
GOOGLE_API_KEY: '',
GOOGLE_CLIENT_ID: '',
DROPBOX_APP_KEY: ''
};

View file

@ -3,37 +3,39 @@ require('./locale');
require('../css/cover.css'); require('../css/cover.css');
require('../css/site.css'); require('../css/site.css');
var common = require('./common'); import {
var checkIfAuth = common.checkIfAuth; checkIfAuth,
var urlpath = common.urlpath; clearLoginState,
var resetCheckAuth = common.resetCheckAuth; getLoginState,
var getLoginState = common.getLoginState; resetCheckAuth,
var clearLoginState = common.clearLoginState; setloginStateChangeEvent
} from './lib/common/login';
var historyModule = require('./history'); import {
var parseStorageToHistory = historyModule.parseStorageToHistory; clearDuplicatedHistory,
var parseHistory = historyModule.parseHistory; deleteServerHistory,
var getStorageHistory = historyModule.getStorageHistory; getHistory,
var getHistory = historyModule.getHistory; getStorageHistory,
var saveHistory = historyModule.saveHistory; parseHistory,
var removeHistory = historyModule.removeHistory; parseServerToHistory,
var postHistoryToServer = historyModule.postHistoryToServer; parseStorageToHistory,
var deleteServerHistory = historyModule.deleteServerHistory; postHistoryToServer,
var parseServerToHistory = historyModule.parseServerToHistory; removeHistory,
var saveStorageHistoryToServer = historyModule.saveStorageHistoryToServer; saveHistory,
var clearDuplicatedHistory = historyModule.clearDuplicatedHistory; saveStorageHistoryToServer
} from './history';
var saveAs = require('file-saver').saveAs; import { saveAs } from 'file-saver';
var List = require('list.js'); import List from 'list.js';
var S = require('string'); import S from 'string';
var 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">\
<span class="id" style="display:none;"></span>\ <span class="id" style="display:none;"></span>\
<a href="#">\ <a href="#">\
<div class="item">\ <div class="item">\
<div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>\ <div class="ui-history-pin fa fa-thumb-tack fa-fw"></div>\
<div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\ <div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\
<div class="content">\ <div class="content">\
<h4 class="text"></h4>\ <h4 class="text"></h4>\
@ -55,15 +57,16 @@ var options = {
}) })
] ]
}; };
var historyList = new List('history', options); const historyList = new List('history', options);
migrateHistoryFromTempCallback = pageInit; migrateHistoryFromTempCallback = pageInit;
loginStateChangeEvent = pageInit; setloginStateChangeEvent(pageInit);
pageInit(); pageInit();
function pageInit() { function pageInit() {
checkIfAuth( checkIfAuth(
function (data) { data => {
$('.ui-signin').hide(); $('.ui-signin').hide();
$('.ui-or').hide(); $('.ui-or').hide();
$('.ui-welcome').show(); $('.ui-welcome').show();
@ -74,7 +77,7 @@ function pageInit() {
$(".ui-history").click(); $(".ui-history").click();
parseServerToHistory(historyList, parseHistoryCallback); parseServerToHistory(historyList, parseHistoryCallback);
}, },
function () { () => {
$('.ui-signin').show(); $('.ui-signin').show();
$('.ui-or').show(); $('.ui-or').show();
$('.ui-welcome').hide(); $('.ui-welcome').hide();
@ -103,7 +106,7 @@ $(".ui-home").click(function (e) {
} }
}); });
$(".ui-history").click(function (e) { $(".ui-history").click(() => {
if (!$("#history").is(':visible')) { if (!$("#history").is(':visible')) {
$(".section:visible").hide(); $(".section:visible").hide();
$("#history").fadeIn(); $("#history").fadeIn();
@ -118,7 +121,7 @@ function checkHistoryList() {
} else if ($("#history-list").children().length == 0) { } else if ($("#history-list").children().length == 0) {
$('.pagination').hide(); $('.pagination').hide();
$(".ui-nohistory").slideDown(); $(".ui-nohistory").slideDown();
getStorageHistory(function (data) { getStorageHistory(data => {
if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) { if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) {
$(".ui-import-from-browser").slideDown(); $(".ui-import-from-browser").slideDown();
} }
@ -128,35 +131,35 @@ 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: function (a, b) { sortFunction(a, b) {
var notea = a.values(); const notea = a.values();
var noteb = b.values(); const noteb = b.values();
if (notea.pinned && !noteb.pinned) { if (notea.pinned && !noteb.pinned) {
return -1; return -1;
} else if (!notea.pinned && noteb.pinned) { } else if (!notea.pinned && noteb.pinned) {
return 1; return 1;
} else { } else {
if (notea.timestamp > noteb.timestamp) { if (notea.timestamp > noteb.timestamp) {
return -1; return -1;
} else if (notea.timestamp < noteb.timestamp) { } else if (notea.timestamp < noteb.timestamp) {
return 1; return 1;
} else { } else {
return 0; return 0;
} }
} }
} }
}); });
// parse filter tags // parse filter tags
var filtertags = []; const filtertags = [];
for (var i = 0, l = list.items.length; i < l; i++) { for (let i = 0, l = list.items.length; i < l; i++) {
var tags = list.items[i]._values.tags; const tags = list.items[i]._values.tags;
if (tags && tags.length > 0) { if (tags && tags.length > 0) {
for (var 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
var found = false; let found = false;
if (filtertags.indexOf(tags[j]) != -1) if (filtertags.includes(tags[j]))
found = true; found = true;
if (!found) if (!found)
filtertags.push(tags[j]); filtertags.push(tags[j]);
@ -167,17 +170,17 @@ function parseHistoryCallback(list, notehistory) {
} }
// update items whenever list updated // update items whenever list updated
historyList.on('updated', function (e) { historyList.on('updated', e => {
for (var i = 0, l = e.items.length; i < l; i++) { for (let i = 0, l = e.items.length; i < l; i++) {
var item = e.items[i]; const item = e.items[i];
if (item.visible()) { if (item.visible()) {
var itemEl = $(item.elm); const itemEl = $(item.elm);
var values = item._values; const values = item._values;
var a = itemEl.find("a"); const a = itemEl.find("a");
var pin = itemEl.find(".ui-history-pin"); const pin = itemEl.find(".ui-history-pin");
var 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');
@ -185,12 +188,12 @@ historyList.on('updated', function (e) {
pin.removeClass('active'); pin.removeClass('active');
} }
//parse tags //parse tags
var 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) {
var labels = []; const labels = [];
for (var 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(' '));
} }
@ -204,21 +207,21 @@ historyList.on('updated', function (e) {
function historyCloseClick(e) { function historyCloseClick(e) {
e.preventDefault(); e.preventDefault();
var id = $(this).closest("a").siblings("span").html(); const id = $(this).closest("a").siblings("span").html();
var value = historyList.get('id', id)[0]._values; const value = historyList.get('id', id)[0]._values;
$('.ui-delete-modal-msg').text('Do you really want to delete below history?'); $('.ui-delete-modal-msg').text('Do you really want to delete below history?');
$('.ui-delete-modal-item').html('<i class="fa fa-file-text"></i> ' + value.text + '<br><i class="fa fa-clock-o"></i> ' + value.time); $('.ui-delete-modal-item').html(`<i class="fa fa-file-text"></i> ${value.text}<br><i class="fa fa-clock-o"></i> ${value.time}`);
clearHistory = false; clearHistory = false;
deleteId = id; deleteId = id;
} }
function historyPinClick(e) { function historyPinClick(e) {
e.preventDefault(); e.preventDefault();
var $this = $(this); const $this = $(this);
var id = $this.closest("a").siblings("span").html(); const id = $this.closest("a").siblings("span").html();
var item = historyList.get('id', id)[0]; const item = historyList.get('id', id)[0];
var values = item._values; const values = item._values;
var pinned = values.pinned; let pinned = values.pinned;
if (!values.pinned) { if (!values.pinned) {
pinned = true; pinned = true;
item._values.pinned = true; item._values.pinned = true;
@ -226,10 +229,10 @@ function historyPinClick(e) {
pinned = false; pinned = false;
item._values.pinned = false; item._values.pinned = false;
} }
checkIfAuth(function () { checkIfAuth(() => {
postHistoryToServer(id, { postHistoryToServer(id, {
pinned: pinned pinned
}, function (err, result) { }, (err, result) => {
if (!err) { if (!err) {
if (pinned) if (pinned)
$this.addClass('active'); $this.addClass('active');
@ -237,9 +240,9 @@ function historyPinClick(e) {
$this.removeClass('active'); $this.removeClass('active');
} }
}); });
}, function () { }, () => {
getHistory(function (notehistory) { getHistory(notehistory => {
for(var i = 0; i < notehistory.length; i++) { for(let i = 0; i < notehistory.length; i++) {
if (notehistory[i].id == id) { if (notehistory[i].id == id) {
notehistory[i].pinned = pinned; notehistory[i].pinned = pinned;
break; break;
@ -258,10 +261,10 @@ function historyPinClick(e) {
setInterval(updateItemFromNow, 60000); setInterval(updateItemFromNow, 60000);
function updateItemFromNow() { function updateItemFromNow() {
var items = $('.item').toArray(); const items = $('.item').toArray();
for (var i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
var item = $(items[i]); const item = $(items[i]);
var timestamp = parseInt(item.find('.timestamp').text()); const timestamp = parseInt(item.find('.timestamp').text());
item.find('.fromNow').text(moment(timestamp).fromNow()); item.find('.fromNow').text(moment(timestamp).fromNow());
} }
} }
@ -270,8 +273,8 @@ var clearHistory = false;
var deleteId = null; var deleteId = null;
function deleteHistory() { function deleteHistory() {
checkIfAuth(function () { checkIfAuth(() => {
deleteServerHistory(deleteId, function (err, result) { deleteServerHistory(deleteId, (err, result) => {
if (!err) { if (!err) {
if (clearHistory) { if (clearHistory) {
historyList.clear(); historyList.clear();
@ -285,7 +288,7 @@ function deleteHistory() {
deleteId = null; deleteId = null;
clearHistory = false; clearHistory = false;
}); });
}, function () { }, () => {
if (clearHistory) { if (clearHistory) {
saveHistory([]); saveHistory([]);
historyList.clear(); historyList.clear();
@ -293,8 +296,8 @@ function deleteHistory() {
deleteId = null; deleteId = null;
} else { } else {
if (!deleteId) return; if (!deleteId) return;
getHistory(function (notehistory) { getHistory(notehistory => {
var newnotehistory = removeHistory(deleteId, notehistory); const newnotehistory = removeHistory(deleteId, notehistory);
saveHistory(newnotehistory); saveHistory(newnotehistory);
historyList.remove('id', deleteId); historyList.remove('id', deleteId);
checkHistoryList(); checkHistoryList();
@ -306,36 +309,36 @@ function deleteHistory() {
}); });
} }
$(".ui-delete-modal-confirm").click(function () { $(".ui-delete-modal-confirm").click(() => {
deleteHistory(); deleteHistory();
}); });
$(".ui-import-from-browser").click(function () { $(".ui-import-from-browser").click(() => {
saveStorageHistoryToServer(function () { saveStorageHistoryToServer(() => {
parseStorageToHistory(historyList, parseHistoryCallback); parseStorageToHistory(historyList, parseHistoryCallback);
}); });
}); });
$(".ui-save-history").click(function () { $(".ui-save-history").click(() => {
getHistory(function (data) { getHistory(data => {
var history = JSON.stringify(data); const history = JSON.stringify(data);
var blob = new Blob([history], { const blob = new Blob([history], {
type: "application/json;charset=utf-8" type: "application/json;charset=utf-8"
}); });
saveAs(blob, 'hackmd_history_' + moment().format('YYYYMMDDHHmmss')); saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true);
}); });
}); });
$(".ui-open-history").bind("change", function (e) { $(".ui-open-history").bind("change", e => {
var files = e.target.files || e.dataTransfer.files; const files = e.target.files || e.dataTransfer.files;
var file = files[0]; const file = files[0];
var reader = new FileReader(); const reader = new FileReader();
reader.onload = function () { reader.onload = () => {
var 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(function (data) { getHistory(data => {
var mergedata = data.concat(notehistory); let mergedata = data.concat(notehistory);
mergedata = clearDuplicatedHistory(mergedata); mergedata = clearDuplicatedHistory(mergedata);
saveHistory(mergedata); saveHistory(mergedata);
parseHistory(historyList, parseHistoryCallback); parseHistory(historyList, parseHistoryCallback);
@ -345,18 +348,18 @@ $(".ui-open-history").bind("change", function (e) {
reader.readAsText(file); reader.readAsText(file);
}); });
$(".ui-clear-history").click(function () { $(".ui-clear-history").click(() => {
$('.ui-delete-modal-msg').text('Do you really want to clear all history?'); $('.ui-delete-modal-msg').text('Do you really want to clear all history?');
$('.ui-delete-modal-item').html('There is no turning back.'); $('.ui-delete-modal-item').html('There is no turning back.');
clearHistory = true; clearHistory = true;
deleteId = null; deleteId = null;
}); });
$(".ui-refresh-history").click(function () { $(".ui-refresh-history").click(() => {
var lastTags = $(".ui-use-tags").select2('val'); const lastTags = $(".ui-use-tags").select2('val');
$(".ui-use-tags").select2('val', ''); $(".ui-use-tags").select2('val', '');
historyList.filter(); historyList.filter();
var lastKeyword = $('.search').val(); const lastKeyword = $('.search').val();
$('.search').val(''); $('.search').val('');
historyList.search(); historyList.search();
$('#history-list').slideUp('fast'); $('#history-list').slideUp('fast');
@ -364,7 +367,7 @@ $(".ui-refresh-history").click(function () {
resetCheckAuth(); resetCheckAuth();
historyList.clear(); historyList.clear();
parseHistory(historyList, function (list, notehistory) { parseHistory(historyList, (list, notehistory) => {
parseHistoryCallback(list, notehistory); parseHistoryCallback(list, notehistory);
$(".ui-use-tags").select2('val', lastTags); $(".ui-use-tags").select2('val', lastTags);
$(".ui-use-tags").trigger('change'); $(".ui-use-tags").trigger('change');
@ -375,16 +378,16 @@ $(".ui-refresh-history").click(function () {
}); });
}); });
$(".ui-logout").click(function () { $(".ui-logout").click(() => {
clearLoginState(); clearLoginState();
location.href = serverurl + '/logout'; location.href = `${serverurl}/logout`;
}); });
var filtertags = []; let filtertags = [];
$(".ui-use-tags").select2({ $(".ui-use-tags").select2({
placeholder: $(".ui-use-tags").attr('placeholder'), placeholder: $(".ui-use-tags").attr('placeholder'),
multiple: true, multiple: true,
data: function () { data() {
return { return {
results: filtertags results: filtertags
}; };
@ -394,7 +397,7 @@ $('.select2-input').css('width', 'inherit');
buildTagsFilter([]); buildTagsFilter([]);
function buildTagsFilter(tags) { function buildTagsFilter(tags) {
for (var i = 0; i < tags.length; i++) for (let i = 0; i < tags.length; i++)
tags[i] = { tags[i] = {
id: i, id: i,
text: S(tags[i]).unescapeHTML().s text: S(tags[i]).unescapeHTML().s
@ -402,17 +405,17 @@ function buildTagsFilter(tags) {
filtertags = tags; filtertags = tags;
} }
$(".ui-use-tags").on('change', function () { $(".ui-use-tags").on('change', function () {
var tags = []; const tags = [];
var data = $(this).select2('data'); const data = $(this).select2('data');
for (var i = 0; i < data.length; i++) for (let i = 0; i < data.length; i++)
tags.push(data[i].text); tags.push(data[i].text);
if (tags.length > 0) { if (tags.length > 0) {
historyList.filter(function (item) { historyList.filter(item => {
var values = item.values(); const values = item.values();
if (!values.tags) return false; if (!values.tags) return false;
var found = false; let found = false;
for (var i = 0; i < tags.length; i++) { for (let i = 0; i < tags.length; i++) {
if (values.tags.indexOf(tags[i]) != -1) { if (values.tags.includes(tags[i])) {
found = true; found = true;
break; break;
} }
@ -425,6 +428,6 @@ $(".ui-use-tags").on('change', function () {
checkHistoryList(); checkHistoryList();
}); });
$('.search').keyup(function () { $('.search').keyup(() => {
checkHistoryList(); checkHistoryList();
}); });

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,7 @@
var view = new google.picker.DocsView(); var view = new google.picker.DocsView();
view.setMimeTypes("text/markdown,text/html"); view.setMimeTypes("text/markdown,text/html");
view.setIncludeFolders(true); view.setIncludeFolders(true);
view.setOwnedByMe(true);
this.picker = new google.picker.PickerBuilder(). this.picker = new google.picker.PickerBuilder().
enableFeature(google.picker.Feature.NAV_HIDDEN). enableFeature(google.picker.Feature.NAV_HIDDEN).
addView(view). addView(view).
@ -115,4 +116,4 @@
}, callback ? callback : function() {}); }, callback ? callback : function() {});
} }
}; };
}()); }());

View file

@ -1,10 +1,13 @@
var store = require('store'); import store from 'store';
var S = require('string'); import S from 'string';
var common = require('./common'); import {
var checkIfAuth = common.checkIfAuth; checkIfAuth
var urlpath = common.urlpath; } from './lib/common/login';
var getLoginState = common.getLoginState;
import {
urlpath
} from './lib/config';
window.migrateHistoryFromTempCallback = null; window.migrateHistoryFromTempCallback = null;
@ -12,22 +15,22 @@ migrateHistoryFromTemp();
function migrateHistoryFromTemp() { function migrateHistoryFromTemp() {
if (url('#tempid')) { if (url('#tempid')) {
$.get(serverurl + '/temp', { $.get(`${serverurl}/temp`, {
tempid: url('#tempid') tempid: url('#tempid')
}) })
.done(function (data) { .done(data => {
if (data && data.temp) { if (data && data.temp) {
getStorageHistory(function (olddata) { getStorageHistory(olddata => {
if (!olddata || olddata.length == 0) { if (!olddata || olddata.length == 0) {
saveHistoryToStorage(JSON.parse(data.temp)); saveHistoryToStorage(JSON.parse(data.temp));
} }
}); });
} }
}) })
.always(function () { .always(() => {
var hash = location.hash.split('#')[1]; let hash = location.hash.split('#')[1];
hash = hash.split('&'); hash = hash.split('&');
for (var i = 0; i < hash.length; i++) for (let i = 0; i < hash.length; i++)
if (hash[i].indexOf('tempid') == 0) { if (hash[i].indexOf('tempid') == 0) {
hash.splice(i, 1); hash.splice(i, 1);
i--; i--;
@ -40,12 +43,12 @@ function migrateHistoryFromTemp() {
} }
} }
function saveHistory(notehistory) { export function saveHistory(notehistory) {
checkIfAuth( checkIfAuth(
function () { () => {
saveHistoryToServer(notehistory); saveHistoryToServer(notehistory);
}, },
function () { () => {
saveHistoryToStorage(notehistory); saveHistoryToStorage(notehistory);
} }
); );
@ -65,7 +68,7 @@ function saveHistoryToCookie(notehistory) {
} }
function saveHistoryToServer(notehistory) { function saveHistoryToServer(notehistory) {
$.post(serverurl + '/history', { $.post(`${serverurl}/history`, {
history: JSON.stringify(notehistory) history: JSON.stringify(notehistory)
}); });
} }
@ -75,37 +78,37 @@ function saveCookieHistoryToStorage(callback) {
callback(); callback();
} }
function saveStorageHistoryToServer(callback) { export function saveStorageHistoryToServer(callback) {
var data = store.get('notehistory'); const data = store.get('notehistory');
if (data) { if (data) {
$.post(serverurl + '/history', { $.post(`${serverurl}/history`, {
history: data history: data
}) })
.done(function (data) { .done(data => {
callback(data); callback(data);
}); });
} }
} }
function saveCookieHistoryToServer(callback) { function saveCookieHistoryToServer(callback) {
$.post(serverurl + '/history', { $.post(`${serverurl}/history`, {
history: Cookies.get('notehistory') history: Cookies.get('notehistory')
}) })
.done(function (data) { .done(data => {
callback(data); callback(data);
}); });
} }
function clearDuplicatedHistory(notehistory) { export function clearDuplicatedHistory(notehistory) {
var newnotehistory = []; const newnotehistory = [];
for (var i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
var found = false; let found = false;
for (var j = 0; j < newnotehistory.length; j++) { for (let j = 0; j < newnotehistory.length; j++) {
var id = notehistory[i].id.replace(/\=+$/, ''); const id = notehistory[i].id.replace(/\=+$/, '');
var newId = newnotehistory[j].id.replace(/\=+$/, ''); const newId = newnotehistory[j].id.replace(/\=+$/, '');
if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) { if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) {
var time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')); const time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
var newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a')); const newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
if(time >= newTime) { if(time >= newTime) {
newnotehistory[j] = notehistory[i]; newnotehistory[j] = notehistory[i];
} }
@ -123,42 +126,42 @@ 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, id,
text: text, text,
time: time, time,
tags: tags, tags,
pinned: pinned pinned
}); });
} }
return notehistory; return notehistory;
} }
function removeHistory(id, notehistory) { export function removeHistory(id, notehistory) {
for (var i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
if (notehistory[i].id == id) { if (notehistory[i].id == id) {
notehistory.splice(i, 1); notehistory.splice(i, 1);
i--; i -= 1;
} }
} }
return notehistory; return notehistory;
} }
//used for inner //used for inner
function writeHistory(title, tags) { export function writeHistory(title, tags) {
checkIfAuth( checkIfAuth(
function () { () => {
// 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);
}, },
function () { () => {
writeHistoryToStorage(title, tags); writeHistoryToStorage(title, tags);
} }
); );
} }
function writeHistoryToServer(title, tags) { function writeHistoryToServer(title, tags) {
$.get(serverurl + '/history') $.get(`${serverurl}/history`)
.done(function (data) { .done(data => {
try { try {
if (data.history) { if (data.history) {
var notehistory = data.history; var notehistory = data.history;
@ -171,10 +174,10 @@ function writeHistoryToServer(title, tags) {
if (!notehistory) if (!notehistory)
notehistory = []; notehistory = [];
var newnotehistory = generateHistory(title, tags, notehistory); const newnotehistory = generateHistory(title, tags, notehistory);
saveHistoryToServer(newnotehistory); saveHistoryToServer(newnotehistory);
}) })
.fail(function (xhr, status, error) { .fail((xhr, status, error) => {
console.error(xhr.responseText); console.error(xhr.responseText);
}); });
} }
@ -188,13 +191,13 @@ function writeHistoryToCookie(title, tags) {
if (!notehistory) if (!notehistory)
notehistory = []; notehistory = [];
var newnotehistory = generateHistory(title, tags, notehistory); const newnotehistory = generateHistory(title, tags, notehistory);
saveHistoryToCookie(newnotehistory); saveHistoryToCookie(newnotehistory);
} }
function writeHistoryToStorage(title, tags) { function writeHistoryToStorage(title, tags) {
if (store.enabled) { if (store.enabled) {
var data = store.get('notehistory'); let data = store.get('notehistory');
if (data) { if (data) {
if (typeof data == "string") if (typeof data == "string")
data = JSON.parse(data); data = JSON.parse(data);
@ -204,7 +207,7 @@ function writeHistoryToStorage(title, tags) {
if (!notehistory) if (!notehistory)
notehistory = []; notehistory = [];
var newnotehistory = generateHistory(title, tags, notehistory); const newnotehistory = generateHistory(title, tags, notehistory);
saveHistoryToStorage(newnotehistory); saveHistoryToStorage(newnotehistory);
} else { } else {
writeHistoryToCookie(title, tags); writeHistoryToCookie(title, tags);
@ -212,32 +215,30 @@ function writeHistoryToStorage(title, tags) {
} }
if (!Array.isArray) { if (!Array.isArray) {
Array.isArray = function(arg) { Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]';
return Object.prototype.toString.call(arg) === '[object Array]';
};
} }
function renderHistory(title, tags) { function renderHistory(title, tags) {
//console.debug(tags); //console.debug(tags);
var 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, id,
text: title, text: title,
time: moment().valueOf(), time: moment().valueOf(),
tags: tags tags
}; };
} }
function generateHistory(title, tags, notehistory) { function generateHistory(title, tags, notehistory) {
var info = renderHistory(title, tags); const info = renderHistory(title, tags);
//keep any pinned data //keep any pinned data
var pinned = false; let pinned = false;
for (var 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) {
pinned = true; pinned = true;
break; break;
} }
} }
notehistory = removeHistory(info.id, notehistory); notehistory = removeHistory(info.id, notehistory);
notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory); notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory);
notehistory = clearDuplicatedHistory(notehistory); notehistory = clearDuplicatedHistory(notehistory);
@ -245,25 +246,25 @@ function generateHistory(title, tags, notehistory) {
} }
//used for outer //used for outer
function getHistory(callback) { export function getHistory(callback) {
checkIfAuth( checkIfAuth(
function () { () => {
getServerHistory(callback); getServerHistory(callback);
}, },
function () { () => {
getStorageHistory(callback); getStorageHistory(callback);
} }
); );
} }
function getServerHistory(callback) { function getServerHistory(callback) {
$.get(serverurl + '/history') $.get(`${serverurl}/history`)
.done(function (data) { .done(data => {
if (data.history) { if (data.history) {
callback(data.history); callback(data.history);
} }
}) })
.fail(function (xhr, status, error) { .fail((xhr, status, error) => {
console.error(xhr.responseText); console.error(xhr.responseText);
}); });
} }
@ -272,9 +273,9 @@ function getCookieHistory(callback) {
callback(Cookies.getJSON('notehistory')); callback(Cookies.getJSON('notehistory'));
} }
function getStorageHistory(callback) { export function getStorageHistory(callback) {
if (store.enabled) { if (store.enabled) {
var data = store.get('notehistory'); let data = store.get('notehistory');
if (data) { if (data) {
if (typeof data == "string") if (typeof data == "string")
data = JSON.parse(data); data = JSON.parse(data);
@ -286,37 +287,37 @@ function getStorageHistory(callback) {
} }
} }
function parseHistory(list, callback) { export function parseHistory(list, callback) {
checkIfAuth( checkIfAuth(
function () { () => {
parseServerToHistory(list, callback); parseServerToHistory(list, callback);
}, },
function () { () => {
parseStorageToHistory(list, callback); parseStorageToHistory(list, callback);
} }
); );
} }
function parseServerToHistory(list, callback) { export function parseServerToHistory(list, callback) {
$.get(serverurl + '/history') $.get(`${serverurl}/history`)
.done(function (data) { .done(data => {
if (data.history) { if (data.history) {
parseToHistory(list, data.history, callback); parseToHistory(list, data.history, callback);
} }
}) })
.fail(function (xhr, status, error) { .fail((xhr, status, error) => {
console.error(xhr.responseText); console.error(xhr.responseText);
}); });
} }
function parseCookieToHistory(list, callback) { function parseCookieToHistory(list, callback) {
var notehistory = Cookies.getJSON('notehistory'); const notehistory = Cookies.getJSON('notehistory');
parseToHistory(list, notehistory, callback); parseToHistory(list, notehistory, callback);
} }
function parseStorageToHistory(list, callback) { export function parseStorageToHistory(list, callback) {
if (store.enabled) { if (store.enabled) {
var data = store.get('notehistory'); let data = store.get('notehistory');
if (data) { if (data) {
if (typeof data == "string") if (typeof data == "string")
data = JSON.parse(data); data = JSON.parse(data);
@ -332,9 +333,9 @@ function parseToHistory(list, notehistory, callback) {
if (!callback) return; if (!callback) return;
else if (!list || !notehistory) callback(list, notehistory); else if (!list || !notehistory) callback(list, notehistory);
else if (notehistory && notehistory.length > 0) { else if (notehistory && notehistory.length > 0) {
for (var i = 0; i < notehistory.length; i++) { for (let i = 0; i < notehistory.length; i++) {
//parse time to timestamp and fromNow //parse time to timestamp and fromNow
var 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');
@ -349,42 +350,23 @@ function parseToHistory(list, notehistory, callback) {
callback(list, notehistory); callback(list, notehistory);
} }
function postHistoryToServer(noteId, data, callback) { export function postHistoryToServer(noteId, data, callback) {
$.post(serverurl + '/history/' + noteId, data) $.post(`${serverurl}/history/${noteId}`, data)
.done(function (result) { .done(result => callback(null, result))
return callback(null, result); .fail((xhr, status, error) => {
})
.fail(function (xhr, status, error) {
console.error(xhr.responseText); console.error(xhr.responseText);
return callback(error, null); return callback(error, null);
}); });
} }
function deleteServerHistory(noteId, callback) { export function deleteServerHistory(noteId, callback) {
$.ajax({ $.ajax({
url: serverurl + '/history' + (noteId ? '/' + noteId : ""), url: `${serverurl}/history${noteId ? '/' + noteId : ""}`,
type: 'DELETE' type: 'DELETE'
}) })
.done(function (result) { .done(result => callback(null, result))
return callback(null, result); .fail((xhr, status, error) => {
})
.fail(function (xhr, status, error) {
console.error(xhr.responseText); console.error(xhr.responseText);
return callback(error, null); return callback(error, null);
}); });
} }
module.exports = {
writeHistory: writeHistory,
parseHistory: parseHistory,
getStorageHistory: getStorageHistory,
getHistory: getHistory,
saveHistory: saveHistory,
removeHistory: removeHistory,
parseStorageToHistory: parseStorageToHistory,
postHistoryToServer: postHistoryToServer,
deleteServerHistory: deleteServerHistory,
parseServerToHistory: parseServerToHistory,
saveStorageHistoryToServer: saveStorageHistoryToServer,
clearDuplicatedHistory: clearDuplicatedHistory
}

View file

@ -17,51 +17,58 @@ var _ = require("lodash");
var List = require('list.js'); var List = require('list.js');
var common = require('./common.js'); import {
var urlpath = common.urlpath; checkLoginStateChanged,
var noteid = common.noteid; setloginStateChangeEvent
var debug = common.debug; } from './lib/common/login';
var version = common.version;
var GOOGLE_API_KEY = common.GOOGLE_API_KEY;
var GOOGLE_CLIENT_ID = common.GOOGLE_CLIENT_ID;
var DROPBOX_APP_KEY = common.DROPBOX_APP_KEY;
var noteurl = common.noteurl;
var checkLoginStateChanged = common.checkLoginStateChanged; import {
debug,
DROPBOX_APP_KEY,
GOOGLE_API_KEY,
GOOGLE_CLIENT_ID,
noteid,
noteurl,
urlpath,
version
} from './lib/config';
var extra = require('./extra'); import {
var md = extra.md; autoLinkify,
var updateLastChange = extra.updateLastChange; deduplicatedHeaderId,
var postProcess = extra.postProcess; exportToHTML,
var finishView = extra.finishView; exportToRawHTML,
var autoLinkify = extra.autoLinkify; finishView,
var generateToc = extra.generateToc; generateToc,
var smoothHashScroll = extra.smoothHashScroll; isValidURL,
var deduplicatedHeaderId = extra.deduplicatedHeaderId; md,
var renderTOC = extra.renderTOC; parseMeta,
var renderTitle = extra.renderTitle; postProcess,
var renderFilename = extra.renderFilename; renderFilename,
var renderTags = extra.renderTags; renderTOC,
var isValidURL = extra.isValidURL; renderTags,
var scrollToHash = extra.scrollToHash; renderTitle,
var updateLastChangeUser = extra.updateLastChangeUser; scrollToHash,
var updateOwner = extra.updateOwner; smoothHashScroll,
var parseMeta = extra.parseMeta; updateLastChange,
var exportToHTML = extra.exportToHTML; updateLastChangeUser,
var exportToRawHTML = extra.exportToRawHTML; updateOwner
} from './extra';
var syncScroll = require('./syncscroll'); import {
var setupSyncAreas = syncScroll.setupSyncAreas; clearMap,
var clearMap = syncScroll.clearMap; setupSyncAreas,
var syncScrollToEdit = syncScroll.syncScrollToEdit; syncScrollToEdit,
var syncScrollToView = syncScroll.syncScrollToView; syncScrollToView
} from './syncscroll';
var historyModule = require('./history'); import {
var writeHistory = historyModule.writeHistory; writeHistory,
var deleteServerHistory = historyModule.deleteServerHistory; deleteServerHistory,
var getHistory = historyModule.getHistory; getHistory,
var saveHistory = historyModule.saveHistory; saveHistory,
var removeHistory = historyModule.removeHistory; removeHistory
} from './history';
var renderer = require('./render'); var renderer = require('./render');
var preventXSS = renderer.preventXSS; var preventXSS = renderer.preventXSS;
@ -401,7 +408,8 @@ window.lastInfo = {
cursor: { cursor: {
line: null, line: null,
ch: null ch: null
} },
selections: null
}, },
view: { view: {
scroll: { scroll: {
@ -963,10 +971,10 @@ function setNeedRefresh() {
showStatus(statusType.offline); showStatus(statusType.offline);
} }
loginStateChangeEvent = function () { setloginStateChangeEvent(function () {
setRefreshModal('user-state-changed'); setRefreshModal('user-state-changed');
setNeedRefresh(); setNeedRefresh();
}; });
//visibility //visibility
var wasFocus = false; var wasFocus = false;
@ -1535,7 +1543,7 @@ ui.toolbar.download.markdown.click(function (e) {
var blob = new Blob([markdown], { var blob = new Blob([markdown], {
type: "text/markdown;charset=utf-8" type: "text/markdown;charset=utf-8"
}); });
saveAs(blob, filename); saveAs(blob, filename, true);
}); });
//html //html
ui.toolbar.download.html.click(function (e) { ui.toolbar.download.html.click(function (e) {
@ -1915,7 +1923,7 @@ $('#revisionModalDownload').click(function () {
var blob = new Blob([revision.content], { var blob = new Blob([revision.content], {
type: "text/markdown;charset=utf-8" type: "text/markdown;charset=utf-8"
}); });
saveAs(blob, filename); saveAs(blob, filename, true);
}); });
$('#revisionModalRevert').click(function () { $('#revisionModalRevert').click(function () {
if (!revision) return; if (!revision) return;
@ -2511,7 +2519,7 @@ var addStyleRule = (function () {
}()); }());
function updateAuthorshipInner() { function updateAuthorshipInner() {
// ignore when ot not synced yet // ignore when ot not synced yet
if (cmClient && Object.keys(cmClient.state).length > 0) return; if (havePendingOperation()) return;
authorMarks = {}; authorMarks = {};
for (var i = 0; i < authorship.length; i++) { for (var i = 0; i < authorship.length; i++) {
var atom = authorship[i]; var atom = authorship[i];
@ -2668,7 +2676,7 @@ editor.on('update', function () {
}); });
// clear tooltip which described element has been removed // clear tooltip which described element has been removed
$('[id^="tooltip"]').each(function (index, element) { $('[id^="tooltip"]').each(function (index, element) {
$ele = $(element); var $ele = $(element);
if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove(); if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove();
}); });
}); });
@ -2726,12 +2734,16 @@ var EditorClient = ot.EditorClient;
var SocketIOAdapter = ot.SocketIOAdapter; var SocketIOAdapter = ot.SocketIOAdapter;
var CodeMirrorAdapter = ot.CodeMirrorAdapter; var CodeMirrorAdapter = ot.CodeMirrorAdapter;
var cmClient = null; var cmClient = null;
var synchronized_ = null;
function havePendingOperation() {
return (cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')) ? true : false;
}
socket.on('doc', function (obj) { socket.on('doc', function (obj) {
var body = obj.str; var body = obj.str;
var bodyMismatch = editor.getValue() !== body; var bodyMismatch = editor.getValue() !== body;
var havePendingOperation = cmClient && Object.keys(cmClient.state).length > 0; var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force;
var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation))) || obj.force;
saveInfo(); saveInfo();
if (setDoc && bodyMismatch) { if (setDoc && bodyMismatch) {
@ -2756,16 +2768,17 @@ socket.on('doc', function (obj) {
obj.revision, obj.clients, obj.revision, obj.clients,
new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) new SocketIOAdapter(socket), new CodeMirrorAdapter(editor)
); );
synchronized_ = cmClient.state;
} else if (setDoc) { } else if (setDoc) {
if (bodyMismatch) { if (bodyMismatch) {
cmClient.undoManager.undoStack.length = 0; cmClient.undoManager.undoStack.length = 0;
cmClient.undoManager.redoStack.length = 0; cmClient.undoManager.redoStack.length = 0;
} }
cmClient.revision = obj.revision; cmClient.revision = obj.revision;
cmClient.setState(new ot.Client.Synchronized()); cmClient.setState(synchronized_);
cmClient.initializeClientList(); cmClient.initializeClientList();
cmClient.initializeClients(obj.clients); cmClient.initializeClients(obj.clients);
} else if (havePendingOperation) { } else if (havePendingOperation()) {
cmClient.serverReconnect(); cmClient.serverReconnect();
} }
@ -3387,6 +3400,7 @@ function saveInfo() {
break; break;
} }
lastInfo.edit.cursor = editor.getCursor(); lastInfo.edit.cursor = editor.getCursor();
lastInfo.edit.selections = editor.listSelections();
lastInfo.needRestore = true; lastInfo.needRestore = true;
} }
@ -3396,6 +3410,7 @@ function restoreInfo() {
var line = lastInfo.edit.cursor.line; var line = lastInfo.edit.cursor.line;
var ch = lastInfo.edit.cursor.ch; var ch = lastInfo.edit.cursor.ch;
editor.setCursor(line, ch); editor.setCursor(line, ch);
editor.setSelections(lastInfo.edit.selections);
switch (currentMode) { switch (currentMode) {
case modeType.edit: case modeType.edit:
if (scrollbarStyle == 'native') { if (scrollbarStyle == 'native') {
@ -3445,6 +3460,7 @@ function updateViewInner() {
var value = editor.getValue(); var value = editor.getValue();
var lastMeta = md.meta; var lastMeta = md.meta;
md.meta = {}; md.meta = {};
delete md.metaError;
var rendered = md.render(value); var rendered = md.render(value);
if (md.meta.type && md.meta.type === 'slide') { if (md.meta.type && md.meta.type === 'slide') {
var slideOptions = { var slideOptions = {
@ -3716,6 +3732,7 @@ function checkCursorMenuInner() {
var offsetLeft = 0; var offsetLeft = 0;
var offsetTop = defaultTextHeight; var offsetTop = defaultTextHeight;
// set up side down // set up side down
window.upSideDown = false;
var lastUpSideDown = upSideDown = false; var lastUpSideDown = upSideDown = false;
// only do when have width and height // only do when have width and height
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
@ -3944,7 +3961,7 @@ $(editor.getInputField())
match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/, match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/,
search: function (term, callback) { search: function (term, callback) {
var line = editor.getLine(editor.getCursor().line); var line = editor.getLine(editor.getCursor().line);
quote = line.match(this.match)[1].trim(); var quote = line.match(this.match)[1].trim();
var list = []; var list = [];
if (quote.indexOf('>') == 0) { if (quote.indexOf('>') == 0) {
$.map(supportExtraTags, function (extratag) { $.map(supportExtraTags, function (extratag) {

View file

@ -0,0 +1,89 @@
import { serverurl } from '../config';
let checkAuth = false;
let profile = null;
let lastLoginState = getLoginState();
let lastUserId = getUserId();
var loginStateChangeEvent = null;
export function setloginStateChangeEvent(func) {
loginStateChangeEvent = func;
}
export function resetCheckAuth() {
checkAuth = false;
}
export function setLoginState(bool, id) {
Cookies.set('loginstate', bool, {
expires: 365
});
if (id) {
Cookies.set('userid', id, {
expires: 365
});
} else {
Cookies.remove('userid');
}
lastLoginState = bool;
lastUserId = id;
checkLoginStateChanged();
}
export function checkLoginStateChanged() {
if (getLoginState() != lastLoginState || getUserId() != lastUserId) {
if (loginStateChangeEvent) setTimeout(loginStateChangeEvent, 100);
return true;
} else {
return false;
}
}
export function getLoginState() {
const state = Cookies.get('loginstate');
return state === "true" || state === true;
}
export function getUserId() {
return Cookies.get('userid');
}
export function clearLoginState() {
Cookies.remove('loginstate');
}
export function checkIfAuth(yesCallback, noCallback) {
const cookieLoginState = getLoginState();
if (checkLoginStateChanged()) checkAuth = false;
if (!checkAuth || typeof cookieLoginState == 'undefined') {
$.get(`${serverurl}/me`)
.done(data => {
if (data && data.status == 'ok') {
profile = data;
yesCallback(profile);
setLoginState(true, data.id);
} else {
noCallback();
setLoginState(false);
}
})
.fail(() => {
noCallback();
})
.always(() => {
checkAuth = true;
});
} else if (cookieLoginState) {
yesCallback(profile);
} else {
noCallback();
}
}
export default {
checkAuth,
profile,
lastLoginState,
lastUserId,
loginStateChangeEvent
};

View file

@ -0,0 +1,19 @@
import configJson from '../../../../config.json'; // root path json config
const config = 'production' === process.env.NODE_ENV ? configJson.production : configJson.development;
export const GOOGLE_API_KEY = (config.google && config.google.apiKey) || '';
export const GOOGLE_CLIENT_ID = (config.google && config.google.clientID) || '';
export const DROPBOX_APP_KEY = (config.dropbox && config.dropbox.appKey) || '';
export const domain = config.domain || ''; // domain name
export const urlpath = config.urlpath || ''; // sub url path, like: www.example.com/<urlpath>
export const debug = config.debug || false;
export const port = window.location.port;
export const serverurl = `${window.location.protocol}//${domain ? domain : window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}`;
window.serverurl = serverurl;
export const noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
export const noteurl = `${serverurl}/${noteid}`;
export const version = '0.5.0';

View file

@ -4,31 +4,34 @@ require('../css/site.css');
require('highlight.js/styles/github-gist.css'); require('highlight.js/styles/github-gist.css');
var extra = require('./extra'); import {
var md = extra.md; autoLinkify,
var finishView = extra.finishView; deduplicatedHeaderId,
var autoLinkify = extra.autoLinkify; finishView,
var deduplicatedHeaderId = extra.deduplicatedHeaderId; generateToc,
var renderTOC = extra.renderTOC; md,
var generateToc = extra.generateToc; parseMeta,
var smoothHashScroll = extra.smoothHashScroll; postProcess,
var postProcess = extra.postProcess; renderTOC,
var updateLastChange = extra.updateLastChange; scrollToHash,
var parseMeta = extra.parseMeta; smoothHashScroll,
var scrollToHash = extra.scrollToHash; updateLastChange
var preventXSS = require('./render').preventXSS; } from './extra';
var markdown = $("#doc.markdown-body"); import { preventXSS } from './render';
var text = markdown.text();
var lastMeta = md.meta; const markdown = $("#doc.markdown-body");
const text = markdown.text();
const lastMeta = md.meta;
md.meta = {}; md.meta = {};
var rendered = md.render(text); delete md.metaError;
let rendered = md.render(text);
if (md.meta.type && md.meta.type === 'slide') { if (md.meta.type && md.meta.type === 'slide') {
var slideOptions = { const slideOptions = {
separator: '^(\r\n?|\n)---(\r\n?|\n)$', separator: '^(\r\n?|\n)---(\r\n?|\n)$',
verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
}; };
var slides = RevealMarkdown.slidify(text, slideOptions); const slides = RevealMarkdown.slidify(text, slideOptions);
markdown.html(slides); markdown.html(slides);
RevealMarkdown.initialize(); RevealMarkdown.initialize();
// prevent XSS // prevent XSS
@ -46,10 +49,11 @@ if (md.meta.type && md.meta.type === 'slide') {
} }
// prevent XSS // prevent XSS
rendered = preventXSS(rendered); rendered = preventXSS(rendered);
var result = postProcess(rendered); const result = postProcess(rendered);
markdown.html(result.html()); markdown.html(result.html());
} }
$(document.body).show(); $(document.body).show();
finishView(markdown); finishView(markdown);
autoLinkify(markdown); autoLinkify(markdown);
deduplicatedHeaderId(markdown); deduplicatedHeaderId(markdown);
@ -60,17 +64,18 @@ smoothHashScroll();
createtime = lastchangeui.time.attr('data-createtime'); createtime = lastchangeui.time.attr('data-createtime');
lastchangetime = lastchangeui.time.attr('data-updatetime'); lastchangetime = lastchangeui.time.attr('data-updatetime');
updateLastChange(); updateLastChange();
var url = window.location.pathname;
$('.ui-edit').attr('href', url + '/edit'); const url = window.location.pathname;
var toc = $('.ui-toc'); $('.ui-edit').attr('href', `${url}/edit`);
var tocAffix = $('.ui-affix-toc'); const toc = $('.ui-toc');
var tocDropdown = $('.ui-toc-dropdown'); const tocAffix = $('.ui-affix-toc');
const tocDropdown = $('.ui-toc-dropdown');
//toc //toc
tocDropdown.click(function (e) { tocDropdown.click(e => {
e.stopPropagation(); e.stopPropagation();
}); });
var enoughForAffixToc = true; let enoughForAffixToc = true;
function generateScrollspy() { function generateScrollspy() {
$(document.body).scrollspy({ $(document.body).scrollspy({
@ -89,18 +94,18 @@ function generateScrollspy() {
function windowResize() { function windowResize() {
//toc right //toc right
var paddingRight = parseFloat(markdown.css('padding-right')); const paddingRight = parseFloat(markdown.css('padding-right'));
var 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
var newbool; let newbool;
var 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;
var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2; const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin; const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
tocAffix.css('left', left + 'px'); tocAffix.css('left', `${left}px`);
} else { } else {
newbool = false; newbool = false;
} }
@ -109,10 +114,10 @@ function windowResize() {
generateScrollspy(); generateScrollspy();
} }
} }
$(window).resize(function () { $(window).resize(() => {
windowResize(); windowResize();
}); });
$(document).ready(function () { $(document).ready(() => {
windowResize(); windowResize();
generateScrollspy(); generateScrollspy();
setTimeout(scrollToHash, 0); setTimeout(scrollToHash, 0);
@ -120,13 +125,13 @@ $(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
}); });
function scrollToTop() { export function scrollToTop() {
$('body, html').stop(true, true).animate({ $('body, html').stop(true, true).animate({
scrollTop: 0 scrollTop: 0
}, 100, "linear"); }, 100, "linear");
} }
function scrollToBottom() { export function scrollToBottom() {
$('body, html').stop(true, true).animate({ $('body, html').stop(true, true).animate({
scrollTop: $(document.body)[0].scrollHeight scrollTop: $(document.body)[0].scrollHeight
}, 100, "linear"); }, 100, "linear");
@ -134,8 +139,3 @@ function scrollToBottom() {
window.scrollToTop = scrollToTop; window.scrollToTop = scrollToTop;
window.scrollToBottom = scrollToBottom; window.scrollToBottom = scrollToBottom;
module.exports = {
scrollToBottom: scrollToBottom,
scrollToTop: scrollToTop
}

View file

@ -9,6 +9,8 @@ var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base
var whiteList = filterXSS.whiteList; var whiteList = filterXSS.whiteList;
// allow ol specify start number // allow ol specify start number
whiteList['ol'] = ['start']; whiteList['ol'] = ['start'];
// allow li specify value number
whiteList['li'] = ['value'];
// allow style tag // allow style tag
whiteList['style'] = []; whiteList['style'] = [];
// allow kbd tag // allow kbd tag

View file

@ -1,67 +1,65 @@
require('../css/extra.css'); require('../css/extra.css');
require('../css/site.css'); require('../css/site.css');
var extraModule = require('./extra'); import { md, updateLastChange, finishView } from './extra';
var md = extraModule.md;
var updateLastChange = extraModule.updateLastChange;
var finishView = extraModule.finishView;
var preventXSS = require('./render').preventXSS; import { preventXSS } from './render';
var body = $(".slides").text(); const body = $(".slides").text();
createtime = lastchangeui.time.attr('data-createtime'); createtime = lastchangeui.time.attr('data-createtime');
lastchangetime = lastchangeui.time.attr('data-updatetime'); lastchangetime = lastchangeui.time.attr('data-updatetime');
updateLastChange(); updateLastChange();
var url = window.location.pathname; const url = window.location.pathname;
$('.ui-edit').attr('href', url + '/edit'); $('.ui-edit').attr('href', `${url}/edit`);
$(document).ready(function () { $(document).ready(() => {
//tooltip //tooltip
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
}); });
function extend() { function extend() {
var target = {}; const target = {};
for (var i = 0; i < arguments.length; i++) {
var source = arguments[i]; for (const source of arguments) {
for (var key in source) { for (const key in source) {
if (source.hasOwnProperty(key)) { if (source.hasOwnProperty(key)) {
target[key] = source[key]; target[key] = source[key];
} }
} }
} }
return target; return target;
} }
// Optional libraries used to extend on reveal.js // Optional libraries used to extend on reveal.js
var deps = [{ const deps = [{
src: serverurl + '/build/reveal.js/lib/js/classList.js', src: `${serverurl}/build/reveal.js/lib/js/classList.js`,
condition: function() { condition() {
return !document.body.classList; return !document.body.classList;
} }
}, { }, {
src: serverurl + '/js/reveal-markdown.js', src: `${serverurl}/js/reveal-markdown.js`,
callback: function () { callback() {
var slideOptions = { const slideOptions = {
separator: '^(\r\n?|\n)---(\r\n?|\n)$', separator: '^(\r\n?|\n)---(\r\n?|\n)$',
verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
}; };
var slides = RevealMarkdown.slidify(body, slideOptions); const slides = RevealMarkdown.slidify(body, slideOptions);
$(".slides").html(slides); $(".slides").html(slides);
RevealMarkdown.initialize(); RevealMarkdown.initialize();
$(".slides").show(); $(".slides").show();
} }
}, { }, {
src: serverurl + '/build/reveal.js/plugin/notes/notes.js', src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`,
async: true, async: true,
condition: function() { condition() {
return !!document.body.classList; return !!document.body.classList;
} }
}]; }];
// default options to init reveal.js // default options to init reveal.js
var defaultOptions = { const defaultOptions = {
controls: true, controls: true,
progress: true, progress: true,
slideNumber: true, slideNumber: true,
@ -72,10 +70,10 @@ var defaultOptions = {
}; };
// options from yaml meta // options from yaml meta
var meta = JSON.parse($("#meta").text()); const meta = JSON.parse($("#meta").text());
var options = meta.slideOptions || {}; var options = meta.slideOptions || {};
var view = $('.reveal'); const view = $('.reveal');
//text language //text language
if (meta.lang && typeof meta.lang == "string") { if (meta.lang && typeof meta.lang == "string") {
@ -97,24 +95,24 @@ if (typeof meta.breaks === 'boolean' && !meta.breaks) {
} }
// options from URL query string // options from URL query string
var queryOptions = Reveal.getQueryHash() || {}; const queryOptions = Reveal.getQueryHash() || {};
var options = extend(defaultOptions, options, queryOptions); var options = extend(defaultOptions, options, queryOptions);
Reveal.initialize(options); Reveal.initialize(options);
window.viewAjaxCallback = function () { window.viewAjaxCallback = () => {
Reveal.layout(); Reveal.layout();
}; };
function renderSlide(event) { function renderSlide(event) {
if (window.location.search.match( /print-pdf/gi )) { if (window.location.search.match( /print-pdf/gi )) {
var slides = $('.slides'); const slides = $('.slides');
var title = document.title; var title = document.title;
finishView(slides); finishView(slides);
document.title = title; document.title = title;
Reveal.layout(); Reveal.layout();
} else { } else {
var markdown = $(event.currentSlide); const markdown = $(event.currentSlide);
if (!markdown.attr('data-rendered')) { if (!markdown.attr('data-rendered')) {
var title = document.title; var title = document.title;
finishView(markdown); finishView(markdown);
@ -125,16 +123,16 @@ function renderSlide(event) {
} }
} }
Reveal.addEventListener('ready', function (event) { Reveal.addEventListener('ready', event => {
renderSlide(event); renderSlide(event);
var markdown = $(event.currentSlide); const markdown = $(event.currentSlide);
// force browser redraw // force browser redraw
setTimeout(function () { setTimeout(() => {
markdown.hide().show(0); markdown.hide().show(0);
}, 0); }, 0);
}); });
Reveal.addEventListener('slidechanged', renderSlide); Reveal.addEventListener('slidechanged', renderSlide);
var isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true : false; const isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true : false;
if (!isMacLike) $('.container').addClass('hidescrollbar'); if (!isMacLike) $('.container').addClass('hidescrollbar');

View file

@ -1,12 +1,13 @@
// Inject line numbers for sync scroll. // Inject line numbers for sync scroll.
var extra = require('./extra'); import markdownitContainer from 'markdown-it-container';
var md = extra.md;
import { md } from './extra';
function addPart(tokens, idx) { function addPart(tokens, idx) {
if (tokens[idx].map && tokens[idx].level === 0) { if (tokens[idx].map && tokens[idx].level === 0) {
var startline = tokens[idx].map[0] + 1; const startline = tokens[idx].map[0] + 1;
var endline = tokens[idx].map[1]; const endline = tokens[idx].map[1];
tokens[idx].attrJoin('class', 'part'); tokens[idx].attrJoin('class', 'part');
tokens[idx].attrJoin('data-startline', startline); tokens[idx].attrJoin('data-startline', startline);
tokens[idx].attrJoin('data-endline', endline); tokens[idx].attrJoin('data-endline', endline);
@ -16,48 +17,48 @@ function addPart(tokens, idx) {
md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
tokens[idx].attrJoin('class', 'raw'); tokens[idx].attrJoin('class', 'raw');
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.table_open = function (tokens, idx, options, env, self) { md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) {
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) {
tokens[idx].attrJoin('class', 'raw'); tokens[idx].attrJoin('class', 'raw');
if (tokens[idx].map) { if (tokens[idx].map) {
var startline = tokens[idx].map[0] + 1; const startline = tokens[idx].map[0] + 1;
var endline = tokens[idx].map[1]; const endline = tokens[idx].map[1];
tokens[idx].attrJoin('data-startline', startline); tokens[idx].attrJoin('data-startline', startline);
tokens[idx].attrJoin('data-endline', endline); tokens[idx].attrJoin('data-endline', endline);
} }
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) { md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) {
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.link_open = function (tokens, idx, options, env, self) { md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) {
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { md.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
tokens[idx].attrJoin('class', 'raw'); tokens[idx].attrJoin('class', 'raw');
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
}; };
md.renderer.rules.fence = function (tokens, idx, options, env, self) { md.renderer.rules.fence = (tokens, idx, options, env, self) => {
var token = tokens[idx], const token = tokens[idx];
info = token.info ? md.utils.unescapeAll(token.info).trim() : '', const info = token.info ? md.utils.unescapeAll(token.info).trim() : '';
langName = '', let langName = '';
highlighted; let highlighted;
if (info) { if (info) {
langName = info.split(/\s+/g)[0]; langName = info.split(/\s+/g)[0];
@ -74,38 +75,33 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) {
} }
if (highlighted.indexOf('<pre') === 0) { if (highlighted.indexOf('<pre') === 0) {
return highlighted + '\n'; return `${highlighted}\n`;
} }
if (tokens[idx].map && tokens[idx].level === 0) { if (tokens[idx].map && tokens[idx].level === 0) {
var startline = tokens[idx].map[0] + 1; const startline = tokens[idx].map[0] + 1;
var endline = tokens[idx].map[1]; const endline = tokens[idx].map[1];
return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code' + self.renderAttrs(token) + '>' return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
+ highlighted
+ '</code></pre>\n';
} }
return '<pre><code' + self.renderAttrs(token) + '>' return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
+ highlighted
+ '</code></pre>\n';
}; };
md.renderer.rules.code_block = function (tokens, idx, options, env, self) { md.renderer.rules.code_block = (tokens, idx, options, env, self) => {
if (tokens[idx].map && tokens[idx].level === 0) { if (tokens[idx].map && tokens[idx].level === 0) {
var startline = tokens[idx].map[0] + 1; const startline = tokens[idx].map[0] + 1;
var endline = tokens[idx].map[1]; const endline = tokens[idx].map[1];
return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code>' + md.utils.escapeHtml(tokens[idx].content) + '</code></pre>\n'; return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`;
} }
return '<pre><code>' + md.utils.escapeHtml(tokens[idx].content) + '</code></pre>\n'; return `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>\n`;
}; };
function renderContainer(tokens, idx, options, env, self) { function renderContainer(tokens, idx, options, env, self) {
tokens[idx].attrJoin('role', 'alert'); tokens[idx].attrJoin('role', 'alert');
tokens[idx].attrJoin('class', 'alert'); tokens[idx].attrJoin('class', 'alert');
tokens[idx].attrJoin('class', 'alert-' + tokens[idx].info.trim()); tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`);
addPart(tokens, idx); addPart(tokens, idx);
return self.renderToken.apply(self, arguments); return self.renderToken(...arguments);
} }
var markdownitContainer = require('markdown-it-container');
md.use(markdownitContainer, 'success', { render: renderContainer }); md.use(markdownitContainer, 'success', { render: renderContainer });
md.use(markdownitContainer, 'info', { render: renderContainer }); md.use(markdownitContainer, 'info', { render: renderContainer });
md.use(markdownitContainer, 'warning', { render: renderContainer }); md.use(markdownitContainer, 'warning', { render: renderContainer });
@ -117,18 +113,18 @@ window.syncscroll = true;
window.preventSyncScrollToEdit = false; window.preventSyncScrollToEdit = false;
window.preventSyncScrollToView = false; window.preventSyncScrollToView = false;
var editScrollThrottle = 5; const editScrollThrottle = 5;
var viewScrollThrottle = 5; const viewScrollThrottle = 5;
var buildMapThrottle = 100; const buildMapThrottle = 100;
var viewScrolling = false; let viewScrolling = false;
var editScrolling = false; let editScrolling = false;
var editArea = null; let editArea = null;
var viewArea = null; let viewArea = null;
var markdownArea = null; let markdownArea = null;
function setupSyncAreas(edit, view, markdown) { export function setupSyncAreas(edit, view, markdown) {
editArea = edit; editArea = edit;
viewArea = view; viewArea = view;
markdownArea = markdown; markdownArea = markdown;
@ -136,26 +132,24 @@ function setupSyncAreas(edit, view, markdown) {
viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)); viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle));
} }
var scrollMap, lineHeightMap, viewTop, viewBottom; let scrollMap, lineHeightMap, viewTop, viewBottom;
window.viewAjaxCallback = clearMap; export function clearMap() {
function clearMap() {
scrollMap = null; scrollMap = null;
lineHeightMap = null; lineHeightMap = null;
viewTop = null; viewTop = null;
viewBottom = null; viewBottom = null;
} }
window.viewAjaxCallback = clearMap;
var buildMap = _.throttle(buildMapInner, buildMapThrottle); const buildMap = _.throttle(buildMapInner, buildMapThrottle);
// Build offsets for each line (lines can be wrapped) // Build offsets for each line (lines can be wrapped)
// That's a bit dirty to process each line everytime, but ok for demo. // That's a bit dirty to process each line everytime, but ok for demo.
// Optimizations are required only for big texts. // Optimizations are required only for big texts.
function buildMapInner(callback) { function buildMapInner(callback) {
if (!viewArea || !markdownArea) return; if (!viewArea || !markdownArea) return;
var i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap;
acc, _scrollMap;
offset = viewArea.scrollTop() - viewArea.offset().top; offset = viewArea.scrollTop() - viewArea.offset().top;
_scrollMap = []; _scrollMap = [];
@ -165,10 +159,10 @@ function buildMapInner(callback) {
viewBottom = viewArea[0].scrollHeight - viewArea.height(); viewBottom = viewArea[0].scrollHeight - viewArea.height();
acc = 0; acc = 0;
var lines = editor.getValue().split('\n'); const lines = editor.getValue().split('\n');
var lineHeight = editor.defaultTextHeight(); const lineHeight = editor.defaultTextHeight();
for (i = 0; i < lines.length; i++) { for (i = 0; i < lines.length; i++) {
var str = lines[i]; const str = lines[i];
_lineHeightMap.push(acc); _lineHeightMap.push(acc);
@ -177,7 +171,7 @@ function buildMapInner(callback) {
continue; continue;
} }
var h = editor.heightAtLine(i + 1) - editor.heightAtLine(i); const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i);
acc += Math.round(h / lineHeight); acc += Math.round(h / lineHeight);
} }
_lineHeightMap.push(acc); _lineHeightMap.push(acc);
@ -191,10 +185,10 @@ function buildMapInner(callback) {
// make the first line go top // make the first line go top
_scrollMap[0] = viewTop; _scrollMap[0] = viewTop;
var parts = markdownArea.find('.part').toArray(); const parts = markdownArea.find('.part').toArray();
for (i = 0; i < parts.length; i++) { for (i = 0; i < parts.length; i++) {
var $el = $(parts[i]), const $el = $(parts[i]);
t = $el.attr('data-startline') - 1; let t = $el.attr('data-startline') - 1;
if (t === '') { if (t === '') {
return; return;
} }
@ -229,9 +223,9 @@ function buildMapInner(callback) {
} }
// sync view scroll progress to edit // sync view scroll progress to edit
var viewScrollingTimer = null; let viewScrollingTimer = null;
function syncScrollToEdit(event, preventAnimate) { export function syncScrollToEdit(event, preventAnimate) {
if (currentMode != modeType.both || !syncscroll || !editArea) return; if (currentMode != modeType.both || !syncscroll || !editArea) return;
if (preventSyncScrollToEdit) { if (preventSyncScrollToEdit) {
if (typeof preventSyncScrollToEdit === 'number') { if (typeof preventSyncScrollToEdit === 'number') {
@ -242,15 +236,15 @@ function syncScrollToEdit(event, preventAnimate) {
return; return;
} }
if (!scrollMap || !lineHeightMap) { if (!scrollMap || !lineHeightMap) {
buildMap(function () { buildMap(() => {
syncScrollToEdit(event, preventAnimate); syncScrollToEdit(event, preventAnimate);
}); });
return; return;
} }
if (editScrolling) return; if (editScrolling) return;
var scrollTop = viewArea[0].scrollTop; const scrollTop = viewArea[0].scrollTop;
var lineIndex = 0; let lineIndex = 0;
for (var i = 0, l = scrollMap.length; i < l; i++) { for (var i = 0, l = scrollMap.length; i < l; i++) {
if (scrollMap[i] > scrollTop) { if (scrollMap[i] > scrollTop) {
break; break;
@ -258,8 +252,8 @@ function syncScrollToEdit(event, preventAnimate) {
lineIndex = i; lineIndex = i;
} }
} }
var lineNo = 0; let lineNo = 0;
var lineDiff = 0; let lineDiff = 0;
for (var i = 0, l = lineHeightMap.length; i < l; i++) { for (var i = 0, l = lineHeightMap.length; i < l; i++) {
if (lineHeightMap[i] > lineIndex) { if (lineHeightMap[i] > lineIndex) {
break; break;
@ -269,14 +263,14 @@ function syncScrollToEdit(event, preventAnimate) {
} }
} }
var posTo = 0; let posTo = 0;
var topDiffPercent = 0; let topDiffPercent = 0;
var posToNextDiff = 0; let posToNextDiff = 0;
var scrollInfo = editor.getScrollInfo(); const scrollInfo = editor.getScrollInfo();
var textHeight = editor.defaultTextHeight(); const textHeight = editor.defaultTextHeight();
var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight; const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight;
var preLastLineNo = Math.round(preLastLineHeight / textHeight); const preLastLineNo = Math.round(preLastLineHeight / textHeight);
var preLastLinePos = scrollMap[preLastLineNo]; const preLastLinePos = scrollMap[preLastLineNo];
if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
posTo = preLastLineHeight; posTo = preLastLineHeight;
@ -293,7 +287,7 @@ function syncScrollToEdit(event, preventAnimate) {
if (preventAnimate) { if (preventAnimate) {
editArea.scrollTop(posTo); editArea.scrollTop(posTo);
} else { } else {
var posDiff = Math.abs(scrollInfo.top - posTo); const posDiff = Math.abs(scrollInfo.top - posTo);
var duration = posDiff / 50; var duration = posDiff / 50;
duration = duration >= 100 ? duration : 100; duration = duration >= 100 ? duration : 100;
editArea.stop(true, true).animate({ editArea.stop(true, true).animate({
@ -311,9 +305,9 @@ function viewScrollingTimeoutInner() {
} }
// sync edit scroll progress to view // sync edit scroll progress to view
var editScrollingTimer = null; let editScrollingTimer = null;
function syncScrollToView(event, preventAnimate) { export function syncScrollToView(event, preventAnimate) {
if (currentMode != modeType.both || !syncscroll || !viewArea) return; if (currentMode != modeType.both || !syncscroll || !viewArea) return;
if (preventSyncScrollToView) { if (preventSyncScrollToView) {
if (typeof preventSyncScrollToView === 'number') { if (typeof preventSyncScrollToView === 'number') {
@ -324,20 +318,20 @@ function syncScrollToView(event, preventAnimate) {
return; return;
} }
if (!scrollMap || !lineHeightMap) { if (!scrollMap || !lineHeightMap) {
buildMap(function () { buildMap(() => {
syncScrollToView(event, preventAnimate); syncScrollToView(event, preventAnimate);
}); });
return; return;
} }
if (viewScrolling) return; if (viewScrolling) return;
var lineNo, posTo; let lineNo, posTo;
var topDiffPercent, posToNextDiff; let topDiffPercent, posToNextDiff;
var scrollInfo = editor.getScrollInfo(); const scrollInfo = editor.getScrollInfo();
var 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
var 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;
posTo = scrollMap[lineNo + 1]; posTo = scrollMap[lineNo + 1];
@ -353,7 +347,7 @@ function syncScrollToView(event, preventAnimate) {
if (preventAnimate) { if (preventAnimate) {
viewArea.scrollTop(posTo); viewArea.scrollTop(posTo);
} else { } else {
var posDiff = Math.abs(viewArea.scrollTop() - posTo); const posDiff = Math.abs(viewArea.scrollTop() - posTo);
var duration = posDiff / 50; var duration = posDiff / 50;
duration = duration >= 100 ? duration : 100; duration = duration >= 100 ? duration : 100;
viewArea.stop(true, true).animate({ viewArea.stop(true, true).animate({
@ -369,10 +363,3 @@ function syncScrollToView(event, preventAnimate) {
function editScrollingTimeoutInner() { function editScrollingTimeoutInner() {
editScrolling = false; editScrolling = false;
} }
module.exports = {
setupSyncAreas: setupSyncAreas,
clearMap: clearMap,
syncScrollToEdit: syncScrollToEdit,
syncScrollToView: syncScrollToView
};

BIN
public/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View file

@ -343,7 +343,9 @@ ot.CodeMirrorAdapter = (function (global) {
}; };
CodeMirrorAdapter.prototype.applyOperation = function (operation) { CodeMirrorAdapter.prototype.applyOperation = function (operation) {
this.ignoreNextChange = true; if (!operation.isNoop()) {
this.ignoreNextChange = true;
}
CodeMirrorAdapter.applyOperationToCodeMirror(operation, this.cm); CodeMirrorAdapter.applyOperationToCodeMirror(operation, this.cm);
}; };

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<%- include head %> <%- include hackmd/head %>
<link rel="stylesheet" href="<%- url %>/css/center.css"> <link rel="stylesheet" href="<%- url %>/css/center.css">
</head> </head>
<body> <body>
<%- include header %> <%- include hackmd/header %>
<div class="container-fluid text-center"> <div class="container-fluid text-center">
<div class="vertical-center-row"> <div class="vertical-center-row">
<h1><%- code %> <%- detail %> <small><%- msg %></small></h1> <h1><%- code %> <%- detail %> <small><%- msg %></small></h1>
</div> </div>
</div> </div>
<%- include footer %> <%- include hackmd/footer %>
</body> </body>
</html> </html>

View file

@ -2,14 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<%- include head %> <%- include hackmd/head %>
</head> </head>
<body> <body>
<%- include header %> <%- include hackmd/header %>
<%- include body %> <%- include hackmd/body %>
<%- include footer %> <%- include hackmd/footer %>
<%- include foot %> <%- include hackmd/foot %>
</body> </body>
</html> </html>

View file

@ -244,7 +244,7 @@
</div> </div>
</div> </div>
</div> </div>
<%- include refresh-modal %> <%- include ../shared/refresh-modal %>
<%- include signin-modal %> <%- include ../shared/signin-modal %>
<%- include help-modal %> <%- include ../shared/help-modal %>
<%- include revision-modal %> <%- include ../shared/revision-modal %>

View file

@ -11,18 +11,18 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/6.0.0/mermaid.min.js" integrity="sha256-Yabf6Mj1TPKd6h4F6z5xRR1/2son0Wg8NhvjYnhcQcY=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.min.js" integrity="sha256-1uR+pqxH5fN/rOZcZTb9c5+bR3OIYEKzu2sI11Dnj9A=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js" integrity="sha256-Cv5v4i4SuYvwRYzIONifZjoc99CkwfncROMSWat1cVA=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js" integrity="sha256-Cv5v4i4SuYvwRYzIONifZjoc99CkwfncROMSWat1cVA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.2/socket.io.min.js" integrity="sha256-WKvqiY0jZHWQZIohYEmr9KUC5rEaYEOFTq+ByllJK8w=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.2/socket.io.min.js" integrity="sha256-WKvqiY0jZHWQZIohYEmr9KUC5rEaYEOFTq+ByllJK8w=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" 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 src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.4.1/viz.js" integrity="sha256-U0a9HpXT7zG0N3tVzo58B5S+QXUxo4FdBIjrBMYrxZI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.7.0/viz.js" integrity="sha256-8t+rndrF+TU4JtelmOH1lDHTMe2ovhO2UbzDArp5lY8=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.8/validator.min.js" integrity="sha256-LHeY7YoYJ0SSXbCx7sR14Pqna+52moaH3bhv0Mjzd/M=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.8/validator.min.js" integrity="sha256-LHeY7YoYJ0SSXbCx7sR14Pqna+52moaH3bhv0Mjzd/M=" crossorigin="anonymous" defer></script>
<%- include build/index-scripts %> <%- include ../build/index-scripts %>
<% } else { %> <% } else { %>
<script src="<%- url %>/build/MathJax/MathJax.js" defer></script> <script src="<%- url %>/build/MathJax/MathJax.js" defer></script>
<script src="<%- url %>/build/MathJax/config/TeX-AMS-MML_HTMLorMML.js" defer></script> <script src="<%- url %>/build/MathJax/config/TeX-AMS-MML_HTMLorMML.js" defer></script>
<%- include build/index-pack-scripts %> <%- include ../build/index-pack-scripts %>
<% } %> <% } %>

View file

@ -14,9 +14,9 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css" integrity="sha256-3iu9jgsy9TpTwXKb7bNQzqWekRX7pPK+2OLj3R922fo=" crossorigin="anonymous" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css" integrity="sha256-3iu9jgsy9TpTwXKb7bNQzqWekRX7pPK+2OLj3R922fo=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/octicons/3.5.0/octicons.min.css" integrity="sha256-QiWfLIsCT02Sdwkogf6YMiQlj4NE84MKkzEMkZnMGdg=" crossorigin="anonymous" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/octicons/3.5.0/octicons.min.css" integrity="sha256-QiWfLIsCT02Sdwkogf6YMiQlj4NE84MKkzEMkZnMGdg=" crossorigin="anonymous" />
<link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'> <link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'>
<%- include build/index-header %> <%- include ../build/index-header %>
<% } else { %> <% } else { %>
<link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'> <link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'>
<%- include build/index-pack-header %> <%- include ../build/index-pack-header %>
<% } %> <% } %>
<%- include polyfill %> <%- include ../shared/polyfill %>

View file

@ -2,219 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <%- include index/head %>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="mobile-web-app-capable" content="yes">
<meta name="description" content="<%= __('Best way to write and share your knowledge in markdown.') %>">
<meta name="keywords" content="Collaborative, Markdown, Notes">
<title>HackMD - <%= __('Collaborative markdown notes') %></title>
<link rel="icon" type="image/png" href="<%- url %>/favicon.png">
<link rel="apple-touch-icon" href="<%- url %>/apple-touch-icon.png">
<% if(useCDN) { %>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-social/4.9.0/bootstrap-social.min.css" integrity="sha256-02JtFTurpwBjQJ6q13iJe82/NF0RbZlJroDegK5g87Y=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.min.css" integrity="sha256-ijlUKKj3hJCiiT2HWo1kqkI79NTEYpzOsw5Rs3k42dI=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2-bootstrap.min.css" integrity="sha256-NAWFcNIZdH+TS1xpWujF/EB/Y8gwBbEOCoaK/eqaer8=" crossorigin="anonymous" />
<%- include build/cover-header %>
<% } else { %>
<%- include build/cover-pack-header %>
<% } %>
<%- include polyfill %>
</head> </head>
<body> <body>
<div class="site-wrapper"> <%- include index/header %>
<div class="site-wrapper-inner"> <%- include index/body %>
<div class="cover-container"> <%- include index/footer %>
<%- include index/foot %>
<div class="masthead clearfix">
<div class="inner">
<h3 class="masthead-brand"></h3>
<nav>
<ul class="nav masthead-nav">
<li class="ui-home<% if(!signin) { %> active<% } %>"><a href="#"><%= __('Intro') %></a>
</li>
<li class="ui-history<% if(signin) { %> active<% } %>"><a href="#"><%= __('History') %></a>
</li>
<div class="ui-signin" style="float: right; margin-top: 8px;<% if(signin) { %> display: none;<% } %>">
<% if(allowAnonymous) { %>
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-link"><i class="fa fa-plus"></i> <%= __('New guest note') %></a>
<% } %>
<% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button>
<% } %>
</div>
<div class="ui-signout" style="float: right; margin-top: 8px;<% if(!signin) { %> display: none;<% } %>">
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-link"><i class="fa fa-plus"></i> <%= __('New note') %></a>
<span class="ui-profile dropdown pull-right">
<button id="profileLabel" class="btn btn-sm btn-link ui-profile-label" style="padding-right: 0;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="ui-avatar" width="20" height="20"><span class="hidden-xs hidden-sm">&ensp;<span class="ui-name"></span></span>&ensp;<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="profileLabel">
<li><a href="<%- url %>/logout"><i class="fa fa-sign-out fa-fw"></i> <%= __('Sign Out') %></a></li>
</ul>
</span>
</div>
</ul>
</nav>
</div>
</div>
<div id="home" class="section"<% if(signin) { %> style="display:none;"<% } %>>
<div class="inner cover">
<h1 class="cover-heading"><i class="fa fa-file-text"></i> HackMD</h1>
<p class="lead">
<%= __('Best way to write and share your knowledge in markdown.') %>
</p>
<% if (infoMessage && infoMessage.length > 0) { %>
<div class="alert alert-info" style="max-width: 400px; margin: 0 auto;"><%= infoMessage %></div>
<% } %>
<% if (errorMessage && errorMessage.length > 0) { %>
<div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
<% } %>
<% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<span class="ui-signin">
<br>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a>
</span>
<span class="ui-or"><%= __('or') %></span>
<% } %>
<span class="ui-signin">
<a type="button" href="<%- url %>/features" class="btn btn-lg btn-primary" style="min-width: 200px;"><%= __('Explore all features') %></a>
<br>
<br>
</span>
<div class="lead row" style="width: 90%; margin: 0 auto;">
<div class="col-md-4 inner">
<a href="<%- url %>/features#share-notes">
<i class="fa fa-bolt fa-3x"></i>
<h4><%= __('Collaborate with URL') %></h4>
</a>
</div>
<div class="col-md-4 inner">
<a href="<%- url %>/features#mathjax">
<i class="fa fa-bar-chart fa-3x"></i>
<h4><%= __('Support charts and MathJax') %></h4>
</a>
</div>
<div class="col-md-4 inner">
<a href="<%- url %>/features#slide-mode">
<i class="fa fa-tv fa-3x"></i>
<h4><%= __('Support slide mode') %></h4>
</a>
</div>
</div>
</div>
</div>
<div id="history" class="section"<% if(!signin) { %> style="display:none;"<% } %>>
<div class="ui-signin"<% if(signin) { %> style="display:none;"<% } %>>
<p><%= __('Below is the history from browser') %></p>
</div>
<br>
<form class="form-inline">
<div class="form-group" style="vertical-align: bottom;">
<input class="form-control ui-use-tags" placeholder="<%= __('Select tags...') %>" />
</div>
<div class="form-group">
<input class="search form-control" placeholder="<%= __('Search keyword...') %>" />
</div>
<a href="#" class="sort btn btn-default" data-sort="text" title="<%= __('Sort by title') %>">
<%= __('Title') %>
</a>
<a href="#" class="sort btn btn-default" data-sort="timestamp" title="<%= __('Sort by time') %>">
<%= __('Time') %>
</a>
<span class="hidden-xs hidden-sm">
<a href="#" class="btn btn-default ui-save-history" title="<%= __('Export history') %>"><i class="fa fa-save"></i></a>
<span class="btn btn-default btn-file ui-open-history" title="<%= __('Import history') %>">
<i class="fa fa-folder-open-o"></i><input type="file" />
</span>
<a href="#" class="btn btn-default ui-clear-history" title="<%= __('Clear history') %>" data-toggle="modal" data-target=".delete-modal"><i class="fa fa-trash-o"></i></a>
</span>
<a href="#" class="btn btn-default ui-refresh-history" title="<%= __('Refresh history') %>"><i class="fa fa-refresh"></i></a>
</form>
<h4 class="ui-nohistory" style="display:none;">
<%= __('No history') %>
</h4>
<a href="#" class="btn btn-primary ui-import-from-browser" style="display:none;"><%= __('Import from browser') %></a>
<ul id="history-list" class="list">
</ul>
<ul class="pagination"></ul>
</div>
<div class="mastfoot">
<div class="inner">
<h6 class="social-foot">
<iframe src="//ghbtns.com/github-btn.html?user=hackmdio&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
</h6>
<p>
&copy; 2017 <a href="https://www.facebook.com/hackmdio" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a>
</p>
<select class="ui-locale">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ja">日本語</option>
<option value="es">Español</option>
<option value="el">Ελληνικά</option>
<option value="pt">Português</option>
<option value="it">italiano</option>
<option value="tr">Türkçe</option>
<option value="ru">Русский</option>
<option value="nl">Nederlands</option>
<option value="hr">hrvatski jezik</option>
<option value="pl">język polski</option>
<option value="uk">Українська</option>
<option value="hi">हिन्दी</option>
<option value="sv">svenska</option>
<option value="eo">Esperanto</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- delete modal -->
<div class="modal fade delete-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel"><%= __('Are you sure?') %></h4>
</div>
<div class="modal-body" style="color:black;">
<h5 class="ui-delete-modal-msg"></h5>
<strong class="ui-delete-modal-item"></strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><%= __('Cancel') %></button>
<button type="button" class="btn btn-danger ui-delete-modal-confirm"><%= __('Yes, do it!') %></button>
</div>
</div>
</div>
</div>
<%- include signin-modal %>
<% 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/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.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" 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/select2/3.5.2/select2.min.js" integrity="sha256-HzzZFiY4t0PIv02Tm8/R3CVvLpcjHhO1z/YAUCp4oQ4=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-url/2.3.0/url.min.js" integrity="sha256-HOZJz4x+1mn1Si84WT5XKXPtOlTytmZLnMb6n1v4+5Q=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.8/validator.min.js" integrity="sha256-LHeY7YoYJ0SSXbCx7sR14Pqna+52moaH3bhv0Mjzd/M=" crossorigin="anonymous" defer></script>
<%- include build/cover-scripts %>
<% } else { %>
<%- include build/cover-pack-scripts %>
<% } %>
</body> </body>
</html> </html>

176
public/views/index/body.ejs Normal file
View file

@ -0,0 +1,176 @@
<div class="site-wrapper">
<div class="site-wrapper-inner">
<div class="cover-container">
<div class="masthead clearfix">
<div class="inner">
<h3 class="masthead-brand"></h3>
<nav>
<ul class="nav masthead-nav">
<li class="ui-home<% if(!signin) { %> active<% } %>"><a href="#"><%= __('Intro') %></a>
</li>
<li class="ui-history<% if(signin) { %> active<% } %>"><a href="#"><%= __('History') %></a>
</li>
<div class="ui-signin" style="float: right; margin-top: 8px;<% if(signin) { %> display: none;<% } %>">
<% if(allowAnonymous) { %>
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-link"><i class="fa fa-plus"></i> <%= __('New guest note') %></a>
<% } %>
<% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button>
<% } %>
</div>
<div class="ui-signout" style="float: right; margin-top: 8px;<% if(!signin) { %> display: none;<% } %>">
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-link"><i class="fa fa-plus"></i> <%= __('New note') %></a>
<span class="ui-profile dropdown pull-right">
<button id="profileLabel" class="btn btn-sm btn-link ui-profile-label" style="padding-right: 0;" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="ui-avatar" width="20" height="20"><span class="hidden-xs hidden-sm">&ensp;<span class="ui-name"></span></span>&ensp;<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="profileLabel">
<li><a href="<%- url %>/features"><i class="fa fa-dot-circle-o fa-fw"></i> <%= __('Features') %></a></li>
<li><a href="<%- url %>/logout"><i class="fa fa-sign-out fa-fw"></i> <%= __('Sign Out') %></a></li>
</ul>
</span>
</div>
</ul>
</nav>
</div>
</div>
<div id="home" class="section"<% if(signin) { %> style="display:none;"<% } %>>
<div class="inner cover">
<h1 class="cover-heading"><i class="fa fa-file-text"></i> HackMD</h1>
<p class="lead">
<%= __('Best way to write and share your knowledge in markdown.') %>
</p>
<% if (infoMessage && infoMessage.length > 0) { %>
<div class="alert alert-info" style="max-width: 400px; margin: 0 auto;"><%= infoMessage %></div>
<% } %>
<% if (errorMessage && errorMessage.length > 0) { %>
<div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
<% } %>
<% if(facebook || twitter || github || gitlab || dropbox || google || ldap || email) { %>
<span class="ui-signin">
<br>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a>
</span>
<span class="ui-or"><%= __('or') %></span>
<% } %>
<span class="ui-signin">
<a type="button" href="<%- url %>/features" class="btn btn-lg btn-primary" style="min-width: 200px;"><%= __('Explore all features') %></a>
<br>
<br>
<img src="<%- url %>/screenshot.png" class="screenshot ui-signin">
</span>
<div class="lead row" style="width: 90%; margin: 0 auto;">
<div class="col-md-4 inner">
<a href="<%- url %>/features#share-notes">
<i class="fa fa-bolt fa-3x"></i>
<h4><%= __('Collaborate with URL') %></h4>
</a>
</div>
<div class="col-md-4 inner">
<a href="<%- url %>/features#mathjax">
<i class="fa fa-bar-chart fa-3x"></i>
<h4><%= __('Support charts and MathJax') %></h4>
</a>
</div>
<div class="col-md-4 inner">
<a href="<%- url %>/features#slide-mode">
<i class="fa fa-tv fa-3x"></i>
<h4><%= __('Support slide mode') %></h4>
</a>
</div>
</div>
</div>
</div>
<div id="history" class="section"<% if(!signin) { %> style="display:none;"<% } %>>
<div class="ui-signin"<% if(signin) { %> style="display:none;"<% } %>>
<p><%= __('Below is the history from browser') %></p>
</div>
<br>
<form class="form-inline">
<div class="form-group" style="vertical-align: bottom;">
<input class="form-control ui-use-tags" placeholder="<%= __('Select tags...') %>" />
</div>
<div class="form-group">
<input class="search form-control" placeholder="<%= __('Search keyword...') %>" />
</div>
<a href="#" class="sort btn btn-default" data-sort="text" title="<%= __('Sort by title') %>">
<%= __('Title') %>
</a>
<a href="#" class="sort btn btn-default" data-sort="timestamp" title="<%= __('Sort by time') %>">
<%= __('Time') %>
</a>
<span class="hidden-xs hidden-sm">
<a href="#" class="btn btn-default ui-save-history" title="<%= __('Export history') %>"><i class="fa fa-save"></i></a>
<span class="btn btn-default btn-file ui-open-history" title="<%= __('Import history') %>">
<i class="fa fa-folder-open-o"></i><input type="file" />
</span>
<a href="#" class="btn btn-default ui-clear-history" title="<%= __('Clear history') %>" data-toggle="modal" data-target=".delete-modal"><i class="fa fa-trash-o"></i></a>
</span>
<a href="#" class="btn btn-default ui-refresh-history" title="<%= __('Refresh history') %>"><i class="fa fa-refresh"></i></a>
</form>
<h4 class="ui-nohistory" style="display:none;">
<%= __('No history') %>
</h4>
<a href="#" class="btn btn-primary ui-import-from-browser" style="display:none;"><%= __('Import from browser') %></a>
<ul id="history-list" class="list">
</ul>
<ul class="pagination"></ul>
</div>
<div class="mastfoot">
<div class="inner">
<h6 class="social-foot">
<iframe src="//ghbtns.com/github-btn.html?user=hackmdio&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
</h6>
<p>
&copy; 2017 <a href="https://www.facebook.com/hackmdio" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a>
</p>
<select class="ui-locale">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ja">日本語</option>
<option value="es">Español</option>
<option value="el">Ελληνικά</option>
<option value="pt">Português</option>
<option value="it">italiano</option>
<option value="tr">Türkçe</option>
<option value="ru">Русский</option>
<option value="nl">Nederlands</option>
<option value="hr">hrvatski jezik</option>
<option value="pl">język polski</option>
<option value="uk">Українська</option>
<option value="hi">हिन्दी</option>
<option value="sv">svenska</option>
<option value="eo">Esperanto</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- delete modal -->
<div class="modal fade delete-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel"><%= __('Are you sure?') %></h4>
</div>
<div class="modal-body" style="color:black;">
<h5 class="ui-delete-modal-msg"></h5>
<strong class="ui-delete-modal-item"></strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><%= __('Cancel') %></button>
<button type="button" class="btn btn-danger ui-delete-modal-confirm"><%= __('Yes, do it!') %></button>
</div>
</div>
</div>
</div>
<%- include ../shared/signin-modal %>

View file

@ -0,0 +1,13 @@
<% 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/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.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" 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/select2/3.5.2/select2.min.js" integrity="sha256-HzzZFiY4t0PIv02Tm8/R3CVvLpcjHhO1z/YAUCp4oQ4=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-url/2.3.0/url.min.js" integrity="sha256-HOZJz4x+1mn1Si84WT5XKXPtOlTytmZLnMb6n1v4+5Q=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.8/validator.min.js" integrity="sha256-LHeY7YoYJ0SSXbCx7sR14Pqna+52moaH3bhv0Mjzd/M=" crossorigin="anonymous" defer></script>
<%- include ../build/cover-scripts %>
<% } else { %>
<%- include ../build/cover-pack-scripts %>
<% } %>

View file

View file

@ -0,0 +1,22 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="mobile-web-app-capable" content="yes">
<meta name="description" content="<%= __('Best way to write and share your knowledge in markdown.') %>">
<meta name="keywords" content="Collaborative, Markdown, Notes">
<title>HackMD - <%= __('Collaborative markdown notes') %></title>
<link rel="icon" type="image/png" href="<%- url %>/favicon.png">
<link rel="apple-touch-icon" href="<%- url %>/apple-touch-icon.png">
<% if(useCDN) { %>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-social/4.9.0/bootstrap-social.min.css" integrity="sha256-02JtFTurpwBjQJ6q13iJe82/NF0RbZlJroDegK5g87Y=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2.min.css" integrity="sha256-ijlUKKj3hJCiiT2HWo1kqkI79NTEYpzOsw5Rs3k42dI=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/3.5.4/select2-bootstrap.min.css" integrity="sha256-NAWFcNIZdH+TS1xpWujF/EB/Y8gwBbEOCoaK/eqaer8=" crossorigin="anonymous" />
<%- include ../build/cover-header %>
<% } else { %>
<%- include ../build/cover-pack-header %>
<% } %>
<%- include ../shared/polyfill %>

View file

View file

@ -28,7 +28,7 @@
<link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'> <link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'>
<%- include build/pretty-pack-header %> <%- include build/pretty-pack-header %>
<% } %> <% } %>
<%- include polyfill %> <%- include shared/polyfill %>
</head> </head>
<body style="display:none;"> <body style="display:none;">
@ -66,7 +66,7 @@
<div id="ui-toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="display:none;"></div> <div id="ui-toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="display:none;"></div>
<% if(typeof disqus !== 'undefined' && disqus) { %> <% if(typeof disqus !== 'undefined' && disqus) { %>
<div class="container-fluid" style="max-width: 758px; margin-bottom: 40px;"> <div class="container-fluid" style="max-width: 758px; margin-bottom: 40px;">
<%- include disqus %> <%- include shared/disqus %>
</div> </div>
<% } %> <% } %>
</body> </body>
@ -84,16 +84,16 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/6.0.0/mermaid.min.js" integrity="sha256-Yabf6Mj1TPKd6h4F6z5xRR1/2son0Wg8NhvjYnhcQcY=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.min.js" integrity="sha256-1uR+pqxH5fN/rOZcZTb9c5+bR3OIYEKzu2sI11Dnj9A=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" 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 src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.4.1/viz.js" integrity="sha256-U0a9HpXT7zG0N3tVzo58B5S+QXUxo4FdBIjrBMYrxZI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.7.0/viz.js" integrity="sha256-8t+rndrF+TU4JtelmOH1lDHTMe2ovhO2UbzDArp5lY8=" crossorigin="anonymous" defer></script>
<%- include build/pretty-scripts %> <%- include build/pretty-scripts %>
<% } else { %> <% } else { %>
<script src="<%- url %>/build/MathJax/MathJax.js" defer></script> <script src="<%- url %>/build/MathJax/MathJax.js" defer></script>
<script src="<%- url %>/build/MathJax/config/TeX-AMS-MML_HTMLorMML.js" defer></script> <script src="<%- url %>/build/MathJax/config/TeX-AMS-MML_HTMLorMML.js" defer></script>
<%- include build/pretty-pack-scripts %> <%- include build/pretty-pack-scripts %>
<% } %> <% } %>
<%- include ga %> <%- include shared/ga %>

View file

@ -45,7 +45,7 @@
document.getElementsByTagName( 'head' )[0].appendChild( link ); document.getElementsByTagName( 'head' )[0].appendChild( link );
</script> </script>
<%- include polyfill %> <%- include shared/polyfill %>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@ -79,7 +79,7 @@
</div> </div>
<% if(typeof disqus !== 'undefined' && disqus) { %> <% if(typeof disqus !== 'undefined' && disqus) { %>
<div style="margin-top: 25px; margin-bottom: 15px;"> <div style="margin-top: 25px; margin-bottom: 15px;">
<%- include disqus %> <%- include shared/disqus %>
</div> </div>
<% } %> <% } %>
</div> </div>
@ -98,12 +98,12 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js" integrity="sha256-yYfngbEKv4RENfGDvNUqJTqGFcKf31NJEe9OTnnMH3Y=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-immzXfCGLhnx3Zfi9F/dUcqxEM8K3o3oTFy9Bh6HCwg=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment-with-locales.min.js" integrity="sha256-vvT7Ok9u6GbfnBPXnbM6FVDEO8E1kTdgHOFZOAXrktA=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/6.0.0/mermaid.min.js" integrity="sha256-Yabf6Mj1TPKd6h4F6z5xRR1/2son0Wg8NhvjYnhcQcY=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.min.js" integrity="sha256-1uR+pqxH5fN/rOZcZTb9c5+bR3OIYEKzu2sI11Dnj9A=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/emojify.js/1.1.0/js/emojify.min.js" integrity="sha256-VAB5tAlKBvgaxw8oJ1crWMVbdmBVl4mP/2M8MNRl+4E=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js" integrity="sha256-1O3BtOwnPyyRzOszK6P+gqaRoXHV6JXj8HkjZmPYhCI=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js" integrity="sha256-KbfTjB0WZ8vvXngdpJGY3Yp3xKk+tttbqClO11anCIU=" 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 src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.4.1/viz.js" integrity="sha256-U0a9HpXT7zG0N3tVzo58B5S+QXUxo4FdBIjrBMYrxZI=" crossorigin="anonymous" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/1.7.0/viz.js" integrity="sha256-8t+rndrF+TU4JtelmOH1lDHTMe2ovhO2UbzDArp5lY8=" crossorigin="anonymous" defer></script>
<%- include build/slide-scripts %> <%- include build/slide-scripts %>
<% } else { %> <% } else { %>
<script src="<%- url %>/build/MathJax/MathJax.js" defer></script> <script src="<%- url %>/build/MathJax/MathJax.js" defer></script>
@ -113,4 +113,4 @@
</body> </body>
</html> </html>
<%- include ga %> <%- include shared/ga %>

View file

@ -3,6 +3,7 @@ var webpack = require('webpack');
var path = require('path'); var path = require('path');
var ExtractTextPlugin = require("extract-text-webpack-plugin"); var ExtractTextPlugin = require("extract-text-webpack-plugin");
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = [Object.assign({}, baseConfig, { module.exports = [Object.assign({}, baseConfig, {
plugins: baseConfig.plugins.concat([ plugins: baseConfig.plugins.concat([
@ -11,12 +12,14 @@ module.exports = [Object.assign({}, baseConfig, {
'NODE_ENV': JSON.stringify('production') 'NODE_ENV': JSON.stringify('production')
} }
}), }),
new webpack.optimize.UglifyJsPlugin({ new ParallelUglifyPlugin({
compress: { uglifyJS: {
warnings: false compress: {
}, warnings: false
mangle: false, },
sourceMap: false mangle: false,
sourceMap: false
}
}), }),
new ExtractTextPlugin("[name].[hash].css") new ExtractTextPlugin("[name].[hash].css")
]), ]),

View file

@ -158,6 +158,7 @@ module.exports = {
"bootstrap" "bootstrap"
], ],
cover: [ cover: [
"babel-polyfill",
path.join(__dirname, 'public/js/cover.js') path.join(__dirname, 'public/js/cover.js')
], ],
"cover-styles-pack": [ "cover-styles-pack": [
@ -168,6 +169,7 @@ module.exports = {
path.join(__dirname, 'node_modules/select2/select2-bootstrap.css'), path.join(__dirname, 'node_modules/select2/select2-bootstrap.css'),
], ],
"cover-pack": [ "cover-pack": [
"babel-polyfill",
"bootstrap-validator", "bootstrap-validator",
"script!listPagnation", "script!listPagnation",
"expose?select2!select2", "expose?select2!select2",
@ -176,6 +178,7 @@ module.exports = {
path.join(__dirname, 'public/js/cover.js') path.join(__dirname, 'public/js/cover.js')
], ],
index: [ index: [
"babel-polyfill",
"script!jquery-ui-resizable", "script!jquery-ui-resizable",
"script!js-url", "script!js-url",
"expose?filterXSS!xss", "expose?filterXSS!xss",
@ -221,6 +224,7 @@ module.exports = {
path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
], ],
"index-pack": [ "index-pack": [
"babel-polyfill",
"expose?Spinner!spin.js", "expose?Spinner!spin.js",
"script!jquery-ui-resizable", "script!jquery-ui-resizable",
"bootstrap-validator", "bootstrap-validator",
@ -251,6 +255,7 @@ module.exports = {
path.join(__dirname, 'public/js/index.js') path.join(__dirname, 'public/js/index.js')
], ],
pretty: [ pretty: [
"babel-polyfill",
"expose?filterXSS!xss", "expose?filterXSS!xss",
"flowchart.js", "flowchart.js",
"js-sequence-diagrams", "js-sequence-diagrams",
@ -270,6 +275,7 @@ module.exports = {
path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
], ],
"pretty-pack": [ "pretty-pack": [
"babel-polyfill",
"expose?jsyaml!js-yaml", "expose?jsyaml!js-yaml",
"script!mermaid", "script!mermaid",
"expose?moment!moment", "expose?moment!moment",
@ -285,6 +291,7 @@ module.exports = {
path.join(__dirname, 'public/js/pretty.js') path.join(__dirname, 'public/js/pretty.js')
], ],
slide: [ slide: [
"babel-polyfill",
"bootstrap-tooltip", "bootstrap-tooltip",
"expose?filterXSS!xss", "expose?filterXSS!xss",
"flowchart.js", "flowchart.js",
@ -304,6 +311,7 @@ module.exports = {
path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
], ],
"slide-pack": [ "slide-pack": [
"babel-polyfill",
"expose?jQuery!expose?$!jquery", "expose?jQuery!expose?$!jquery",
"velocity-animate", "velocity-animate",
"imports?$=jquery!jquery-mousewheel", "imports?$=jquery!jquery-mousewheel",
@ -372,6 +380,10 @@ module.exports = {
loaders: [{ loaders: [{
test: /\.json$/, test: /\.json$/,
loader: 'json-loader' loader: 'json-loader'
}, {
test: /\.js$/,
loader: 'babel',
exclude: [/node_modules/, /public\/vendor/]
}, { }, {
test: /\.css$/, test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader') loader: ExtractTextPlugin.extract('style-loader', 'css-loader')