diff options
62 files changed, 1381 insertions, 1404 deletions
diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..cbd3ac60 --- /dev/null +++ b/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "transform-runtime" + ] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..121531af --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +*.min.js @@ -16,7 +16,6 @@ ], "array-callback-return": "error", "arrow-body-style": "error", - "arrow-parens": "error", "arrow-spacing": "error", "block-scoped-var": "off", "block-spacing": "error", @@ -123,7 +122,7 @@ "no-extend-native": "error", "no-extra-bind": "error", "no-extra-label": "error", - "no-extra-parens": "error", + "no-extra-parens": "warn", "no-floating-decimal": "error", "no-global-assign": "error", "no-implicit-coercion": "error", @@ -195,7 +194,7 @@ "no-unneeded-ternary": "error", "no-unsafe-negation": "error", "no-unused-expressions": "error", - "no-use-before-define": "error", + "no-use-before-define": "warn", "no-useless-call": "error", "no-useless-computed-key": "error", "no-useless-concat": "error", @@ -18,7 +18,6 @@ backups/ # ignore config files config.json -public/js/config.js .sequelizerc # ignore webpack build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..ed8ab42f --- /dev/null +++ b/.travis.yml @@ -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 @@ -1,7 +1,9 @@ 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. Inspired by Hackpad, with more focus on speed and flexibility. @@ -48,9 +50,9 @@ Browsers Requirement 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` -- 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 --- @@ -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. 3. Setup the configs, see more below 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) 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) 2. `git pull` or do whatever that updates the files 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 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 @@ -97,19 +99,9 @@ Configuration files There are some configs you need to change in the files below ``` -./config.json --- for server settings -./public/js/config.js --- for client settings +./config.json ----application 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) --- @@ -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_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_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_FACEBOOK_CLIENTID | no example | Facebook API client id | | 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_GOOGLE_CLIENTID | no example | Google API client id | | 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_BINDCREDENTIALS | no example | bindCredentials for LDAP access | -| 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_SEARCHFILTER | (uid={{username}}) | LDAP filter to search with | +| 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_SEARCHFILTER | `(uid={{username}})` | LDAP filter 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_PROVIDERNAME | My institution | Optional name to be displayed at login form indicating the LDAP provider | +| 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_IMGUR_CLIENTID | no example | Imgur API client id | | 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`) | @@ -158,7 +151,7 @@ Environment variables (will overwrite other server configs) | HMD_S3_REGION | `ap-northeast-1` | AWS S3 region | | HMD_S3_BUCKET | no example | AWS S3 bucket name | -Server settings `config.json` +Application settings `config.json` --- | 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`) | | 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 | +| 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 | | 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) | @@ -207,7 +201,7 @@ Third-party integration api key settings | ------- | --------- | ----------- | | facebook, twitter, github, gitlab, dropbox, google, ldap | environment variables or `config.json` | for signin | | 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 --- @@ -230,3 +224,7 @@ Additionally, now can show other clients' selections. See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) **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 @@ -26,7 +26,6 @@ var validator = require('validator'); var config = require("./lib/config.js"); var logger = require("./lib/logger.js"); var auth = require("./lib/auth.js"); -var history = require("./lib/history.js"); var response = require("./lib/response.js"); var models = require("./lib/models"); @@ -443,6 +442,7 @@ app.get('/logout', function (req, res) { req.logout(); res.redirect(config.serverurl + '/'); }); +var history = require("./lib/history.js"); //get history app.get('/history', history.historyGet); //post history @@ -502,7 +502,7 @@ app.post('/uploadimage', function (req, res) { switch (config.imageUploadType) { case 'filesystem': 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; @@ -608,7 +608,7 @@ function startListen() { // sync db then start listen models.sequelize.sync().then(function () { // check if realtime is ready - if (history.isReady() && realtime.isReady()) { + if (realtime.isReady()) { models.Revision.checkAllNotesRevision(function (err, notes) { if (err) throw new Error(err); if (!notes || notes.length <= 0) return startListen(); @@ -639,7 +639,7 @@ function handleTermSignals() { }, 0); }); var checkCleanTimer = setInterval(function () { - if (history.isReady() && realtime.isReady()) { + if (realtime.isReady()) { models.Revision.checkAllNotesRevision(function (err, notes) { if (err) return logger.error(err); if (!notes || notes.length <= 0) { @@ -28,8 +28,6 @@ EOF EOF - cp public/js/config.js.example public/js/config.js - # build app - npm run build:prod + npm run build fi @@ -1,5 +1,7 @@ #!/bin/bash +set -e + # run command at repo root CURRENT_PATH=$PWD if [ -d .git ]; then @@ -21,10 +23,6 @@ if [ ! -f config.json ]; then cp config.json.example config.json fi -if [ ! -f publis/js/config.js ]; then - cp public/js/config.js.example public/js/config.js -fi - if [ ! -f .sequelizerc ]; then cp .sequelizerc.example .sequelizerc fi diff --git a/config.json.example b/config.json.example index 57669cf9..9ee00c09 100644 --- a/config.json.example +++ b/config.json.example @@ -2,18 +2,13 @@ "test": { "db": { "dialect": "sqlite", - "storage": "./db.hackmd.sqlite" + "storage": ":memory:" } }, "development": { - "domain": "localhost", "db": { - "username": "", - "password": "", - "database": "hackmd", - "host": "localhost", - "port": "3306", - "dialect": "mysql" + "dialect": "sqlite", + "storage": "./db.hackmd.sqlite" } }, "production": { @@ -45,11 +40,13 @@ }, "dropbox": { "clientID": "change this", - "clientSecret": "change this" + "clientSecret": "change this", + "appKey": "change this" }, "google": { "clientID": "change this", - "clientSecret": "change this" + "clientSecret": "change this", + "apiKey": "change this" }, "ldap": { "url": "ldap://change_this", diff --git a/lib/config.js b/lib/config.js index ab2f67b5..1e5838e8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,4 +1,5 @@ // external modules +var fs = require('fs'); var path = require('path'); 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 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 -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 || {}; // 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, clientSecret: handleDockerSecret('gitlab_clientSecret') || process.env.HMD_GITLAB_CLIENTSECRET } : 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, clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_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')) ? { - clientID: process.env.HMD_GOOGLE_CLIENTID, - clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET -} : config.google || false; -var ldap = config.ldap || ( +} : (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'))) ? { + clientID: handleDockerSecret('google_clientID') || process.env.HMD_GOOGLE_CLIENTID, + clientSecret: handleDockerSecret('google_clientSecret') || process.env.HMD_GOOGLE_CLIENTSECRET +} : (config.google && config.google.clientID && config.google.clientSecret && config.google) || false; +var ldap = config.ldap || (( process.env.HMD_LDAP_URL || process.env.HMD_LDAP_BINDDN || process.env.HMD_LDAP_BINDCREDENTIALS || @@ -107,10 +117,9 @@ var ldap = config.ldap || ( process.env.HMD_LDAP_SEARCHBASE || process.env.HMD_LDAP_SEARCHFILTER || process.env.HMD_LDAP_SEARCHATTRIBUTES || + process.env.HMD_LDAP_TLS_CA || process.env.HMD_LDAP_PROVIDERNAME -) || false; -if (ldap == true) - ldap = {}; +) ? {} : false); if (process.env.HMD_LDAP_URL) ldap.url = process.env.HMD_LDAP_URL; if (process.env.HMD_LDAP_BINDDN) @@ -127,9 +136,17 @@ if (process.env.HMD_LDAP_SEARCHATTRIBUTES) ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES; if (process.env.HMD_LDAP_TLS_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) { ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME; @@ -169,6 +186,7 @@ module.exports = { usecdn: usecdn, allowanonymous: allowanonymous, allowfreeurl: allowfreeurl, + defaultpermission: defaultpermission, dburl: dburl, db: db, sslkeypath: path.join(cwd, sslkeypath), diff --git a/lib/history.js b/lib/history.js index 2723422c..e7fb3087 100644 --- a/lib/history.js +++ b/lib/history.js @@ -1,7 +1,6 @@ //history //external modules var async = require('async'); -var moment = require('moment'); //core var config = require("./config.js"); @@ -14,45 +13,32 @@ var History = { historyGet: historyGet, historyPost: historyPost, historyDelete: historyDelete, - isReady: isReady, updateHistory: updateHistory }; -var caches = {}; -//update when the history is dirty -var updater = setInterval(function () { - var deleted = []; - 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 getHistory(userid, callback) { + models.User.findOne({ + where: { + id: userid } - }, function (err) { - if (err) return logger.error('history updater error', err); + }).then(function (user) { + 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({ - history: JSON.stringify(history) + history: JSON.stringify(parseHistoryToArray(history)) }, { where: { id: userid @@ -60,72 +46,27 @@ function finishUpdateHistory(userid, history, callback) { }).then(function (count) { return callback(null, count); }).catch(function (err) { + logger.error('set history failed: ' + err); return callback(err, null); }); } -function isReady() { - 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) { +function updateHistory(userid, noteId, document, time) { if (userid && noteId && typeof document !== 'undefined') { getHistory(userid, function (err, history) { if (err || !history) return; - if (!caches[userid].history[noteId]) { - caches[userid].history[noteId] = {}; + if (!history[noteId]) { + history[noteId] = {}; } - var noteHistory = caches[userid].history[noteId]; + var noteHistory = history[noteId]; var noteInfo = models.Note.parseNoteInfo(document); noteHistory.id = noteId; noteHistory.text = noteInfo.title; - noteHistory.time = moment().valueOf(); + noteHistory.time = time || Date.now(); 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); } if (Array.isArray(history)) { - setHistory(req.user.id, history); - caches[req.user.id].isDirty = true; - res.end(); + setHistory(req.user.id, history, function (err, count) { + if (err) return response.errorInternalError(res); + res.end(); + }); } else { return response.errorBadRequest(res); } @@ -186,11 +128,13 @@ function historyPost(req, res) { getHistory(req.user.id, function (err, history) { if (err) return response.errorInternalError(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') { - caches[req.user.id].history[noteId].pinned = (req.body.pinned === 'true'); - caches[req.user.id].isDirty = true; - res.end(); + history[noteId].pinned = (req.body.pinned === 'true'); + setHistory(req.user.id, history, function (err, count) { + if (err) return response.errorInternalError(res); + res.end(); + }); } else { return response.errorBadRequest(res); } @@ -205,16 +149,19 @@ function historyDelete(req, res) { if (req.isAuthenticated()) { var noteId = req.params.noteId; if (!noteId) { - setHistory(req.user.id, []); - caches[req.user.id].isDirty = true; - res.end(); + setHistory(req.user.id, [], function (err, count) { + if (err) return response.errorInternalError(res); + res.end(); + }); } else { getHistory(req.user.id, function (err, history) { if (err) return response.errorInternalError(res); if (!history) return response.errorNotFound(res); - delete caches[req.user.id].history[noteId]; - caches[req.user.id].isDirty = true; - res.end(); + delete history[noteId]; + setHistory(req.user.id, history, function (err, count) { + if (err) return response.errorInternalError(res); + res.end(); + }); }); } } else { diff --git a/lib/migrations/20161009040430-support-delete-note.js b/lib/migrations/20161009040430-support-delete-note.js index f478b6fe..92ff6f7b 100644 --- a/lib/migrations/20161009040430-support-delete-note.js +++ b/lib/migrations/20161009040430-support-delete-note.js @@ -6,6 +6,6 @@ module.exports = { }, down: function (queryInterface, Sequelize) { - queryInterface.removeColumn('Notes', 'deletedAt', Sequelize.DATE); + queryInterface.removeColumn('Notes', 'deletedAt'); } }; diff --git a/lib/migrations/20161201050312-support-email-signin.js b/lib/migrations/20161201050312-support-email-signin.js index bdea7c8c..b5aaf777 100644 --- a/lib/migrations/20161201050312-support-email-signin.js +++ b/lib/migrations/20161201050312-support-email-signin.js @@ -7,7 +7,7 @@ module.exports = { }, down: function (queryInterface, Sequelize) { - queryInterface.removeColumn('Users', 'email', Sequelize.TEXT); - queryInterface.removeColumn('Users', 'password', Sequelize.TEXT); + queryInterface.removeColumn('Users', 'email'); + queryInterface.removeColumn('Users', 'password'); } }; diff --git a/lib/models/note.js b/lib/models/note.js index 86112973..8b38d3f9 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -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.ownerId) { - note.permission = "editable"; + note.permission = config.defaultpermission; } else { note.permission = "freely"; } diff --git a/lib/models/user.js b/lib/models/user.js index 7d27242c..dd93bf78 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -79,39 +79,54 @@ module.exports = function (sequelize, DataTypes) { if (profile) { profile = { name: profile.displayName || profile.username, - photo: User.parsePhotoByProfile(profile) + photo: User.parsePhotoByProfile(profile), + biggerphoto: User.parsePhotoByProfile(profile, true) } } return profile; }, - parsePhotoByProfile: function (profile) { + parsePhotoByProfile: function (profile, bigger) { var photo = null; switch (profile.provider) { 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; 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; 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; 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; case "dropbox": //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; 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; case "ldap": //no image api provided, //use gravatar if email exists, //otherwise generate a letter avatar 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 { photo = letterAvatars(profile.username); } @@ -123,7 +138,8 @@ module.exports = function (sequelize, DataTypes) { var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email); return { name: email.substring(0, email.lastIndexOf("@")), - photo: photoUrl += '?s=96' + photo: photoUrl += '?s=96', + biggerphoto: photoUrl += '?s=400' }; } } diff --git a/lib/realtime.js b/lib/realtime.js index fadea4f2..c1db6886 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -122,6 +122,12 @@ function updateNote(note, callback) { } }).then(function (_note) { 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.lastchangeuserId != note.lastchangeuser) { models.User.findOne({ @@ -348,9 +354,12 @@ function clearSocketQueue(queue, socket) { } function connectNextSocket() { - isConnectionBusy = false; - if (connectionSocketQueue.length > 0) - startConnection(connectionSocketQueue[0]); + setTimeout(function () { + isConnectionBusy = false; + if (connectionSocketQueue.length > 0) { + startConnection(connectionSocketQueue[0]); + } + }, 1); } function interruptConnection(socket, note, user) { @@ -405,10 +414,7 @@ function finishConnection(socket, note, user) { note.server.setColor(socket, user.color); // update user note history - setTimeout(function () { - var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id); - if (note.server) history.updateHistory(user.userid, noteId, note.server.document); - }, 0); + updateHistory(user.userid, note); emitOnlineUsers(socket); emitRefresh(socket); @@ -497,6 +503,7 @@ function startConnection(socket) { lastchangeuserprofile: lastchangeuserprofile, socks: [], users: {}, + tempUsers: {}, createtime: moment(createtime).valueOf(), updatetime: moment(updatetime).valueOf(), server: server, @@ -687,15 +694,17 @@ function operationCallback(socket, operation) { return logger.error('operation callback failed: ' + err); }); } - // update user note history - setTimeout(function() { - var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id); - if (note.server) history.updateHistory(userId, noteId, note.server.document); - }, 0); - + note.tempUsers[userId] = Date.now(); } - // save authorship - note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship); + // save authorship - use timer here because it's an O(n) complexity algorithm + 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) { @@ -925,4 +934,4 @@ function connection(socket) { }); } -module.exports = realtime; +module.exports = realtime;
\ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 4d8ba05f..41d89777 100644 --- a/locales/de.json +++ b/locales/de.json @@ -47,7 +47,7 @@ "View": "Anzeigen", "Both": "Beides", "Help": "Hilfe", - "Upload Image": "Foto hochloaden", + "Upload Image": "Foto hochladen", "Menu": "Menü", "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", diff --git a/locales/el.json b/locales/el.json index ea68aac3..8f65dfa2 100644 --- a/locales/el.json +++ b/locales/el.json @@ -1,7 +1,7 @@ { "Collaborative markdown notes": "Συνεργατικές σημειώσεις 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": "Εισαγωγή", "History": "Ιστορία", "New guest note": "Νέα σημείωση επισκέπτη", @@ -54,9 +54,9 @@ "Refresh to update.": "Ανανεώστε για ενημέρωση", "New version available!": "Νέα διαθέσιμη έκδοση ", "See releases notes here": "Δείτε τις κυκλοφορίες της σημείωσης εδώ", - "Refresh to enjoy new features.": "Ανανεώστε για να δείτε τις κανούργίες λειτουργίες", + "Refresh to enjoy new features.": "Ανανεώστε για να δείτε τις κανούργιες λειτουργίες", "Your user state has changed.": "Η κατάσταση χρήστη έχει αλλάξει.", - "Refresh to load new user state.": "Ανανεώστε για να φορτωσετε την νέα κατάσταση χρήστη.", + "Refresh to load new user state.": "Ανανεώστε για να φορτώσετε την νέα κατάσταση χρήστη.", "Refresh": "Ανανέωση", "Contacts": "Επαφές", "Report an issue": "Αναφέρετε ένα θέμα", @@ -73,32 +73,32 @@ "Ordered List": "Αριθμημένη λίστα", "Todo List": "Todo List", "Blockquote": "Παράγραφος", - "Bold font": "Εντονη γραμματόσειρά", - "Italics font": "Italics γραμματόσειρά", - "Strikethrough": "Διακριτή διαγραφή", + "Bold font": "Εντονη γραμματοσειρά", + "Italics font": "Πλάγια γραμματοσειρά", + "Strikethrough": "Διαγραμένη γραμματοσειρά", "Inserted text": "Εισαγμένο κείμενο", - "Marked text": "Επιλεγμένο κέιμενο", + "Marked text": "Επιλεγμένο κείμενο", "Link": "Σύνδεσμος", "Image": "Εικόνα", "Code": "Κώδικας", "Externals": "Εξωτερικά", - "This is a alert area.": "Αυτή είνια μια περιοχή ειδοποίησης", + "This is a alert area.": "Αυτή είναι μια περιοχή ειδοποίησης", "Revert": "Επαναστροφή", "Import from clipboard": "Εισαγωγή από πρόχειρο", - "Paste your markdown or webpage here...": "Επικολλήστε το markdown ή την ιστοσελίδα σας εδώ...", + "Paste your markdown or webpage here...": "Επικολλήστε markdown ή την ιστοσελίδα σας εδώ...", "Clear": "Καθαρισμός", "This note is locked": "Η σημείωση είναι κλειδωμένη", - "Sorry, only owner can edit this note.": "Συγνώμη, μόνο ο ιδικτήτης μπορεί να επεξεργαστεί αυτη την σημείωση.", + "Sorry, only owner can edit this note.": "Συγνώμη, μόνο ο ιδιοκτήτης μπορεί να επεξεργαστεί αυτη την σημείωση.", "OK": "Εντάξει", "Reach the limit": "Φτάσατε το όριο", "Sorry, you've reached the max length this note can be.": "Συγνώμη, φτάσατε το μέγιστο μέγεθος αυτής της σημείωσης.", - "Please reduce the content or divide it to more notes, thank you!": "Παρακαλώ μειώστε το περιεχόμενο η διαιρεστε το σε περισσότερες σημειώσεις, ευχαριστώ!", + "Please reduce the content or divide it to more notes, thank you!": "Παρακαλώ μειώστε το περιεχόμενο η διαιρέστε το σε περισσότερες σημειώσεις, ευχαριστώ!", "Import from Gist": "Εισαγωγή από Gist", "Paste your gist url here...": "Κάντε επικκόληση του gist url εδώ...", - "Import from Snippet": "Εισαγωγή απο Snippet", + "Import from Snippet": "Εισαγωγή από Snippet", "Select From Available Projects": "Eπιλογή από διαθέσιμα Projects", "Select From Available Snippets": "Eπιλογή από διαθέσιμα Snippets", "OR": "Ή", "Export to Snippet": "Eξαγωγή σε Snippet", "Select Visibility Level": "Επιλέξτε επίπεδο ορατότητας" -}
\ No newline at end of file +} diff --git a/package.json b/package.json index a13ac979..a179d93e 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "main": "app.js", "license": "MIT", "scripts": { - "build:dev": "webpack --config webpack.config.js --progress --colors --watch", - "build:prod": "webpack --config webpack.production.js --progress --colors", + "test": "npm run-script lint", + "lint": "eslint .", + "dev": "webpack --config webpack.config.js --progress --colors --watch", + "build": "webpack --config webpack.production.js --progress --colors", "postinstall": "bin/heroku", "start": "node app.js" }, @@ -71,7 +73,7 @@ "markdown-it-sup": "^1.0.0", "markdown-pdf": "^7.0.0", "mathjax": "~2.7.0", - "mermaid": "~6.0.0", + "mermaid": "~7.0.0", "meta-marked": "^0.4.2", "method-override": "^2.3.7", "moment": "^2.17.1", @@ -115,12 +117,12 @@ "validator": "^6.2.0", "velocity-animate": "^1.4.0", "visibilityjs": "^1.2.4", - "viz.js": "^1.4.1", + "viz.js": "^1.7.0", "winston": "^2.3.0", "xss": "^0.3.3" }, "engines": { - "node": ">=4.x" + "node": ">=6.x" }, "bugs": "https://github.com/hackmdio/hackmd/issues", "keywords": [ @@ -140,9 +142,17 @@ "url": "https://github.com/hackmdio/hackmd.git" }, "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", "css-loader": "^0.26.1", "ejs-loader": "^0.3.0", + "eslint": "^3.15.0", "exports-loader": "^0.6.3", "expose-loader": "^0.7.1", "extract-text-webpack-plugin": "^1.0.1", @@ -156,6 +166,7 @@ "script-loader": "^0.7.0", "style-loader": "^0.13.1", "url-loader": "^0.5.7", - "webpack": "^1.14.0" + "webpack": "^1.14.0", + "webpack-parallel-uglify-plugin": "^0.2.0" } } diff --git a/public/css/cover.css b/public/css/cover.css index a1527bfa..6e191d2b 100644 --- a/public/css/cover.css +++ b/public/css/cover.css @@ -354,6 +354,12 @@ input { color: white; } +.screenshot { + margin: 30px auto; + width: 100%; + border-radius: 3px; +} + select { color: black; } diff --git a/public/docs/features.md b/public/docs/features.md index 1c25bfea..1e9d48fa 100644 --- a/public/docs/features.md +++ b/public/docs/features.md @@ -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-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. **Only the owner of the note can change the note's permissions.** diff --git a/public/js/common.js b/public/js/common.js deleted file mode 100644 index 7eee1071..00000000 --- a/public/js/common.js +++ /dev/null @@ -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 -}; diff --git a/public/js/config.js.example b/public/js/config.js.example deleted file mode 100644 index c5de388f..00000000 --- a/public/js/config.js.example +++ /dev/null @@ -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: '' -}; diff --git a/public/js/cover.js b/public/js/cover.js index f8ffe9bf..bc6e73f9 100644 --- a/public/js/cover.js +++ b/public/js/cover.js @@ -3,37 +3,39 @@ require('./locale'); require('../css/cover.css'); require('../css/site.css'); -var common = require('./common'); -var checkIfAuth = common.checkIfAuth; -var urlpath = common.urlpath; -var resetCheckAuth = common.resetCheckAuth; -var getLoginState = common.getLoginState; -var clearLoginState = common.clearLoginState; - -var historyModule = require('./history'); -var parseStorageToHistory = historyModule.parseStorageToHistory; -var parseHistory = historyModule.parseHistory; -var getStorageHistory = historyModule.getStorageHistory; -var getHistory = historyModule.getHistory; -var saveHistory = historyModule.saveHistory; -var removeHistory = historyModule.removeHistory; -var postHistoryToServer = historyModule.postHistoryToServer; -var deleteServerHistory = historyModule.deleteServerHistory; -var parseServerToHistory = historyModule.parseServerToHistory; -var saveStorageHistoryToServer = historyModule.saveStorageHistoryToServer; -var clearDuplicatedHistory = historyModule.clearDuplicatedHistory; - -var saveAs = require('file-saver').saveAs; -var List = require('list.js'); -var S = require('string'); - -var options = { +import { + checkIfAuth, + clearLoginState, + getLoginState, + resetCheckAuth, + setloginStateChangeEvent +} from './lib/common/login'; + +import { + clearDuplicatedHistory, + deleteServerHistory, + getHistory, + getStorageHistory, + parseHistory, + parseServerToHistory, + parseStorageToHistory, + postHistoryToServer, + removeHistory, + saveHistory, + saveStorageHistoryToServer +} from './history'; + +import { saveAs } from 'file-saver'; +import List from 'list.js'; +import S from 'string'; + +const options = { valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'], item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">\ <span class="id" style="display:none;"></span>\ <a href="#">\ <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="content">\ <h4 class="text"></h4>\ @@ -55,15 +57,16 @@ var options = { }) ] }; -var historyList = new List('history', options); +const historyList = new List('history', options); migrateHistoryFromTempCallback = pageInit; -loginStateChangeEvent = pageInit; +setloginStateChangeEvent(pageInit); + pageInit(); function pageInit() { checkIfAuth( - function (data) { + data => { $('.ui-signin').hide(); $('.ui-or').hide(); $('.ui-welcome').show(); @@ -74,7 +77,7 @@ function pageInit() { $(".ui-history").click(); parseServerToHistory(historyList, parseHistoryCallback); }, - function () { + () => { $('.ui-signin').show(); $('.ui-or').show(); $('.ui-welcome').hide(); @@ -103,7 +106,7 @@ $(".ui-home").click(function (e) { } }); -$(".ui-history").click(function (e) { +$(".ui-history").click(() => { if (!$("#history").is(':visible')) { $(".section:visible").hide(); $("#history").fadeIn(); @@ -118,7 +121,7 @@ function checkHistoryList() { } else if ($("#history-list").children().length == 0) { $('.pagination').hide(); $(".ui-nohistory").slideDown(); - getStorageHistory(function (data) { + getStorageHistory(data => { if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) { $(".ui-import-from-browser").slideDown(); } @@ -128,35 +131,35 @@ function checkHistoryList() { function parseHistoryCallback(list, notehistory) { checkHistoryList(); - //sort by pinned then timestamp - list.sort('', { - sortFunction: function (a, b) { - var notea = a.values(); - var noteb = b.values(); - if (notea.pinned && !noteb.pinned) { + //sort by pinned then timestamp + list.sort('', { + sortFunction(a, b) { + const notea = a.values(); + const noteb = b.values(); + if (notea.pinned && !noteb.pinned) { return -1; } else if (!notea.pinned && noteb.pinned) { return 1; } else { - if (notea.timestamp > noteb.timestamp) { - return -1; - } else if (notea.timestamp < noteb.timestamp) { - return 1; - } else { - return 0; - } - } - } - }); + if (notea.timestamp > noteb.timestamp) { + return -1; + } else if (notea.timestamp < noteb.timestamp) { + return 1; + } else { + return 0; + } + } + } + }); // parse filter tags - var filtertags = []; - for (var i = 0, l = list.items.length; i < l; i++) { - var tags = list.items[i]._values.tags; + const filtertags = []; + for (let i = 0, l = list.items.length; i < l; i++) { + const tags = list.items[i]._values.tags; 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 - var found = false; - if (filtertags.indexOf(tags[j]) != -1) + let found = false; + if (filtertags.includes(tags[j])) found = true; if (!found) filtertags.push(tags[j]); @@ -167,17 +170,17 @@ function parseHistoryCallback(list, notehistory) { } // update items whenever list updated -historyList.on('updated', function (e) { - for (var i = 0, l = e.items.length; i < l; i++) { - var item = e.items[i]; +historyList.on('updated', e => { + for (let i = 0, l = e.items.length; i < l; i++) { + const item = e.items[i]; if (item.visible()) { - var itemEl = $(item.elm); - var values = item._values; - var a = itemEl.find("a"); - var pin = itemEl.find(".ui-history-pin"); - var tagsEl = itemEl.find(".tags"); + const itemEl = $(item.elm); + const values = item._values; + const a = itemEl.find("a"); + const pin = itemEl.find(".ui-history-pin"); + const tagsEl = itemEl.find(".tags"); //parse link to element a - a.attr('href', serverurl + '/' + values.id); + a.attr('href', `${serverurl}/${values.id}`); //parse pinned if (values.pinned) { pin.addClass('active'); @@ -185,12 +188,12 @@ historyList.on('updated', function (e) { pin.removeClass('active'); } //parse tags - var tags = values.tags; + const tags = values.tags; if (tags && tags.length > 0 && tagsEl.children().length <= 0) { - var labels = []; - for (var j = 0; j < tags.length; j++) { + const labels = []; + for (let j = 0; j < tags.length; j++) { //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(' ')); } @@ -204,21 +207,21 @@ historyList.on('updated', function (e) { function historyCloseClick(e) { e.preventDefault(); - var id = $(this).closest("a").siblings("span").html(); - var value = historyList.get('id', id)[0]._values; + const id = $(this).closest("a").siblings("span").html(); + const value = historyList.get('id', id)[0]._values; $('.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; deleteId = id; } function historyPinClick(e) { e.preventDefault(); - var $this = $(this); - var id = $this.closest("a").siblings("span").html(); - var item = historyList.get('id', id)[0]; - var values = item._values; - var pinned = values.pinned; + const $this = $(this); + const id = $this.closest("a").siblings("span").html(); + const item = historyList.get('id', id)[0]; + const values = item._values; + let pinned = values.pinned; if (!values.pinned) { pinned = true; item._values.pinned = true; @@ -226,10 +229,10 @@ function historyPinClick(e) { pinned = false; item._values.pinned = false; } - checkIfAuth(function () { + checkIfAuth(() => { postHistoryToServer(id, { - pinned: pinned - }, function (err, result) { + pinned + }, (err, result) => { if (!err) { if (pinned) $this.addClass('active'); @@ -237,9 +240,9 @@ function historyPinClick(e) { $this.removeClass('active'); } }); - }, function () { - getHistory(function (notehistory) { - for(var i = 0; i < notehistory.length; i++) { + }, () => { + getHistory(notehistory => { + for(let i = 0; i < notehistory.length; i++) { if (notehistory[i].id == id) { notehistory[i].pinned = pinned; break; @@ -258,10 +261,10 @@ function historyPinClick(e) { setInterval(updateItemFromNow, 60000); function updateItemFromNow() { - var items = $('.item').toArray(); - for (var i = 0; i < items.length; i++) { - var item = $(items[i]); - var timestamp = parseInt(item.find('.timestamp').text()); + const items = $('.item').toArray(); + for (let i = 0; i < items.length; i++) { + const item = $(items[i]); + const timestamp = parseInt(item.find('.timestamp').text()); item.find('.fromNow').text(moment(timestamp).fromNow()); } } @@ -270,8 +273,8 @@ var clearHistory = false; var deleteId = null; function deleteHistory() { - checkIfAuth(function () { - deleteServerHistory(deleteId, function (err, result) { + checkIfAuth(() => { + deleteServerHistory(deleteId, (err, result) => { if (!err) { if (clearHistory) { historyList.clear(); @@ -285,7 +288,7 @@ function deleteHistory() { deleteId = null; clearHistory = false; }); - }, function () { + }, () => { if (clearHistory) { saveHistory([]); historyList.clear(); @@ -293,8 +296,8 @@ function deleteHistory() { deleteId = null; } else { if (!deleteId) return; - getHistory(function (notehistory) { - var newnotehistory = removeHistory(deleteId, notehistory); + getHistory(notehistory => { + const newnotehistory = removeHistory(deleteId, notehistory); saveHistory(newnotehistory); historyList.remove('id', deleteId); checkHistoryList(); @@ -306,36 +309,36 @@ function deleteHistory() { }); } -$(".ui-delete-modal-confirm").click(function () { +$(".ui-delete-modal-confirm").click(() => { deleteHistory(); }); -$(".ui-import-from-browser").click(function () { - saveStorageHistoryToServer(function () { +$(".ui-import-from-browser").click(() => { + saveStorageHistoryToServer(() => { parseStorageToHistory(historyList, parseHistoryCallback); }); }); -$(".ui-save-history").click(function () { - getHistory(function (data) { - var history = JSON.stringify(data); - var blob = new Blob([history], { +$(".ui-save-history").click(() => { + getHistory(data => { + const history = JSON.stringify(data); + const blob = new Blob([history], { 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) { - var files = e.target.files || e.dataTransfer.files; - var file = files[0]; - var reader = new FileReader(); - reader.onload = function () { - var notehistory = JSON.parse(reader.result); +$(".ui-open-history").bind("change", e => { + const files = e.target.files || e.dataTransfer.files; + const file = files[0]; + const reader = new FileReader(); + reader.onload = () => { + const notehistory = JSON.parse(reader.result); //console.log(notehistory); if (!reader.result) return; - getHistory(function (data) { - var mergedata = data.concat(notehistory); + getHistory(data => { + let mergedata = data.concat(notehistory); mergedata = clearDuplicatedHistory(mergedata); saveHistory(mergedata); parseHistory(historyList, parseHistoryCallback); @@ -345,18 +348,18 @@ $(".ui-open-history").bind("change", function (e) { 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-item').html('There is no turning back.'); clearHistory = true; deleteId = null; }); -$(".ui-refresh-history").click(function () { - var lastTags = $(".ui-use-tags").select2('val'); +$(".ui-refresh-history").click(() => { + const lastTags = $(".ui-use-tags").select2('val'); $(".ui-use-tags").select2('val', ''); historyList.filter(); - var lastKeyword = $('.search').val(); + const lastKeyword = $('.search').val(); $('.search').val(''); historyList.search(); $('#history-list').slideUp('fast'); @@ -364,7 +367,7 @@ $(".ui-refresh-history").click(function () { resetCheckAuth(); historyList.clear(); - parseHistory(historyList, function (list, notehistory) { + parseHistory(historyList, (list, notehistory) => { parseHistoryCallback(list, notehistory); $(".ui-use-tags").select2('val', lastTags); $(".ui-use-tags").trigger('change'); @@ -375,16 +378,16 @@ $(".ui-refresh-history").click(function () { }); }); -$(".ui-logout").click(function () { +$(".ui-logout").click(() => { clearLoginState(); - location.href = serverurl + '/logout'; + location.href = `${serverurl}/logout`; }); -var filtertags = []; +let filtertags = []; $(".ui-use-tags").select2({ placeholder: $(".ui-use-tags").attr('placeholder'), multiple: true, - data: function () { + data() { return { results: filtertags }; @@ -394,7 +397,7 @@ $('.select2-input').css('width', 'inherit'); buildTagsFilter([]); function buildTagsFilter(tags) { - for (var i = 0; i < tags.length; i++) + for (let i = 0; i < tags.length; i++) tags[i] = { id: i, text: S(tags[i]).unescapeHTML().s @@ -402,17 +405,17 @@ function buildTagsFilter(tags) { filtertags = tags; } $(".ui-use-tags").on('change', function () { - var tags = []; - var data = $(this).select2('data'); - for (var i = 0; i < data.length; i++) + const tags = []; + const data = $(this).select2('data'); + for (let i = 0; i < data.length; i++) tags.push(data[i].text); if (tags.length > 0) { - historyList.filter(function (item) { - var values = item.values(); + historyList.filter(item => { + const values = item.values(); if (!values.tags) return false; - var found = false; - for (var i = 0; i < tags.length; i++) { - if (values.tags.indexOf(tags[i]) != -1) { + let found = false; + for (let i = 0; i < tags.length; i++) { + if (values.tags.includes(tags[i])) { found = true; break; } @@ -425,6 +428,6 @@ $(".ui-use-tags").on('change', function () { checkHistoryList(); }); -$('.search').keyup(function () { +$('.search').keyup(() => { checkHistoryList(); }); diff --git a/public/js/extra.js b/public/js/extra.js index 4a9cc76e..a3e840d2 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -1,15 +1,17 @@ require('prismjs/themes/prism.css'); - -var Prism = require('prismjs'); require('prismjs/components/prism-wiki'); require('prismjs/components/prism-haskell'); require('prismjs/components/prism-go'); require('prismjs/components/prism-typescript'); require('prismjs/components/prism-jsx'); -var hljs = require('highlight.js'); -var PDFObject = require('pdfobject'); -var S = require('string'); -var saveAs = require('file-saver').saveAs; + +import Prism from 'prismjs'; +import hljs from 'highlight.js'; +import PDFObject from 'pdfobject'; +import S from 'string'; +import { saveAs } from 'file-saver'; + +require('./lib/common/login'); require('../vendor/md-toc'); var Viz = require("viz.js"); @@ -22,9 +24,10 @@ window.lastchangeui = { user: $(".ui-lastchangeuser"), nouser: $(".ui-no-lastchangeuser") } -var ownerui = $(".ui-owner"); -function updateLastChange() { +const ownerui = $(".ui-owner"); + +export function updateLastChange() { if (!lastchangeui) return; if (createtime) { if (createtime && !lastchangetime) { @@ -32,7 +35,7 @@ function updateLastChange() { } else { lastchangeui.status.text('changed'); } - var time = lastchangetime || createtime; + const time = lastchangetime || createtime; lastchangeui.time.html(moment(time).fromNow()); lastchangeui.time.attr('title', moment(time).format('llll')); } @@ -41,13 +44,14 @@ setInterval(updateLastChange, 60000); window.lastchangeuser = null; window.lastchangeuserprofile = null; -function updateLastChangeUser() { + +export function updateLastChangeUser() { if (lastchangeui) { if (lastchangeuser && lastchangeuserprofile) { - var icon = lastchangeui.user.children('i'); + const icon = lastchangeui.user.children('i'); icon.attr('title', lastchangeuserprofile.name).tooltip('fixTitle'); if (lastchangeuserprofile.photo) - icon.attr('style', 'background-image:url(' + lastchangeuserprofile.photo + ')'); + icon.attr('style', `background-image:url(${lastchangeuserprofile.photo})`); lastchangeui.user.show(); lastchangeui.nouser.hide(); } else { @@ -59,12 +63,13 @@ function updateLastChangeUser() { window.owner = null; window.ownerprofile = null; -function updateOwner() { + +export function updateOwner() { if (ownerui) { if (owner && ownerprofile && owner !== lastchangeuser) { - var icon = ownerui.children('i'); + const icon = ownerui.children('i'); icon.attr('title', ownerprofile.name).tooltip('fixTitle'); - var styleString = 'background-image:url(' + ownerprofile.photo + ')'; + const styleString = `background-image:url(${ownerprofile.photo})`; if (ownerprofile.photo && icon.attr('style') !== styleString) icon.attr('style', styleString); ownerui.show(); @@ -76,11 +81,11 @@ function updateOwner() { //get title function getTitle(view) { - var title = ""; + let title = ""; if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) { title = md.meta.title; } else { - var h1s = view.find("h1"); + const h1s = view.find("h1"); if (h1s.length > 0) { title = h1s.first().text(); } else { @@ -91,8 +96,8 @@ function getTitle(view) { } //render title -function renderTitle(view) { - var title = getTitle(view); +export function renderTitle(view) { + let title = getTitle(view); if (title) { title += ' - HackMD'; } else { @@ -102,8 +107,8 @@ function renderTitle(view) { } //render filename -function renderFilename(view) { - var filename = getTitle(view); +export function renderFilename(view) { + let filename = getTitle(view); if (!filename) { filename = 'Untitled'; } @@ -111,29 +116,29 @@ function renderFilename(view) { } // render tags -function renderTags(view) { - var tags = []; - var rawtags = []; +export function renderTags(view) { + const tags = []; + const rawtags = []; if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) { - var metaTags = ('' + md.meta.tags).split(','); + const metaTags = (`${md.meta.tags}`).split(','); for (var i = 0; i < metaTags.length; i++) { - var text = metaTags[i].trim(); + const text = metaTags[i].trim(); if (text) rawtags.push(text); } } else { - view.find('h6').each(function (key, value) { + view.find('h6').each((key, value) => { if (/^tags/gmi.test($(value).text())) { - var codes = $(value).find("code"); - for (var i = 0; i < codes.length; i++) { - var text = codes[i].innerHTML.trim(); + const codes = $(value).find("code"); + for (let i = 0; i < codes.length; i++) { + const text = codes[i].innerHTML.trim(); if (text) rawtags.push(text); } } }); } for (var i = 0; i < rawtags.length; i++) { - var found = false; - for (var j = 0; j < tags.length; j++) { + let found = false; + for (let j = 0; j < tags.length; j++) { if (tags[j] == rawtags[i]) { found = true; break; @@ -146,13 +151,13 @@ function renderTags(view) { } function slugifyWithUTF8(text) { - var newText = S(text.toLowerCase()).trim().stripTags().dasherize().s; + let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s; newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, ''); return newText; } -function isValidURL(str) { - var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol +export function isValidURL(str) { + const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path @@ -166,12 +171,12 @@ function isValidURL(str) { } //parse meta -function parseMeta(md, edit, view, toc, tocAffix) { - var lang = null; - var dir = null; - var breaks = true; +export function parseMeta(md, edit, view, toc, tocAffix) { + let lang = null; + let dir = null; + let breaks = true; if (md && md.meta) { - var meta = md.meta; + const meta = md.meta; lang = meta.lang; dir = meta.dir; breaks = meta.breaks; @@ -211,13 +216,13 @@ function parseMeta(md, edit, view, toc, tocAffix) { window.viewAjaxCallback = null; //regex for extra tags -var spaceregex = /\s*/; -var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; -var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/; +const spaceregex = /\s*/; +const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/; +let coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/; coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g"); -var nameregex = /\[name=(.*?)\]/; -var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/; -var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); +let nameregex = /\[name=(.*?)\]/; +let timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/; +const nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g"); nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g"); timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g"); @@ -232,35 +237,36 @@ function replaceExtraTags(html) { if (typeof mermaid !== 'undefined' && mermaid) mermaid.startOnLoad = false; //dynamic event or object binding here -function finishView(view) { +export function finishView(view) { //todo list - var lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray(); - for (var i = 0; i < lis.length; i++) { - var li = lis[i]; - var html = $(li).clone()[0].innerHTML; - var p = $(li).children('p'); + const lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray(); + + for (let li of lis) { + let html = $(li).clone()[0].innerHTML; + const p = $(li).children('p'); if (p.length == 1) { html = p.html(); li = p[0]; } html = replaceExtraTags(html); li.innerHTML = html; - var disabled = 'disabled'; + let disabled = 'disabled'; if(typeof editor !== 'undefined' && havePermission()) disabled = ''; if (/^\s*\[[x ]\]\s*/.test(html)) { - li.innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox "' + disabled + '><label></label>') - .replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked ' + disabled + '><label></label>'); - lis[i].setAttribute('class', 'task-list-item'); + li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`) + .replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`); + li.setAttribute('class', 'task-list-item'); } if (typeof editor !== 'undefined' && havePermission()) $(li).find('input').change(toggleTodoEvent); //color tag in list will convert it to tag icon with color - var tag_color = $(li).closest('ul').find(".color"); - tag_color.each(function (key, value) { + const tag_color = $(li).closest('ul').find(".color"); + tag_color.each((key, value) => { $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color')); }); } + //youtube view.find("div.youtube.raw").removeClass("raw") .click(function () { @@ -271,41 +277,41 @@ function finishView(view) { .click(function () { imgPlayiframe(this, '//player.vimeo.com/video/'); }) - .each(function (key, value) { + .each((key, value) => { $.ajax({ type: 'GET', - url: '//vimeo.com/api/v2/video/' + $(value).attr('data-videoid') + '.json', + url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`, jsonp: 'callback', dataType: 'jsonp', - success: function (data) { - var thumbnail_src = data[0].thumbnail_large; - var image = '<img src="' + thumbnail_src + '" />'; + success(data) { + const thumbnail_src = data[0].thumbnail_large; + const image = `<img src="${thumbnail_src}" />`; $(value).prepend(image); if(viewAjaxCallback) viewAjaxCallback(); } }); }); //gist - view.find("code[data-gist-id]").each(function (key, value) { + view.find("code[data-gist-id]").each((key, value) => { if ($(value).children().length == 0) $(value).gist(viewAjaxCallback); }); //sequence diagram - var sequences = view.find("div.sequence-diagram.raw").removeClass("raw"); - sequences.each(function (key, value) { + const sequences = view.find("div.sequence-diagram.raw").removeClass("raw"); + sequences.each((key, value) => { try { var $value = $(value); - var $ele = $(value).parent().parent(); + const $ele = $(value).parent().parent(); - var sequence = $value; + const sequence = $value; sequence.sequenceDiagram({ theme: 'simple' }); $ele.addClass('sequence-diagram'); $value.children().unwrap().unwrap(); - var svg = $ele.find('> svg'); - svg[0].setAttribute('viewBox', '0 0 ' + svg.attr('width') + ' ' + svg.attr('height')); + const svg = $ele.find('> svg'); + svg[0].setAttribute('viewBox', `0 0 ${svg.attr('width')} ${svg.attr('height')}`); svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet'); } catch (err) { $value.unwrap(); @@ -314,13 +320,13 @@ function finishView(view) { } }); //flowchart - var flow = view.find("div.flow-chart.raw").removeClass("raw"); - flow.each(function (key, value) { + const flow = view.find("div.flow-chart.raw").removeClass("raw"); + flow.each((key, value) => { try { var $value = $(value); - var $ele = $(value).parent().parent(); + const $ele = $(value).parent().parent(); - var chart = flowchart.parse($value.text()); + const chart = flowchart.parse($value.text()); $value.html(''); chart.drawSVG(value, { 'line-width': 2, @@ -339,41 +345,32 @@ function finishView(view) { }); //graphviz var graphvizs = view.find("div.graphviz.raw").removeClass("raw"); - function parseGraphviz(key, value) { - var $value = $(value); - var $ele = $(value).parent().parent(); - - var graphviz = Viz($value.text()); - if (!graphviz) throw Error('viz.js output empty graph'); - $value.html(graphviz); - - $ele.addClass('graphviz'); - $value.children().unwrap().unwrap(); - } graphvizs.each(function (key, value) { try { - parseGraphviz(key, value); + var $value = $(value); + var $ele = $(value).parent().parent(); + + var graphviz = Viz($value.text()); + if (!graphviz) throw Error('viz.js output empty graph'); + $value.html(graphviz); + + $ele.addClass('graphviz'); + $value.children().unwrap().unwrap(); } catch (err) { - // workaround for graphviz not recover from error - try { - parseGraphviz(key, value); - } catch (err) { - var $value = $(value); - $value.unwrap(); - $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); - console.warn(err); - } + $value.unwrap(); + $value.parent().append('<div class="alert alert-warning">' + err + '</div>'); + console.warn(err); } }); //mermaid - var mermaids = view.find("div.mermaid.raw").removeClass("raw"); - mermaids.each(function (key, value) { + const mermaids = view.find("div.mermaid.raw").removeClass("raw"); + mermaids.each((key, value) => { try { var $value = $(value); - var $ele = $(value).closest('pre'); + const $ele = $(value).closest('pre'); - var mermaidError = null; - mermaid.parseError = function (err, hash) { + let mermaidError = null; + mermaid.parseError = (err, hash) => { mermaidError = err; }; @@ -391,44 +388,44 @@ function finishView(view) { } }); //image href new window(emoji not included) - var images = view.find("img.raw[src]").removeClass("raw"); - images.each(function (key, value) { + const images = view.find("img.raw[src]").removeClass("raw"); + images.each((key, value) => { // if it's already wrapped by link, then ignore - var $value = $(value); - $value[0].onload = function (e) { + const $value = $(value); + $value[0].onload = e => { if(viewAjaxCallback) viewAjaxCallback(); }; }); //blockquote - var blockquote = view.find("blockquote.raw").removeClass("raw"); - var blockquote_p = blockquote.find("p"); - blockquote_p.each(function (key, value) { - var html = $(value).html(); + const blockquote = view.find("blockquote.raw").removeClass("raw"); + const blockquote_p = blockquote.find("p"); + blockquote_p.each((key, value) => { + let html = $(value).html(); html = replaceExtraTags(html); $(value).html(html); }); //color tag in blockquote will change its left border color - var blockquote_color = blockquote.find(".color"); - blockquote_color.each(function (key, value) { + const blockquote_color = blockquote.find(".color"); + blockquote_color.each((key, value) => { $(value).closest("blockquote").css('border-left-color', $(value).attr('data-color')); }); //slideshare view.find("div.slideshare.raw").removeClass("raw") - .each(function (key, value) { + .each((key, value) => { $.ajax({ type: 'GET', - url: '//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/' + $(value).attr('data-slideshareid') + '&format=json', + url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`, jsonp: 'callback', dataType: 'jsonp', - success: function (data) { - var $html = $(data.html); - var iframe = $html.closest('iframe'); - var caption = $html.closest('div'); - var inner = $('<div class="inner"></div>').append(iframe); - var height = iframe.attr('height'); - var width = iframe.attr('width'); - var ratio = (height / width) * 100; - inner.css('padding-bottom', ratio + '%'); + success(data) { + const $html = $(data.html); + const iframe = $html.closest('iframe'); + const caption = $html.closest('div'); + const inner = $('<div class="inner"></div>').append(iframe); + const height = iframe.attr('height'); + const width = iframe.attr('width'); + const ratio = (height / width) * 100; + inner.css('padding-bottom', `${ratio}%`); $(value).html(inner).append(caption); if(viewAjaxCallback) viewAjaxCallback(); } @@ -436,31 +433,31 @@ function finishView(view) { }); //speakerdeck view.find("div.speakerdeck.raw").removeClass("raw") - .each(function (key, value) { - var url = 'https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F' + encodeURIComponent($(value).attr('data-speakerdeckid')); + .each((key, value) => { + const url = `https://speakerdeck.com/oembed.json?url=https%3A%2F%2Fspeakerdeck.com%2F${encodeURIComponent($(value).attr('data-speakerdeckid'))}`; //use yql because speakerdeck not support jsonp $.ajax({ url: 'https://query.yahooapis.com/v1/public/yql', data: { - q: "select * from json where url ='" + url + "'", + q: `select * from json where url ='${url}'`, format: "json" }, dataType: "jsonp", - success: function (data) { + success(data) { if (!data.query || !data.query.results) return; - var json = data.query.results.json; - var html = json.html; + const json = data.query.results.json; + const html = json.html; var ratio = json.height / json.width; $(value).html(html); - var iframe = $(value).children('iframe'); - var src = iframe.attr('src'); + const iframe = $(value).children('iframe'); + const src = iframe.attr('src'); if (src.indexOf('//') == 0) - iframe.attr('src', 'https:' + src); - var inner = $('<div class="inner"></div>').append(iframe); - var height = iframe.attr('height'); - var width = iframe.attr('width'); + iframe.attr('src', `https:${src}`); + const inner = $('<div class="inner"></div>').append(iframe); + const height = iframe.attr('height'); + const width = iframe.attr('width'); var ratio = (height / width) * 100; - inner.css('padding-bottom', ratio + '%'); + inner.css('padding-bottom', `${ratio}%`); $(value).html(inner); if(viewAjaxCallback) viewAjaxCallback(); } @@ -469,8 +466,8 @@ function finishView(view) { //pdf view.find("div.pdf.raw").removeClass("raw") .each(function (key, value) { - var url = $(value).attr('data-pdfurl'); - var inner = $('<div></div>'); + const url = $(value).attr('data-pdfurl'); + const inner = $('<div></div>'); $(this).append(inner); PDFObject.embed(url, inner, { height: '400px' @@ -478,12 +475,12 @@ function finishView(view) { }); //syntax highlighting view.find("code.raw").removeClass("raw") - .each(function (key, value) { - var langDiv = $(value); + .each((key, value) => { + const langDiv = $(value); if (langDiv.length > 0) { - var reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim(); - var codeDiv = langDiv.find('.code'); - var code = ""; + const reallang = langDiv[0].className.replace(/hljs|wrap/g, '').trim(); + const codeDiv = langDiv.find('.code'); + let code = ""; if (codeDiv.length > 0) code = codeDiv.html(); else code = langDiv.html(); if (!reallang) { @@ -502,8 +499,8 @@ function finishView(view) { }; } else { code = S(code).unescapeHTML().s; - var languages = hljs.listLanguages(); - if (languages.indexOf(reallang) == -1) { + const languages = hljs.listLanguages(); + if (!languages.includes(reallang)) { var result = hljs.highlightAuto(code); } else { var result = hljs.highlight(reallang, code); @@ -514,7 +511,7 @@ function finishView(view) { } }); //mathjax - var mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray(); + const mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray(); try { if (mathjaxdivs.length > 1) { MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs]); @@ -531,16 +528,16 @@ function finishView(view) { } //only static transform should be here -function postProcess(code) { - var result = $('<div>' + code + '</div>'); +export function postProcess(code) { + const result = $(`<div>${code}</div>`); //link should open in new window or tab result.find('a:not([href^="#"]):not([target])').attr('target', '_blank'); //update continue line numbers - var linenumberdivs = result.find('.gutter.linenumber').toArray(); - for (var i = 0; i < linenumberdivs.length; i++) { + const linenumberdivs = result.find('.gutter.linenumber').toArray(); + for (let i = 0; i < linenumberdivs.length; i++) { if ($(linenumberdivs[i]).hasClass('continue')) { - var startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0; - $(linenumberdivs[i]).find('> span').each(function(key, value) { + const startnumber = linenumberdivs[i - 1] ? parseInt($(linenumberdivs[i - 1]).find('> span').last().attr('data-linenumber')) : 0; + $(linenumberdivs[i]).find('> span').each((key, value) => { $(value).attr('data-linenumber', startnumber + key + 1); }); } @@ -560,8 +557,8 @@ function postProcess(code) { window.postProcess = postProcess; function generateCleanHTML(view) { - var src = view.clone(); - var eles = src.find('*'); + const src = view.clone(); + const eles = src.find('*'); //remove syncscroll parts eles.removeClass('part'); src.find('*[class=""]').removeAttr('class'); @@ -572,24 +569,24 @@ function generateCleanHTML(view) { //disable todo list src.find("input.task-list-item-checkbox").attr('disabled', ''); //replace emoji image path - src.find("img.emoji").each(function (key, value) { - var name = $(value).attr('alt'); + src.find("img.emoji").each((key, value) => { + let name = $(value).attr('alt'); name = name.substr(1); name = name.slice(0, name.length - 1); - $(value).attr('src', 'https://www.tortue.me/emoji/' + name + '.png'); + $(value).attr('src', `https://www.tortue.me/emoji/${name}.png`); }); //replace video to iframe - src.find("div[data-videoid]").each(function (key, value) { - var id = $(value).attr('data-videoid'); - var style = $(value).attr('style'); - var url = null; + src.find("div[data-videoid]").each((key, value) => { + const id = $(value).attr('data-videoid'); + const style = $(value).attr('style'); + let url = null; if ($(value).hasClass('youtube')) { url = 'https://www.youtube.com/embed/'; } else if ($(value).hasClass('vimeo')) { url = 'https://player.vimeo.com/video/'; } if (url) { - var iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'); + const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'); iframe.attr('src', url + id); iframe.attr('style', style); $(value).html(iframe); @@ -598,65 +595,63 @@ function generateCleanHTML(view) { return src; } -function exportToRawHTML(view) { - var filename = renderFilename(ui.area.markdown) + '.html'; - var src = generateCleanHTML(view); +export function exportToRawHTML(view) { + const filename = `${renderFilename(ui.area.markdown)}.html`; + const src = generateCleanHTML(view); $(src).find('a.anchor').remove(); - var html = src[0].outerHTML; - var blob = new Blob([html], { + const html = src[0].outerHTML; + const blob = new Blob([html], { type: "text/html;charset=utf-8" }); - saveAs(blob, filename); + saveAs(blob, filename, true); } -var common = require('./common.js'); //extract markdown body to html and compile to template -function exportToHTML(view) { - var title = renderTitle(ui.area.markdown); - var filename = renderFilename(ui.area.markdown) + '.html'; - var src = generateCleanHTML(view); +export function exportToHTML(view) { + const title = renderTitle(ui.area.markdown); + const filename = `${renderFilename(ui.area.markdown)}.html`; + const src = generateCleanHTML(view); //generate toc - var toc = $('#ui-toc').clone(); + const toc = $('#ui-toc').clone(); toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll'); - var tocAffix = $('#ui-toc-affix').clone(); + const tocAffix = $('#ui-toc-affix').clone(); tocAffix.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll'); //generate html via template - $.get(serverurl + '/build/html.min.css', function (css) { - $.get(serverurl + '/views/html.hbs', function (data) { - var template = Handlebars.compile(data); - var context = { + $.get(`${serverurl}/build/html.min.css`, css => { + $.get(`${serverurl}/views/html.hbs`, data => { + const template = Handlebars.compile(data); + const context = { url: serverurl, - title: title, - css: css, + title, + css, html: src[0].outerHTML, 'ui-toc': toc.html(), 'ui-toc-affix': tocAffix.html(), - lang: (md && md.meta && md.meta.lang) ? 'lang="' + md.meta.lang + '"' : null, - dir: (md && md.meta && md.meta.dir) ? 'dir="' + md.meta.dir + '"' : null + lang: (md && md.meta && md.meta.lang) ? `lang="${md.meta.lang}"` : null, + dir: (md && md.meta && md.meta.dir) ? `dir="${md.meta.dir}"` : null }; - var html = template(context); + const html = template(context); // console.log(html); - var blob = new Blob([html], { + const blob = new Blob([html], { type: "text/html;charset=utf-8" }); - saveAs(blob, filename); + saveAs(blob, filename, true); }); }); } //jQuery sortByDepth $.fn.sortByDepth = function () { - var ar = this.map(function () { + const ar = this.map(function () { return { length: $(this).parents().length, elt: this } - }).get(), - result = [], - i = ar.length; - ar.sort(function (a, b) { - return a.length - b.length; - }); + }).get(); + + const result = []; + let i = ar.length; + ar.sort((a, b) => a.length - b.length); while (i--) { result.push(ar[i].elt); } @@ -664,16 +659,16 @@ $.fn.sortByDepth = function () { }; function toggleTodoEvent(e) { - var startline = $(this).closest('li').attr('data-startline') - 1; - var line = editor.getLine(startline); - var matches = line.match(/^[>\s]*[\-\+\*]\s\[([x ])\]/); + const startline = $(this).closest('li').attr('data-startline') - 1; + const line = editor.getLine(startline); + const matches = line.match(/^[>\s]*[\-\+\*]\s\[([x ])\]/); if (matches && matches.length >= 2) { - var checked = null; + let checked = null; if (matches[1] == 'x') checked = true; else if (matches[1] == ' ') checked = false; - var replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/); + const replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/); editor.replaceRange(checked ? ' ' : 'x', { line: startline, ch: replacements[1].length @@ -689,11 +684,11 @@ function removeHash() { history.pushState("", document.title, window.location.pathname + window.location.search); } -var tocExpand = false; +let tocExpand = false; function checkExpandToggle() { - var toc = $('.ui-toc-dropdown .toc'); - var toggle = $('.expand-toggle'); + const toc = $('.ui-toc-dropdown .toc'); + const toggle = $('.expand-toggle'); if (!tocExpand) { toc.removeClass('expand'); toggle.text('Expand all'); @@ -704,8 +699,8 @@ function checkExpandToggle() { } //toc -function generateToc(id) { - var target = $('#' + id); +export function generateToc(id) { + const target = $(`#${id}`); target.html(''); new Toc('doc', { 'level': 3, @@ -717,25 +712,25 @@ function generateToc(id) { }); if (target.text() == 'undefined') target.html(''); - var tocMenu = $('<div class="toc-menu"></div'); - var toggle = $('<a class="expand-toggle" href="#">Expand all</a>'); - var backtotop = $('<a class="back-to-top" href="#">Back to top</a>'); - var gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>'); + const tocMenu = $('<div class="toc-menu"></div'); + const toggle = $('<a class="expand-toggle" href="#">Expand all</a>'); + const backtotop = $('<a class="back-to-top" href="#">Back to top</a>'); + const gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>'); checkExpandToggle(); - toggle.click(function (e) { + toggle.click(e => { e.preventDefault(); e.stopPropagation(); tocExpand = !tocExpand; checkExpandToggle(); }); - backtotop.click(function (e) { + backtotop.click(e => { e.preventDefault(); e.stopPropagation(); if (scrollToTop) scrollToTop(); removeHash(); }); - gotobottom.click(function (e) { + gotobottom.click(e => { e.preventDefault(); e.stopPropagation(); if (scrollToBottom) @@ -747,18 +742,18 @@ function generateToc(id) { } //smooth all hash trigger scrolling -function smoothHashScroll() { - var hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray(); - for (var i = 0; i < hashElements.length; i++) { - var element = hashElements[i]; - var $element = $(element); - var hash = element.hash; +export function smoothHashScroll() { + const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray(); + + for (const element of hashElements) { + const $element = $(element); + const hash = element.hash; if (hash) { $element.on('click', function (e) { // store hash - var hash = decodeURIComponent(this.hash); + const hash = decodeURIComponent(this.hash); // escape special characters in jquery selector - var $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1")); + const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1")); // return if no element been selected if ($hash.length <= 0) return; // prevent default anchor click behavior @@ -766,7 +761,7 @@ function smoothHashScroll() { // animate $('body, html').stop(true, true).animate({ scrollTop: $hash.offset().top - }, 100, "linear", function () { + }, 100, "linear", () => { // when done, add hash to url // (default click behaviour) window.location.hash = hash; @@ -779,29 +774,30 @@ function smoothHashScroll() { function imgPlayiframe(element, src) { if (!$(element).attr("data-videoid")) return; - var iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"); - $(iframe).attr("src", src + $(element).attr("data-videoid") + '?autoplay=1'); + const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"); + $(iframe).attr("src", `${src + $(element).attr("data-videoid")}?autoplay=1`); $(element).find('img').css('visibility', 'hidden'); $(element).append(iframe); } -var anchorForId = function (id) { - var anchor = document.createElement("a"); +const anchorForId = id => { + const anchor = document.createElement("a"); anchor.className = "anchor hidden-xs"; - anchor.href = "#" + id; + anchor.href = `#${id}`; anchor.innerHTML = "<span class=\"octicon octicon-link\"></span>"; anchor.title = id; return anchor; }; -var linkifyAnchors = function (level, containingElement) { - var headers = containingElement.getElementsByTagName("h" + level); - for (var h = 0; h < headers.length; h++) { - var header = headers[h]; +const linkifyAnchors = (level, containingElement) => { + const headers = containingElement.getElementsByTagName(`h${level}`); + + for (let i = 0, l = headers.length; i < l; i++) { + let header = headers[i]; if (header.getElementsByClassName("anchor").length == 0) { if (typeof header.id == "undefined" || header.id == "") { //to escape characters not allow in css and humanize - var id = slugifyWithUTF8(getHeaderContent(header)); + const id = slugifyWithUTF8(getHeaderContent(header)); header.id = id; } header.insertBefore(anchorForId(header.id), header.firstChild); @@ -809,49 +805,49 @@ var linkifyAnchors = function (level, containingElement) { } }; -function autoLinkify(view) { - var contentBlock = view[0]; +export function autoLinkify(view) { + const contentBlock = view[0]; if (!contentBlock) { return; } - for (var level = 1; level <= 6; level++) { + for (let level = 1; level <= 6; level++) { linkifyAnchors(level, contentBlock); } } function getHeaderContent(header) { - var headerHTML = $(header).clone(); + const headerHTML = $(header).clone(); headerHTML.find('.MathJax_Preview').remove(); headerHTML.find('.MathJax').remove(); return headerHTML[0].innerHTML; } -function deduplicatedHeaderId(view) { - var headers = view.find(':header.raw').removeClass('raw').toArray(); - for (var i = 0; i < headers.length; i++) { - var id = $(headers[i]).attr('id'); +export function deduplicatedHeaderId(view) { + const headers = view.find(':header.raw').removeClass('raw').toArray(); + for (let i = 0; i < headers.length; i++) { + const id = $(headers[i]).attr('id'); if (!id) continue; - var duplicatedHeaders = view.find(':header[id="' + id + '"]').toArray(); - for (var j = 0; j < duplicatedHeaders.length; j++) { + const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray(); + for (let j = 0; j < duplicatedHeaders.length; j++) { if (duplicatedHeaders[j] != headers[i]) { - var newId = id + j; - var $duplicatedHeader = $(duplicatedHeaders[j]); + const newId = id + j; + const $duplicatedHeader = $(duplicatedHeaders[j]); $duplicatedHeader.attr('id', newId); - var $headerLink = $duplicatedHeader.find('> .header-link'); - $headerLink.attr('href', '#' + newId); + const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`); + $headerLink.attr('href', `#${newId}`); $headerLink.attr('title', newId); } } } } -function renderTOC(view) { - var tocs = view.find('.toc').toArray(); - for (var i = 0; i < tocs.length; i++) { - var toc = $(tocs[i]); - var id = 'toc' + i; +export function renderTOC(view) { + const tocs = view.find('.toc').toArray(); + for (let i = 0; i < tocs.length; i++) { + const toc = $(tocs[i]); + const id = `toc${i}`; toc.attr('id', id); - var target = $('#' + id); + const target = $(`#${id}`); target.html(''); new Toc('doc', { 'level': 3, @@ -866,8 +862,8 @@ function renderTOC(view) { } } -function scrollToHash() { - var hash = location.hash; +export function scrollToHash() { + const hash = location.hash; location.hash = ""; location.hash = hash; } @@ -877,39 +873,39 @@ function highlightRender(code, lang) { return; code = S(code).escapeHTML().s if (lang == 'sequence') { - return '<div class="sequence-diagram raw">' + code + '</div>'; + return `<div class="sequence-diagram raw">${code}</div>`; } else if (lang == 'flow') { - return '<div class="flow-chart raw">' + code + '</div>'; + return `<div class="flow-chart raw">${code}</div>`; } else if (lang == 'graphviz') { - return '<div class="graphviz raw">' + code + '</div>'; + return `<div class="graphviz raw">${code}</div>`; } else if (lang == 'mermaid') { - return '<div class="mermaid raw">' + code + '</div>'; + return `<div class="mermaid raw">${code}</div>`; } - var result = { + const result = { value: code }; - var showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang); + const showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang); if (showlinenumbers) { - var startnumber = 1; - var matches = lang.match(/\=(\d+)$/); + let startnumber = 1; + const matches = lang.match(/\=(\d+)$/); if (matches) startnumber = parseInt(matches[1]); - var lines = result.value.split('\n'); - var linenumbers = []; - for (var i = 0; i < lines.length - 1; i++) { - linenumbers[i] = "<span data-linenumber='" + (startnumber + i) + "'></span>"; + const lines = result.value.split('\n'); + const linenumbers = []; + for (let i = 0; i < lines.length - 1; i++) { + linenumbers[i] = `<span data-linenumber='${startnumber + i}'></span>`; } - var continuelinenumber = /\=\+$/.test(lang); - var linegutter = "<div class='gutter linenumber" + (continuelinenumber ? " continue" : "") + "'>" + linenumbers.join('\n') + "</div>"; - result.value = "<div class='wrapper'>" + linegutter + "<div class='code'>" + result.value + "</div></div>"; + const continuelinenumber = /\=\+$/.test(lang); + const linegutter = `<div class='gutter linenumber${continuelinenumber ? " continue" : ""}'>${linenumbers.join('\n')}</div>`; + result.value = `<div class='wrapper'>${linegutter}<div class='code'>${result.value}</div></div>`; } return result.value; } -var markdownit = require('markdown-it'); -var markdownitContainer = require('markdown-it-container'); +import markdownit from 'markdown-it'; +import markdownitContainer from 'markdown-it-container'; -var md = markdownit('default', { +export let md = markdownit('default', { html: true, breaks: true, langPrefix: "", @@ -945,19 +941,17 @@ emojify.setConfig({ elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'], classes: ['no-emojify'] }, - img_dir: serverurl + '/build/emojify.js/dist/images/basic', + img_dir: `${serverurl}/build/emojify.js/dist/images/basic`, ignore_emoticons: true }); -md.renderer.rules.emoji = function(token, idx) { - return emojify.replace(':' + token[idx].markup + ':'); -}; +md.renderer.rules.emoji = (token, idx) => emojify.replace(`:${token[idx].markup}:`); function renderContainer(tokens, idx, options, env, self) { tokens[idx].attrJoin('role', 'alert'); tokens[idx].attrJoin('class', 'alert'); - tokens[idx].attrJoin('class', 'alert-' + tokens[idx].info.trim()); - return self.renderToken.apply(self, arguments); + tokens[idx].attrJoin('class', `alert-${tokens[idx].info.trim()}`); + return self.renderToken(...arguments); } md.use(markdownitContainer, 'success', { render: renderContainer }); md.use(markdownitContainer, 'info', { render: renderContainer }); @@ -966,25 +960,25 @@ md.use(markdownitContainer, 'danger', { render: renderContainer }); md.renderer.rules.image = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw'); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw'); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw'); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw'); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; -md.renderer.rules.fence = function (tokens, idx, options, env, self) { - var token = tokens[idx], - info = token.info ? md.utils.unescapeAll(token.info).trim() : '', - langName = '', - highlighted; +md.renderer.rules.fence = (tokens, idx, options, env, self) => { + const token = tokens[idx]; + const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''; + let langName = ''; + let highlighted; if (info) { langName = info.split(/\s+/g)[0]; @@ -1001,110 +995,99 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) { } if (highlighted.indexOf('<pre') === 0) { - return highlighted + '\n'; + return `${highlighted}\n`; } - return '<pre><code' + self.renderAttrs(token) + '>' - + highlighted - + '</code></pre>\n'; + return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`; }; /* Defined regex markdown it plugins */ -var Plugin = require('markdown-it-regexp'); +import Plugin from 'markdown-it-regexp'; //youtube -var youtubePlugin = new Plugin( +const youtubePlugin = new Plugin( // regexp to match /{%youtube\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var videoid = match[1]; + (match, utils) => { + const videoid = match[1]; if (!videoid) return; - var div = $('<div class="youtube raw"></div>'); + const div = $('<div class="youtube raw"></div>'); div.attr('data-videoid', videoid); - var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg'; - var image = '<img src="' + thumbnail_src + '" />'; + const thumbnail_src = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`; + const image = `<img src="${thumbnail_src}" />`; div.append(image); - var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'; + const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'; div.append(icon); return div[0].outerHTML; } ); //vimeo -var vimeoPlugin = new Plugin( +const vimeoPlugin = new Plugin( // regexp to match /{%vimeo\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var videoid = match[1]; + (match, utils) => { + const videoid = match[1]; if (!videoid) return; - var div = $('<div class="vimeo raw"></div>'); + const div = $('<div class="vimeo raw"></div>'); div.attr('data-videoid', videoid); - var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'; + const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'; div.append(icon); return div[0].outerHTML; } ); //gist -var gistPlugin = new Plugin( +const gistPlugin = new Plugin( // regexp to match /{%gist\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var gistid = match[1]; - var code = '<code data-gist-id="' + gistid + '"/>'; + (match, utils) => { + const gistid = match[1]; + const code = `<code data-gist-id="${gistid}"/>`; return code; } ); //TOC -var tocPlugin = new Plugin( +const tocPlugin = new Plugin( // regexp to match /^\[TOC\]$/i, - // this function will be called when something matches - function (match, utils) { - return '<div class="toc"></div>'; - } + (match, utils) => '<div class="toc"></div>' ); //slideshare -var slidesharePlugin = new Plugin( +const slidesharePlugin = new Plugin( // regexp to match /{%slideshare\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var slideshareid = match[1]; - var div = $('<div class="slideshare raw"></div>'); + (match, utils) => { + const slideshareid = match[1]; + const div = $('<div class="slideshare raw"></div>'); div.attr('data-slideshareid', slideshareid); return div[0].outerHTML; } ); //speakerdeck -var speakerdeckPlugin = new Plugin( +const speakerdeckPlugin = new Plugin( // regexp to match /{%speakerdeck\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var speakerdeckid = match[1]; - var div = $('<div class="speakerdeck raw"></div>'); + (match, utils) => { + const speakerdeckid = match[1]; + const div = $('<div class="speakerdeck raw"></div>'); div.attr('data-speakerdeckid', speakerdeckid); return div[0].outerHTML; } ); //pdf -var pdfPlugin = new Plugin( +const pdfPlugin = new Plugin( // regexp to match /{%pdf\s*([\d\D]*?)\s*%}/, - // this function will be called when something matches - function (match, utils) { - var pdfurl = match[1]; + (match, utils) => { + const pdfurl = match[1]; if (!isValidURL(pdfurl)) return match[0]; - var div = $('<div class="pdf raw"></div>'); + const div = $('<div class="pdf raw"></div>'); div.attr('data-pdfurl', pdfurl); return div[0].outerHTML; } @@ -1112,8 +1095,8 @@ var pdfPlugin = new Plugin( //yaml meta, from https://github.com/eugeneware/remarkable-meta function get(state, line) { - var pos = state.bMarks[line]; - var max = state.eMarks[line]; + const pos = state.bMarks[line]; + const max = state.eMarks[line]; return state.src.substr(pos, max - pos); } @@ -1122,9 +1105,9 @@ function meta(state, start, end, silent) { if (state.tShift[start] < 0) return false; if (!get(state, start).match(/^---$/)) return false; - var data = []; + const data = []; for (var line = start + 1; line < end; line++) { - var str = get(state, line); + const str = get(state, line); if (str.match(/^(\.{3}|-{3})$/)) break; if (state.tShift[line] < 0) break; data.push(str); @@ -1162,24 +1145,6 @@ md.use(slidesharePlugin); md.use(speakerdeckPlugin); md.use(pdfPlugin); -module.exports = { - md: md, - updateLastChange: updateLastChange, - postProcess: postProcess, - finishView: finishView, - autoLinkify: autoLinkify, - deduplicatedHeaderId: deduplicatedHeaderId, - renderTOC: renderTOC, - renderTitle: renderTitle, - renderFilename: renderFilename, - renderTags: renderTags, - isValidURL: isValidURL, - generateToc: generateToc, - smoothHashScroll: smoothHashScroll, - scrollToHash: scrollToHash, - updateLastChangeUser: updateLastChangeUser, - updateOwner: updateOwner, - parseMeta: parseMeta, - exportToHTML: exportToHTML, - exportToRawHTML: exportToRawHTML +export default { + md }; diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js index e653653c..94aa77ff 100644 --- a/public/js/google-drive-picker.js +++ b/public/js/google-drive-picker.js @@ -52,6 +52,7 @@ var view = new google.picker.DocsView(); view.setMimeTypes("text/markdown,text/html"); view.setIncludeFolders(true); + view.setOwnedByMe(true); this.picker = new google.picker.PickerBuilder(). enableFeature(google.picker.Feature.NAV_HIDDEN). addView(view). @@ -115,4 +116,4 @@ }, callback ? callback : function() {}); } }; -}());
\ No newline at end of file +}()); diff --git a/public/js/history.js b/public/js/history.js index 6972f24c..34b2cba7 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -1,10 +1,13 @@ -var store = require('store'); -var S = require('string'); +import store from 'store'; +import S from 'string'; -var common = require('./common'); -var checkIfAuth = common.checkIfAuth; -var urlpath = common.urlpath; -var getLoginState = common.getLoginState; +import { + checkIfAuth +} from './lib/common/login'; + +import { + urlpath +} from './lib/config'; window.migrateHistoryFromTempCallback = null; @@ -12,22 +15,22 @@ migrateHistoryFromTemp(); function migrateHistoryFromTemp() { if (url('#tempid')) { - $.get(serverurl + '/temp', { + $.get(`${serverurl}/temp`, { tempid: url('#tempid') }) - .done(function (data) { + .done(data => { if (data && data.temp) { - getStorageHistory(function (olddata) { + getStorageHistory(olddata => { if (!olddata || olddata.length == 0) { saveHistoryToStorage(JSON.parse(data.temp)); } }); } }) - .always(function () { - var hash = location.hash.split('#')[1]; + .always(() => { + let hash = location.hash.split('#')[1]; 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) { hash.splice(i, 1); i--; @@ -40,12 +43,12 @@ function migrateHistoryFromTemp() { } } -function saveHistory(notehistory) { +export function saveHistory(notehistory) { checkIfAuth( - function () { + () => { saveHistoryToServer(notehistory); }, - function () { + () => { saveHistoryToStorage(notehistory); } ); @@ -65,7 +68,7 @@ function saveHistoryToCookie(notehistory) { } function saveHistoryToServer(notehistory) { - $.post(serverurl + '/history', { + $.post(`${serverurl}/history`, { history: JSON.stringify(notehistory) }); } @@ -75,37 +78,37 @@ function saveCookieHistoryToStorage(callback) { callback(); } -function saveStorageHistoryToServer(callback) { - var data = store.get('notehistory'); +export function saveStorageHistoryToServer(callback) { + const data = store.get('notehistory'); if (data) { - $.post(serverurl + '/history', { + $.post(`${serverurl}/history`, { history: data }) - .done(function (data) { + .done(data => { callback(data); }); } } function saveCookieHistoryToServer(callback) { - $.post(serverurl + '/history', { + $.post(`${serverurl}/history`, { history: Cookies.get('notehistory') }) - .done(function (data) { + .done(data => { callback(data); }); } -function clearDuplicatedHistory(notehistory) { - var newnotehistory = []; - for (var i = 0; i < notehistory.length; i++) { - var found = false; - for (var j = 0; j < newnotehistory.length; j++) { - var id = notehistory[i].id.replace(/\=+$/, ''); - var newId = newnotehistory[j].id.replace(/\=+$/, ''); +export function clearDuplicatedHistory(notehistory) { + const newnotehistory = []; + for (let i = 0; i < notehistory.length; i++) { + let found = false; + for (let j = 0; j < newnotehistory.length; j++) { + const id = notehistory[i].id.replace(/\=+$/, ''); + const newId = newnotehistory[j].id.replace(/\=+$/, ''); 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')); - var newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[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')); + 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) { newnotehistory[j] = notehistory[i]; } @@ -123,42 +126,42 @@ function addHistory(id, text, time, tags, pinned, notehistory) { // only add when note id exists if (id) { notehistory.push({ - id: id, - text: text, - time: time, - tags: tags, - pinned: pinned + id, + text, + time, + tags, + pinned }); } return notehistory; } -function removeHistory(id, notehistory) { - for (var i = 0; i < notehistory.length; i++) { +export function removeHistory(id, notehistory) { + for (let i = 0; i < notehistory.length; i++) { if (notehistory[i].id == id) { notehistory.splice(i, 1); - i--; - } + i -= 1; + } } return notehistory; } //used for inner -function writeHistory(title, tags) { +export function writeHistory(title, tags) { checkIfAuth( - function () { + () => { // no need to do this anymore, this will count from server-side // writeHistoryToServer(title, tags); }, - function () { + () => { writeHistoryToStorage(title, tags); } ); } function writeHistoryToServer(title, tags) { - $.get(serverurl + '/history') - .done(function (data) { + $.get(`${serverurl}/history`) + .done(data => { try { if (data.history) { var notehistory = data.history; @@ -171,10 +174,10 @@ function writeHistoryToServer(title, tags) { if (!notehistory) notehistory = []; - var newnotehistory = generateHistory(title, tags, notehistory); + const newnotehistory = generateHistory(title, tags, notehistory); saveHistoryToServer(newnotehistory); }) - .fail(function (xhr, status, error) { + .fail((xhr, status, error) => { console.error(xhr.responseText); }); } @@ -188,13 +191,13 @@ function writeHistoryToCookie(title, tags) { if (!notehistory) notehistory = []; - var newnotehistory = generateHistory(title, tags, notehistory); + const newnotehistory = generateHistory(title, tags, notehistory); saveHistoryToCookie(newnotehistory); } function writeHistoryToStorage(title, tags) { if (store.enabled) { - var data = store.get('notehistory'); + let data = store.get('notehistory'); if (data) { if (typeof data == "string") data = JSON.parse(data); @@ -204,7 +207,7 @@ function writeHistoryToStorage(title, tags) { if (!notehistory) notehistory = []; - var newnotehistory = generateHistory(title, tags, notehistory); + const newnotehistory = generateHistory(title, tags, notehistory); saveHistoryToStorage(newnotehistory); } else { writeHistoryToCookie(title, tags); @@ -212,32 +215,30 @@ function writeHistoryToStorage(title, tags) { } if (!Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; + Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]'; } function renderHistory(title, 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 { - id: id, + id, text: title, time: moment().valueOf(), - tags: tags + tags }; } function generateHistory(title, tags, notehistory) { - var info = renderHistory(title, tags); - //keep any pinned data - var pinned = false; - for (var i = 0; i < notehistory.length; i++) { - if (notehistory[i].id == info.id && notehistory[i].pinned) { - pinned = true; - break; - } - } + const info = renderHistory(title, tags); + //keep any pinned data + let pinned = false; + for (let i = 0; i < notehistory.length; i++) { + if (notehistory[i].id == info.id && notehistory[i].pinned) { + pinned = true; + break; + } + } notehistory = removeHistory(info.id, notehistory); notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory); notehistory = clearDuplicatedHistory(notehistory); @@ -245,25 +246,25 @@ function generateHistory(title, tags, notehistory) { } //used for outer -function getHistory(callback) { +export function getHistory(callback) { checkIfAuth( - function () { + () => { getServerHistory(callback); }, - function () { + () => { getStorageHistory(callback); } ); } function getServerHistory(callback) { - $.get(serverurl + '/history') - .done(function (data) { + $.get(`${serverurl}/history`) + .done(data => { if (data.history) { callback(data.history); } }) - .fail(function (xhr, status, error) { + .fail((xhr, status, error) => { console.error(xhr.responseText); }); } @@ -272,9 +273,9 @@ function getCookieHistory(callback) { callback(Cookies.getJSON('notehistory')); } -function getStorageHistory(callback) { +export function getStorageHistory(callback) { if (store.enabled) { - var data = store.get('notehistory'); + let data = store.get('notehistory'); if (data) { if (typeof data == "string") data = JSON.parse(data); @@ -286,37 +287,37 @@ function getStorageHistory(callback) { } } -function parseHistory(list, callback) { +export function parseHistory(list, callback) { checkIfAuth( - function () { + () => { parseServerToHistory(list, callback); }, - function () { + () => { parseStorageToHistory(list, callback); } ); } -function parseServerToHistory(list, callback) { - $.get(serverurl + '/history') - .done(function (data) { +export function parseServerToHistory(list, callback) { + $.get(`${serverurl}/history`) + .done(data => { if (data.history) { parseToHistory(list, data.history, callback); } }) - .fail(function (xhr, status, error) { + .fail((xhr, status, error) => { console.error(xhr.responseText); }); } function parseCookieToHistory(list, callback) { - var notehistory = Cookies.getJSON('notehistory'); + const notehistory = Cookies.getJSON('notehistory'); parseToHistory(list, notehistory, callback); } -function parseStorageToHistory(list, callback) { +export function parseStorageToHistory(list, callback) { if (store.enabled) { - var data = store.get('notehistory'); + let data = store.get('notehistory'); if (data) { if (typeof data == "string") data = JSON.parse(data); @@ -332,9 +333,9 @@ function parseToHistory(list, notehistory, callback) { if (!callback) return; else if (!list || !notehistory) callback(list, notehistory); 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 - 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].fromNow = timestamp.fromNow(); notehistory[i].time = timestamp.format('llll'); @@ -349,42 +350,23 @@ function parseToHistory(list, notehistory, callback) { callback(list, notehistory); } -function postHistoryToServer(noteId, data, callback) { - $.post(serverurl + '/history/' + noteId, data) - .done(function (result) { - return callback(null, result); - }) - .fail(function (xhr, status, error) { +export function postHistoryToServer(noteId, data, callback) { + $.post(`${serverurl}/history/${noteId}`, data) + .done(result => callback(null, result)) + .fail((xhr, status, error) => { console.error(xhr.responseText); return callback(error, null); }); } -function deleteServerHistory(noteId, callback) { +export function deleteServerHistory(noteId, callback) { $.ajax({ - url: serverurl + '/history' + (noteId ? '/' + noteId : ""), + url: `${serverurl}/history${noteId ? '/' + noteId : ""}`, type: 'DELETE' }) - .done(function (result) { - return callback(null, result); - }) - .fail(function (xhr, status, error) { + .done(result => callback(null, result)) + .fail((xhr, status, error) => { console.error(xhr.responseText); 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 -} diff --git a/public/js/index.js b/public/js/index.js index a018e513..f0c476ef 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -17,51 +17,58 @@ var _ = require("lodash"); var List = require('list.js'); -var common = require('./common.js'); -var urlpath = common.urlpath; -var noteid = common.noteid; -var debug = common.debug; -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; - -var extra = require('./extra'); -var md = extra.md; -var updateLastChange = extra.updateLastChange; -var postProcess = extra.postProcess; -var finishView = extra.finishView; -var autoLinkify = extra.autoLinkify; -var generateToc = extra.generateToc; -var smoothHashScroll = extra.smoothHashScroll; -var deduplicatedHeaderId = extra.deduplicatedHeaderId; -var renderTOC = extra.renderTOC; -var renderTitle = extra.renderTitle; -var renderFilename = extra.renderFilename; -var renderTags = extra.renderTags; -var isValidURL = extra.isValidURL; -var scrollToHash = extra.scrollToHash; -var updateLastChangeUser = extra.updateLastChangeUser; -var updateOwner = extra.updateOwner; -var parseMeta = extra.parseMeta; -var exportToHTML = extra.exportToHTML; -var exportToRawHTML = extra.exportToRawHTML; - -var syncScroll = require('./syncscroll'); -var setupSyncAreas = syncScroll.setupSyncAreas; -var clearMap = syncScroll.clearMap; -var syncScrollToEdit = syncScroll.syncScrollToEdit; -var syncScrollToView = syncScroll.syncScrollToView; - -var historyModule = require('./history'); -var writeHistory = historyModule.writeHistory; -var deleteServerHistory = historyModule.deleteServerHistory; -var getHistory = historyModule.getHistory; -var saveHistory = historyModule.saveHistory; -var removeHistory = historyModule.removeHistory; +import { + checkLoginStateChanged, + setloginStateChangeEvent +} from './lib/common/login'; + +import { + debug, + DROPBOX_APP_KEY, + GOOGLE_API_KEY, + GOOGLE_CLIENT_ID, + noteid, + noteurl, + urlpath, + version +} from './lib/config'; + +import { + autoLinkify, + deduplicatedHeaderId, + exportToHTML, + exportToRawHTML, + finishView, + generateToc, + isValidURL, + md, + parseMeta, + postProcess, + renderFilename, + renderTOC, + renderTags, + renderTitle, + scrollToHash, + smoothHashScroll, + updateLastChange, + updateLastChangeUser, + updateOwner +} from './extra'; + +import { + clearMap, + setupSyncAreas, + syncScrollToEdit, + syncScrollToView +} from './syncscroll'; + +import { + writeHistory, + deleteServerHistory, + getHistory, + saveHistory, + removeHistory +} from './history'; var renderer = require('./render'); var preventXSS = renderer.preventXSS; @@ -401,7 +408,8 @@ window.lastInfo = { cursor: { line: null, ch: null - } + }, + selections: null }, view: { scroll: { @@ -963,10 +971,10 @@ function setNeedRefresh() { showStatus(statusType.offline); } -loginStateChangeEvent = function () { +setloginStateChangeEvent(function () { setRefreshModal('user-state-changed'); setNeedRefresh(); -}; +}); //visibility var wasFocus = false; @@ -1535,7 +1543,7 @@ ui.toolbar.download.markdown.click(function (e) { var blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" }); - saveAs(blob, filename); + saveAs(blob, filename, true); }); //html ui.toolbar.download.html.click(function (e) { @@ -1915,7 +1923,7 @@ $('#revisionModalDownload').click(function () { var blob = new Blob([revision.content], { type: "text/markdown;charset=utf-8" }); - saveAs(blob, filename); + saveAs(blob, filename, true); }); $('#revisionModalRevert').click(function () { if (!revision) return; @@ -2511,7 +2519,7 @@ var addStyleRule = (function () { }()); function updateAuthorshipInner() { // ignore when ot not synced yet - if (cmClient && Object.keys(cmClient.state).length > 0) return; + if (havePendingOperation()) return; authorMarks = {}; for (var i = 0; i < authorship.length; i++) { var atom = authorship[i]; @@ -2668,7 +2676,7 @@ editor.on('update', function () { }); // clear tooltip which described element has been removed $('[id^="tooltip"]').each(function (index, element) { - $ele = $(element); + var $ele = $(element); if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove(); }); }); @@ -2726,12 +2734,16 @@ var EditorClient = ot.EditorClient; var SocketIOAdapter = ot.SocketIOAdapter; var CodeMirrorAdapter = ot.CodeMirrorAdapter; var cmClient = null; +var synchronized_ = null; + +function havePendingOperation() { + return (cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')) ? true : false; +} socket.on('doc', function (obj) { var body = obj.str; 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(); if (setDoc && bodyMismatch) { @@ -2756,16 +2768,17 @@ socket.on('doc', function (obj) { obj.revision, obj.clients, new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) ); + synchronized_ = cmClient.state; } else if (setDoc) { if (bodyMismatch) { cmClient.undoManager.undoStack.length = 0; cmClient.undoManager.redoStack.length = 0; } cmClient.revision = obj.revision; - cmClient.setState(new ot.Client.Synchronized()); + cmClient.setState(synchronized_); cmClient.initializeClientList(); cmClient.initializeClients(obj.clients); - } else if (havePendingOperation) { + } else if (havePendingOperation()) { cmClient.serverReconnect(); } @@ -3387,6 +3400,7 @@ function saveInfo() { break; } lastInfo.edit.cursor = editor.getCursor(); + lastInfo.edit.selections = editor.listSelections(); lastInfo.needRestore = true; } @@ -3396,6 +3410,7 @@ function restoreInfo() { var line = lastInfo.edit.cursor.line; var ch = lastInfo.edit.cursor.ch; editor.setCursor(line, ch); + editor.setSelections(lastInfo.edit.selections); switch (currentMode) { case modeType.edit: if (scrollbarStyle == 'native') { @@ -3445,6 +3460,7 @@ function updateViewInner() { var value = editor.getValue(); var lastMeta = md.meta; md.meta = {}; + delete md.metaError; var rendered = md.render(value); if (md.meta.type && md.meta.type === 'slide') { var slideOptions = { @@ -3716,6 +3732,7 @@ function checkCursorMenuInner() { var offsetLeft = 0; var offsetTop = defaultTextHeight; // set up side down + window.upSideDown = false; var lastUpSideDown = upSideDown = false; // only do when have width and height if (width > 0 && height > 0) { @@ -3944,7 +3961,7 @@ $(editor.getInputField()) match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/, search: function (term, callback) { var line = editor.getLine(editor.getCursor().line); - quote = line.match(this.match)[1].trim(); + var quote = line.match(this.match)[1].trim(); var list = []; if (quote.indexOf('>') == 0) { $.map(supportExtraTags, function (extratag) { diff --git a/public/js/lib/common/login.js b/public/js/lib/common/login.js new file mode 100644 index 00000000..58fa55c6 --- /dev/null +++ b/public/js/lib/common/login.js @@ -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 +}; diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js new file mode 100644 index 00000000..2b73679f --- /dev/null +++ b/public/js/lib/config/index.js @@ -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'; diff --git a/public/js/pretty.js b/public/js/pretty.js index c1a471a1..18d0dc0d 100644 --- a/public/js/pretty.js +++ b/public/js/pretty.js @@ -4,31 +4,34 @@ require('../css/site.css'); require('highlight.js/styles/github-gist.css'); -var extra = require('./extra'); -var md = extra.md; -var finishView = extra.finishView; -var autoLinkify = extra.autoLinkify; -var deduplicatedHeaderId = extra.deduplicatedHeaderId; -var renderTOC = extra.renderTOC; -var generateToc = extra.generateToc; -var smoothHashScroll = extra.smoothHashScroll; -var postProcess = extra.postProcess; -var updateLastChange = extra.updateLastChange; -var parseMeta = extra.parseMeta; -var scrollToHash = extra.scrollToHash; -var preventXSS = require('./render').preventXSS; +import { + autoLinkify, + deduplicatedHeaderId, + finishView, + generateToc, + md, + parseMeta, + postProcess, + renderTOC, + scrollToHash, + smoothHashScroll, + updateLastChange +} from './extra'; -var markdown = $("#doc.markdown-body"); -var text = markdown.text(); -var lastMeta = md.meta; +import { preventXSS } from './render'; + +const markdown = $("#doc.markdown-body"); +const text = markdown.text(); +const lastMeta = 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') { - var slideOptions = { + const slideOptions = { separator: '^(\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); RevealMarkdown.initialize(); // prevent XSS @@ -46,10 +49,11 @@ if (md.meta.type && md.meta.type === 'slide') { } // prevent XSS rendered = preventXSS(rendered); - var result = postProcess(rendered); + const result = postProcess(rendered); markdown.html(result.html()); } $(document.body).show(); + finishView(markdown); autoLinkify(markdown); deduplicatedHeaderId(markdown); @@ -60,17 +64,18 @@ smoothHashScroll(); createtime = lastchangeui.time.attr('data-createtime'); lastchangetime = lastchangeui.time.attr('data-updatetime'); updateLastChange(); -var url = window.location.pathname; -$('.ui-edit').attr('href', url + '/edit'); -var toc = $('.ui-toc'); -var tocAffix = $('.ui-affix-toc'); -var tocDropdown = $('.ui-toc-dropdown'); + +const url = window.location.pathname; +$('.ui-edit').attr('href', `${url}/edit`); +const toc = $('.ui-toc'); +const tocAffix = $('.ui-affix-toc'); +const tocDropdown = $('.ui-toc-dropdown'); //toc -tocDropdown.click(function (e) { +tocDropdown.click(e => { e.stopPropagation(); }); -var enoughForAffixToc = true; +let enoughForAffixToc = true; function generateScrollspy() { $(document.body).scrollspy({ @@ -89,18 +94,18 @@ function generateScrollspy() { function windowResize() { //toc right - var paddingRight = parseFloat(markdown.css('padding-right')); - var right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight)); - toc.css('right', right + 'px'); + const paddingRight = parseFloat(markdown.css('padding-right')); + const right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight)); + toc.css('right', `${right}px`); //affix toc left - var newbool; - var rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2; + let newbool; + const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2; //for ipad or wider device if (rightMargin >= 133) { newbool = true; - var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2; - var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin; - tocAffix.css('left', left + 'px'); + const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2; + const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin; + tocAffix.css('left', `${left}px`); } else { newbool = false; } @@ -109,10 +114,10 @@ function windowResize() { generateScrollspy(); } } -$(window).resize(function () { +$(window).resize(() => { windowResize(); }); -$(document).ready(function () { +$(document).ready(() => { windowResize(); generateScrollspy(); setTimeout(scrollToHash, 0); @@ -120,13 +125,13 @@ $(document).ready(function () { $('[data-toggle="tooltip"]').tooltip(); }); -function scrollToTop() { +export function scrollToTop() { $('body, html').stop(true, true).animate({ scrollTop: 0 }, 100, "linear"); } -function scrollToBottom() { +export function scrollToBottom() { $('body, html').stop(true, true).animate({ scrollTop: $(document.body)[0].scrollHeight }, 100, "linear"); @@ -134,8 +139,3 @@ function scrollToBottom() { window.scrollToTop = scrollToTop; window.scrollToBottom = scrollToBottom; - -module.exports = { - scrollToBottom: scrollToBottom, - scrollToTop: scrollToTop -} diff --git a/public/js/render.js b/public/js/render.js index a61fc8fb..5d6d0aa2 100644 --- a/public/js/render.js +++ b/public/js/render.js @@ -9,6 +9,8 @@ var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base var whiteList = filterXSS.whiteList; // allow ol specify start number whiteList['ol'] = ['start']; +// allow li specify value number +whiteList['li'] = ['value']; // allow style tag whiteList['style'] = []; // allow kbd tag diff --git a/public/js/slide.js b/public/js/slide.js index 1ff388a5..63cf64c6 100644 --- a/public/js/slide.js +++ b/public/js/slide.js @@ -1,67 +1,65 @@ require('../css/extra.css'); require('../css/site.css'); -var extraModule = require('./extra'); -var md = extraModule.md; -var updateLastChange = extraModule.updateLastChange; -var finishView = extraModule.finishView; +import { md, updateLastChange, finishView } from './extra'; -var preventXSS = require('./render').preventXSS; +import { preventXSS } from './render'; -var body = $(".slides").text(); +const body = $(".slides").text(); createtime = lastchangeui.time.attr('data-createtime'); lastchangetime = lastchangeui.time.attr('data-updatetime'); updateLastChange(); -var url = window.location.pathname; -$('.ui-edit').attr('href', url + '/edit'); +const url = window.location.pathname; +$('.ui-edit').attr('href', `${url}/edit`); -$(document).ready(function () { +$(document).ready(() => { //tooltip $('[data-toggle="tooltip"]').tooltip(); }); function extend() { - var target = {}; - for (var i = 0; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { + const target = {}; + + for (const source of arguments) { + for (const key in source) { if (source.hasOwnProperty(key)) { target[key] = source[key]; } } } + return target; } // Optional libraries used to extend on reveal.js -var deps = [{ - src: serverurl + '/build/reveal.js/lib/js/classList.js', - condition: function() { +const deps = [{ + src: `${serverurl}/build/reveal.js/lib/js/classList.js`, + condition() { return !document.body.classList; } }, { - src: serverurl + '/js/reveal-markdown.js', - callback: function () { - var slideOptions = { + src: `${serverurl}/js/reveal-markdown.js`, + callback() { + const slideOptions = { separator: '^(\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); RevealMarkdown.initialize(); $(".slides").show(); } }, { - src: serverurl + '/build/reveal.js/plugin/notes/notes.js', + src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`, async: true, - condition: function() { + condition() { return !!document.body.classList; } }]; // default options to init reveal.js -var defaultOptions = { +const defaultOptions = { controls: true, progress: true, slideNumber: true, @@ -72,10 +70,10 @@ var defaultOptions = { }; // options from yaml meta -var meta = JSON.parse($("#meta").text()); +const meta = JSON.parse($("#meta").text()); var options = meta.slideOptions || {}; -var view = $('.reveal'); +const view = $('.reveal'); //text language if (meta.lang && typeof meta.lang == "string") { @@ -97,24 +95,24 @@ if (typeof meta.breaks === 'boolean' && !meta.breaks) { } // options from URL query string -var queryOptions = Reveal.getQueryHash() || {}; +const queryOptions = Reveal.getQueryHash() || {}; var options = extend(defaultOptions, options, queryOptions); Reveal.initialize(options); -window.viewAjaxCallback = function () { +window.viewAjaxCallback = () => { Reveal.layout(); }; function renderSlide(event) { if (window.location.search.match( /print-pdf/gi )) { - var slides = $('.slides'); + const slides = $('.slides'); var title = document.title; finishView(slides); document.title = title; Reveal.layout(); } else { - var markdown = $(event.currentSlide); + const markdown = $(event.currentSlide); if (!markdown.attr('data-rendered')) { var title = document.title; finishView(markdown); @@ -125,16 +123,16 @@ function renderSlide(event) { } } -Reveal.addEventListener('ready', function (event) { +Reveal.addEventListener('ready', event => { renderSlide(event); - var markdown = $(event.currentSlide); + const markdown = $(event.currentSlide); // force browser redraw - setTimeout(function () { + setTimeout(() => { markdown.hide().show(0); }, 0); }); 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'); diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js index 47d0e1c4..c9693176 100644 --- a/public/js/syncscroll.js +++ b/public/js/syncscroll.js @@ -1,12 +1,13 @@ // Inject line numbers for sync scroll. -var extra = require('./extra'); -var md = extra.md; +import markdownitContainer from 'markdown-it-container'; + +import { md } from './extra'; function addPart(tokens, idx) { if (tokens[idx].map && tokens[idx].level === 0) { - var startline = tokens[idx].map[0] + 1; - var endline = tokens[idx].map[1]; + const startline = tokens[idx].map[0] + 1; + const endline = tokens[idx].map[1]; tokens[idx].attrJoin('class', 'part'); tokens[idx].attrJoin('data-startline', startline); 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) { tokens[idx].attrJoin('class', 'raw'); addPart(tokens, idx); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.table_open = function (tokens, idx, options, env, self) { 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) { 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) { tokens[idx].attrJoin('class', 'raw'); if (tokens[idx].map) { - var startline = tokens[idx].map[0] + 1; - var endline = tokens[idx].map[1]; + const startline = tokens[idx].map[0] + 1; + const endline = tokens[idx].map[1]; tokens[idx].attrJoin('data-startline', startline); 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) { addPart(tokens, idx); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.link_open = function (tokens, idx, options, env, self) { addPart(tokens, idx); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) { addPart(tokens, idx); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; md.renderer.rules.heading_open = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw'); addPart(tokens, idx); - return self.renderToken.apply(self, arguments); + return self.renderToken(...arguments); }; -md.renderer.rules.fence = function (tokens, idx, options, env, self) { - var token = tokens[idx], - info = token.info ? md.utils.unescapeAll(token.info).trim() : '', - langName = '', - highlighted; +md.renderer.rules.fence = (tokens, idx, options, env, self) => { + const token = tokens[idx]; + const info = token.info ? md.utils.unescapeAll(token.info).trim() : ''; + let langName = ''; + let highlighted; if (info) { 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) { - return highlighted + '\n'; + return `${highlighted}\n`; } if (tokens[idx].map && tokens[idx].level === 0) { - var startline = tokens[idx].map[0] + 1; - var endline = tokens[idx].map[1]; - return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code' + self.renderAttrs(token) + '>' - + highlighted - + '</code></pre>\n'; + const startline = tokens[idx].map[0] + 1; + const endline = tokens[idx].map[1]; + return `<pre class="part" data-startline="${startline}" data-endline="${endline}"><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`; } - return '<pre><code' + self.renderAttrs(token) + '>' - + highlighted - + '</code></pre>\n'; + return `<pre><code${self.renderAttrs(token)}>${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) { - var startline = tokens[idx].map[0] + 1; - var 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'; + const startline = tokens[idx].map[0] + 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><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) { tokens[idx].attrJoin('role', '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); - 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, 'info', { render: renderContainer }); md.use(markdownitContainer, 'warning', { render: renderContainer }); @@ -117,18 +113,18 @@ window.syncscroll = true; window.preventSyncScrollToEdit = false; window.preventSyncScrollToView = false; -var editScrollThrottle = 5; -var viewScrollThrottle = 5; -var buildMapThrottle = 100; +const editScrollThrottle = 5; +const viewScrollThrottle = 5; +const buildMapThrottle = 100; -var viewScrolling = false; -var editScrolling = false; +let viewScrolling = false; +let editScrolling = false; -var editArea = null; -var viewArea = null; -var markdownArea = null; +let editArea = null; +let viewArea = null; +let markdownArea = null; -function setupSyncAreas(edit, view, markdown) { +export function setupSyncAreas(edit, view, markdown) { editArea = edit; viewArea = view; markdownArea = markdown; @@ -136,26 +132,24 @@ function setupSyncAreas(edit, view, markdown) { viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle)); } -var scrollMap, lineHeightMap, viewTop, viewBottom; - -window.viewAjaxCallback = clearMap; +let scrollMap, lineHeightMap, viewTop, viewBottom; -function clearMap() { +export function clearMap() { scrollMap = null; lineHeightMap = null; viewTop = 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) // That's a bit dirty to process each line everytime, but ok for demo. // Optimizations are required only for big texts. function buildMapInner(callback) { if (!viewArea || !markdownArea) return; - var i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, - acc, _scrollMap; + let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap; offset = viewArea.scrollTop() - viewArea.offset().top; _scrollMap = []; @@ -165,10 +159,10 @@ function buildMapInner(callback) { viewBottom = viewArea[0].scrollHeight - viewArea.height(); acc = 0; - var lines = editor.getValue().split('\n'); - var lineHeight = editor.defaultTextHeight(); + const lines = editor.getValue().split('\n'); + const lineHeight = editor.defaultTextHeight(); for (i = 0; i < lines.length; i++) { - var str = lines[i]; + const str = lines[i]; _lineHeightMap.push(acc); @@ -177,7 +171,7 @@ function buildMapInner(callback) { continue; } - var h = editor.heightAtLine(i + 1) - editor.heightAtLine(i); + const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i); acc += Math.round(h / lineHeight); } _lineHeightMap.push(acc); @@ -191,10 +185,10 @@ function buildMapInner(callback) { // make the first line go top _scrollMap[0] = viewTop; - var parts = markdownArea.find('.part').toArray(); + const parts = markdownArea.find('.part').toArray(); for (i = 0; i < parts.length; i++) { - var $el = $(parts[i]), - t = $el.attr('data-startline') - 1; + const $el = $(parts[i]); + let t = $el.attr('data-startline') - 1; if (t === '') { return; } @@ -229,9 +223,9 @@ function buildMapInner(callback) { } // 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 (preventSyncScrollToEdit) { if (typeof preventSyncScrollToEdit === 'number') { @@ -242,15 +236,15 @@ function syncScrollToEdit(event, preventAnimate) { return; } if (!scrollMap || !lineHeightMap) { - buildMap(function () { + buildMap(() => { syncScrollToEdit(event, preventAnimate); }); return; } if (editScrolling) return; - var scrollTop = viewArea[0].scrollTop; - var lineIndex = 0; + const scrollTop = viewArea[0].scrollTop; + let lineIndex = 0; for (var i = 0, l = scrollMap.length; i < l; i++) { if (scrollMap[i] > scrollTop) { break; @@ -258,8 +252,8 @@ function syncScrollToEdit(event, preventAnimate) { lineIndex = i; } } - var lineNo = 0; - var lineDiff = 0; + let lineNo = 0; + let lineDiff = 0; for (var i = 0, l = lineHeightMap.length; i < l; i++) { if (lineHeightMap[i] > lineIndex) { break; @@ -269,14 +263,14 @@ function syncScrollToEdit(event, preventAnimate) { } } - var posTo = 0; - var topDiffPercent = 0; - var posToNextDiff = 0; - var scrollInfo = editor.getScrollInfo(); - var textHeight = editor.defaultTextHeight(); - var preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight; - var preLastLineNo = Math.round(preLastLineHeight / textHeight); - var preLastLinePos = scrollMap[preLastLineNo]; + let posTo = 0; + let topDiffPercent = 0; + let posToNextDiff = 0; + const scrollInfo = editor.getScrollInfo(); + const textHeight = editor.defaultTextHeight(); + const preLastLineHeight = scrollInfo.height - scrollInfo.clientHeight - textHeight; + const preLastLineNo = Math.round(preLastLineHeight / textHeight); + const preLastLinePos = scrollMap[preLastLineNo]; if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) { posTo = preLastLineHeight; @@ -293,7 +287,7 @@ function syncScrollToEdit(event, preventAnimate) { if (preventAnimate) { editArea.scrollTop(posTo); } else { - var posDiff = Math.abs(scrollInfo.top - posTo); + const posDiff = Math.abs(scrollInfo.top - posTo); var duration = posDiff / 50; duration = duration >= 100 ? duration : 100; editArea.stop(true, true).animate({ @@ -311,9 +305,9 @@ function viewScrollingTimeoutInner() { } // 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 (preventSyncScrollToView) { if (typeof preventSyncScrollToView === 'number') { @@ -324,20 +318,20 @@ function syncScrollToView(event, preventAnimate) { return; } if (!scrollMap || !lineHeightMap) { - buildMap(function () { + buildMap(() => { syncScrollToView(event, preventAnimate); }); return; } if (viewScrolling) return; - var lineNo, posTo; - var topDiffPercent, posToNextDiff; - var scrollInfo = editor.getScrollInfo(); - var textHeight = editor.defaultTextHeight(); + let lineNo, posTo; + let topDiffPercent, posToNextDiff; + const scrollInfo = editor.getScrollInfo(); + const textHeight = editor.defaultTextHeight(); lineNo = Math.floor(scrollInfo.top / textHeight); // 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) { topDiffPercent = diffToBottom / textHeight; posTo = scrollMap[lineNo + 1]; @@ -353,7 +347,7 @@ function syncScrollToView(event, preventAnimate) { if (preventAnimate) { viewArea.scrollTop(posTo); } else { - var posDiff = Math.abs(viewArea.scrollTop() - posTo); + const posDiff = Math.abs(viewArea.scrollTop() - posTo); var duration = posDiff / 50; duration = duration >= 100 ? duration : 100; viewArea.stop(true, true).animate({ @@ -369,10 +363,3 @@ function syncScrollToView(event, preventAnimate) { function editScrollingTimeoutInner() { editScrolling = false; } - -module.exports = { - setupSyncAreas: setupSyncAreas, - clearMap: clearMap, - syncScrollToEdit: syncScrollToEdit, - syncScrollToView: syncScrollToView -}; diff --git a/public/screenshot.png b/public/screenshot.png Binary files differnew file mode 100644 index 00000000..e1e77431 --- /dev/null +++ b/public/screenshot.png diff --git a/public/vendor/ot/codemirror-adapter.js b/public/vendor/ot/codemirror-adapter.js index d858c411..cc23be01 100755 --- a/public/vendor/ot/codemirror-adapter.js +++ b/public/vendor/ot/codemirror-adapter.js @@ -343,7 +343,9 @@ ot.CodeMirrorAdapter = (function (global) { }; CodeMirrorAdapter.prototype.applyOperation = function (operation) { - this.ignoreNextChange = true; + if (!operation.isNoop()) { + this.ignoreNextChange = true; + } CodeMirrorAdapter.applyOperationToCodeMirror(operation, this.cm); }; diff --git a/public/vendor/ot/ot.min.js b/public/vendor/ot/ot.min.js index aba0960e..d942cb5d 100644 --- a/public/vendor/ot/ot.min.js +++ b/public/vendor/ot/ot.min.js @@ -1 +1 @@ -function hex2rgb(t){if("#"==t[0]&&(t=t.substr(1)),3==t.length){var e=t;t="",e=/^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(e).slice(1);for(var n=0;n<3;n++)t+=e[n]+e[n]}var o=/^([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(t).slice(1);return{red:parseInt(o[0],16),green:parseInt(o[1],16),blue:parseInt(o[2],16)}}if("undefined"==typeof ot)var ot={};if(ot.TextOperation=function(){"use strict";function t(){return this&&this.constructor===t?(this.ops=[],this.baseLength=0,void(this.targetLength=0)):new t}function e(e,n){var o=e.ops,r=t.isRetain;switch(o.length){case 1:return o[0];case 2:return r(o[0])?o[1]:r(o[1])?o[0]:null;case 3:if(r(o[0])&&r(o[2]))return o[1]}return null}function n(t){return o(t.ops[0])?t.ops[0]:0}t.prototype.equals=function(t){if(this.baseLength!==t.baseLength)return!1;if(this.targetLength!==t.targetLength)return!1;if(this.ops.length!==t.ops.length)return!1;for(var e=0;e<this.ops.length;e++)if(this.ops[e]!==t.ops[e])return!1;return!0};var o=t.isRetain=function(t){return"number"==typeof t&&t>0},r=t.isInsert=function(t){return"string"==typeof t},i=t.isDelete=function(t){return"number"==typeof t&&t<0};return t.prototype.retain=function(t){if("number"!=typeof t)throw new Error("retain expects an integer");return 0===t?this:(this.baseLength+=t,this.targetLength+=t,o(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.insert=function(t){if("string"!=typeof t)throw new Error("insert expects a string");if(""===t)return this;this.targetLength+=t.length;var e=this.ops;return r(e[e.length-1])?e[e.length-1]+=t:i(e[e.length-1])?r(e[e.length-2])?e[e.length-2]+=t:(e[e.length]=e[e.length-1],e[e.length-2]=t):e.push(t),this},t.prototype["delete"]=function(t){if("string"==typeof t&&(t=t.length),"number"!=typeof t)throw new Error("delete expects an integer or a string");return 0===t?this:(t>0&&(t=-t),this.baseLength-=t,i(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.isNoop=function(){return 0===this.ops.length||1===this.ops.length&&o(this.ops[0])},t.prototype.toString=function(){var t=Array.prototype.map||function(t){for(var e=this,n=[],o=0,r=e.length;o<r;o++)n[o]=t(e[o]);return n};return t.call(this.ops,function(t){return o(t)?"retain "+t:r(t)?"insert '"+t+"'":"delete "+-t}).join(", ")},t.prototype.toJSON=function(){return this.ops},t.fromJSON=function(e){for(var n=new t,s=0,a=e.length;s<a;s++){var h=e[s];if(o(h))n.retain(h);else if(r(h))n.insert(h);else{if(!i(h))throw new Error("unknown operation: "+JSON.stringify(h));n["delete"](h)}}return n},t.prototype.apply=function(t){var e=this;if(t.length!==e.baseLength)throw new Error("The operation's base length must be equal to the string's length.");for(var n=[],i=0,s=0,a=this.ops,h=0,p=a.length;h<p;h++){var c=a[h];if(o(c)){if(s+c>t.length)throw new Error("Operation can't retain more characters than are left in the string.");n[i++]=t.slice(s,s+c),s+=c}else r(c)?n[i++]=c:s-=c}if(s!==t.length)throw new Error("The operation didn't operate on the whole string.");return n.join("")},t.prototype.invert=function(e){for(var n=0,i=new t,s=this.ops,a=0,h=s.length;a<h;a++){var p=s[a];o(p)?(i.retain(p),n+=p):r(p)?i["delete"](p.length):(i.insert(e.slice(n,n-p)),n-=p)}return i},t.prototype.compose=function(e){var n=this;if(n.targetLength!==e.baseLength)throw new Error("The base length of the second operation has to be the target length of the first operation");for(var s=new t,a=n.ops,h=e.ops,p=0,c=0,l=a[p++],u=h[c++];;){if("undefined"==typeof l&&"undefined"==typeof u)break;if(i(l))s["delete"](l),l=a[p++];else if(r(u))s.insert(u),u=h[c++];else{if("undefined"==typeof l)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too long.");if(o(l)&&o(u))l>u?(s.retain(u),l-=u,u=h[c++]):l===u?(s.retain(l),l=a[p++],u=h[c++]):(s.retain(l),u-=l,l=a[p++]);else if(r(l)&&i(u))l.length>-u?(l=l.slice(-u),u=h[c++]):l.length===-u?(l=a[p++],u=h[c++]):(u+=l.length,l=a[p++]);else if(r(l)&&o(u))l.length>u?(s.insert(l.slice(0,u)),l=l.slice(u),u=h[c++]):l.length===u?(s.insert(l),l=a[p++],u=h[c++]):(s.insert(l),u-=l.length,l=a[p++]);else{if(!o(l)||!i(u))throw new Error("This shouldn't happen: op1: "+JSON.stringify(l)+", op2: "+JSON.stringify(u));l>-u?(s["delete"](u),l+=u,u=h[c++]):l===-u?(s["delete"](u),l=a[p++],u=h[c++]):(s["delete"](l),u+=l,l=a[p++])}}}return s},t.prototype.shouldBeComposedWith=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s:!(!i(a)||!i(h))&&(s-h===o||o===s))},t.prototype.shouldBeComposedWithInverted=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s||o===s:!(!i(a)||!i(h))&&s-h===o)},t.transform=function(e,n){if(e.baseLength!==n.baseLength)throw new Error("Both operations have to have the same base length");for(var s=new t,a=new t,h=e.ops,p=n.ops,c=0,l=0,u=h[c++],f=p[l++];;){if("undefined"==typeof u&&"undefined"==typeof f)break;if(r(u))s.insert(u),a.retain(u.length),u=h[c++];else if(r(f))s.retain(f.length),a.insert(f),f=p[l++];else{if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof f)throw new Error("Cannot compose operations: first operation is too long.");var d;if(o(u)&&o(f))u>f?(d=f,u-=f,f=p[l++]):u===f?(d=f,u=h[c++],f=p[l++]):(d=u,f-=u,u=h[c++]),s.retain(d),a.retain(d);else if(i(u)&&i(f))-u>-f?(u-=f,f=p[l++]):u===f?(u=h[c++],f=p[l++]):(f-=u,u=h[c++]);else if(i(u)&&o(f))-u>f?(d=f,u+=f,f=p[l++]):-u===f?(d=f,u=h[c++],f=p[l++]):(d=-u,f+=u,u=h[c++]),s["delete"](d);else{if(!o(u)||!i(f))throw new Error("The two operations aren't compatible");u>-f?(d=-f,u+=f,f=p[l++]):u===-f?(d=u,u=h[c++],f=p[l++]):(d=u,f+=u,u=h[c++]),a["delete"](d)}}}return[s,a]},t}(),"object"==typeof module&&(module.exports=ot.TextOperation),"undefined"==typeof ot)var ot={};if(ot.Selection=function(t){"use strict";function e(t,e){this.anchor=t,this.head=e}function n(t){this.ranges=t||[]}var o=t.ot?t.ot.TextOperation:require("./text-operation");return e.fromJSON=function(t){return new e(t.anchor,t.head)},e.prototype.equals=function(t){return this.anchor===t.anchor&&this.head===t.head},e.prototype.isEmpty=function(){return this.anchor===this.head},e.prototype.transform=function(t){function n(e){for(var n=e,r=t.ops,i=0,s=t.ops.length;i<s&&(o.isRetain(r[i])?e-=r[i]:o.isInsert(r[i])?n+=r[i].length:(n-=Math.min(e,-r[i]),e+=r[i]),!(e<0));i++);return n}var r=n(this.anchor);return this.anchor===this.head?new e(r,r):new e(r,n(this.head))},n.Range=e,n.createCursor=function(t){return new n([new e(t,t)])},n.fromJSON=function(t){for(var o=t.ranges||t,r=0,i=[];r<o.length;r++)i[r]=e.fromJSON(o[r]);return new n(i)},n.prototype.equals=function(t){if(this.position!==t.position)return!1;if(this.ranges.length!==t.ranges.length)return!1;for(var e=0;e<this.ranges.length;e++)if(!this.ranges[e].equals(t.ranges[e]))return!1;return!0},n.prototype.somethingSelected=function(){for(var t=0;t<this.ranges.length;t++)if(!this.ranges[t].isEmpty())return!0;return!1},n.prototype.compose=function(t){return t},n.prototype.transform=function(t){for(var e=0,o=[];e<this.ranges.length;e++)o[e]=this.ranges[e].transform(t);return new n(o)},n}(this),"object"==typeof module&&(module.exports=ot.Selection),"undefined"==typeof ot)var ot={};if(ot.WrappedOperation=function(t){"use strict";function e(t,e){this.wrapped=t,this.meta=e}function n(t,e){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])}function o(t,e){if(t&&"object"==typeof t){if("function"==typeof t.compose)return t.compose(e);var o={};return n(t,o),n(e,o),o}return e}function r(t,e){return t&&"object"==typeof t&&"function"==typeof t.transform?t.transform(e):t}return e.prototype.apply=function(){return this.wrapped.apply.apply(this.wrapped,arguments)},e.prototype.invert=function(){var t=this.meta;return new e(this.wrapped.invert.apply(this.wrapped,arguments),t&&"object"==typeof t&&"function"==typeof t.invert?t.invert.apply(t,arguments):t)},e.prototype.compose=function(t){return new e(this.wrapped.compose(t.wrapped),o(this.meta,t.meta))},e.transform=function(t,n){var o=t.wrapped.constructor.transform,i=o(t.wrapped,n.wrapped);return[new e(i[0],r(t.meta,n.wrapped)),new e(i[1],r(n.meta,t.wrapped))]},e}(this),"object"==typeof module&&(module.exports=ot.WrappedOperation),"undefined"==typeof ot)var ot={};if(ot.UndoManager=function(){"use strict";function t(t){this.maxItems=t||50,this.state=n,this.dontCompose=!1,this.undoStack=[],this.redoStack=[]}function e(t,e){for(var n=[],o=e.constructor,r=t.length-1;r>=0;r--){var i=o.transform(t[r],e);"function"==typeof i[0].isNoop&&i[0].isNoop()||n.push(i[0]),e=i[1]}return n.reverse()}var n="normal",o="undoing",r="redoing";return t.prototype.add=function(t,e){if(this.state===o)this.redoStack.push(t),this.dontCompose=!0;else if(this.state===r)this.undoStack.push(t),this.dontCompose=!0;else{var n=this.undoStack;!this.dontCompose&&e&&n.length>0?n.push(t.compose(n.pop())):(n.push(t),n.length>this.maxItems&&n.shift()),this.dontCompose=!1,this.redoStack=[]}},t.prototype.transform=function(t){this.undoStack=e(this.undoStack,t),this.redoStack=e(this.redoStack,t)},t.prototype.performUndo=function(t){if(this.state=o,0===this.undoStack.length)throw new Error("undo not possible");t(this.undoStack.pop()),this.state=n},t.prototype.performRedo=function(t){if(this.state=r,0===this.redoStack.length)throw new Error("redo not possible");t(this.redoStack.pop()),this.state=n},t.prototype.canUndo=function(){return 0!==this.undoStack.length},t.prototype.canRedo=function(){return 0!==this.redoStack.length},t.prototype.isUndoing=function(){return this.state===o},t.prototype.isRedoing=function(){return this.state===r},t}(),"object"==typeof module&&(module.exports=ot.UndoManager),"undefined"==typeof ot)var ot={};ot.Client=function(t){"use strict";function e(t){this.revision=t,this.setState(a)}function n(){}function o(t){this.outstanding=t}function r(t,e){this.outstanding=t,this.buffer=e}function i(t,e,n){this.acknowlaged=t,this.client=e,this.revision=n}function s(t,e,n,o){this.acknowlaged=t,this.buffer=e,this.client=n,this.revision=o}e.prototype.setState=function(t){this.state=t},e.prototype.applyClient=function(t){this.setState(this.state.applyClient(this,t))},e.prototype.applyServer=function(t,e){this.setState(this.state.applyServer(this,t,e))},e.prototype.applyOperations=function(t,e){this.setState(this.state.applyOperations(this,t,e))},e.prototype.serverAck=function(t){this.setState(this.state.serverAck(this,t))},e.prototype.serverReconnect=function(){"function"==typeof this.state.resend&&this.state.resend(this)},e.prototype.transformSelection=function(t){return this.state.transformSelection(t)},e.prototype.sendOperation=function(t,e){throw new Error("sendOperation must be defined in child class")},e.prototype.applyOperation=function(t){throw new Error("applyOperation must be defined in child class")},e.Synchronized=n,n.prototype.applyClient=function(t,e){return t.sendOperation(t.revision,e),new o(e)},n.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");return t.revision=e,t.applyOperation(n),this},n.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},n.prototype.transformSelection=function(t){return t};var a=new n;return e.AwaitingConfirm=o,o.prototype.applyClient=function(t,e){return new r(this.outstanding,e)},o.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var r=n.constructor.transform(this.outstanding,n);return t.applyOperation(r[1]),new o(r[0])},o.prototype.serverAck=function(t,e){return e-t.revision>1?new i(this.outstanding,t,e).getOperations():(t.revision=e,a)},o.prototype.transformSelection=function(t){return t.transform(this.outstanding)},o.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.AwaitingWithBuffer=r,r.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new r(this.outstanding,n)},r.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var o=n.constructor.transform,i=o(this.outstanding,n),s=o(this.buffer,i[1]);return t.applyOperation(s[1]),new r(i[0],s[0])},r.prototype.serverAck=function(t,e){return e-t.revision>1?new s(this.outstanding,this.buffer,t,e).getOperations():(t.revision=e,t.sendOperation(t.revision,this.buffer),new o(this.buffer))},r.prototype.transformSelection=function(t){return t.transform(this.outstanding).transform(this.buffer)},r.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.Stale=i,i.prototype.applyClient=function(t,e){return new s(this.acknowlaged,e,t,this.revision)},i.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},i.prototype.applyOperations=function(t,e,n){for(var o=this.acknowlaged.constructor.transform,r=0;r<n.length;r++){var i=ot.TextOperation.fromJSON(n[r]),s=o(this.acknowlaged,i);t.applyOperation(s[1]),this.acknowlaged=s[0]}return t.revision=this.revision,a},i.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},i.prototype.transformSelection=function(t){return t},i.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e.StaleWithBuffer=s,s.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new s(this.acknowlaged,n,t,this.revision)},s.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},s.prototype.applyOperations=function(t,e,n){for(var r=this.acknowlaged.constructor.transform,i=0;i<n.length;i++){var s=ot.TextOperation.fromJSON(n[i]),a=r(this.acknowlaged,s),h=r(this.buffer,a[1]);t.applyOperation(h[1]),this.acknowlaged=a[0],this.buffer=h[0]}return t.revision=this.revision,t.sendOperation(t.revision,this.buffer),new o(this.buffer)},s.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},s.prototype.transformSelection=function(t){return t},s.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e}(this),"object"==typeof module&&(module.exports=ot.Client),ot.CodeMirrorAdapter=function(t){"use strict";function e(t){this.cm=t,this.ignoreNextChange=!1,this.changeInProgress=!1,this.selectionChanged=!1,a(this,"onChanges"),a(this,"onChange"),a(this,"onCursorActivity"),a(this,"onFocus"),a(this,"onBlur"),t.on("changes",this.onChanges),t.on("change",this.onChange),t.on("cursorActivity",this.onCursorActivity),t.on("focus",this.onFocus),t.on("blur",this.onBlur)}function n(t,e){return t.line<e.line?-1:t.line>e.line?1:t.ch<e.ch?-1:t.ch>e.ch?1:0}function o(t,e){return n(t,e)<=0}function r(t,e){return o(t,e)?t:e}function i(t,e){return o(t,e)?e:t}function s(t){return t.indexFromPos({line:t.lastLine(),ch:0})+t.getLine(t.lastLine()).length}function a(t,e){var n=t[e];t[e]=function(){n.apply(t,arguments)}}var h=ot.TextOperation,p=ot.Selection;e.prototype.detach=function(){this.cm.off("changes",this.onChanges),this.cm.off("change",this.onChange),this.cm.off("cursorActivity",this.onCursorActivity),this.cm.off("focus",this.onFocus),this.cm.off("blur",this.onBlur)},e.operationFromCodeMirrorChanges=function(t,e){function n(t){return t[t.length-1]}function r(t){if(0===t.length)return 0;for(var e=0,n=0;n<t.length;n++)e+=t[n].length;return e+t.length-1}function i(t,e){return function(i){return o(i,e.from)?t(i):o(e.to,i)?t({line:i.line+e.text.length-1-(e.to.line-e.from.line),ch:e.to.line<i.line?i.ch:e.text.length<=1?i.ch-(e.to.ch-e.from.ch)+r(e.text):i.ch-e.to.ch+n(e.text).length})+r(e.removed)-r(e.text):e.from.line===i.line?t(e.from)+i.ch-e.from.ch:t(e.from)+r(e.removed.slice(0,i.line-e.from.line))+1+i.ch}}for(var a=s(e),p=(new h).retain(a),c=(new h).retain(a),l=function(t){return e.indexFromPos(t)},u=t.length-1;u>=0;u--){var f=t[u];l=i(l,f);var d=l(f.from),g=a-d-r(f.text);p=(new h).retain(d)["delete"](r(f.removed)).insert(f.text.join("\n")).retain(g).compose(p),c=c.compose((new h).retain(d)["delete"](r(f.text)).insert(f.removed.join("\n")).retain(g)),a+=r(f.removed)-r(f.text)}return[p,c]},e.operationFromCodeMirrorChange=e.operationFromCodeMirrorChanges,e.applyOperationToCodeMirror=function(t,e){e.operation(function(){for(var n=t.ops,o=0,r=0,i=n.length;r<i;r++){var s=n[r];if(h.isRetain(s))o+=s;else if(h.isInsert(s))e.replaceRange(s,e.posFromIndex(o),null,"ignoreHistory"),o+=s.length;else if(h.isDelete(s)){var a=e.posFromIndex(o),p=e.posFromIndex(o-s);e.replaceRange("",a,p,"ignoreHistory")}}})},e.prototype.registerCallbacks=function(t){this.callbacks=t},e.prototype.onChange=function(){this.changeInProgress=!0},e.prototype.onChanges=function(t,n){if(!this.ignoreNextChange){var o=e.operationFromCodeMirrorChanges(n,this.cm);this.trigger("change",o[0],o[1])}this.selectionChanged&&this.trigger("selectionChange"),this.changeInProgress=!1,this.ignoreNextChange=!1},e.prototype.onCursorActivity=e.prototype.onFocus=function(){this.changeInProgress?this.selectionChanged=!0:this.trigger("selectionChange")},e.prototype.onBlur=function(){this.cm.somethingSelected()||this.trigger("blur")},e.prototype.getValue=function(){return this.cm.getValue()},e.prototype.getSelection=function(){for(var t=this.cm,e=t.listSelections(),n=[],o=0;o<e.length;o++)n[o]=new p.Range(t.indexFromPos(e[o].anchor),t.indexFromPos(e[o].head));return new p(n)},e.prototype.setSelection=function(t){for(var e=[],n=0;t&&n<t.ranges.length;n++){var o=t.ranges[n];e[n]={anchor:this.cm.posFromIndex(o.anchor),head:this.cm.posFromIndex(o.head)}}this.cm.setSelections(e)};var c=function(){var t={},e=document.createElement("style");document.documentElement.getElementsByTagName("head")[0].appendChild(e);var n=e.sheet;return function(e){t[e]||(t[e]=!0,n.insertRule(e,(n.cssRules||n.rules).length))}}();return e.prototype.setOtherCursor=function(t,e,n){var o=this.cm.posFromIndex(t),r=(this.cm.cursorCoords(o),document.createElement("span"));return r.className="other-client",r.style.display="none",r.setAttribute("data-clientid",n),this.cm.setBookmark(o,{widget:r,insertLeft:!0})},e.prototype.setOtherSelectionRange=function(t,e,n){var o=/^#([0-9a-fA-F]{6})$/.exec(e);if(!o)throw new Error("only six-digit hex colors are allowed.");var s="selection-"+o[1],a=hex2rgb(e),h="."+s+" { background: rgba("+a.red+","+a.green+","+a.blue+",0.2); }";c(h);var p=this.cm.posFromIndex(t.anchor),l=this.cm.posFromIndex(t.head);return this.cm.markText(r(p,l),i(p,l),{className:s})},e.prototype.setOtherSelection=function(t,e,n){for(var o=[],r=0;r<t.ranges.length;r++){var i=t.ranges[r];i.isEmpty()||(o[r]=this.setOtherSelectionRange(i,e,n))}return{clear:function(){for(var t=0;t<o.length;t++)o[t]&&o[t].clear()}}},e.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},e.prototype.applyOperation=function(t){this.ignoreNextChange=!0,e.applyOperationToCodeMirror(t,this.cm)},e.prototype.registerUndo=function(t){this.cm.undo=t},e.prototype.registerRedo=function(t){this.cm.redo=t},e}(this),ot.SocketIOAdapter=function(){"use strict";function t(t){this.socket=t;var e=this;t.on("client_left",function(t){e.trigger("client_left",t)}),t.on("set_name",function(t,n){e.trigger("set_name",t,n)}),t.on("set_color",function(t,n){e.trigger("set_color",t,n)}),t.on("ack",function(t){e.trigger("ack",t)}),t.on("operation",function(t,n,o,r){e.trigger("operation",n,o),e.trigger("selection",t,r)}),t.on("operations",function(t,n){e.trigger("operations",t,n)}),t.on("selection",function(t,n){e.trigger("selection",t,n)}),t.on("reconnect",function(){e.trigger("reconnect")})}return t.prototype.sendOperation=function(t,e,n){this.socket.emit("operation",t,e,n)},t.prototype.sendSelection=function(t){this.socket.emit("selection",t)},t.prototype.getOperations=function(t,e){this.socket.emit("get_operations",t,e)},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.AjaxAdapter=function(){"use strict";function t(t,e,n){"/"!==t[t.length-1]&&(t+="/"),this.path=t,this.ownUserName=e,this.majorRevision=n.major||0,this.minorRevision=n.minor||0,this.poll()}return t.prototype.renderRevisionPath=function(){return"revision/"+this.majorRevision+"-"+this.minorRevision},t.prototype.handleResponse=function(t){var e,n=t.operations;for(e=0;e<n.length;e++)n[e].user===this.ownUserName?this.trigger("ack"):this.trigger("operation",n[e].operation);n.length>0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e<o.length;e++){var r=o[e].user;if(r!==this.ownUserName)switch(o[e].event){case"joined":this.trigger("set_name",r,r);break;case"left":this.trigger("client_left",r);break;case"selection":this.trigger("selection",r,o[e].selection)}}this.minorRevision+=o.length}var i=t.users;i&&(delete i[this.ownUserName],this.trigger("clients",i)),t.revision&&(this.majorRevision=t.revision.major,this.minorRevision=t.revision.minor)},t.prototype.poll=function(){var t=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"GET",dataType:"json",timeout:5e3,success:function(e){t.handleResponse(e),t.poll()},error:function(){setTimeout(function(){t.poll()},500)}})},t.prototype.sendOperation=function(t,e,n){if(t!==this.majorRevision)throw new Error("Revision numbers out of sync");var o=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"POST",data:JSON.stringify({operation:e,selection:n}),contentType:"application/json",processData:!1,success:function(t){},error:function(){setTimeout(function(){o.sendOperation(t,e,n)},500)}})},t.prototype.sendSelection=function(t){$.ajax({url:this.path+this.renderRevisionPath()+"/selection",type:"POST",data:JSON.stringify(t),contentType:"application/json",processData:!1,timeout:1e3})},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.EditorClient=function(){"use strict";function t(t,e){this.selectionBefore=t,this.selectionAfter=e}function e(t,e){this.clientId=t,this.selection=e}function n(t,e,n,o,r,i){this.id=t,this.listEl=e,this.editorAdapter=n,this.name=o,this.color=r,this.li=document.createElement("li"),o&&(this.li.textContent=o,this.listEl.appendChild(this.li)),r?this.setForceColor(r):this.setColor(o?s(o):Math.random()),i&&this.updateSelection(i)}function o(t,e,n,o){c.call(this,t),this.serverAdapter=n,this.editorAdapter=o,this.undoManager=new u,this.initializeClientList(),this.initializeClients(e);var r=this;this.editorAdapter.registerCallbacks({change:function(t,e){r.onChange(t,e)},selectionChange:function(){r.onSelectionChange()},blur:function(){r.onBlur()}}),this.editorAdapter.registerUndo(function(){r.undo()}),this.editorAdapter.registerRedo(function(){r.redo()}),this.serverAdapter.registerCallbacks({client_left:function(t){r.onClientLeft(t)},set_name:function(t,e){r.getClientObject(t).setName(e)},set_color:function(t,e){r.getClientObject(t).setForceColor(e)},ack:function(t){r.serverAck(t)},operation:function(t,e){r.applyServer(t,f.fromJSON(e))},operations:function(t,e){r.applyOperations(t,e)},selection:function(t,e){e?r.getClientObject(t).updateSelection(r.transformSelection(l.fromJSON(e))):r.getClientObject(t).removeSelection()},clients:function(t){var e;for(e in r.clients)r.clients.hasOwnProperty(e)&&!t.hasOwnProperty(e)&&r.onClientLeft(e);for(e in t)if(t.hasOwnProperty(e)){var n=r.getClientObject(e);t[e].name&&n.setName(t[e].name);var o=t[e].selection;o?r.clients[e].updateSelection(r.transformSelection(l.fromJSON(o))):r.clients[e].removeSelection()}},reconnect:function(){r.serverReconnect()}})}function r(t,e,n){function o(t){var e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e}return"#"+o(t)+o(e)+o(n)}function i(t,e,n){if(0===e)return r(n,n,n);var o=n<.5?n*(1+e):n+e-e*n,i=2*n-o,s=function(t){return t<0&&(t+=1),t>1&&(t-=1),6*t<1?i+6*(o-i)*t:2*t<1?o:3*t<2?i+6*(o-i)*(2/3-t):i};return r(s(t+1/3),s(t),s(t-1/3))}function s(t){for(var e=1,n=0;n<t.length;n++)e=17*(e+t.charCodeAt(n))%360;return e/360}function a(t,e){function n(){}n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function h(t){return t[t.length-1]}function p(t){t.parentNode&&t.parentNode.removeChild(t)}var c=ot.Client,l=ot.Selection,u=ot.UndoManager,f=ot.TextOperation,d=ot.WrappedOperation;return t.prototype.invert=function(){return new t(this.selectionAfter,this.selectionBefore)},t.prototype.compose=function(e){return new t(this.selectionBefore,e.selectionAfter)},t.prototype.transform=function(e){return new t(this.selectionBefore?this.selectionBefore.transform(e):null,this.selectionAfter?this.selectionAfter.transform(e):null)},e.fromJSON=function(t){return new e(t.clientId,t.selection&&l.fromJSON(t.selection))},e.prototype.transform=function(t){return new e(this.clientId,this.selection&&this.selection.transform(t))},n.prototype.setColor=function(t){this.hue=t,this.color=i(t,.75,.5),this.lightColor=i(t,.5,.9),this.li&&(this.li.style.color=this.color)},n.prototype.setForceColor=function(t){this.hue=null,this.color=t,this.lightColor=t,this.li&&(this.li.style.color=this.color)},n.prototype.setName=function(t){this.name!==t&&(this.name=t,this.li.textContent=t,this.li.parentNode||this.listEl.appendChild(this.li),this.setColor(s(t)))},n.prototype.updateSelection=function(t){this.removeSelection(),this.selection=t,this.mark=this.editorAdapter.setOtherSelection(t,t.position===t.selectionEnd?this.color:this.lightColor,this.id)},n.prototype.remove=function(){this.li&&p(this.li),this.removeSelection()},n.prototype.removeSelection=function(){this.mark&&(this.mark.clear(),this.mark=null)},a(o,c),o.prototype.addClient=function(t,e){this.clients[t]=new n(t,this.clientListEl,this.editorAdapter,e.name||t,e.color||null,e.selection?l.fromJSON(e.selection):null)},o.prototype.initializeClients=function(t){this.clients={};for(var e in t)t.hasOwnProperty(e)&&this.addClient(e,t[e])},o.prototype.getClientObject=function(t){var e=this.clients[t];return e?e:this.clients[t]=new n(t,this.clientListEl,this.editorAdapter)},o.prototype.onClientLeft=function(t){var e=this.clients[t];e&&(e.remove(),delete this.clients[t])},o.prototype.initializeClientList=function(){this.clientListEl=document.createElement("ul")},o.prototype.applyUnredo=function(t){this.undoManager.add(t.invert(this.editorAdapter.getValue())),this.editorAdapter.applyOperation(t.wrapped),this.selection=t.meta.selectionAfter,this.editorAdapter.setSelection(this.selection),this.applyClient(t.wrapped)},o.prototype.undo=function(){var t=this;this.undoManager.canUndo()&&this.undoManager.performUndo(function(e){t.applyUnredo(e)})},o.prototype.redo=function(){var t=this;this.undoManager.canRedo()&&this.undoManager.performRedo(function(e){t.applyUnredo(e)})},o.prototype.onChange=function(e,n){var o=this.selection;this.updateSelection();var r=new t(o,this.selection),i=(new d(e,r),this.undoManager.undoStack.length>0&&n.shouldBeComposedWithInverted(h(this.undoManager.undoStack).wrapped)),s=new t(this.selection,o);this.undoManager.add(new d(n,s),i),this.applyClient(e)},o.prototype.updateSelection=function(){this.selection=this.editorAdapter.getSelection()},o.prototype.onSelectionChange=function(){var t=this.selection;this.updateSelection(),t&&this.selection.equals(t)||this.sendSelection(this.selection)},o.prototype.onBlur=function(){this.selection=null,this.sendSelection(null)},o.prototype.sendSelection=function(t){this.state instanceof c.AwaitingWithBuffer||this.serverAdapter.sendSelection(t)},o.prototype.sendOperation=function(t,e){this.serverAdapter.sendOperation(t,e.toJSON(),this.selection)},o.prototype.getOperations=function(t,e){this.serverAdapter.getOperations(t,e)},o.prototype.applyOperation=function(t){this.editorAdapter.applyOperation(t),this.updateSelection(),this.undoManager.transform(new d(t,null))},o}();
\ No newline at end of file +function hex2rgb(t){if("#"==t[0]&&(t=t.substr(1)),3==t.length){var e=t;t="",e=/^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(e).slice(1);for(var n=0;n<3;n++)t+=e[n]+e[n]}var o=/^([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(t).slice(1);return{red:parseInt(o[0],16),green:parseInt(o[1],16),blue:parseInt(o[2],16)}}if("undefined"==typeof ot)var ot={};if(ot.TextOperation=function(){"use strict";function t(){return this&&this.constructor===t?(this.ops=[],this.baseLength=0,void(this.targetLength=0)):new t}function e(e,n){var o=e.ops,r=t.isRetain;switch(o.length){case 1:return o[0];case 2:return r(o[0])?o[1]:r(o[1])?o[0]:null;case 3:if(r(o[0])&&r(o[2]))return o[1]}return null}function n(t){return o(t.ops[0])?t.ops[0]:0}t.prototype.equals=function(t){if(this.baseLength!==t.baseLength)return!1;if(this.targetLength!==t.targetLength)return!1;if(this.ops.length!==t.ops.length)return!1;for(var e=0;e<this.ops.length;e++)if(this.ops[e]!==t.ops[e])return!1;return!0};var o=t.isRetain=function(t){return"number"==typeof t&&t>0},r=t.isInsert=function(t){return"string"==typeof t},i=t.isDelete=function(t){return"number"==typeof t&&t<0};return t.prototype.retain=function(t){if("number"!=typeof t)throw new Error("retain expects an integer");return 0===t?this:(this.baseLength+=t,this.targetLength+=t,o(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.insert=function(t){if("string"!=typeof t)throw new Error("insert expects a string");if(""===t)return this;this.targetLength+=t.length;var e=this.ops;return r(e[e.length-1])?e[e.length-1]+=t:i(e[e.length-1])?r(e[e.length-2])?e[e.length-2]+=t:(e[e.length]=e[e.length-1],e[e.length-2]=t):e.push(t),this},t.prototype["delete"]=function(t){if("string"==typeof t&&(t=t.length),"number"!=typeof t)throw new Error("delete expects an integer or a string");return 0===t?this:(t>0&&(t=-t),this.baseLength-=t,i(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.isNoop=function(){return 0===this.ops.length||1===this.ops.length&&o(this.ops[0])},t.prototype.toString=function(){var t=Array.prototype.map||function(t){for(var e=this,n=[],o=0,r=e.length;o<r;o++)n[o]=t(e[o]);return n};return t.call(this.ops,function(t){return o(t)?"retain "+t:r(t)?"insert '"+t+"'":"delete "+-t}).join(", ")},t.prototype.toJSON=function(){return this.ops},t.fromJSON=function(e){for(var n=new t,s=0,a=e.length;s<a;s++){var h=e[s];if(o(h))n.retain(h);else if(r(h))n.insert(h);else{if(!i(h))throw new Error("unknown operation: "+JSON.stringify(h));n["delete"](h)}}return n},t.prototype.apply=function(t){var e=this;if(t.length!==e.baseLength)throw new Error("The operation's base length must be equal to the string's length.");for(var n=[],i=0,s=0,a=this.ops,h=0,p=a.length;h<p;h++){var c=a[h];if(o(c)){if(s+c>t.length)throw new Error("Operation can't retain more characters than are left in the string.");n[i++]=t.slice(s,s+c),s+=c}else r(c)?n[i++]=c:s-=c}if(s!==t.length)throw new Error("The operation didn't operate on the whole string.");return n.join("")},t.prototype.invert=function(e){for(var n=0,i=new t,s=this.ops,a=0,h=s.length;a<h;a++){var p=s[a];o(p)?(i.retain(p),n+=p):r(p)?i["delete"](p.length):(i.insert(e.slice(n,n-p)),n-=p)}return i},t.prototype.compose=function(e){var n=this;if(n.targetLength!==e.baseLength)throw new Error("The base length of the second operation has to be the target length of the first operation");for(var s=new t,a=n.ops,h=e.ops,p=0,c=0,l=a[p++],u=h[c++];;){if("undefined"==typeof l&&"undefined"==typeof u)break;if(i(l))s["delete"](l),l=a[p++];else if(r(u))s.insert(u),u=h[c++];else{if("undefined"==typeof l)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too long.");if(o(l)&&o(u))l>u?(s.retain(u),l-=u,u=h[c++]):l===u?(s.retain(l),l=a[p++],u=h[c++]):(s.retain(l),u-=l,l=a[p++]);else if(r(l)&&i(u))l.length>-u?(l=l.slice(-u),u=h[c++]):l.length===-u?(l=a[p++],u=h[c++]):(u+=l.length,l=a[p++]);else if(r(l)&&o(u))l.length>u?(s.insert(l.slice(0,u)),l=l.slice(u),u=h[c++]):l.length===u?(s.insert(l),l=a[p++],u=h[c++]):(s.insert(l),u-=l.length,l=a[p++]);else{if(!o(l)||!i(u))throw new Error("This shouldn't happen: op1: "+JSON.stringify(l)+", op2: "+JSON.stringify(u));l>-u?(s["delete"](u),l+=u,u=h[c++]):l===-u?(s["delete"](u),l=a[p++],u=h[c++]):(s["delete"](l),u+=l,l=a[p++])}}}return s},t.prototype.shouldBeComposedWith=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s:!(!i(a)||!i(h))&&(s-h===o||o===s))},t.prototype.shouldBeComposedWithInverted=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s||o===s:!(!i(a)||!i(h))&&s-h===o)},t.transform=function(e,n){if(e.baseLength!==n.baseLength)throw new Error("Both operations have to have the same base length");for(var s=new t,a=new t,h=e.ops,p=n.ops,c=0,l=0,u=h[c++],f=p[l++];;){if("undefined"==typeof u&&"undefined"==typeof f)break;if(r(u))s.insert(u),a.retain(u.length),u=h[c++];else if(r(f))s.retain(f.length),a.insert(f),f=p[l++];else{if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof f)throw new Error("Cannot compose operations: first operation is too long.");var d;if(o(u)&&o(f))u>f?(d=f,u-=f,f=p[l++]):u===f?(d=f,u=h[c++],f=p[l++]):(d=u,f-=u,u=h[c++]),s.retain(d),a.retain(d);else if(i(u)&&i(f))-u>-f?(u-=f,f=p[l++]):u===f?(u=h[c++],f=p[l++]):(f-=u,u=h[c++]);else if(i(u)&&o(f))-u>f?(d=f,u+=f,f=p[l++]):-u===f?(d=f,u=h[c++],f=p[l++]):(d=-u,f+=u,u=h[c++]),s["delete"](d);else{if(!o(u)||!i(f))throw new Error("The two operations aren't compatible");u>-f?(d=-f,u+=f,f=p[l++]):u===-f?(d=u,u=h[c++],f=p[l++]):(d=u,f+=u,u=h[c++]),a["delete"](d)}}}return[s,a]},t}(),"object"==typeof module&&(module.exports=ot.TextOperation),"undefined"==typeof ot)var ot={};if(ot.Selection=function(t){"use strict";function e(t,e){this.anchor=t,this.head=e}function n(t){this.ranges=t||[]}var o=t.ot?t.ot.TextOperation:require("./text-operation");return e.fromJSON=function(t){return new e(t.anchor,t.head)},e.prototype.equals=function(t){return this.anchor===t.anchor&&this.head===t.head},e.prototype.isEmpty=function(){return this.anchor===this.head},e.prototype.transform=function(t){function n(e){for(var n=e,r=t.ops,i=0,s=t.ops.length;i<s&&(o.isRetain(r[i])?e-=r[i]:o.isInsert(r[i])?n+=r[i].length:(n-=Math.min(e,-r[i]),e+=r[i]),!(e<0));i++);return n}var r=n(this.anchor);return this.anchor===this.head?new e(r,r):new e(r,n(this.head))},n.Range=e,n.createCursor=function(t){return new n([new e(t,t)])},n.fromJSON=function(t){for(var o=t.ranges||t,r=0,i=[];r<o.length;r++)i[r]=e.fromJSON(o[r]);return new n(i)},n.prototype.equals=function(t){if(this.position!==t.position)return!1;if(this.ranges.length!==t.ranges.length)return!1;for(var e=0;e<this.ranges.length;e++)if(!this.ranges[e].equals(t.ranges[e]))return!1;return!0},n.prototype.somethingSelected=function(){for(var t=0;t<this.ranges.length;t++)if(!this.ranges[t].isEmpty())return!0;return!1},n.prototype.compose=function(t){return t},n.prototype.transform=function(t){for(var e=0,o=[];e<this.ranges.length;e++)o[e]=this.ranges[e].transform(t);return new n(o)},n}(this),"object"==typeof module&&(module.exports=ot.Selection),"undefined"==typeof ot)var ot={};if(ot.WrappedOperation=function(t){"use strict";function e(t,e){this.wrapped=t,this.meta=e}function n(t,e){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])}function o(t,e){if(t&&"object"==typeof t){if("function"==typeof t.compose)return t.compose(e);var o={};return n(t,o),n(e,o),o}return e}function r(t,e){return t&&"object"==typeof t&&"function"==typeof t.transform?t.transform(e):t}return e.prototype.apply=function(){return this.wrapped.apply.apply(this.wrapped,arguments)},e.prototype.invert=function(){var t=this.meta;return new e(this.wrapped.invert.apply(this.wrapped,arguments),t&&"object"==typeof t&&"function"==typeof t.invert?t.invert.apply(t,arguments):t)},e.prototype.compose=function(t){return new e(this.wrapped.compose(t.wrapped),o(this.meta,t.meta))},e.transform=function(t,n){var o=t.wrapped.constructor.transform,i=o(t.wrapped,n.wrapped);return[new e(i[0],r(t.meta,n.wrapped)),new e(i[1],r(n.meta,t.wrapped))]},e}(this),"object"==typeof module&&(module.exports=ot.WrappedOperation),"undefined"==typeof ot)var ot={};if(ot.UndoManager=function(){"use strict";function t(t){this.maxItems=t||50,this.state=n,this.dontCompose=!1,this.undoStack=[],this.redoStack=[]}function e(t,e){for(var n=[],o=e.constructor,r=t.length-1;r>=0;r--){var i=o.transform(t[r],e);"function"==typeof i[0].isNoop&&i[0].isNoop()||n.push(i[0]),e=i[1]}return n.reverse()}var n="normal",o="undoing",r="redoing";return t.prototype.add=function(t,e){if(this.state===o)this.redoStack.push(t),this.dontCompose=!0;else if(this.state===r)this.undoStack.push(t),this.dontCompose=!0;else{var n=this.undoStack;!this.dontCompose&&e&&n.length>0?n.push(t.compose(n.pop())):(n.push(t),n.length>this.maxItems&&n.shift()),this.dontCompose=!1,this.redoStack=[]}},t.prototype.transform=function(t){this.undoStack=e(this.undoStack,t),this.redoStack=e(this.redoStack,t)},t.prototype.performUndo=function(t){if(this.state=o,0===this.undoStack.length)throw new Error("undo not possible");t(this.undoStack.pop()),this.state=n},t.prototype.performRedo=function(t){if(this.state=r,0===this.redoStack.length)throw new Error("redo not possible");t(this.redoStack.pop()),this.state=n},t.prototype.canUndo=function(){return 0!==this.undoStack.length},t.prototype.canRedo=function(){return 0!==this.redoStack.length},t.prototype.isUndoing=function(){return this.state===o},t.prototype.isRedoing=function(){return this.state===r},t}(),"object"==typeof module&&(module.exports=ot.UndoManager),"undefined"==typeof ot)var ot={};ot.Client=function(t){"use strict";function e(t){this.revision=t,this.setState(a)}function n(){}function o(t){this.outstanding=t}function r(t,e){this.outstanding=t,this.buffer=e}function i(t,e,n){this.acknowlaged=t,this.client=e,this.revision=n}function s(t,e,n,o){this.acknowlaged=t,this.buffer=e,this.client=n,this.revision=o}e.prototype.setState=function(t){this.state=t},e.prototype.applyClient=function(t){this.setState(this.state.applyClient(this,t))},e.prototype.applyServer=function(t,e){this.setState(this.state.applyServer(this,t,e))},e.prototype.applyOperations=function(t,e){this.setState(this.state.applyOperations(this,t,e))},e.prototype.serverAck=function(t){this.setState(this.state.serverAck(this,t))},e.prototype.serverReconnect=function(){"function"==typeof this.state.resend&&this.state.resend(this)},e.prototype.transformSelection=function(t){return this.state.transformSelection(t)},e.prototype.sendOperation=function(t,e){throw new Error("sendOperation must be defined in child class")},e.prototype.applyOperation=function(t){throw new Error("applyOperation must be defined in child class")},e.Synchronized=n,n.prototype.applyClient=function(t,e){return t.sendOperation(t.revision,e),new o(e)},n.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");return t.revision=e,t.applyOperation(n),this},n.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},n.prototype.transformSelection=function(t){return t};var a=new n;return e.AwaitingConfirm=o,o.prototype.applyClient=function(t,e){return new r(this.outstanding,e)},o.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var r=n.constructor.transform(this.outstanding,n);return t.applyOperation(r[1]),new o(r[0])},o.prototype.serverAck=function(t,e){return e-t.revision>1?new i(this.outstanding,t,e).getOperations():(t.revision=e,a)},o.prototype.transformSelection=function(t){return t.transform(this.outstanding)},o.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.AwaitingWithBuffer=r,r.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new r(this.outstanding,n)},r.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var o=n.constructor.transform,i=o(this.outstanding,n),s=o(this.buffer,i[1]);return t.applyOperation(s[1]),new r(i[0],s[0])},r.prototype.serverAck=function(t,e){return e-t.revision>1?new s(this.outstanding,this.buffer,t,e).getOperations():(t.revision=e,t.sendOperation(t.revision,this.buffer),new o(this.buffer))},r.prototype.transformSelection=function(t){return t.transform(this.outstanding).transform(this.buffer)},r.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.Stale=i,i.prototype.applyClient=function(t,e){return new s(this.acknowlaged,e,t,this.revision)},i.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},i.prototype.applyOperations=function(t,e,n){for(var o=this.acknowlaged.constructor.transform,r=0;r<n.length;r++){var i=ot.TextOperation.fromJSON(n[r]),s=o(this.acknowlaged,i);t.applyOperation(s[1]),this.acknowlaged=s[0]}return t.revision=this.revision,a},i.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},i.prototype.transformSelection=function(t){return t},i.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e.StaleWithBuffer=s,s.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new s(this.acknowlaged,n,t,this.revision)},s.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},s.prototype.applyOperations=function(t,e,n){for(var r=this.acknowlaged.constructor.transform,i=0;i<n.length;i++){var s=ot.TextOperation.fromJSON(n[i]),a=r(this.acknowlaged,s),h=r(this.buffer,a[1]);t.applyOperation(h[1]),this.acknowlaged=a[0],this.buffer=h[0]}return t.revision=this.revision,t.sendOperation(t.revision,this.buffer),new o(this.buffer)},s.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},s.prototype.transformSelection=function(t){return t},s.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e}(this),"object"==typeof module&&(module.exports=ot.Client),ot.CodeMirrorAdapter=function(t){"use strict";function e(t){this.cm=t,this.ignoreNextChange=!1,this.changeInProgress=!1,this.selectionChanged=!1,a(this,"onChanges"),a(this,"onChange"),a(this,"onCursorActivity"),a(this,"onFocus"),a(this,"onBlur"),t.on("changes",this.onChanges),t.on("change",this.onChange),t.on("cursorActivity",this.onCursorActivity),t.on("focus",this.onFocus),t.on("blur",this.onBlur)}function n(t,e){return t.line<e.line?-1:t.line>e.line?1:t.ch<e.ch?-1:t.ch>e.ch?1:0}function o(t,e){return n(t,e)<=0}function r(t,e){return o(t,e)?t:e}function i(t,e){return o(t,e)?e:t}function s(t){return t.indexFromPos({line:t.lastLine(),ch:0})+t.getLine(t.lastLine()).length}function a(t,e){var n=t[e];t[e]=function(){n.apply(t,arguments)}}var h=ot.TextOperation,p=ot.Selection;e.prototype.detach=function(){this.cm.off("changes",this.onChanges),this.cm.off("change",this.onChange),this.cm.off("cursorActivity",this.onCursorActivity),this.cm.off("focus",this.onFocus),this.cm.off("blur",this.onBlur)},e.operationFromCodeMirrorChanges=function(t,e){function n(t){return t[t.length-1]}function r(t){if(0===t.length)return 0;for(var e=0,n=0;n<t.length;n++)e+=t[n].length;return e+t.length-1}function i(t,e){return function(i){return o(i,e.from)?t(i):o(e.to,i)?t({line:i.line+e.text.length-1-(e.to.line-e.from.line),ch:e.to.line<i.line?i.ch:e.text.length<=1?i.ch-(e.to.ch-e.from.ch)+r(e.text):i.ch-e.to.ch+n(e.text).length})+r(e.removed)-r(e.text):e.from.line===i.line?t(e.from)+i.ch-e.from.ch:t(e.from)+r(e.removed.slice(0,i.line-e.from.line))+1+i.ch}}for(var a=s(e),p=(new h).retain(a),c=(new h).retain(a),l=function(t){return e.indexFromPos(t)},u=t.length-1;u>=0;u--){var f=t[u];l=i(l,f);var d=l(f.from),g=a-d-r(f.text);p=(new h).retain(d)["delete"](r(f.removed)).insert(f.text.join("\n")).retain(g).compose(p),c=c.compose((new h).retain(d)["delete"](r(f.text)).insert(f.removed.join("\n")).retain(g)),a+=r(f.removed)-r(f.text)}return[p,c]},e.operationFromCodeMirrorChange=e.operationFromCodeMirrorChanges,e.applyOperationToCodeMirror=function(t,e){e.operation(function(){for(var n=t.ops,o=0,r=0,i=n.length;r<i;r++){var s=n[r];if(h.isRetain(s))o+=s;else if(h.isInsert(s))e.replaceRange(s,e.posFromIndex(o),null,"ignoreHistory"),o+=s.length;else if(h.isDelete(s)){var a=e.posFromIndex(o),p=e.posFromIndex(o-s);e.replaceRange("",a,p,"ignoreHistory")}}})},e.prototype.registerCallbacks=function(t){this.callbacks=t},e.prototype.onChange=function(){this.changeInProgress=!0},e.prototype.onChanges=function(t,n){if(!this.ignoreNextChange){var o=e.operationFromCodeMirrorChanges(n,this.cm);this.trigger("change",o[0],o[1])}this.selectionChanged&&this.trigger("selectionChange"),this.changeInProgress=!1,this.ignoreNextChange=!1},e.prototype.onCursorActivity=e.prototype.onFocus=function(){this.changeInProgress?this.selectionChanged=!0:this.trigger("selectionChange")},e.prototype.onBlur=function(){this.cm.somethingSelected()||this.trigger("blur")},e.prototype.getValue=function(){return this.cm.getValue()},e.prototype.getSelection=function(){for(var t=this.cm,e=t.listSelections(),n=[],o=0;o<e.length;o++)n[o]=new p.Range(t.indexFromPos(e[o].anchor),t.indexFromPos(e[o].head));return new p(n)},e.prototype.setSelection=function(t){for(var e=[],n=0;t&&n<t.ranges.length;n++){var o=t.ranges[n];e[n]={anchor:this.cm.posFromIndex(o.anchor),head:this.cm.posFromIndex(o.head)}}this.cm.setSelections(e)};var c=function(){var t={},e=document.createElement("style");document.documentElement.getElementsByTagName("head")[0].appendChild(e);var n=e.sheet;return function(e){t[e]||(t[e]=!0,n.insertRule(e,(n.cssRules||n.rules).length))}}();return e.prototype.setOtherCursor=function(t,e,n){var o=this.cm.posFromIndex(t),r=(this.cm.cursorCoords(o),document.createElement("span"));return r.className="other-client",r.style.display="none",r.setAttribute("data-clientid",n),this.cm.setBookmark(o,{widget:r,insertLeft:!0})},e.prototype.setOtherSelectionRange=function(t,e,n){var o=/^#([0-9a-fA-F]{6})$/.exec(e);if(!o)throw new Error("only six-digit hex colors are allowed.");var s="selection-"+o[1],a=hex2rgb(e),h="."+s+" { background: rgba("+a.red+","+a.green+","+a.blue+",0.2); }";c(h);var p=this.cm.posFromIndex(t.anchor),l=this.cm.posFromIndex(t.head);return this.cm.markText(r(p,l),i(p,l),{className:s})},e.prototype.setOtherSelection=function(t,e,n){for(var o=[],r=0;r<t.ranges.length;r++){var i=t.ranges[r];i.isEmpty()||(o[r]=this.setOtherSelectionRange(i,e,n))}return{clear:function(){for(var t=0;t<o.length;t++)o[t]&&o[t].clear()}}},e.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},e.prototype.applyOperation=function(t){t.isNoop()||(this.ignoreNextChange=!0),e.applyOperationToCodeMirror(t,this.cm)},e.prototype.registerUndo=function(t){this.cm.undo=t},e.prototype.registerRedo=function(t){this.cm.redo=t},e}(this),ot.SocketIOAdapter=function(){"use strict";function t(t){this.socket=t;var e=this;t.on("client_left",function(t){e.trigger("client_left",t)}),t.on("set_name",function(t,n){e.trigger("set_name",t,n)}),t.on("set_color",function(t,n){e.trigger("set_color",t,n)}),t.on("ack",function(t){e.trigger("ack",t)}),t.on("operation",function(t,n,o,r){e.trigger("operation",n,o),e.trigger("selection",t,r)}),t.on("operations",function(t,n){e.trigger("operations",t,n)}),t.on("selection",function(t,n){e.trigger("selection",t,n)}),t.on("reconnect",function(){e.trigger("reconnect")})}return t.prototype.sendOperation=function(t,e,n){this.socket.emit("operation",t,e,n)},t.prototype.sendSelection=function(t){this.socket.emit("selection",t)},t.prototype.getOperations=function(t,e){this.socket.emit("get_operations",t,e)},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.AjaxAdapter=function(){"use strict";function t(t,e,n){"/"!==t[t.length-1]&&(t+="/"),this.path=t,this.ownUserName=e,this.majorRevision=n.major||0,this.minorRevision=n.minor||0,this.poll()}return t.prototype.renderRevisionPath=function(){return"revision/"+this.majorRevision+"-"+this.minorRevision},t.prototype.handleResponse=function(t){var e,n=t.operations;for(e=0;e<n.length;e++)n[e].user===this.ownUserName?this.trigger("ack"):this.trigger("operation",n[e].operation);n.length>0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e<o.length;e++){var r=o[e].user;if(r!==this.ownUserName)switch(o[e].event){case"joined":this.trigger("set_name",r,r);break;case"left":this.trigger("client_left",r);break;case"selection":this.trigger("selection",r,o[e].selection)}}this.minorRevision+=o.length}var i=t.users;i&&(delete i[this.ownUserName],this.trigger("clients",i)),t.revision&&(this.majorRevision=t.revision.major,this.minorRevision=t.revision.minor)},t.prototype.poll=function(){var t=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"GET",dataType:"json",timeout:5e3,success:function(e){t.handleResponse(e),t.poll()},error:function(){setTimeout(function(){t.poll()},500)}})},t.prototype.sendOperation=function(t,e,n){if(t!==this.majorRevision)throw new Error("Revision numbers out of sync");var o=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"POST",data:JSON.stringify({operation:e,selection:n}),contentType:"application/json",processData:!1,success:function(t){},error:function(){setTimeout(function(){o.sendOperation(t,e,n)},500)}})},t.prototype.sendSelection=function(t){$.ajax({url:this.path+this.renderRevisionPath()+"/selection",type:"POST",data:JSON.stringify(t),contentType:"application/json",processData:!1,timeout:1e3})},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.EditorClient=function(){"use strict";function t(t,e){this.selectionBefore=t,this.selectionAfter=e}function e(t,e){this.clientId=t,this.selection=e}function n(t,e,n,o,r,i){this.id=t,this.listEl=e,this.editorAdapter=n,this.name=o,this.color=r,this.li=document.createElement("li"),o&&(this.li.textContent=o,this.listEl.appendChild(this.li)),r?this.setForceColor(r):this.setColor(o?s(o):Math.random()),i&&this.updateSelection(i)}function o(t,e,n,o){c.call(this,t),this.serverAdapter=n,this.editorAdapter=o,this.undoManager=new u,this.initializeClientList(),this.initializeClients(e);var r=this;this.editorAdapter.registerCallbacks({change:function(t,e){r.onChange(t,e)},selectionChange:function(){r.onSelectionChange()},blur:function(){r.onBlur()}}),this.editorAdapter.registerUndo(function(){r.undo()}),this.editorAdapter.registerRedo(function(){r.redo()}),this.serverAdapter.registerCallbacks({client_left:function(t){r.onClientLeft(t)},set_name:function(t,e){r.getClientObject(t).setName(e)},set_color:function(t,e){r.getClientObject(t).setForceColor(e)},ack:function(t){r.serverAck(t)},operation:function(t,e){r.applyServer(t,f.fromJSON(e))},operations:function(t,e){r.applyOperations(t,e)},selection:function(t,e){e?r.getClientObject(t).updateSelection(r.transformSelection(l.fromJSON(e))):r.getClientObject(t).removeSelection()},clients:function(t){var e;for(e in r.clients)r.clients.hasOwnProperty(e)&&!t.hasOwnProperty(e)&&r.onClientLeft(e);for(e in t)if(t.hasOwnProperty(e)){var n=r.getClientObject(e);t[e].name&&n.setName(t[e].name);var o=t[e].selection;o?r.clients[e].updateSelection(r.transformSelection(l.fromJSON(o))):r.clients[e].removeSelection()}},reconnect:function(){r.serverReconnect()}})}function r(t,e,n){function o(t){var e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e}return"#"+o(t)+o(e)+o(n)}function i(t,e,n){if(0===e)return r(n,n,n);var o=n<.5?n*(1+e):n+e-e*n,i=2*n-o,s=function(t){return t<0&&(t+=1),t>1&&(t-=1),6*t<1?i+6*(o-i)*t:2*t<1?o:3*t<2?i+6*(o-i)*(2/3-t):i};return r(s(t+1/3),s(t),s(t-1/3))}function s(t){for(var e=1,n=0;n<t.length;n++)e=17*(e+t.charCodeAt(n))%360;return e/360}function a(t,e){function n(){}n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function h(t){return t[t.length-1]}function p(t){t.parentNode&&t.parentNode.removeChild(t)}var c=ot.Client,l=ot.Selection,u=ot.UndoManager,f=ot.TextOperation,d=ot.WrappedOperation;return t.prototype.invert=function(){return new t(this.selectionAfter,this.selectionBefore)},t.prototype.compose=function(e){return new t(this.selectionBefore,e.selectionAfter)},t.prototype.transform=function(e){return new t(this.selectionBefore?this.selectionBefore.transform(e):null,this.selectionAfter?this.selectionAfter.transform(e):null)},e.fromJSON=function(t){return new e(t.clientId,t.selection&&l.fromJSON(t.selection))},e.prototype.transform=function(t){return new e(this.clientId,this.selection&&this.selection.transform(t))},n.prototype.setColor=function(t){this.hue=t,this.color=i(t,.75,.5),this.lightColor=i(t,.5,.9),this.li&&(this.li.style.color=this.color)},n.prototype.setForceColor=function(t){this.hue=null,this.color=t,this.lightColor=t,this.li&&(this.li.style.color=this.color)},n.prototype.setName=function(t){this.name!==t&&(this.name=t,this.li.textContent=t,this.li.parentNode||this.listEl.appendChild(this.li),this.setColor(s(t)))},n.prototype.updateSelection=function(t){this.removeSelection(),this.selection=t,this.mark=this.editorAdapter.setOtherSelection(t,t.position===t.selectionEnd?this.color:this.lightColor,this.id)},n.prototype.remove=function(){this.li&&p(this.li),this.removeSelection()},n.prototype.removeSelection=function(){this.mark&&(this.mark.clear(),this.mark=null)},a(o,c),o.prototype.addClient=function(t,e){this.clients[t]=new n(t,this.clientListEl,this.editorAdapter,e.name||t,e.color||null,e.selection?l.fromJSON(e.selection):null)},o.prototype.initializeClients=function(t){this.clients={};for(var e in t)t.hasOwnProperty(e)&&this.addClient(e,t[e])},o.prototype.getClientObject=function(t){var e=this.clients[t];return e?e:this.clients[t]=new n(t,this.clientListEl,this.editorAdapter)},o.prototype.onClientLeft=function(t){var e=this.clients[t];e&&(e.remove(),delete this.clients[t])},o.prototype.initializeClientList=function(){this.clientListEl=document.createElement("ul")},o.prototype.applyUnredo=function(t){this.undoManager.add(t.invert(this.editorAdapter.getValue())),this.editorAdapter.applyOperation(t.wrapped),this.selection=t.meta.selectionAfter,this.editorAdapter.setSelection(this.selection),this.applyClient(t.wrapped)},o.prototype.undo=function(){var t=this;this.undoManager.canUndo()&&this.undoManager.performUndo(function(e){t.applyUnredo(e)})},o.prototype.redo=function(){var t=this;this.undoManager.canRedo()&&this.undoManager.performRedo(function(e){t.applyUnredo(e)})},o.prototype.onChange=function(e,n){var o=this.selection;this.updateSelection();var r=new t(o,this.selection),i=(new d(e,r),this.undoManager.undoStack.length>0&&n.shouldBeComposedWithInverted(h(this.undoManager.undoStack).wrapped)),s=new t(this.selection,o);this.undoManager.add(new d(n,s),i),this.applyClient(e)},o.prototype.updateSelection=function(){this.selection=this.editorAdapter.getSelection()},o.prototype.onSelectionChange=function(){var t=this.selection;this.updateSelection(),t&&this.selection.equals(t)||this.sendSelection(this.selection)},o.prototype.onBlur=function(){this.selection=null,this.sendSelection(null)},o.prototype.sendSelection=function(t){this.state instanceof c.AwaitingWithBuffer||this.serverAdapter.sendSelection(t)},o.prototype.sendOperation=function(t,e){this.serverAdapter.sendOperation(t,e.toJSON(),this.selection)},o.prototype.getOperations=function(t,e){this.serverAdapter.getOperations(t,e)},o.prototype.applyOperation=function(t){this.editorAdapter.applyOperation(t),this.updateSelection(),this.undoManager.transform(new d(t,null))},o}();
\ No newline at end of file diff --git a/public/views/error.ejs b/public/views/error.ejs index 402b5eb0..a40ed39c 100644 --- a/public/views/error.ejs +++ b/public/views/error.ejs @@ -2,18 +2,18 @@ <html lang="en"> <head> - <%- include head %> + <%- include hackmd/head %> <link rel="stylesheet" href="<%- url %>/css/center.css"> </head> <body> - <%- include header %> + <%- include hackmd/header %> <div class="container-fluid text-center"> <div class="vertical-center-row"> <h1><%- code %> <%- detail %> <small><%- msg %></small></h1> </div> </div> - <%- include footer %> + <%- include hackmd/footer %> </body> </html>
\ No newline at end of file diff --git a/public/views/hackmd.ejs b/public/views/hackmd.ejs index c5778fc9..49084a63 100644 --- a/public/views/hackmd.ejs +++ b/public/views/hackmd.ejs @@ -2,14 +2,14 @@ <html lang="en"> <head> - <%- include head %> + <%- include hackmd/head %> </head> <body> - <%- include header %> - <%- include body %> - <%- include footer %> - <%- include foot %> + <%- include hackmd/header %> + <%- include hackmd/body %> + <%- include hackmd/footer %> + <%- include hackmd/foot %> </body> -</html>
\ No newline at end of file +</html> diff --git a/public/views/body.ejs b/public/views/hackmd/body.ejs index 5ad1733e..d8a3f108 100644 --- a/public/views/body.ejs +++ b/public/views/hackmd/body.ejs @@ -244,7 +244,7 @@ </div> </div> </div> -<%- include refresh-modal %> -<%- include signin-modal %> -<%- include help-modal %> -<%- include revision-modal %> +<%- include ../shared/refresh-modal %> +<%- include ../shared/signin-modal %> +<%- include ../shared/help-modal %> +<%- include ../shared/revision-modal %> diff --git a/public/views/foot.ejs b/public/views/hackmd/foot.ejs index c1df65c2..445b0213 100644 --- a/public/views/foot.ejs +++ b/public/views/hackmd/foot.ejs @@ -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/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/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/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/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/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> -<%- include build/index-scripts %> +<%- include ../build/index-scripts %> <% } else { %> <script src="<%- url %>/build/MathJax/MathJax.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 %> <% } %> diff --git a/public/views/footer.ejs b/public/views/hackmd/footer.ejs index e69de29b..e69de29b 100644 --- a/public/views/footer.ejs +++ b/public/views/hackmd/footer.ejs diff --git a/public/views/head.ejs b/public/views/hackmd/head.ejs index 218847f7..d0663993 100644 --- a/public/views/head.ejs +++ b/public/views/hackmd/head.ejs @@ -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/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'> -<%- include build/index-header %> +<%- include ../build/index-header %> <% } else { %> <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 %>
\ No newline at end of file +<%- include ../shared/polyfill %>
\ No newline at end of file diff --git a/public/views/header.ejs b/public/views/hackmd/header.ejs index bb4e3174..bb4e3174 100644 --- a/public/views/header.ejs +++ b/public/views/hackmd/header.ejs diff --git a/public/views/index.ejs b/public/views/index.ejs index 25139907..5732db4a 100644 --- a/public/views/index.ejs +++ b/public/views/index.ejs @@ -2,219 +2,14 @@ <html lang="en"> <head> - <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 polyfill %> + <%- include index/head %> </head> <body> - <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"> <span class="ui-name"></span></span> <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> - © 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">×</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 %> - <% } %> + <%- include index/header %> + <%- include index/body %> + <%- include index/footer %> + <%- include index/foot %> </body> </html> diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs new file mode 100644 index 00000000..584d67ed --- /dev/null +++ b/public/views/index/body.ejs @@ -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"> <span class="ui-name"></span></span> <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> + © 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">×</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 %>
\ No newline at end of file diff --git a/public/views/index/foot.ejs b/public/views/index/foot.ejs new file mode 100644 index 00000000..293c6698 --- /dev/null +++ b/public/views/index/foot.ejs @@ -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 %> +<% } %>
\ No newline at end of file diff --git a/public/views/index/footer.ejs b/public/views/index/footer.ejs new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/public/views/index/footer.ejs diff --git a/public/views/index/head.ejs b/public/views/index/head.ejs new file mode 100644 index 00000000..bbd14567 --- /dev/null +++ b/public/views/index/head.ejs @@ -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 %>
\ No newline at end of file diff --git a/public/views/index/header.ejs b/public/views/index/header.ejs new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/public/views/index/header.ejs diff --git a/public/views/pretty.ejs b/public/views/pretty.ejs index ced65ed8..64b65c70 100644 --- a/public/views/pretty.ejs +++ b/public/views/pretty.ejs @@ -28,7 +28,7 @@ <link rel="stylesheet" href='<%- url %>/build/emojify.js/dist/css/basic/emojify.min.css'> <%- include build/pretty-pack-header %> <% } %> - <%- include polyfill %> + <%- include shared/polyfill %> </head> <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> <% if(typeof disqus !== 'undefined' && disqus) { %> <div class="container-fluid" style="max-width: 758px; margin-bottom: 40px;"> - <%- include disqus %> + <%- include shared/disqus %> </div> <% } %> </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/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/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/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/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 %> <% } else { %> <script src="<%- url %>/build/MathJax/MathJax.js" defer></script> <script src="<%- url %>/build/MathJax/config/TeX-AMS-MML_HTMLorMML.js" defer></script> <%- include build/pretty-pack-scripts %> <% } %> -<%- include ga %> +<%- include shared/ga %> diff --git a/public/views/disqus.ejs b/public/views/shared/disqus.ejs index cceaa85c..cceaa85c 100644 --- a/public/views/disqus.ejs +++ b/public/views/shared/disqus.ejs diff --git a/public/views/ga.ejs b/public/views/shared/ga.ejs index 66d4acd9..66d4acd9 100644 --- a/public/views/ga.ejs +++ b/public/views/shared/ga.ejs diff --git a/public/views/help-modal.ejs b/public/views/shared/help-modal.ejs index b1ea681d..b1ea681d 100644 --- a/public/views/help-modal.ejs +++ b/public/views/shared/help-modal.ejs diff --git a/public/views/polyfill.ejs b/public/views/shared/polyfill.ejs index 5c885642..5c885642 100644 --- a/public/views/polyfill.ejs +++ b/public/views/shared/polyfill.ejs diff --git a/public/views/refresh-modal.ejs b/public/views/shared/refresh-modal.ejs index 5be41b2a..5be41b2a 100644 --- a/public/views/refresh-modal.ejs +++ b/public/views/shared/refresh-modal.ejs diff --git a/public/views/revision-modal.ejs b/public/views/shared/revision-modal.ejs index f824cf06..f824cf06 100644 --- a/public/views/revision-modal.ejs +++ b/public/views/shared/revision-modal.ejs diff --git a/public/views/signin-modal.ejs b/public/views/shared/signin-modal.ejs index a8af62e7..a8af62e7 100644 --- a/public/views/signin-modal.ejs +++ b/public/views/shared/signin-modal.ejs diff --git a/public/views/slide.ejs b/public/views/slide.ejs index b0323a0e..a3e2d1df 100644 --- a/public/views/slide.ejs +++ b/public/views/slide.ejs @@ -45,7 +45,7 @@ document.getElementsByTagName( 'head' )[0].appendChild( link ); </script> - <%- include polyfill %> + <%- include shared/polyfill %> </head> <body> <div class="container"> @@ -79,7 +79,7 @@ </div> <% if(typeof disqus !== 'undefined' && disqus) { %> <div style="margin-top: 25px; margin-bottom: 15px;"> - <%- include disqus %> + <%- include shared/disqus %> </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/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/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/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/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 %> <% } else { %> <script src="<%- url %>/build/MathJax/MathJax.js" defer></script> @@ -113,4 +113,4 @@ </body> </html> -<%- include ga %> +<%- include shared/ga %> diff --git a/webpack.production.js b/webpack.production.js index b28c34ae..7c690d28 100644 --- a/webpack.production.js +++ b/webpack.production.js @@ -3,6 +3,7 @@ var webpack = require('webpack'); var path = require('path'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); module.exports = [Object.assign({}, baseConfig, { plugins: baseConfig.plugins.concat([ @@ -11,12 +12,14 @@ module.exports = [Object.assign({}, baseConfig, { 'NODE_ENV': JSON.stringify('production') } }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false - }, - mangle: false, - sourceMap: false + new ParallelUglifyPlugin({ + uglifyJS: { + compress: { + warnings: false + }, + mangle: false, + sourceMap: false + } }), new ExtractTextPlugin("[name].[hash].css") ]), diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js index 496afce4..419149c7 100644 --- a/webpackBaseConfig.js +++ b/webpackBaseConfig.js @@ -158,6 +158,7 @@ module.exports = { "bootstrap" ], cover: [ + "babel-polyfill", path.join(__dirname, 'public/js/cover.js') ], "cover-styles-pack": [ @@ -168,6 +169,7 @@ module.exports = { path.join(__dirname, 'node_modules/select2/select2-bootstrap.css'), ], "cover-pack": [ + "babel-polyfill", "bootstrap-validator", "script!listPagnation", "expose?select2!select2", @@ -176,6 +178,7 @@ module.exports = { path.join(__dirname, 'public/js/cover.js') ], index: [ + "babel-polyfill", "script!jquery-ui-resizable", "script!js-url", "expose?filterXSS!xss", @@ -221,6 +224,7 @@ module.exports = { path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') ], "index-pack": [ + "babel-polyfill", "expose?Spinner!spin.js", "script!jquery-ui-resizable", "bootstrap-validator", @@ -251,6 +255,7 @@ module.exports = { path.join(__dirname, 'public/js/index.js') ], pretty: [ + "babel-polyfill", "expose?filterXSS!xss", "flowchart.js", "js-sequence-diagrams", @@ -270,6 +275,7 @@ module.exports = { path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') ], "pretty-pack": [ + "babel-polyfill", "expose?jsyaml!js-yaml", "script!mermaid", "expose?moment!moment", @@ -285,6 +291,7 @@ module.exports = { path.join(__dirname, 'public/js/pretty.js') ], slide: [ + "babel-polyfill", "bootstrap-tooltip", "expose?filterXSS!xss", "flowchart.js", @@ -304,6 +311,7 @@ module.exports = { path.join(__dirname, 'node_modules/octicons/octicons/octicons.css') ], "slide-pack": [ + "babel-polyfill", "expose?jQuery!expose?$!jquery", "velocity-animate", "imports?$=jquery!jquery-mousewheel", @@ -373,6 +381,10 @@ module.exports = { test: /\.json$/, loader: 'json-loader' }, { + test: /\.js$/, + loader: 'babel', + exclude: [/node_modules/, /public\/vendor/] + }, { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') }, { |