summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWu Cheng-Han2017-03-13 18:56:32 +0800
committerWu Cheng-Han2017-03-13 18:56:32 +0800
commitedb1b4aa0a72ac8b0215211c9dbc54156c3ff91f (patch)
treefb5f8ddbfe9001e266b3b2487c3b2e62fbd2bbb5
parentc818cde78285490ec2931b68a72898f9754a6d81 (diff)
parent8246ac38506f8d62e2dd9699dcc4d62f14b65784 (diff)
Merge branch 'master' of https://github.com/jackycute/HackMD
-rw-r--r--.travis.yml2
-rw-r--r--README.md2
-rw-r--r--app.js1110
-rw-r--r--lib/auth.js348
-rw-r--r--lib/config.js332
-rw-r--r--lib/history.js297
-rw-r--r--lib/letter-avatars.js38
-rw-r--r--lib/logger.js36
-rw-r--r--lib/migrations/20160515114000-user-add-tokens.js22
-rw-r--r--lib/migrations/20160607060246-support-revision.js14
-rw-r--r--lib/migrations/20160703062241-support-authorship.js18
-rw-r--r--lib/migrations/20161009040430-support-delete-note.js8
-rw-r--r--lib/migrations/20161201050312-support-email-signin.js12
-rw-r--r--lib/models/author.js74
-rw-r--r--lib/models/index.js68
-rw-r--r--lib/models/note.js1011
-rw-r--r--lib/models/revision.js580
-rw-r--r--lib/models/temp.js32
-rw-r--r--lib/models/user.js278
-rw-r--r--lib/realtime.js1705
-rwxr-xr-xlib/response.js1086
-rw-r--r--lib/workers/dmpWorker.js249
-rw-r--r--locales/ru.json4
-rw-r--r--package.json12
-rw-r--r--public/js/cover.js739
-rw-r--r--public/js/extra.js1944
-rw-r--r--public/js/google-drive-picker.js227
-rw-r--r--public/js/google-drive-upload.js225
-rw-r--r--public/js/history.js500
-rw-r--r--public/js/htmlExport.js12
-rw-r--r--public/js/index.js7123
-rw-r--r--public/js/lib/common/login.js133
-rw-r--r--public/js/lib/config/index.js28
-rw-r--r--public/js/locale.js42
-rw-r--r--public/js/pretty.js211
-rw-r--r--public/js/render.js76
-rwxr-xr-xpublic/js/reveal-markdown.js741
-rw-r--r--public/js/slide.js195
-rw-r--r--public/js/syncscroll.js604
-rwxr-xr-xpublic/vendor/md-toc.js214
-rw-r--r--webpack.config.js60
-rw-r--r--webpack.production.js118
-rw-r--r--webpackBaseConfig.js846
43 files changed, 10545 insertions, 10831 deletions
diff --git a/.travis.yml b/.travis.yml
index ed8ab42f..235f84b7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: node_js
node_js:
- 6
- 7
- - stable
+ - lts/boron
env:
- CXX=g++-4.8
addons:
diff --git a/README.md b/README.md
index 2afeba26..77aff69a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
HackMD
===
+[![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
+
[![Join the chat at https://gitter.im/hackmdio/hackmd][gitter-image]][gitter-url]
[![build status][travis-image]][travis-url]
diff --git a/app.js b/app.js
index 67a62540..c68652bb 100644
--- a/app.js
+++ b/app.js
@@ -1,654 +1,656 @@
-//app
-//external modules
-var express = require('express');
-var toobusy = require('toobusy-js');
-var ejs = require('ejs');
-var passport = require('passport');
-var methodOverride = require('method-override');
-var cookieParser = require('cookie-parser');
-var bodyParser = require('body-parser');
+// app
+// external modules
+var express = require('express')
+var toobusy = require('toobusy-js')
+var ejs = require('ejs')
+var passport = require('passport')
+var methodOverride = require('method-override')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
var compression = require('compression')
-var session = require('express-session');
-var SequelizeStore = require('connect-session-sequelize')(session.Store);
-var fs = require('fs');
-var url = require('url');
-var path = require('path');
-var imgur = require('imgur');
-var formidable = require('formidable');
-var morgan = require('morgan');
-var passportSocketIo = require("passport.socketio");
-var helmet = require('helmet');
-var i18n = require('i18n');
-var flash = require('connect-flash');
-var validator = require('validator');
-
-//core
-var config = require("./lib/config.js");
-var logger = require("./lib/logger.js");
-var auth = require("./lib/auth.js");
-var response = require("./lib/response.js");
-var models = require("./lib/models");
-
-//server setup
+var session = require('express-session')
+var SequelizeStore = require('connect-session-sequelize')(session.Store)
+var fs = require('fs')
+var url = require('url')
+var path = require('path')
+var imgur = require('imgur')
+var formidable = require('formidable')
+var morgan = require('morgan')
+var passportSocketIo = require('passport.socketio')
+var helmet = require('helmet')
+var i18n = require('i18n')
+var flash = require('connect-flash')
+var validator = require('validator')
+
+// core
+var config = require('./lib/config.js')
+var logger = require('./lib/logger.js')
+var auth = require('./lib/auth.js')
+var response = require('./lib/response.js')
+var models = require('./lib/models')
+
+// server setup
+var app = express()
+var server = null
if (config.usessl) {
- var ca = (function () {
- var i, len, results;
- results = [];
- for (i = 0, len = config.sslcapath.length; i < len; i++) {
- results.push(fs.readFileSync(config.sslcapath[i], 'utf8'));
- }
- return results;
- })();
- var options = {
- key: fs.readFileSync(config.sslkeypath, 'utf8'),
- cert: fs.readFileSync(config.sslcertpath, 'utf8'),
- ca: ca,
- dhparam: fs.readFileSync(config.dhparampath, 'utf8'),
- requestCert: false,
- rejectUnauthorized: false
- };
- var app = express();
- var server = require('https').createServer(options, app);
+ var ca = (function () {
+ var i, len, results
+ results = []
+ for (i = 0, len = config.sslcapath.length; i < len; i++) {
+ results.push(fs.readFileSync(config.sslcapath[i], 'utf8'))
+ }
+ return results
+ })()
+ var options = {
+ key: fs.readFileSync(config.sslkeypath, 'utf8'),
+ cert: fs.readFileSync(config.sslcertpath, 'utf8'),
+ ca: ca,
+ dhparam: fs.readFileSync(config.dhparampath, 'utf8'),
+ requestCert: false,
+ rejectUnauthorized: false
+ }
+ server = require('https').createServer(options, app)
} else {
- var app = express();
- var server = require('http').createServer(app);
+ server = require('http').createServer(app)
}
-//logger
+// logger
app.use(morgan('combined', {
- "stream": logger.stream
-}));
+ 'stream': logger.stream
+}))
-//socket io
-var io = require('socket.io')(server);
+// socket io
+var io = require('socket.io')(server)
io.engine.ws = new (require('uws').Server)({
- noServer: true,
- perMessageDeflate: false
-});
+ noServer: true,
+ perMessageDeflate: false
+})
-//others
-var realtime = require("./lib/realtime.js");
+// others
+var realtime = require('./lib/realtime.js')
-//assign socket io to realtime
-realtime.io = io;
+// assign socket io to realtime
+realtime.io = io
-//methodOverride
-app.use(methodOverride('_method'));
-
-// create application/json parser
-var jsonParser = bodyParser.json({
- limit: 1024 * 1024 * 10 // 10 mb
-});
+// methodOverride
+app.use(methodOverride('_method'))
// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({
- extended: false,
- limit: 1024 * 1024 * 10 // 10 mb
-});
+ extended: false,
+ limit: 1024 * 1024 * 10 // 10 mb
+})
-//session store
+// session store
var sessionStore = new SequelizeStore({
- db: models.sequelize
-});
+ db: models.sequelize
+})
-//compression
-app.use(compression());
+// compression
+app.use(compression())
// use hsts to tell https users stick to this
app.use(helmet.hsts({
- maxAge: 31536000 * 1000, // 365 days
- includeSubdomains: true,
- preload: true
-}));
+ maxAge: 31536000 * 1000, // 365 days
+ includeSubdomains: true,
+ preload: true
+}))
i18n.configure({
- locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo'],
- cookie: 'locale',
- directory: __dirname + '/locales'
-});
+ locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo'],
+ cookie: 'locale',
+ directory: path.join(__dirname, '/locales')
+})
-app.use(cookieParser());
+app.use(cookieParser())
-app.use(i18n.init);
+app.use(i18n.init)
// routes without sessions
// static files
-app.use('/', express.static(__dirname + '/public', { maxAge: config.staticcachetime }));
+app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticcachetime }))
-//session
+// session
app.use(session({
- name: config.sessionname,
- secret: config.sessionsecret,
- resave: false, //don't save session if unmodified
- saveUninitialized: true, //always create session to ensure the origin
- rolling: true, // reset maxAge on every response
- cookie: {
- maxAge: config.sessionlife
- },
- store: sessionStore
-}));
+ name: config.sessionname,
+ secret: config.sessionsecret,
+ resave: false, // don't save session if unmodified
+ saveUninitialized: true, // always create session to ensure the origin
+ rolling: true, // reset maxAge on every response
+ cookie: {
+ maxAge: config.sessionlife
+ },
+ store: sessionStore
+}))
// session resumption
-var tlsSessionStore = {};
+var tlsSessionStore = {}
server.on('newSession', function (id, data, cb) {
- tlsSessionStore[id.toString('hex')] = data;
- cb();
-});
+ tlsSessionStore[id.toString('hex')] = data
+ cb()
+})
server.on('resumeSession', function (id, cb) {
- cb(null, tlsSessionStore[id.toString('hex')] || null);
-});
+ cb(null, tlsSessionStore[id.toString('hex')] || null)
+})
-//middleware which blocks requests when we're too busy
+// middleware which blocks requests when we're too busy
app.use(function (req, res, next) {
- if (toobusy()) {
- response.errorServiceUnavailable(res);
- } else {
- next();
- }
-});
+ if (toobusy()) {
+ response.errorServiceUnavailable(res)
+ } else {
+ next()
+ }
+})
-app.use(flash());
+app.use(flash())
-//passport
-app.use(passport.initialize());
-app.use(passport.session());
+// passport
+app.use(passport.initialize())
+app.use(passport.session())
+auth.registerAuthMethod()
-//serialize and deserialize
+// serialize and deserialize
passport.serializeUser(function (user, done) {
- logger.info('serializeUser: ' + user.id);
- return done(null, user.id);
-});
+ logger.info('serializeUser: ' + user.id)
+ return done(null, user.id)
+})
passport.deserializeUser(function (id, done) {
- models.User.findOne({
- where: {
- id: id
- }
- }).then(function (user) {
- logger.info('deserializeUser: ' + user.id);
- return done(null, user);
- }).catch(function (err) {
- logger.error(err);
- return done(err, null);
- });
-});
+ models.User.findOne({
+ where: {
+ id: id
+ }
+ }).then(function (user) {
+ logger.info('deserializeUser: ' + user.id)
+ return done(null, user)
+ }).catch(function (err) {
+ logger.error(err)
+ return done(err, null)
+ })
+})
// check uri is valid before going further
-app.use(function(req, res, next) {
- try {
- decodeURIComponent(req.path);
- } catch (err) {
- logger.error(err);
- return response.errorBadRequest(res);
- }
- next();
-});
+app.use(function (req, res, next) {
+ try {
+ decodeURIComponent(req.path)
+ } catch (err) {
+ logger.error(err)
+ return response.errorBadRequest(res)
+ }
+ next()
+})
// redirect url without trailing slashes
-app.use(function(req, res, next) {
- if ("GET" == req.method && req.path.substr(-1) == '/' && req.path.length > 1) {
- var query = req.url.slice(req.path.length);
- var urlpath = req.path.slice(0, -1);
- var serverurl = config.serverurl;
- if (config.urlpath) serverurl = serverurl.slice(0, -(config.urlpath.length + 1));
- res.redirect(301, serverurl + urlpath + query);
- } else {
- next();
- }
-});
+app.use(function (req, res, next) {
+ if (req.method === 'GET' && req.path.substr(-1) === '/' && req.path.length > 1) {
+ var query = req.url.slice(req.path.length)
+ var urlpath = req.path.slice(0, -1)
+ var serverurl = config.serverurl
+ if (config.urlpath) serverurl = serverurl.slice(0, -(config.urlpath.length + 1))
+ res.redirect(301, serverurl + urlpath + query)
+ } else {
+ next()
+ }
+})
// routes need sessions
-//template files
-app.set('views', __dirname + '/public/views');
-//set render engine
-app.engine('ejs', ejs.renderFile);
-//set view engine
-app.set('view engine', 'ejs');
-//get index
-app.get("/", response.showIndex);
-//get 403 forbidden
-app.get("/403", function (req, res) {
- response.errorForbidden(res);
-});
-//get 404 not found
-app.get("/404", function (req, res) {
- response.errorNotFound(res);
-});
-//get 500 internal error
-app.get("/500", function (req, res) {
- response.errorInternalError(res);
-});
-//get status
-app.get("/status", function (req, res, next) {
- realtime.getStatus(function (data) {
- res.set({
- 'Cache-Control': 'private', // only cache by client
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.send(data);
- });
-});
-//get status
-app.get("/temp", function (req, res) {
- var host = req.get('host');
- if (config.alloworigin.indexOf(host) == -1)
- response.errorForbidden(res);
- else {
- var tempid = req.query.tempid;
- if (!tempid)
- response.errorForbidden(res);
- else {
- models.Temp.findOne({
- where: {
- id: tempid
- }
- }).then(function (temp) {
- if (!temp)
- response.errorNotFound(res);
- else {
- res.header("Access-Control-Allow-Origin", "*");
- res.send({
- temp: temp.data
- });
- temp.destroy().catch(function (err) {
- if (err)
- logger.error('remove temp failed: ' + err);
- });
- }
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
+// template files
+app.set('views', path.join(__dirname, '/public/views'))
+// set render engine
+app.engine('ejs', ejs.renderFile)
+// set view engine
+app.set('view engine', 'ejs')
+// get index
+app.get('/', response.showIndex)
+// get 403 forbidden
+app.get('/403', function (req, res) {
+ response.errorForbidden(res)
+})
+// get 404 not found
+app.get('/404', function (req, res) {
+ response.errorNotFound(res)
+})
+// get 500 internal error
+app.get('/500', function (req, res) {
+ response.errorInternalError(res)
+})
+// get status
+app.get('/status', function (req, res, next) {
+ realtime.getStatus(function (data) {
+ res.set({
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.send(data)
+ })
+})
+// get status
+app.get('/temp', function (req, res) {
+ var host = req.get('host')
+ if (config.alloworigin.indexOf(host) === -1) {
+ response.errorForbidden(res)
+ } else {
+ var tempid = req.query.tempid
+ if (!tempid) {
+ response.errorForbidden(res)
+ } else {
+ models.Temp.findOne({
+ where: {
+ id: tempid
}
+ }).then(function (temp) {
+ if (!temp) {
+ response.errorNotFound(res)
+ } else {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.send({
+ temp: temp.data
+ })
+ temp.destroy().catch(function (err) {
+ if (err) {
+ logger.error('remove temp failed: ' + err)
+ }
+ })
+ }
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
}
-});
-//post status
-app.post("/temp", urlencodedParser, function (req, res) {
- var host = req.get('host');
- if (config.alloworigin.indexOf(host) == -1)
- response.errorForbidden(res);
- else {
- var data = req.body.data;
- if (!data)
- response.errorForbidden(res);
- else {
- if (config.debug)
- logger.info('SERVER received temp from [' + host + ']: ' + req.body.data);
- models.Temp.create({
- data: data
- }).then(function (temp) {
- if (temp) {
- res.header("Access-Control-Allow-Origin", "*");
- res.send({
- status: 'ok',
- id: temp.id
- });
- } else
- response.errorInternalError(res);
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
+ }
+})
+// post status
+app.post('/temp', urlencodedParser, function (req, res) {
+ var host = req.get('host')
+ if (config.alloworigin.indexOf(host) === -1) {
+ response.errorForbidden(res)
+ } else {
+ var data = req.body.data
+ if (!data) {
+ response.errorForbidden(res)
+ } else {
+ if (config.debug) {
+ logger.info('SERVER received temp from [' + host + ']: ' + req.body.data)
+ }
+ models.Temp.create({
+ data: data
+ }).then(function (temp) {
+ if (temp) {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.send({
+ status: 'ok',
+ id: temp.id
+ })
+ } else {
+ response.errorInternalError(res)
}
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
}
-});
+ }
+})
-function setReturnToFromReferer(req) {
- var referer = req.get('referer');
- if (!req.session) req.session = {};
- req.session.returnTo = referer;
+function setReturnToFromReferer (req) {
+ var referer = req.get('referer')
+ if (!req.session) req.session = {}
+ req.session.returnTo = referer
}
-//facebook auth
+// facebook auth
if (config.facebook) {
- app.get('/auth/facebook', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('facebook')(req, res, next);
- });
- //facebook auth callback
- app.get('/auth/facebook/callback',
+ app.get('/auth/facebook', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('facebook')(req, res, next)
+ })
+ // facebook auth callback
+ app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
}
-//twitter auth
+// twitter auth
if (config.twitter) {
- app.get('/auth/twitter', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('twitter')(req, res, next);
- });
- //twitter auth callback
- app.get('/auth/twitter/callback',
+ app.get('/auth/twitter', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('twitter')(req, res, next)
+ })
+ // twitter auth callback
+ app.get('/auth/twitter/callback',
passport.authenticate('twitter', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
}
-//github auth
+// github auth
if (config.github) {
- app.get('/auth/github', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('github')(req, res, next);
- });
- //github auth callback
- app.get('/auth/github/callback',
+ app.get('/auth/github', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('github')(req, res, next)
+ })
+ // github auth callback
+ app.get('/auth/github/callback',
passport.authenticate('github', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
- //github callback actions
- app.get('/auth/github/callback/:noteId/:action', response.githubActions);
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
+ // github callback actions
+ app.get('/auth/github/callback/:noteId/:action', response.githubActions)
}
-//gitlab auth
+// gitlab auth
if (config.gitlab) {
- app.get('/auth/gitlab', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('gitlab')(req, res, next);
- });
- //gitlab auth callback
- app.get('/auth/gitlab/callback',
+ app.get('/auth/gitlab', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('gitlab')(req, res, next)
+ })
+ // gitlab auth callback
+ app.get('/auth/gitlab/callback',
passport.authenticate('gitlab', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
- //gitlab callback actions
- app.get('/auth/gitlab/callback/:noteId/:action', response.gitlabActions);
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
+ // gitlab callback actions
+ app.get('/auth/gitlab/callback/:noteId/:action', response.gitlabActions)
}
-//dropbox auth
+// dropbox auth
if (config.dropbox) {
- app.get('/auth/dropbox', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('dropbox-oauth2')(req, res, next);
- });
- //dropbox auth callback
- app.get('/auth/dropbox/callback',
+ app.get('/auth/dropbox', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('dropbox-oauth2')(req, res, next)
+ })
+ // dropbox auth callback
+ app.get('/auth/dropbox/callback',
passport.authenticate('dropbox-oauth2', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
}
-//google auth
+// google auth
if (config.google) {
- app.get('/auth/google', function (req, res, next) {
- setReturnToFromReferer(req);
- passport.authenticate('google', { scope: ['profile'] })(req, res, next);
- });
- //google auth callback
- app.get('/auth/google/callback',
+ app.get('/auth/google', function (req, res, next) {
+ setReturnToFromReferer(req)
+ passport.authenticate('google', { scope: ['profile'] })(req, res, next)
+ })
+ // google auth callback
+ app.get('/auth/google/callback',
passport.authenticate('google', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/'
- }));
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/'
+ }))
}
// ldap auth
if (config.ldap) {
- app.post('/auth/ldap', urlencodedParser, function (req, res, next) {
- if (!req.body.username || !req.body.password) return response.errorBadRequest(res);
- setReturnToFromReferer(req);
- passport.authenticate('ldapauth', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/',
- failureFlash: true
- })(req, res, next);
- });
+ app.post('/auth/ldap', urlencodedParser, function (req, res, next) {
+ if (!req.body.username || !req.body.password) return response.errorBadRequest(res)
+ setReturnToFromReferer(req)
+ passport.authenticate('ldapauth', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/',
+ failureFlash: true
+ })(req, res, next)
+ })
}
// email auth
if (config.email) {
- if (config.allowemailregister)
- app.post('/register', urlencodedParser, function (req, res, next) {
- if (!req.body.email || !req.body.password) return response.errorBadRequest(res);
- if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res);
- models.User.findOrCreate({
- where: {
- email: req.body.email
- },
- defaults: {
- password: req.body.password
- }
- }).spread(function (user, created) {
- if (user) {
- if (created) {
- if (config.debug) logger.info('user registered: ' + user.id);
- req.flash('info', "You've successfully registered, please signin.");
- } else {
- if (config.debug) logger.info('user found: ' + user.id);
- req.flash('error', "This email has been used, please try another one.");
- }
- return res.redirect(config.serverurl + '/');
- }
- req.flash('error', "Failed to register your account, please try again.");
- return res.redirect(config.serverurl + '/');
- }).catch(function (err) {
- logger.error('auth callback failed: ' + err);
- return response.errorInternalError(res);
- });
- });
-
- app.post('/login', urlencodedParser, function (req, res, next) {
- if (!req.body.email || !req.body.password) return response.errorBadRequest(res);
- if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res);
- setReturnToFromReferer(req);
- passport.authenticate('local', {
- successReturnToOrRedirect: config.serverurl + '/',
- failureRedirect: config.serverurl + '/',
- failureFlash: 'Invalid email or password.'
- })(req, res, next);
- });
+ if (config.allowemailregister) {
+ app.post('/register', urlencodedParser, function (req, res, next) {
+ if (!req.body.email || !req.body.password) return response.errorBadRequest(res)
+ if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res)
+ models.User.findOrCreate({
+ where: {
+ email: req.body.email
+ },
+ defaults: {
+ password: req.body.password
+ }
+ }).spread(function (user, created) {
+ if (user) {
+ if (created) {
+ if (config.debug) {
+ logger.info('user registered: ' + user.id)
+ }
+ req.flash('info', "You've successfully registered, please signin.")
+ } else {
+ if (config.debug) {
+ logger.info('user found: ' + user.id)
+ }
+ req.flash('error', 'This email has been used, please try another one.')
+ }
+ return res.redirect(config.serverurl + '/')
+ }
+ req.flash('error', 'Failed to register your account, please try again.')
+ return res.redirect(config.serverurl + '/')
+ }).catch(function (err) {
+ logger.error('auth callback failed: ' + err)
+ return response.errorInternalError(res)
+ })
+ })
+ }
+
+ app.post('/login', urlencodedParser, function (req, res, next) {
+ if (!req.body.email || !req.body.password) return response.errorBadRequest(res)
+ if (!validator.isEmail(req.body.email)) return response.errorBadRequest(res)
+ setReturnToFromReferer(req)
+ passport.authenticate('local', {
+ successReturnToOrRedirect: config.serverurl + '/',
+ failureRedirect: config.serverurl + '/',
+ failureFlash: 'Invalid email or password.'
+ })(req, res, next)
+ })
}
-//logout
+// logout
app.get('/logout', function (req, res) {
- if (config.debug && req.isAuthenticated())
- logger.info('user logout: ' + req.user.id);
- req.logout();
- res.redirect(config.serverurl + '/');
-});
-var history = require("./lib/history.js");
-//get history
-app.get('/history', history.historyGet);
-//post history
-app.post('/history', urlencodedParser, history.historyPost);
-//post history by note id
-app.post('/history/:noteId', urlencodedParser, history.historyPost);
-//delete history
-app.delete('/history', history.historyDelete);
-//delete history by note id
-app.delete('/history/:noteId', history.historyDelete);
-//get me info
+ if (config.debug && req.isAuthenticated()) { logger.info('user logout: ' + req.user.id) }
+ req.logout()
+ res.redirect(config.serverurl + '/')
+})
+var history = require('./lib/history.js')
+// get history
+app.get('/history', history.historyGet)
+// post history
+app.post('/history', urlencodedParser, history.historyPost)
+// post history by note id
+app.post('/history/:noteId', urlencodedParser, history.historyPost)
+// delete history
+app.delete('/history', history.historyDelete)
+// delete history by note id
+app.delete('/history/:noteId', history.historyDelete)
+// get me info
app.get('/me', function (req, res) {
- if (req.isAuthenticated()) {
- models.User.findOne({
- where: {
- id: req.user.id
- }
- }).then(function (user) {
- if (!user)
- return response.errorNotFound(res);
- var profile = models.User.getProfile(user);
- res.send({
- status: 'ok',
- id: req.user.id,
- name: profile.name,
- photo: profile.photo
- });
- }).catch(function (err) {
- logger.error('read me failed: ' + err);
- return response.errorInternalError(res);
- });
- } else {
- res.send({
- status: 'forbidden'
- });
- }
-});
-
-//upload image
+ if (req.isAuthenticated()) {
+ models.User.findOne({
+ where: {
+ id: req.user.id
+ }
+ }).then(function (user) {
+ if (!user) { return response.errorNotFound(res) }
+ var profile = models.User.getProfile(user)
+ res.send({
+ status: 'ok',
+ id: req.user.id,
+ name: profile.name,
+ photo: profile.photo
+ })
+ }).catch(function (err) {
+ logger.error('read me failed: ' + err)
+ return response.errorInternalError(res)
+ })
+ } else {
+ res.send({
+ status: 'forbidden'
+ })
+ }
+})
+
+// upload image
app.post('/uploadimage', function (req, res) {
- var form = new formidable.IncomingForm();
+ var form = new formidable.IncomingForm()
- form.keepExtensions = true;
+ form.keepExtensions = true
- if (config.imageUploadType === 'filesystem') {
- form.uploadDir = "public/uploads";
- }
+ if (config.imageUploadType === 'filesystem') {
+ form.uploadDir = 'public/uploads'
+ }
- form.parse(req, function (err, fields, files) {
- if (err || !files.image || !files.image.path) {
- response.errorForbidden(res);
- } else {
- if (config.debug)
- logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image));
-
- try {
- switch (config.imageUploadType) {
- case 'filesystem':
- res.send({
- link: url.resolve(config.serverurl + '/', files.image.path.match(/^public\/(.+$)/)[1])
- });
-
- break;
-
- case 's3':
- var AWS = require('aws-sdk');
- var awsConfig = new AWS.Config(config.s3);
- var s3 = new AWS.S3(awsConfig);
-
- fs.readFile(files.image.path, function (err, buffer) {
- var params = {
- Bucket: config.s3bucket,
- Key: path.join('uploads', path.basename(files.image.path)),
- Body: buffer
- };
-
- s3.putObject(params, function (err, data) {
- if (err) {
- logger.error(err);
- res.status(500).end('upload image error');
- } else {
- res.send({
- link: `https://s3-${config.s3.region}.amazonaws.com/${config.s3bucket}/${params.Key}`
- });
- }
- });
-
- });
-
- break;
-
- case 'imgur':
- default:
- imgur.setClientId(config.imgur.clientID);
- imgur.uploadFile(files.image.path)
- .then(function (json) {
- if (config.debug)
- logger.info('SERVER uploadimage success: ' + JSON.stringify(json));
- res.send({
- link: json.data.link.replace(/^http:\/\//i, 'https://')
- });
- })
- .catch(function (err) {
- logger.error(err);
- return res.status(500).end('upload image error');
- });
- break;
+ form.parse(req, function (err, fields, files) {
+ if (err || !files.image || !files.image.path) {
+ response.errorForbidden(res)
+ } else {
+ if (config.debug) { logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image)) }
+
+ try {
+ switch (config.imageUploadType) {
+ case 'filesystem':
+ res.send({
+ link: url.resolve(config.serverurl + '/', files.image.path.match(/^public\/(.+$)/)[1])
+ })
+
+ break
+
+ case 's3':
+ var AWS = require('aws-sdk')
+ var awsConfig = new AWS.Config(config.s3)
+ var s3 = new AWS.S3(awsConfig)
+
+ fs.readFile(files.image.path, function (err, buffer) {
+ if (err) {
+ logger.error(err)
+ res.status(500).end('upload image error')
+ return
+ }
+ var params = {
+ Bucket: config.s3bucket,
+ Key: path.join('uploads', path.basename(files.image.path)),
+ Body: buffer
+ }
+
+ s3.putObject(params, function (err, data) {
+ if (err) {
+ logger.error(err)
+ res.status(500).end('upload image error')
+ return
}
- } catch (err) {
- logger.error(err);
- return res.status(500).end('upload image error');
- }
+ res.send({
+ link: `https://s3-${config.s3.region}.amazonaws.com/${config.s3bucket}/${params.Key}`
+ })
+ })
+ })
+ break
+ case 'imgur':
+ default:
+ imgur.setClientId(config.imgur.clientID)
+ imgur.uploadFile(files.image.path)
+ .then(function (json) {
+ if (config.debug) { logger.info('SERVER uploadimage success: ' + JSON.stringify(json)) }
+ res.send({
+ link: json.data.link.replace(/^http:\/\//i, 'https://')
+ })
+ })
+ .catch(function (err) {
+ logger.error(err)
+ return res.status(500).end('upload image error')
+ })
+ break
}
- });
-});
-//get new note
-app.get("/new", response.newNote);
-//get publish note
-app.get("/s/:shortid", response.showPublishNote);
-//publish note actions
-app.get("/s/:shortid/:action", response.publishNoteActions);
-//get publish slide
-app.get("/p/:shortid", response.showPublishSlide);
-//publish slide actions
-app.get("/p/:shortid/:action", response.publishSlideActions);
-//get note by id
-app.get("/:noteId", response.showNote);
-//note actions
-app.get("/:noteId/:action", response.noteActions);
-//note actions with action id
-app.get("/:noteId/:action/:actionId", response.noteActions);
+ } catch (err) {
+ logger.error(err)
+ return res.status(500).end('upload image error')
+ }
+ }
+ })
+})
+// get new note
+app.get('/new', response.newNote)
+// get publish note
+app.get('/s/:shortid', response.showPublishNote)
+// publish note actions
+app.get('/s/:shortid/:action', response.publishNoteActions)
+// get publish slide
+app.get('/p/:shortid', response.showPublishSlide)
+// publish slide actions
+app.get('/p/:shortid/:action', response.publishSlideActions)
+// get note by id
+app.get('/:noteId', response.showNote)
+// note actions
+app.get('/:noteId/:action', response.noteActions)
+// note actions with action id
+app.get('/:noteId/:action/:actionId', response.noteActions)
// response not found if no any route matches
app.get('*', function (req, res) {
- response.errorNotFound(res);
-});
+ response.errorNotFound(res)
+})
-//socket.io secure
-io.use(realtime.secure);
-//socket.io auth
+// socket.io secure
+io.use(realtime.secure)
+// socket.io auth
io.use(passportSocketIo.authorize({
- cookieParser: cookieParser,
- key: config.sessionname,
- secret: config.sessionsecret,
- store: sessionStore,
- success: realtime.onAuthorizeSuccess,
- fail: realtime.onAuthorizeFail
-}));
-//socket.io heartbeat
-io.set('heartbeat interval', config.heartbeatinterval);
-io.set('heartbeat timeout', config.heartbeattimeout);
-//socket.io connection
-io.sockets.on('connection', realtime.connection);
-
-//listen
-function startListen() {
- server.listen(config.port, function () {
- var schema = config.usessl ? 'HTTPS' : 'HTTP';
- logger.info('%s Server listening at port %d', schema, config.port);
- config.maintenance = false;
- });
+ cookieParser: cookieParser,
+ key: config.sessionname,
+ secret: config.sessionsecret,
+ store: sessionStore,
+ success: realtime.onAuthorizeSuccess,
+ fail: realtime.onAuthorizeFail
+}))
+// socket.io heartbeat
+io.set('heartbeat interval', config.heartbeatinterval)
+io.set('heartbeat timeout', config.heartbeattimeout)
+// socket.io connection
+io.sockets.on('connection', realtime.connection)
+
+// listen
+function startListen () {
+ server.listen(config.port, function () {
+ var schema = config.usessl ? 'HTTPS' : 'HTTP'
+ logger.info('%s Server listening at port %d', schema, config.port)
+ config.maintenance = false
+ })
}
// sync db then start listen
models.sequelize.sync().then(function () {
- // check if realtime is ready
- if (realtime.isReady()) {
- models.Revision.checkAllNotesRevision(function (err, notes) {
- if (err) throw new Error(err);
- if (!notes || notes.length <= 0) return startListen();
- });
- } else {
- throw new Error('server still not ready after db synced');
- }
-});
+ // check if realtime is ready
+ if (realtime.isReady()) {
+ models.Revision.checkAllNotesRevision(function (err, notes) {
+ if (err) throw new Error(err)
+ if (!notes || notes.length <= 0) return startListen()
+ })
+ } else {
+ throw new Error('server still not ready after db synced')
+ }
+})
// log uncaught exception
process.on('uncaughtException', function (err) {
- logger.error('An uncaught exception has occured.');
- logger.error(err);
- logger.error('Process will exit now.');
- process.exit(1);
-});
+ logger.error('An uncaught exception has occured.')
+ logger.error(err)
+ logger.error('Process will exit now.')
+ process.exit(1)
+})
// install exit handler
-function handleTermSignals() {
- config.maintenance = true;
- // disconnect all socket.io clients
- Object.keys(io.sockets.sockets).forEach(function (key) {
- var socket = io.sockets.sockets[key];
- // notify client server going into maintenance status
- socket.emit('maintenance');
- setTimeout(function () {
- socket.disconnect(true);
- }, 0);
- });
- var checkCleanTimer = setInterval(function () {
- if (realtime.isReady()) {
- models.Revision.checkAllNotesRevision(function (err, notes) {
- if (err) return logger.error(err);
- if (!notes || notes.length <= 0) {
- clearInterval(checkCleanTimer);
- return process.exit(0);
- }
- });
+function handleTermSignals () {
+ config.maintenance = true
+ // disconnect all socket.io clients
+ Object.keys(io.sockets.sockets).forEach(function (key) {
+ var socket = io.sockets.sockets[key]
+ // notify client server going into maintenance status
+ socket.emit('maintenance')
+ setTimeout(function () {
+ socket.disconnect(true)
+ }, 0)
+ })
+ var checkCleanTimer = setInterval(function () {
+ if (realtime.isReady()) {
+ models.Revision.checkAllNotesRevision(function (err, notes) {
+ if (err) return logger.error(err)
+ if (!notes || notes.length <= 0) {
+ clearInterval(checkCleanTimer)
+ return process.exit(0)
}
- }, 100);
+ })
+ }
+ }, 100)
}
-process.on('SIGINT', handleTermSignals);
-process.on('SIGTERM', handleTermSignals);
+process.on('SIGINT', handleTermSignals)
+process.on('SIGTERM', handleTermSignals)
diff --git a/lib/auth.js b/lib/auth.js
index 4b14e42c..ef1d6464 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -1,190 +1,192 @@
-//auth
-//external modules
-var passport = require('passport');
-var FacebookStrategy = require('passport-facebook').Strategy;
-var TwitterStrategy = require('passport-twitter').Strategy;
-var GithubStrategy = require('passport-github').Strategy;
-var GitlabStrategy = require('passport-gitlab2').Strategy;
-var DropboxStrategy = require('passport-dropbox-oauth2').Strategy;
-var GoogleStrategy = require('passport-google-oauth20').Strategy;
-var LdapStrategy = require('passport-ldapauth');
-var LocalStrategy = require('passport-local').Strategy;
-var validator = require('validator');
+// auth
+// external modules
+var passport = require('passport')
+var FacebookStrategy = require('passport-facebook').Strategy
+var TwitterStrategy = require('passport-twitter').Strategy
+var GithubStrategy = require('passport-github').Strategy
+var GitlabStrategy = require('passport-gitlab2').Strategy
+var DropboxStrategy = require('passport-dropbox-oauth2').Strategy
+var GoogleStrategy = require('passport-google-oauth20').Strategy
+var LdapStrategy = require('passport-ldapauth')
+var LocalStrategy = require('passport-local').Strategy
+var validator = require('validator')
-//core
-var config = require('./config.js');
-var logger = require("./logger.js");
-var models = require("./models");
+// core
+var config = require('./config.js')
+var logger = require('./logger.js')
+var models = require('./models')
-function callback(accessToken, refreshToken, profile, done) {
- //logger.info(profile.displayName || profile.username);
- var stringifiedProfile = JSON.stringify(profile);
- models.User.findOrCreate({
- where: {
- profileid: profile.id.toString()
- },
- defaults: {
- profile: stringifiedProfile,
- accessToken: accessToken,
- refreshToken: refreshToken
- }
- }).spread(function (user, created) {
- if (user) {
- var needSave = false;
- if (user.profile != stringifiedProfile) {
- user.profile = stringifiedProfile;
- needSave = true;
- }
- if (user.accessToken != accessToken) {
- user.accessToken = accessToken;
- needSave = true;
- }
- if (user.refreshToken != refreshToken) {
- user.refreshToken = refreshToken;
- needSave = true;
- }
- if (needSave) {
- user.save().then(function () {
- if (config.debug)
- logger.info('user login: ' + user.id);
- return done(null, user);
- });
- } else {
- if (config.debug)
- logger.info('user login: ' + user.id);
- return done(null, user);
- }
- }
- }).catch(function (err) {
- logger.error('auth callback failed: ' + err);
- return done(err, null);
- });
+function callback (accessToken, refreshToken, profile, done) {
+ // logger.info(profile.displayName || profile.username);
+ var stringifiedProfile = JSON.stringify(profile)
+ models.User.findOrCreate({
+ where: {
+ profileid: profile.id.toString()
+ },
+ defaults: {
+ profile: stringifiedProfile,
+ accessToken: accessToken,
+ refreshToken: refreshToken
+ }
+ }).spread(function (user, created) {
+ if (user) {
+ var needSave = false
+ if (user.profile !== stringifiedProfile) {
+ user.profile = stringifiedProfile
+ needSave = true
+ }
+ if (user.accessToken !== accessToken) {
+ user.accessToken = accessToken
+ needSave = true
+ }
+ if (user.refreshToken !== refreshToken) {
+ user.refreshToken = refreshToken
+ needSave = true
+ }
+ if (needSave) {
+ user.save().then(function () {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ })
+ } else {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ }
+ }
+ }).catch(function (err) {
+ logger.error('auth callback failed: ' + err)
+ return done(err, null)
+ })
}
-//facebook
-if (config.facebook) {
- module.exports = passport.use(new FacebookStrategy({
- clientID: config.facebook.clientID,
- clientSecret: config.facebook.clientSecret,
- callbackURL: config.serverurl + '/auth/facebook/callback'
- }, callback));
-}
-//twitter
-if (config.twitter) {
+function registerAuthMethod () {
+// facebook
+ if (config.facebook) {
+ passport.use(new FacebookStrategy({
+ clientID: config.facebook.clientID,
+ clientSecret: config.facebook.clientSecret,
+ callbackURL: config.serverurl + '/auth/facebook/callback'
+ }, callback))
+ }
+// twitter
+ if (config.twitter) {
passport.use(new TwitterStrategy({
- consumerKey: config.twitter.consumerKey,
- consumerSecret: config.twitter.consumerSecret,
- callbackURL: config.serverurl + '/auth/twitter/callback'
- }, callback));
-}
-//github
-if (config.github) {
+ consumerKey: config.twitter.consumerKey,
+ consumerSecret: config.twitter.consumerSecret,
+ callbackURL: config.serverurl + '/auth/twitter/callback'
+ }, callback))
+ }
+// github
+ if (config.github) {
passport.use(new GithubStrategy({
- clientID: config.github.clientID,
- clientSecret: config.github.clientSecret,
- callbackURL: config.serverurl + '/auth/github/callback'
- }, callback));
-}
-//gitlab
-if (config.gitlab) {
+ clientID: config.github.clientID,
+ clientSecret: config.github.clientSecret,
+ callbackURL: config.serverurl + '/auth/github/callback'
+ }, callback))
+ }
+// gitlab
+ if (config.gitlab) {
passport.use(new GitlabStrategy({
- baseURL: config.gitlab.baseURL,
- clientID: config.gitlab.clientID,
- clientSecret: config.gitlab.clientSecret,
- callbackURL: config.serverurl + '/auth/gitlab/callback'
- }, callback));
-}
-//dropbox
-if (config.dropbox) {
+ baseURL: config.gitlab.baseURL,
+ clientID: config.gitlab.clientID,
+ clientSecret: config.gitlab.clientSecret,
+ callbackURL: config.serverurl + '/auth/gitlab/callback'
+ }, callback))
+ }
+// dropbox
+ if (config.dropbox) {
passport.use(new DropboxStrategy({
- apiVersion: '2',
- clientID: config.dropbox.clientID,
- clientSecret: config.dropbox.clientSecret,
- callbackURL: config.serverurl + '/auth/dropbox/callback'
- }, callback));
-}
-//google
-if (config.google) {
+ apiVersion: '2',
+ clientID: config.dropbox.clientID,
+ clientSecret: config.dropbox.clientSecret,
+ callbackURL: config.serverurl + '/auth/dropbox/callback'
+ }, callback))
+ }
+// google
+ if (config.google) {
passport.use(new GoogleStrategy({
- clientID: config.google.clientID,
- clientSecret: config.google.clientSecret,
- callbackURL: config.serverurl + '/auth/google/callback'
- }, callback));
-}
+ clientID: config.google.clientID,
+ clientSecret: config.google.clientSecret,
+ callbackURL: config.serverurl + '/auth/google/callback'
+ }, callback))
+ }
// ldap
-if (config.ldap) {
+ if (config.ldap) {
passport.use(new LdapStrategy({
- server: {
- url: config.ldap.url || null,
- bindDn: config.ldap.bindDn || null,
- bindCredentials: config.ldap.bindCredentials || null,
- searchBase: config.ldap.searchBase || null,
- searchFilter: config.ldap.searchFilter || null,
- searchAttributes: config.ldap.searchAttributes || null,
- tlsOptions: config.ldap.tlsOptions || null
- },
+ server: {
+ url: config.ldap.url || null,
+ bindDn: config.ldap.bindDn || null,
+ bindCredentials: config.ldap.bindCredentials || null,
+ searchBase: config.ldap.searchBase || null,
+ searchFilter: config.ldap.searchFilter || null,
+ searchAttributes: config.ldap.searchAttributes || null,
+ tlsOptions: config.ldap.tlsOptions || null
+ }
},
- function(user, done) {
- var profile = {
- id: 'LDAP-' + user.uidNumber,
- username: user.uid,
- displayName: user.displayName,
- emails: user.mail ? [user.mail] : [],
- avatarUrl: null,
- profileUrl: null,
- provider: 'ldap',
+ function (user, done) {
+ var profile = {
+ id: 'LDAP-' + user.uidNumber,
+ username: user.uid,
+ displayName: user.displayName,
+ emails: user.mail ? [user.mail] : [],
+ avatarUrl: null,
+ profileUrl: null,
+ provider: 'ldap'
+ }
+ var stringifiedProfile = JSON.stringify(profile)
+ models.User.findOrCreate({
+ where: {
+ profileid: profile.id.toString()
+ },
+ defaults: {
+ profile: stringifiedProfile
}
- var stringifiedProfile = JSON.stringify(profile);
- models.User.findOrCreate({
- where: {
- profileid: profile.id.toString()
- },
- defaults: {
- profile: stringifiedProfile,
- }
- }).spread(function (user, created) {
- if (user) {
- var needSave = false;
- if (user.profile != stringifiedProfile) {
- user.profile = stringifiedProfile;
- needSave = true;
- }
- if (needSave) {
- user.save().then(function () {
- if (config.debug)
- logger.info('user login: ' + user.id);
- return done(null, user);
- });
- } else {
- if (config.debug)
- logger.info('user login: ' + user.id);
- return done(null, user);
- }
- }
- }).catch(function (err) {
- logger.error('ldap auth failed: ' + err);
- return done(err, null);
- });
- }));
-}
+ }).spread(function (user, created) {
+ if (user) {
+ var needSave = false
+ if (user.profile !== stringifiedProfile) {
+ user.profile = stringifiedProfile
+ needSave = true
+ }
+ if (needSave) {
+ user.save().then(function () {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ })
+ } else {
+ if (config.debug) { logger.info('user login: ' + user.id) }
+ return done(null, user)
+ }
+ }
+ }).catch(function (err) {
+ logger.error('ldap auth failed: ' + err)
+ return done(err, null)
+ })
+ }))
+ }
// email
-if (config.email) {
+ if (config.email) {
passport.use(new LocalStrategy({
- usernameField: 'email'
+ usernameField: 'email'
},
- function(email, password, done) {
- if (!validator.isEmail(email)) return done(null, false);
- models.User.findOne({
- where: {
- email: email
- }
- }).then(function (user) {
- if (!user) return done(null, false);
- if (!user.verifyPassword(password)) return done(null, false);
- return done(null, user);
- }).catch(function (err) {
- logger.error(err);
- return done(err);
- });
- }));
+ function (email, password, done) {
+ if (!validator.isEmail(email)) return done(null, false)
+ models.User.findOne({
+ where: {
+ email: email
+ }
+ }).then(function (user) {
+ if (!user) return done(null, false)
+ if (!user.verifyPassword(password)) return done(null, false)
+ return done(null, user)
+ }).catch(function (err) {
+ logger.error(err)
+ return done(err)
+ })
+ }))
+ }
+}
+
+module.exports = {
+ registerAuthMethod: registerAuthMethod
}
diff --git a/lib/config.js b/lib/config.js
index 4d2fbf74..af4c22cd 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -1,118 +1,117 @@
// external modules
-var fs = require('fs');
-var path = require('path');
-var fs = require('fs');
+var fs = require('fs')
+var path = require('path')
// configs
-var env = process.env.NODE_ENV || 'development';
-var config = require(path.join(__dirname, '..', 'config.json'))[env];
-var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development'));
+var env = process.env.NODE_ENV || 'development'
+var config = require(path.join(__dirname, '..', 'config.json'))[env]
+var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development'))
// Create function that reads docker secrets but fails fast in case of a non docker environment
-var handleDockerSecret = fs.existsSync('/run/secrets/') ? function(secret) {
- return fs.existsSync('/run/secrets/' + secret) ? fs.readFileSync('/run/secrets/' + secret) : null;
-} : function() {
- return null
-};
+var handleDockerSecret = fs.existsSync('/run/secrets/') ? function (secret) {
+ return fs.existsSync('/run/secrets/' + secret) ? fs.readFileSync('/run/secrets/' + secret) : null
+} : function () {
+ return null
+}
// url
-var domain = process.env.DOMAIN || process.env.HMD_DOMAIN || config.domain || '';
-var urlpath = process.env.URL_PATH || process.env.HMD_URL_PATH || config.urlpath || '';
-var port = process.env.PORT || process.env.HMD_PORT || config.port || 3000;
-var alloworigin = process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : (config.alloworigin || ['localhost']);
+var domain = process.env.DOMAIN || process.env.HMD_DOMAIN || config.domain || ''
+var urlpath = process.env.URL_PATH || process.env.HMD_URL_PATH || config.urlpath || ''
+var port = process.env.PORT || process.env.HMD_PORT || config.port || 3000
+var alloworigin = process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : (config.alloworigin || ['localhost'])
-var usessl = !!config.usessl;
+var usessl = !!config.usessl
var protocolusessl = (usessl === true && typeof process.env.HMD_PROTOCOL_USESSL === 'undefined' && typeof config.protocolusessl === 'undefined')
- ? true : (process.env.HMD_PROTOCOL_USESSL ? (process.env.HMD_PROTOCOL_USESSL === 'true') : !!config.protocolusessl);
-var urladdport = process.env.HMD_URL_ADDPORT ? (process.env.HMD_URL_ADDPORT === 'true') : !!config.urladdport;
+ ? true : (process.env.HMD_PROTOCOL_USESSL ? (process.env.HMD_PROTOCOL_USESSL === 'true') : !!config.protocolusessl)
+var urladdport = process.env.HMD_URL_ADDPORT ? (process.env.HMD_URL_ADDPORT === 'true') : !!config.urladdport
-var usecdn = process.env.HMD_USECDN ? (process.env.HMD_USECDN === 'true') : ((typeof config.usecdn === 'boolean') ? config.usecdn : true);
+var usecdn = process.env.HMD_USECDN ? (process.env.HMD_USECDN === 'true') : ((typeof config.usecdn === 'boolean') ? config.usecdn : true)
-var allowanonymous = process.env.HMD_ALLOW_ANONYMOUS ? (process.env.HMD_ALLOW_ANONYMOUS === 'true') : ((typeof config.allowanonymous === 'boolean') ? config.allowanonymous : true);
+var allowanonymous = process.env.HMD_ALLOW_ANONYMOUS ? (process.env.HMD_ALLOW_ANONYMOUS === 'true') : ((typeof config.allowanonymous === 'boolean') ? config.allowanonymous : true)
-var allowfreeurl = process.env.HMD_ALLOW_FREEURL ? (process.env.HMD_ALLOW_FREEURL === 'true') : !!config.allowfreeurl;
+var allowfreeurl = process.env.HMD_ALLOW_FREEURL ? (process.env.HMD_ALLOW_FREEURL === 'true') : !!config.allowfreeurl
-var permissions = ['editable', 'limited', 'locked', 'protected', 'private'];
+var permissions = ['editable', 'limited', 'locked', 'protected', 'private']
if (allowanonymous) {
- permissions.unshift('freely');
+ permissions.unshift('freely')
}
-var defaultpermission = process.env.HMD_DEFAULT_PERMISSION || config.defaultpermission;
-defaultpermission = permissions.indexOf(defaultpermission) != -1 ? defaultpermission : 'editable';
+var defaultpermission = process.env.HMD_DEFAULT_PERMISSION || config.defaultpermission
+defaultpermission = permissions.indexOf(defaultpermission) !== -1 ? defaultpermission : 'editable'
// db
-var dburl = process.env.HMD_DB_URL || process.env.DATABASE_URL || config.dburl;
-var db = config.db || {};
+var dburl = process.env.HMD_DB_URL || process.env.DATABASE_URL || config.dburl
+var db = config.db || {}
// ssl path
-var sslkeypath = (fs.existsSync('/run/secrets/key.pem') ? '/run/secrets/key.pem' : null) || config.sslkeypath || '';
-var sslcertpath = (fs.existsSync('/run/secrets/cert.pem') ? '/run/secrets/cert.pem' : null) || config.sslcertpath || '';
-var sslcapath = (fs.existsSync('/run/secrets/ca.pem') ? '/run/secrets/ca.pem' : null) || config.sslcapath || '';
-var dhparampath = (fs.existsSync('/run/secrets/dhparam.pem') ? '/run/secrets/dhparam.pem' : null) || config.dhparampath || '';
+var sslkeypath = (fs.existsSync('/run/secrets/key.pem') ? '/run/secrets/key.pem' : null) || config.sslkeypath || ''
+var sslcertpath = (fs.existsSync('/run/secrets/cert.pem') ? '/run/secrets/cert.pem' : null) || config.sslcertpath || ''
+var sslcapath = (fs.existsSync('/run/secrets/ca.pem') ? '/run/secrets/ca.pem' : null) || config.sslcapath || ''
+var dhparampath = (fs.existsSync('/run/secrets/dhparam.pem') ? '/run/secrets/dhparam.pem' : null) || config.dhparampath || ''
// other path
-var tmppath = config.tmppath || './tmp';
-var defaultnotepath = config.defaultnotepath || './public/default.md';
-var docspath = config.docspath || './public/docs';
-var indexpath = config.indexpath || './public/views/index.ejs';
-var hackmdpath = config.hackmdpath || './public/views/hackmd.ejs';
-var errorpath = config.errorpath || './public/views/error.ejs';
-var prettypath = config.prettypath || './public/views/pretty.ejs';
-var slidepath = config.slidepath || './public/views/slide.ejs';
+var tmppath = config.tmppath || './tmp'
+var defaultnotepath = config.defaultnotepath || './public/default.md'
+var docspath = config.docspath || './public/docs'
+var indexpath = config.indexpath || './public/views/index.ejs'
+var hackmdpath = config.hackmdpath || './public/views/hackmd.ejs'
+var errorpath = config.errorpath || './public/views/error.ejs'
+var prettypath = config.prettypath || './public/views/pretty.ejs'
+var slidepath = config.slidepath || './public/views/slide.ejs'
// session
-var sessionname = config.sessionname || 'connect.sid';
-var sessionsecret = handleDockerSecret('sessionsecret') || config.sessionsecret || 'secret';
-var sessionlife = config.sessionlife || 14 * 24 * 60 * 60 * 1000; //14 days
+var sessionname = config.sessionname || 'connect.sid'
+var sessionsecret = handleDockerSecret('sessionsecret') || config.sessionsecret || 'secret'
+var sessionlife = config.sessionlife || 14 * 24 * 60 * 60 * 1000 // 14 days
// static files
-var staticcachetime = config.staticcachetime || 1 * 24 * 60 * 60 * 1000; // 1 day
+var staticcachetime = config.staticcachetime || 1 * 24 * 60 * 60 * 1000 // 1 day
// socket.io
-var heartbeatinterval = config.heartbeatinterval || 5000;
-var heartbeattimeout = config.heartbeattimeout || 10000;
+var heartbeatinterval = config.heartbeatinterval || 5000
+var heartbeattimeout = config.heartbeattimeout || 10000
// document
-var documentmaxlength = config.documentmaxlength || 100000;
+var documentmaxlength = config.documentmaxlength || 100000
// image upload setting, available options are imgur/s3/filesystem
-var imageUploadType = process.env.HMD_IMAGE_UPLOAD_TYPE || config.imageUploadType || 'imgur';
+var imageUploadType = process.env.HMD_IMAGE_UPLOAD_TYPE || config.imageUploadType || 'imgur'
-config.s3 = config.s3 || {};
+config.s3 = config.s3 || {}
var s3 = {
- accessKeyId: handleDockerSecret('s3_acccessKeyId') || process.env.HMD_S3_ACCESS_KEY_ID || config.s3.accessKeyId,
- secretAccessKey: handleDockerSecret('s3_secretAccessKey') || process.env.HMD_S3_SECRET_ACCESS_KEY || config.s3.secretAccessKey,
- region: process.env.HMD_S3_REGION || config.s3.region
+ accessKeyId: handleDockerSecret('s3_acccessKeyId') || process.env.HMD_S3_ACCESS_KEY_ID || config.s3.accessKeyId,
+ secretAccessKey: handleDockerSecret('s3_secretAccessKey') || process.env.HMD_S3_SECRET_ACCESS_KEY || config.s3.secretAccessKey,
+ region: process.env.HMD_S3_REGION || config.s3.region
}
-var s3bucket = process.env.HMD_S3_BUCKET || config.s3.bucket;
+var s3bucket = process.env.HMD_S3_BUCKET || config.s3.bucket
// auth
-var facebook = (process.env.HMD_FACEBOOK_CLIENTID && process.env.HMD_FACEBOOK_CLIENTSECRET || fs.existsSync('/run/secrets/facebook_clientID') && fs.existsSync('/run/secrets/facebook_clientSecret')) ? {
- clientID: handleDockerSecret('facebook_clientID') || process.env.HMD_FACEBOOK_CLIENTID,
- clientSecret: handleDockerSecret('facebook_clientSecret') || process.env.HMD_FACEBOOK_CLIENTSECRET
-} : config.facebook || false;
-var twitter = (process.env.HMD_TWITTER_CONSUMERKEY && process.env.HMD_TWITTER_CONSUMERSECRET || fs.existsSync('/run/secrets/twitter_consumerKey') && fs.existsSync('/run/secrets/twitter_consumerSecret')) ? {
- consumerKey: handleDockerSecret('twitter_consumerKey') || process.env.HMD_TWITTER_CONSUMERKEY,
- consumerSecret: handleDockerSecret('twitter_consumerSecret') || process.env.HMD_TWITTER_CONSUMERSECRET
-} : config.twitter || false;
-var github = (process.env.HMD_GITHUB_CLIENTID && process.env.HMD_GITHUB_CLIENTSECRET || fs.existsSync('/run/secrets/github_clientID') && fs.existsSync('/run/secrets/github_clientSecret')) ? {
- clientID: handleDockerSecret('github_clientID') || process.env.HMD_GITHUB_CLIENTID,
- clientSecret: handleDockerSecret('github_clientSecret') || process.env.HMD_GITHUB_CLIENTSECRET
-} : config.github || false;
-var gitlab = (process.env.HMD_GITLAB_CLIENTID && process.env.HMD_GITLAB_CLIENTSECRET || fs.existsSync('/run/secrets/gitlab_clientID') && fs.existsSync('/run/secrets/gitlab_clientSecret')) ? {
- baseURL: process.env.HMD_GITLAB_BASEURL,
- clientID: handleDockerSecret('gitlab_clientID') || process.env.HMD_GITLAB_CLIENTID,
- clientSecret: handleDockerSecret('gitlab_clientSecret') || process.env.HMD_GITLAB_CLIENTSECRET
-} : config.gitlab || false;
+var facebook = ((process.env.HMD_FACEBOOK_CLIENTID && process.env.HMD_FACEBOOK_CLIENTSECRET) || (fs.existsSync('/run/secrets/facebook_clientID') && fs.existsSync('/run/secrets/facebook_clientSecret'))) ? {
+ clientID: handleDockerSecret('facebook_clientID') || process.env.HMD_FACEBOOK_CLIENTID,
+ clientSecret: handleDockerSecret('facebook_clientSecret') || process.env.HMD_FACEBOOK_CLIENTSECRET
+} : config.facebook || false
+var twitter = ((process.env.HMD_TWITTER_CONSUMERKEY && process.env.HMD_TWITTER_CONSUMERSECRET) || (fs.existsSync('/run/secrets/twitter_consumerKey') && fs.existsSync('/run/secrets/twitter_consumerSecret'))) ? {
+ consumerKey: handleDockerSecret('twitter_consumerKey') || process.env.HMD_TWITTER_CONSUMERKEY,
+ consumerSecret: handleDockerSecret('twitter_consumerSecret') || process.env.HMD_TWITTER_CONSUMERSECRET
+} : config.twitter || false
+var github = ((process.env.HMD_GITHUB_CLIENTID && process.env.HMD_GITHUB_CLIENTSECRET) || (fs.existsSync('/run/secrets/github_clientID') && fs.existsSync('/run/secrets/github_clientSecret'))) ? {
+ clientID: handleDockerSecret('github_clientID') || process.env.HMD_GITHUB_CLIENTID,
+ clientSecret: handleDockerSecret('github_clientSecret') || process.env.HMD_GITHUB_CLIENTSECRET
+} : config.github || false
+var gitlab = ((process.env.HMD_GITLAB_CLIENTID && process.env.HMD_GITLAB_CLIENTSECRET) || (fs.existsSync('/run/secrets/gitlab_clientID') && fs.existsSync('/run/secrets/gitlab_clientSecret'))) ? {
+ baseURL: process.env.HMD_GITLAB_BASEURL,
+ 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'))) ? {
- clientID: handleDockerSecret('dropbox_clientID') || process.env.HMD_DROPBOX_CLIENTID,
- clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_DROPBOX_CLIENTSECRET
-} : (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;
+ clientID: handleDockerSecret('dropbox_clientID') || process.env.HMD_DROPBOX_CLIENTID,
+ clientSecret: handleDockerSecret('dropbox_clientSecret') || process.env.HMD_DROPBOX_CLIENTSECRET
+} : (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 ||
@@ -123,106 +122,97 @@ var ldap = config.ldap || ((
process.env.HMD_LDAP_SEARCHATTRIBUTES ||
process.env.HMD_LDAP_TLS_CA ||
process.env.HMD_LDAP_PROVIDERNAME
-) ? {} : false);
-if (process.env.HMD_LDAP_URL)
- ldap.url = process.env.HMD_LDAP_URL;
-if (process.env.HMD_LDAP_BINDDN)
- ldap.bindDn = process.env.HMD_LDAP_BINDDN;
-if (process.env.HMD_LDAP_BINDCREDENTIALS)
- ldap.bindCredentials = process.env.HMD_LDAP_BINDCREDENTIALS;
-if (process.env.HMD_LDAP_TOKENSECRET)
- ldap.tokenSecret = process.env.HMD_LDAP_TOKENSECRET;
-if (process.env.HMD_LDAP_SEARCHBASE)
- ldap.searchBase = process.env.HMD_LDAP_SEARCHBASE;
-if (process.env.HMD_LDAP_SEARCHFILTER)
- ldap.searchFilter = process.env.HMD_LDAP_SEARCHFILTER;
-if (process.env.HMD_LDAP_SEARCHATTRIBUTES)
- ldap.searchAttributes = process.env.HMD_LDAP_SEARCHATTRIBUTES;
+) ? {} : false)
+if (process.env.HMD_LDAP_URL) { ldap.url = process.env.HMD_LDAP_URL }
+if (process.env.HMD_LDAP_BINDDN) { ldap.bindDn = process.env.HMD_LDAP_BINDDN }
+if (process.env.HMD_LDAP_BINDCREDENTIALS) { ldap.bindCredentials = process.env.HMD_LDAP_BINDCREDENTIALS }
+if (process.env.HMD_LDAP_TOKENSECRET) { ldap.tokenSecret = process.env.HMD_LDAP_TOKENSECRET }
+if (process.env.HMD_LDAP_SEARCHBASE) { ldap.searchBase = process.env.HMD_LDAP_SEARCHBASE }
+if (process.env.HMD_LDAP_SEARCHFILTER) { ldap.searchFilter = process.env.HMD_LDAP_SEARCHFILTER }
+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.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;
+ var 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
+ }
}
if (process.env.HMD_LDAP_PROVIDERNAME) {
- ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME;
+ ldap.providerName = process.env.HMD_LDAP_PROVIDERNAME
}
-var imgur = handleDockerSecret('imgur_clientid') || process.env.HMD_IMGUR_CLIENTID || config.imgur || false;
-var email = process.env.HMD_EMAIL ? (process.env.HMD_EMAIL === 'true') : !!config.email;
-var allowemailregister = process.env.HMD_ALLOW_EMAIL_REGISTER ? (process.env.HMD_ALLOW_EMAIL_REGISTER === 'true') : ((typeof config.allowemailregister === 'boolean') ? config.allowemailregister : true);
-
-function getserverurl() {
- var url = '';
- if (domain) {
- var protocol = protocolusessl ? 'https://' : 'http://';
- url = protocol + domain;
- if (urladdport && ((usessl && port != 443) || (!usessl && port != 80)))
- url += ':' + port;
- }
- if (urlpath)
- url += '/' + urlpath;
- return url;
+var imgur = handleDockerSecret('imgur_clientid') || process.env.HMD_IMGUR_CLIENTID || config.imgur || false
+var email = process.env.HMD_EMAIL ? (process.env.HMD_EMAIL === 'true') : !!config.email
+var allowemailregister = process.env.HMD_ALLOW_EMAIL_REGISTER ? (process.env.HMD_ALLOW_EMAIL_REGISTER === 'true') : ((typeof config.allowemailregister === 'boolean') ? config.allowemailregister : true)
+
+function getserverurl () {
+ var url = ''
+ if (domain) {
+ var protocol = protocolusessl ? 'https://' : 'http://'
+ url = protocol + domain
+ if (urladdport && ((usessl && port !== 443) || (!usessl && port !== 80))) { url += ':' + port }
+ }
+ if (urlpath) { url += '/' + urlpath }
+ return url
}
-var version = '0.5.0';
-var minimumCompatibleVersion = '0.5.0';
-var maintenance = true;
-var cwd = path.join(__dirname, '..');
+var version = '0.5.0'
+var minimumCompatibleVersion = '0.5.0'
+var maintenance = true
+var cwd = path.join(__dirname, '..')
module.exports = {
- version: version,
- minimumCompatibleVersion: minimumCompatibleVersion,
- maintenance: maintenance,
- debug: debug,
- urlpath: urlpath,
- port: port,
- alloworigin: alloworigin,
- usessl: usessl,
- serverurl: getserverurl(),
- usecdn: usecdn,
- allowanonymous: allowanonymous,
- allowfreeurl: allowfreeurl,
- defaultpermission: defaultpermission,
- dburl: dburl,
- db: db,
- sslkeypath: path.join(cwd, sslkeypath),
- sslcertpath: path.join(cwd, sslcertpath),
- sslcapath: path.join(cwd, sslcapath),
- dhparampath: path.join(cwd, dhparampath),
- tmppath: path.join(cwd, tmppath),
- defaultnotepath: path.join(cwd, defaultnotepath),
- docspath: path.join(cwd, docspath),
- indexpath: path.join(cwd, indexpath),
- hackmdpath: path.join(cwd, hackmdpath),
- errorpath: path.join(cwd, errorpath),
- prettypath: path.join(cwd, prettypath),
- slidepath: path.join(cwd, slidepath),
- sessionname: sessionname,
- sessionsecret: sessionsecret,
- sessionlife: sessionlife,
- staticcachetime: staticcachetime,
- heartbeatinterval: heartbeatinterval,
- heartbeattimeout: heartbeattimeout,
- documentmaxlength: documentmaxlength,
- facebook: facebook,
- twitter: twitter,
- github: github,
- gitlab: gitlab,
- dropbox: dropbox,
- google: google,
- ldap: ldap,
- imgur: imgur,
- email: email,
- allowemailregister: allowemailregister,
- imageUploadType: imageUploadType,
- s3: s3,
- s3bucket: s3bucket
-};
+ version: version,
+ minimumCompatibleVersion: minimumCompatibleVersion,
+ maintenance: maintenance,
+ debug: debug,
+ urlpath: urlpath,
+ port: port,
+ alloworigin: alloworigin,
+ usessl: usessl,
+ serverurl: getserverurl(),
+ usecdn: usecdn,
+ allowanonymous: allowanonymous,
+ allowfreeurl: allowfreeurl,
+ defaultpermission: defaultpermission,
+ dburl: dburl,
+ db: db,
+ sslkeypath: path.join(cwd, sslkeypath),
+ sslcertpath: path.join(cwd, sslcertpath),
+ sslcapath: path.join(cwd, sslcapath),
+ dhparampath: path.join(cwd, dhparampath),
+ tmppath: path.join(cwd, tmppath),
+ defaultnotepath: path.join(cwd, defaultnotepath),
+ docspath: path.join(cwd, docspath),
+ indexpath: path.join(cwd, indexpath),
+ hackmdpath: path.join(cwd, hackmdpath),
+ errorpath: path.join(cwd, errorpath),
+ prettypath: path.join(cwd, prettypath),
+ slidepath: path.join(cwd, slidepath),
+ sessionname: sessionname,
+ sessionsecret: sessionsecret,
+ sessionlife: sessionlife,
+ staticcachetime: staticcachetime,
+ heartbeatinterval: heartbeatinterval,
+ heartbeattimeout: heartbeattimeout,
+ documentmaxlength: documentmaxlength,
+ facebook: facebook,
+ twitter: twitter,
+ github: github,
+ gitlab: gitlab,
+ dropbox: dropbox,
+ google: google,
+ ldap: ldap,
+ imgur: imgur,
+ email: email,
+ allowemailregister: allowemailregister,
+ imageUploadType: imageUploadType,
+ s3: s3,
+ s3bucket: s3bucket
+}
diff --git a/lib/history.js b/lib/history.js
index e7fb3087..69337dc5 100644
--- a/lib/history.js
+++ b/lib/history.js
@@ -1,172 +1,175 @@
-//history
-//external modules
-var async = require('async');
+// history
+// external modules
-//core
-var config = require("./config.js");
-var logger = require("./logger.js");
-var response = require("./response.js");
-var models = require("./models");
+// core
+var config = require('./config.js')
+var logger = require('./logger.js')
+var response = require('./response.js')
+var models = require('./models')
-//public
+// public
var History = {
- historyGet: historyGet,
- historyPost: historyPost,
- historyDelete: historyDelete,
- updateHistory: updateHistory
-};
-
-function getHistory(userid, callback) {
- models.User.findOne({
- where: {
- id: userid
- }
- }).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);
- });
+ historyGet: historyGet,
+ historyPost: historyPost,
+ historyDelete: historyDelete,
+ updateHistory: updateHistory
}
-function setHistory(userid, history, callback) {
- models.User.update({
- history: JSON.stringify(parseHistoryToArray(history))
- }, {
- where: {
- id: userid
- }
- }).then(function (count) {
- return callback(null, count);
- }).catch(function (err) {
- logger.error('set history failed: ' + err);
- return callback(err, null);
- });
+function getHistory (userid, callback) {
+ models.User.findOne({
+ where: {
+ id: userid
+ }
+ }).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)
+ })
}
-function updateHistory(userid, noteId, document, time) {
- if (userid && noteId && typeof document !== 'undefined') {
- getHistory(userid, function (err, history) {
- if (err || !history) return;
- if (!history[noteId]) {
- history[noteId] = {};
- }
- var noteHistory = history[noteId];
- var noteInfo = models.Note.parseNoteInfo(document);
- noteHistory.id = noteId;
- noteHistory.text = noteInfo.title;
- noteHistory.time = time || Date.now();
- noteHistory.tags = noteInfo.tags;
- setHistory(userid, history, function (err, count) {
- return;
- });
- });
+function setHistory (userid, history, callback) {
+ models.User.update({
+ history: JSON.stringify(parseHistoryToArray(history))
+ }, {
+ where: {
+ id: userid
}
+ }).then(function (count) {
+ return callback(null, count)
+ }).catch(function (err) {
+ logger.error('set history failed: ' + err)
+ return callback(err, null)
+ })
}
-function parseHistoryToArray(history) {
- var _history = [];
- Object.keys(history).forEach(function (key) {
- var item = history[key];
- _history.push(item);
- });
- return _history;
+function updateHistory (userid, noteId, document, time) {
+ if (userid && noteId && typeof document !== 'undefined') {
+ getHistory(userid, function (err, history) {
+ if (err || !history) return
+ if (!history[noteId]) {
+ history[noteId] = {}
+ }
+ var noteHistory = history[noteId]
+ var noteInfo = models.Note.parseNoteInfo(document)
+ noteHistory.id = noteId
+ noteHistory.text = noteInfo.title
+ noteHistory.time = time || Date.now()
+ noteHistory.tags = noteInfo.tags
+ setHistory(userid, history, function (err, count) {
+ if (err) {
+ logger.log(err)
+ }
+ })
+ })
+ }
}
-function parseHistoryToObject(history) {
- var _history = {};
- for (var i = 0, l = history.length; i < l; i++) {
- var item = history[i];
- _history[item.id] = item;
- }
- return _history;
+function parseHistoryToArray (history) {
+ var _history = []
+ Object.keys(history).forEach(function (key) {
+ var item = history[key]
+ _history.push(item)
+ })
+ return _history
}
-function historyGet(req, res) {
- if (req.isAuthenticated()) {
- getHistory(req.user.id, function (err, history) {
- if (err) return response.errorInternalError(res);
- if (!history) return response.errorNotFound(res);
- res.send({
- history: parseHistoryToArray(history)
- });
- });
- } else {
- return response.errorForbidden(res);
- }
+function parseHistoryToObject (history) {
+ var _history = {}
+ for (var i = 0, l = history.length; i < l; i++) {
+ var item = history[i]
+ _history[item.id] = item
+ }
+ return _history
+}
+
+function historyGet (req, res) {
+ if (req.isAuthenticated()) {
+ getHistory(req.user.id, function (err, history) {
+ if (err) return response.errorInternalError(res)
+ if (!history) return response.errorNotFound(res)
+ res.send({
+ history: parseHistoryToArray(history)
+ })
+ })
+ } else {
+ return response.errorForbidden(res)
+ }
}
-function historyPost(req, res) {
- if (req.isAuthenticated()) {
- var noteId = req.params.noteId;
- if (!noteId) {
- if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res);
- if (config.debug)
- logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history);
- try {
- var history = JSON.parse(req.body.history);
- } catch (err) {
- return response.errorBadRequest(res);
- }
- if (Array.isArray(history)) {
- setHistory(req.user.id, history, function (err, count) {
- if (err) return response.errorInternalError(res);
- res.end();
- });
- } else {
- return response.errorBadRequest(res);
- }
+function historyPost (req, res) {
+ if (req.isAuthenticated()) {
+ var noteId = req.params.noteId
+ if (!noteId) {
+ if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res)
+ if (config.debug) { logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history) }
+ try {
+ var history = JSON.parse(req.body.history)
+ } catch (err) {
+ return response.errorBadRequest(res)
+ }
+ if (Array.isArray(history)) {
+ setHistory(req.user.id, history, function (err, count) {
+ if (err) return response.errorInternalError(res)
+ res.end()
+ })
+ } else {
+ return response.errorBadRequest(res)
+ }
+ } else {
+ if (typeof req.body['pinned'] === 'undefined') return response.errorBadRequest(res)
+ getHistory(req.user.id, function (err, history) {
+ if (err) return response.errorInternalError(res)
+ if (!history) return response.errorNotFound(res)
+ if (!history[noteId]) return response.errorNotFound(res)
+ if (req.body.pinned === 'true' || req.body.pinned === 'false') {
+ history[noteId].pinned = (req.body.pinned === 'true')
+ setHistory(req.user.id, history, function (err, count) {
+ if (err) return response.errorInternalError(res)
+ res.end()
+ })
} else {
- if (typeof req.body['pinned'] === 'undefined') return response.errorBadRequest(res);
- getHistory(req.user.id, function (err, history) {
- if (err) return response.errorInternalError(res);
- if (!history) return response.errorNotFound(res);
- if (!history[noteId]) return response.errorNotFound(res);
- if (req.body.pinned === 'true' || req.body.pinned === 'false') {
- 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);
- }
- });
+ return response.errorBadRequest(res)
}
- } else {
- return response.errorForbidden(res);
+ })
}
+ } else {
+ return response.errorForbidden(res)
+ }
}
-function historyDelete(req, res) {
- if (req.isAuthenticated()) {
- var noteId = req.params.noteId;
- if (!noteId) {
- 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 history[noteId];
- setHistory(req.user.id, history, function (err, count) {
- if (err) return response.errorInternalError(res);
- res.end();
- });
- });
- }
+function historyDelete (req, res) {
+ if (req.isAuthenticated()) {
+ var noteId = req.params.noteId
+ if (!noteId) {
+ setHistory(req.user.id, [], function (err, count) {
+ if (err) return response.errorInternalError(res)
+ res.end()
+ })
} else {
- return response.errorForbidden(res);
+ getHistory(req.user.id, function (err, history) {
+ if (err) return response.errorInternalError(res)
+ if (!history) return response.errorNotFound(res)
+ delete history[noteId]
+ setHistory(req.user.id, history, function (err, count) {
+ if (err) return response.errorInternalError(res)
+ res.end()
+ })
+ })
}
+ } else {
+ return response.errorForbidden(res)
+ }
}
-module.exports = History; \ No newline at end of file
+module.exports = History
diff --git a/lib/letter-avatars.js b/lib/letter-avatars.js
index 3afa03fe..92bd36ee 100644
--- a/lib/letter-avatars.js
+++ b/lib/letter-avatars.js
@@ -1,25 +1,23 @@
-"use strict";
-
// external modules
-var randomcolor = require('randomcolor');
+var randomcolor = require('randomcolor')
// core
-module.exports = function(name) {
- var color = randomcolor({
- seed: name,
- luminosity: 'dark'
- });
- var letter = name.substring(0, 1).toUpperCase();
+module.exports = function (name) {
+ var color = randomcolor({
+ seed: name,
+ luminosity: 'dark'
+ })
+ var letter = name.substring(0, 1).toUpperCase()
- var svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';
- svg += '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="96" width="96" version="1.1" viewBox="0 0 96 96">';
- svg += '<g>';
- svg += '<rect width="96" height="96" fill="' + color + '" />';
- svg += '<text font-size="64px" font-family="sans-serif" text-anchor="middle" fill="#ffffff">';
- svg += '<tspan x="48" y="72" stroke-width=".26458px" fill="#ffffff">' + letter + '</tspan>';
- svg += '</text>';
- svg += '</g>';
- svg += '</svg>';
+ var svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
+ svg += '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="96" width="96" version="1.1" viewBox="0 0 96 96">'
+ svg += '<g>'
+ svg += '<rect width="96" height="96" fill="' + color + '" />'
+ svg += '<text font-size="64px" font-family="sans-serif" text-anchor="middle" fill="#ffffff">'
+ svg += '<tspan x="48" y="72" stroke-width=".26458px" fill="#ffffff">' + letter + '</tspan>'
+ svg += '</text>'
+ svg += '</g>'
+ svg += '</svg>'
- return 'data:image/svg+xml;base64,' + new Buffer(svg).toString('base64');
-};
+ return 'data:image/svg+xml;base64,' + new Buffer(svg).toString('base64')
+}
diff --git a/lib/logger.js b/lib/logger.js
index 61299c10..23e302da 100644
--- a/lib/logger.js
+++ b/lib/logger.js
@@ -1,22 +1,22 @@
-var winston = require('winston');
-winston.emitErrs = true;
+var winston = require('winston')
+winston.emitErrs = true
var logger = new winston.Logger({
- transports: [
- new winston.transports.Console({
- level: 'debug',
- handleExceptions: true,
- json: false,
- colorize: true,
- timestamp: true
- })
- ],
- exitOnError: false
-});
+ transports: [
+ new winston.transports.Console({
+ level: 'debug',
+ handleExceptions: true,
+ json: false,
+ colorize: true,
+ timestamp: true
+ })
+ ],
+ exitOnError: false
+})
-module.exports = logger;
+module.exports = logger
module.exports.stream = {
- write: function(message, encoding){
- logger.info(message);
- }
-}; \ No newline at end of file
+ write: function (message, encoding) {
+ logger.info(message)
+ }
+}
diff --git a/lib/migrations/20160515114000-user-add-tokens.js b/lib/migrations/20160515114000-user-add-tokens.js
index 3af490a9..20c0e03c 100644
--- a/lib/migrations/20160515114000-user-add-tokens.js
+++ b/lib/migrations/20160515114000-user-add-tokens.js
@@ -1,15 +1,11 @@
-"use strict";
-
module.exports = {
- up: function (queryInterface, Sequelize) {
- queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING);
- queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING);
- return;
- },
+ up: function (queryInterface, Sequelize) {
+ queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING)
+ queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING)
+ },
- down: function (queryInterface, Sequelize) {
- queryInterface.removeColumn('Users', 'accessToken');
- queryInterface.removeColumn('Users', 'refreshToken');
- return;
- }
-}; \ No newline at end of file
+ down: function (queryInterface, Sequelize) {
+ queryInterface.removeColumn('Users', 'accessToken')
+ queryInterface.removeColumn('Users', 'refreshToken')
+ }
+}
diff --git a/lib/migrations/20160607060246-support-revision.js b/lib/migrations/20160607060246-support-revision.js
index fa647d93..618bb4d7 100644
--- a/lib/migrations/20160607060246-support-revision.js
+++ b/lib/migrations/20160607060246-support-revision.js
@@ -1,8 +1,6 @@
-'use strict';
-
module.exports = {
up: function (queryInterface, Sequelize) {
- queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE);
+ queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE)
queryInterface.createTable('Revisions', {
id: {
type: Sequelize.UUID,
@@ -15,13 +13,11 @@ module.exports = {
length: Sequelize.INTEGER,
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE
- });
- return;
+ })
},
down: function (queryInterface, Sequelize) {
- queryInterface.dropTable('Revisions');
- queryInterface.removeColumn('Notes', 'savedAt');
- return;
+ queryInterface.dropTable('Revisions')
+ queryInterface.removeColumn('Notes', 'savedAt')
}
-};
+}
diff --git a/lib/migrations/20160703062241-support-authorship.js b/lib/migrations/20160703062241-support-authorship.js
index 239327ec..98381d4e 100644
--- a/lib/migrations/20160703062241-support-authorship.js
+++ b/lib/migrations/20160703062241-support-authorship.js
@@ -1,9 +1,7 @@
-'use strict';
-
module.exports = {
up: function (queryInterface, Sequelize) {
- queryInterface.addColumn('Notes', 'authorship', Sequelize.TEXT);
- queryInterface.addColumn('Revisions', 'authorship', Sequelize.TEXT);
+ queryInterface.addColumn('Notes', 'authorship', Sequelize.TEXT)
+ queryInterface.addColumn('Revisions', 'authorship', Sequelize.TEXT)
queryInterface.createTable('Authors', {
id: {
type: Sequelize.INTEGER,
@@ -15,14 +13,12 @@ module.exports = {
userId: Sequelize.UUID,
createdAt: Sequelize.DATE,
updatedAt: Sequelize.DATE
- });
- return;
+ })
},
down: function (queryInterface, Sequelize) {
- queryInterface.dropTable('Authors');
- queryInterface.removeColumn('Revisions', 'authorship');
- queryInterface.removeColumn('Notes', 'authorship');
- return;
+ queryInterface.dropTable('Authors')
+ queryInterface.removeColumn('Revisions', 'authorship')
+ queryInterface.removeColumn('Notes', 'authorship')
}
-};
+}
diff --git a/lib/migrations/20161009040430-support-delete-note.js b/lib/migrations/20161009040430-support-delete-note.js
index 92ff6f7b..984920b8 100644
--- a/lib/migrations/20161009040430-support-delete-note.js
+++ b/lib/migrations/20161009040430-support-delete-note.js
@@ -1,11 +1,9 @@
-'use strict';
-
module.exports = {
up: function (queryInterface, Sequelize) {
- queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE);
+ queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE)
},
down: function (queryInterface, Sequelize) {
- queryInterface.removeColumn('Notes', 'deletedAt');
+ queryInterface.removeColumn('Notes', 'deletedAt')
}
-};
+}
diff --git a/lib/migrations/20161201050312-support-email-signin.js b/lib/migrations/20161201050312-support-email-signin.js
index b5aaf777..a97d3be5 100644
--- a/lib/migrations/20161201050312-support-email-signin.js
+++ b/lib/migrations/20161201050312-support-email-signin.js
@@ -1,13 +1,11 @@
-'use strict';
-
module.exports = {
up: function (queryInterface, Sequelize) {
- queryInterface.addColumn('Users', 'email', Sequelize.TEXT);
- queryInterface.addColumn('Users', 'password', Sequelize.TEXT);
+ queryInterface.addColumn('Users', 'email', Sequelize.TEXT)
+ queryInterface.addColumn('Users', 'password', Sequelize.TEXT)
},
down: function (queryInterface, Sequelize) {
- queryInterface.removeColumn('Users', 'email');
- queryInterface.removeColumn('Users', 'password');
+ queryInterface.removeColumn('Users', 'email')
+ queryInterface.removeColumn('Users', 'password')
}
-};
+}
diff --git a/lib/models/author.js b/lib/models/author.js
index 0b0f149d..5e39c347 100644
--- a/lib/models/author.js
+++ b/lib/models/author.js
@@ -1,43 +1,37 @@
-"use strict";
-
// external modules
-var Sequelize = require("sequelize");
-
-// core
-var logger = require("../logger.js");
+var Sequelize = require('sequelize')
module.exports = function (sequelize, DataTypes) {
- var Author = sequelize.define("Author", {
- id: {
- type: Sequelize.INTEGER,
- primaryKey: true,
- autoIncrement: true
- },
- color: {
- type: DataTypes.STRING
- }
- }, {
- indexes: [
- {
- unique: true,
- fields: ['noteId', 'userId']
- }
- ],
- classMethods: {
- associate: function (models) {
- Author.belongsTo(models.Note, {
- foreignKey: "noteId",
- as: "note",
- constraints: false
- });
- Author.belongsTo(models.User, {
- foreignKey: "userId",
- as: "user",
- constraints: false
- });
- }
- }
- });
-
- return Author;
-}; \ No newline at end of file
+ var Author = sequelize.define('Author', {
+ id: {
+ type: Sequelize.INTEGER,
+ primaryKey: true,
+ autoIncrement: true
+ },
+ color: {
+ type: DataTypes.STRING
+ }
+ }, {
+ indexes: [
+ {
+ unique: true,
+ fields: ['noteId', 'userId']
+ }
+ ],
+ classMethods: {
+ associate: function (models) {
+ Author.belongsTo(models.Note, {
+ foreignKey: 'noteId',
+ as: 'note',
+ constraints: false
+ })
+ Author.belongsTo(models.User, {
+ foreignKey: 'userId',
+ as: 'user',
+ constraints: false
+ })
+ }
+ }
+ })
+ return Author
+}
diff --git a/lib/models/index.js b/lib/models/index.js
index e83956e5..96babc2a 100644
--- a/lib/models/index.js
+++ b/lib/models/index.js
@@ -1,57 +1,55 @@
-"use strict";
-
// external modules
-var fs = require("fs");
-var path = require("path");
-var Sequelize = require("sequelize");
+var fs = require('fs')
+var path = require('path')
+var Sequelize = require('sequelize')
// core
-var config = require('../config.js');
-var logger = require("../logger.js");
+var config = require('../config.js')
+var logger = require('../logger.js')
-var dbconfig = config.db;
-dbconfig.logging = config.debug ? logger.info : false;
+var dbconfig = config.db
+dbconfig.logging = config.debug ? logger.info : false
-var sequelize = null;
+var sequelize = null
// Heroku specific
-if (config.dburl)
- sequelize = new Sequelize(config.dburl, dbconfig);
-else
- sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig);
+if (config.dburl) {
+ sequelize = new Sequelize(config.dburl, dbconfig)
+} else {
+ sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig)
+}
// [Postgres] Handling NULL bytes
// https://github.com/sequelize/sequelize/issues/6485
-function stripNullByte(value) {
- return value ? value.replace(/\u0000/g, "") : value;
+function stripNullByte (value) {
+ return value ? value.replace(/\u0000/g, '') : value
}
-sequelize.stripNullByte = stripNullByte;
+sequelize.stripNullByte = stripNullByte
-function processData(data, _default, process) {
- if (data === undefined) return data;
- else return data === null ? _default : (process ? process(data) : data);
+function processData (data, _default, process) {
+ if (data === undefined) return data
+ else return data === null ? _default : (process ? process(data) : data)
}
-sequelize.processData = processData;
+sequelize.processData = processData
-var db = {};
+var db = {}
-fs
- .readdirSync(__dirname)
+fs.readdirSync(__dirname)
.filter(function (file) {
- return (file.indexOf(".") !== 0) && (file !== "index.js");
+ return (file.indexOf('.') !== 0) && (file !== 'index.js')
})
.forEach(function (file) {
- var model = sequelize.import(path.join(__dirname, file));
- db[model.name] = model;
- });
+ var model = sequelize.import(path.join(__dirname, file))
+ db[model.name] = model
+ })
Object.keys(db).forEach(function (modelName) {
- if ("associate" in db[modelName]) {
- db[modelName].associate(db);
- }
-});
+ if ('associate' in db[modelName]) {
+ db[modelName].associate(db)
+ }
+})
-db.sequelize = sequelize;
-db.Sequelize = Sequelize;
+db.sequelize = sequelize
+db.Sequelize = Sequelize
-module.exports = db;
+module.exports = db
diff --git a/lib/models/note.js b/lib/models/note.js
index 8b38d3f9..bef9ee21 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -1,535 +1,524 @@
-"use strict";
-
// external modules
-var fs = require('fs');
-var path = require('path');
-var LZString = require('lz-string');
-var md = require('markdown-it')();
-var metaMarked = require('meta-marked');
-var cheerio = require('cheerio');
-var shortId = require('shortid');
-var Sequelize = require("sequelize");
-var async = require('async');
-var moment = require('moment');
-var DiffMatchPatch = require('diff-match-patch');
-var dmp = new DiffMatchPatch();
-var S = require('string');
+var fs = require('fs')
+var path = require('path')
+var LZString = require('lz-string')
+var md = require('markdown-it')()
+var metaMarked = require('meta-marked')
+var cheerio = require('cheerio')
+var shortId = require('shortid')
+var Sequelize = require('sequelize')
+var async = require('async')
+var moment = require('moment')
+var DiffMatchPatch = require('diff-match-patch')
+var dmp = new DiffMatchPatch()
+var S = require('string')
// core
-var config = require("../config.js");
-var logger = require("../logger.js");
+var config = require('../config.js')
+var logger = require('../logger.js')
-//ot
-var ot = require("../ot/index.js");
+// ot
+var ot = require('../ot/index.js')
// permission types
-var permissionTypes = ["freely", "editable", "limited", "locked", "protected", "private"];
+var permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', 'private']
module.exports = function (sequelize, DataTypes) {
- var Note = sequelize.define("Note", {
- id: {
- type: DataTypes.UUID,
- primaryKey: true,
- defaultValue: Sequelize.UUIDV4
- },
- shortid: {
- type: DataTypes.STRING,
- unique: true,
- allowNull: false,
- defaultValue: shortId.generate
- },
- alias: {
- type: DataTypes.STRING,
- unique: true
- },
- permission: {
- type: DataTypes.ENUM,
- values: permissionTypes
- },
- viewcount: {
- type: DataTypes.INTEGER,
- allowNull: false,
- defaultValue: 0
- },
- title: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('title'), "");
- },
- set: function (value) {
- this.setDataValue('title', sequelize.stripNullByte(value));
- }
- },
- content: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('content'), "");
- },
- set: function (value) {
- this.setDataValue('content', sequelize.stripNullByte(value));
- }
- },
- authorship: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
- },
- set: function (value) {
- this.setDataValue('authorship', JSON.stringify(value));
- }
- },
- lastchangeAt: {
- type: DataTypes.DATE
- },
- savedAt: {
- type: DataTypes.DATE
+ var Note = sequelize.define('Note', {
+ id: {
+ type: DataTypes.UUID,
+ primaryKey: true,
+ defaultValue: Sequelize.UUIDV4
+ },
+ shortid: {
+ type: DataTypes.STRING,
+ unique: true,
+ allowNull: false,
+ defaultValue: shortId.generate
+ },
+ alias: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ permission: {
+ type: DataTypes.ENUM,
+ values: permissionTypes
+ },
+ viewcount: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 0
+ },
+ title: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('title'), '')
+ },
+ set: function (value) {
+ this.setDataValue('title', sequelize.stripNullByte(value))
+ }
+ },
+ content: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('content'), '')
+ },
+ set: function (value) {
+ this.setDataValue('content', sequelize.stripNullByte(value))
+ }
+ },
+ authorship: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse)
+ },
+ set: function (value) {
+ this.setDataValue('authorship', JSON.stringify(value))
+ }
+ },
+ lastchangeAt: {
+ type: DataTypes.DATE
+ },
+ savedAt: {
+ type: DataTypes.DATE
+ }
+ }, {
+ paranoid: true,
+ classMethods: {
+ associate: function (models) {
+ Note.belongsTo(models.User, {
+ foreignKey: 'ownerId',
+ as: 'owner',
+ constraints: false
+ })
+ Note.belongsTo(models.User, {
+ foreignKey: 'lastchangeuserId',
+ as: 'lastchangeuser',
+ constraints: false
+ })
+ Note.hasMany(models.Revision, {
+ foreignKey: 'noteId',
+ constraints: false
+ })
+ Note.hasMany(models.Author, {
+ foreignKey: 'noteId',
+ as: 'authors',
+ constraints: false
+ })
+ },
+ checkFileExist: function (filePath) {
+ try {
+ return fs.statSync(filePath).isFile()
+ } catch (err) {
+ return false
}
- }, {
- paranoid: true,
- classMethods: {
- associate: function (models) {
- Note.belongsTo(models.User, {
- foreignKey: "ownerId",
- as: "owner",
- constraints: false
- });
- Note.belongsTo(models.User, {
- foreignKey: "lastchangeuserId",
- as: "lastchangeuser",
- constraints: false
- });
- Note.hasMany(models.Revision, {
- foreignKey: "noteId",
- constraints: false
- });
- Note.hasMany(models.Author, {
- foreignKey: "noteId",
- as: "authors",
- constraints: false
- });
- },
- checkFileExist: function (filePath) {
- try {
- return fs.statSync(filePath).isFile();
- } catch (err) {
- return false;
- }
- },
- checkNoteIdValid: function (id) {
- var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
- var result = id.match(uuidRegex);
- if (result && result.length == 1)
- return true;
- else
- return false;
- },
- parseNoteId: function (noteId, callback) {
- async.series({
- parseNoteIdByAlias: function (_callback) {
- // try to parse note id by alias (e.g. doc)
- Note.findOne({
- where: {
- alias: noteId
- }
+ },
+ checkNoteIdValid: function (id) {
+ var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
+ var result = id.match(uuidRegex)
+ if (result && result.length === 1) { return true } else { return false }
+ },
+ parseNoteId: function (noteId, callback) {
+ async.series({
+ parseNoteIdByAlias: function (_callback) {
+ // try to parse note id by alias (e.g. doc)
+ Note.findOne({
+ where: {
+ alias: noteId
+ }
+ }).then(function (note) {
+ if (note) {
+ let filePath = path.join(config.docspath, noteId + '.md')
+ if (Note.checkFileExist(filePath)) {
+ // if doc in filesystem have newer modified time than last change time
+ // then will update the doc in db
+ var fsModifiedTime = moment(fs.statSync(filePath).mtime)
+ var dbModifiedTime = moment(note.lastchangeAt || note.createdAt)
+ var body = fs.readFileSync(filePath, 'utf8')
+ var contentLength = body.length
+ var title = Note.parseNoteTitle(body)
+ if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
+ note.update({
+ title: title,
+ content: body,
+ lastchangeAt: fsModifiedTime
+ }).then(function (note) {
+ sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
+ if (err) return _callback(err, null)
+ // update authorship on after making revision of docs
+ var patch = dmp.patch_fromText(revision.patch)
+ var operations = Note.transformPatchToOperations(patch, contentLength)
+ var authorship = note.authorship
+ for (let i = 0; i < operations.length; i++) {
+ authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship)
+ }
+ note.update({
+ authorship: JSON.stringify(authorship)
}).then(function (note) {
- if (note) {
- var filePath = path.join(config.docspath, noteId + '.md');
- if (Note.checkFileExist(filePath)) {
- // if doc in filesystem have newer modified time than last change time
- // then will update the doc in db
- var fsModifiedTime = moment(fs.statSync(filePath).mtime);
- var dbModifiedTime = moment(note.lastchangeAt || note.createdAt);
- var body = fs.readFileSync(filePath, 'utf8');
- var contentLength = body.length;
- var title = Note.parseNoteTitle(body);
- if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
- note.update({
- title: title,
- content: body,
- lastchangeAt: fsModifiedTime
- }).then(function (note) {
- sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
- if (err) return _callback(err, null);
- // update authorship on after making revision of docs
- var patch = dmp.patch_fromText(revision.patch);
- var operations = Note.transformPatchToOperations(patch, contentLength);
- var authorship = note.authorship;
- for (var i = 0; i < operations.length; i++) {
- authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
- }
- note.update({
- authorship: JSON.stringify(authorship)
- }).then(function (note) {
- return callback(null, note.id);
- }).catch(function (err) {
- return _callback(err, null);
- });
- });
- }).catch(function (err) {
- return _callback(err, null);
- });
- } else {
- return callback(null, note.id);
- }
- } else {
- return callback(null, note.id);
- }
- } else {
- var filePath = path.join(config.docspath, noteId + '.md');
- if (Note.checkFileExist(filePath)) {
- Note.create({
- alias: noteId,
- owner: null,
- permission: 'locked'
- }).then(function (note) {
- return callback(null, note.id);
- }).catch(function (err) {
- return _callback(err, null);
- });
- } else {
- return _callback(null, null);
- }
- }
+ return callback(null, note.id)
}).catch(function (err) {
- return _callback(err, null);
- });
- },
- parseNoteIdByLZString: function (_callback) {
- // try to parse note id by LZString Base64
- try {
- var id = LZString.decompressFromBase64(noteId);
- if (id && Note.checkNoteIdValid(id))
- return callback(null, id);
- else
- return _callback(null, null);
- } catch (err) {
- return _callback(err, null);
- }
- },
- parseNoteIdByShortId: function (_callback) {
- // try to parse note id by shortId
- try {
- if (shortId.isValid(noteId)) {
- Note.findOne({
- where: {
- shortid: noteId
- }
- }).then(function (note) {
- if (!note) return _callback(null, null);
- return callback(null, note.id);
- }).catch(function (err) {
- return _callback(err, null);
- });
- } else {
- return _callback(null, null);
- }
- } catch (err) {
- return _callback(err, null);
- }
- }
- }, function (err, result) {
- if (err) {
- logger.error(err);
- return callback(err, null);
- }
- return callback(null, null);
- });
- },
- parseNoteInfo: function (body) {
- var parsed = Note.extractMeta(body);
- var $ = cheerio.load(md.render(parsed.markdown));
- return {
- title: Note.extractNoteTitle(parsed.meta, $),
- tags: Note.extractNoteTags(parsed.meta, $)
- };
- },
- parseNoteTitle: function (body) {
- var parsed = Note.extractMeta(body);
- var $ = cheerio.load(md.render(parsed.markdown));
- return Note.extractNoteTitle(parsed.meta, $);
- },
- extractNoteTitle: function (meta, $) {
- var title = "";
- if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) {
- title = meta.title;
+ return _callback(err, null)
+ })
+ })
+ }).catch(function (err) {
+ return _callback(err, null)
+ })
+ } else {
+ return callback(null, note.id)
+ }
} else {
- var h1s = $("h1");
- if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
- title = S(h1s.first().text()).stripTags().s;
+ return callback(null, note.id)
}
- if (!title) title = "Untitled";
- return title;
- },
- generateDescription: function (markdown) {
- return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
- },
- decodeTitle: function (title) {
- return title ? title : 'Untitled';
- },
- generateWebTitle: function (title) {
- title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
- return title;
- },
- extractNoteTags: function (meta, $) {
- var tags = [];
- var rawtags = [];
- if (meta.tags && (typeof meta.tags == "string" || typeof meta.tags == "number")) {
- var metaTags = ('' + meta.tags).split(',');
- for (var i = 0; i < metaTags.length; i++) {
- var text = metaTags[i].trim();
- if (text) rawtags.push(text);
- }
+ } else {
+ var filePath = path.join(config.docspath, noteId + '.md')
+ if (Note.checkFileExist(filePath)) {
+ Note.create({
+ alias: noteId,
+ owner: null,
+ permission: 'locked'
+ }).then(function (note) {
+ return callback(null, note.id)
+ }).catch(function (err) {
+ return _callback(err, null)
+ })
} else {
- var h6s = $("h6");
- h6s.each(function (key, value) {
- if (/^tags/gmi.test($(value).text())) {
- var codes = $(value).find("code");
- for (var i = 0; i < codes.length; i++) {
- var text = S($(codes[i]).text().trim()).stripTags().s;
- if (text) rawtags.push(text);
- }
- }
- });
- }
- for (var i = 0; i < rawtags.length; i++) {
- var found = false;
- for (var j = 0; j < tags.length; j++) {
- if (tags[j] == rawtags[i]) {
- found = true;
- break;
- }
- }
- if (!found)
- tags.push(rawtags[i]);
- }
- return tags;
- },
- extractMeta: function (content) {
- try {
- var obj = metaMarked(content);
- if (!obj.markdown) obj.markdown = "";
- if (!obj.meta) obj.meta = {};
- } catch (err) {
- var obj = {
- markdown: content,
- meta: {}
- };
- }
- return obj;
- },
- parseMeta: function (meta) {
- var _meta = {};
- if (meta) {
- if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number"))
- _meta.title = meta.title;
- if (meta.description && (typeof meta.description == "string" || typeof meta.description == "number"))
- _meta.description = meta.description;
- if (meta.robots && (typeof meta.robots == "string" || typeof meta.robots == "number"))
- _meta.robots = meta.robots;
- if (meta.GA && (typeof meta.GA == "string" || typeof meta.GA == "number"))
- _meta.GA = meta.GA;
- if (meta.disqus && (typeof meta.disqus == "string" || typeof meta.disqus == "number"))
- _meta.disqus = meta.disqus;
- if (meta.slideOptions && (typeof meta.slideOptions == "object"))
- _meta.slideOptions = meta.slideOptions;
+ return _callback(null, null)
}
- return _meta;
- },
- updateAuthorshipByOperation: function (operation, userId, authorships) {
- var index = 0;
- var timestamp = Date.now();
- for (var i = 0; i < operation.length; i++) {
- var op = operation[i];
- if (ot.TextOperation.isRetain(op)) {
- index += op;
- } else if (ot.TextOperation.isInsert(op)) {
- var opStart = index;
- var opEnd = index + op.length;
- var inserted = false;
- // authorship format: [userId, startPos, endPos, createdAt, updatedAt]
- if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
- else {
- for (var j = 0; j < authorships.length; j++) {
- var authorship = authorships[j];
- if (!inserted) {
- var nextAuthorship = authorships[j + 1] || -1;
- if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
- if (authorship[1] < opStart && authorship[2] > opStart) {
- // divide
- var postLength = authorship[2] - opStart;
- authorship[2] = opStart;
- authorship[4] = timestamp;
- authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
- authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
- j += 2;
- inserted = true;
- } else if (authorship[1] >= opStart) {
- authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
- j += 1;
- inserted = true;
- } else if (authorship[2] <= opStart) {
- authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
- j += 1;
- inserted = true;
- }
- }
- }
- if (authorship[1] >= opStart) {
- authorship[1] += op.length;
- authorship[2] += op.length;
- }
- }
- }
- index += op.length;
- } else if (ot.TextOperation.isDelete(op)) {
- var opStart = index;
- var opEnd = index - op;
- if (operation.length == 1) {
- authorships = [];
- } else if (authorships.length > 0) {
- for (var j = 0; j < authorships.length; j++) {
- var authorship = authorships[j];
- if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
- authorships.splice(j, 1);
- j -= 1;
- } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
- authorship[2] += op;
- authorship[4] = timestamp;
- } else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
- authorship[2] = opStart;
- authorship[4] = timestamp;
- } else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
- authorship[1] = opEnd;
- authorship[4] = timestamp;
- }
- if (authorship[1] >= opEnd) {
- authorship[1] += op;
- authorship[2] += op;
- }
- }
- }
- index += op;
- }
- }
- // merge
- for (var j = 0; j < authorships.length; j++) {
- var authorship = authorships[j];
- for (var k = j + 1; k < authorships.length; k++) {
- var nextAuthorship = authorships[k];
- if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
- var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
- var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
- authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
- authorships.splice(k, 1);
- j -= 1;
- break;
- }
- }
- }
- // clear
- for (var j = 0; j < authorships.length; j++) {
- var authorship = authorships[j];
- if (!authorship[0]) {
- authorships.splice(j, 1);
- j -= 1;
+ }
+ }).catch(function (err) {
+ return _callback(err, null)
+ })
+ },
+ parseNoteIdByLZString: function (_callback) {
+ // try to parse note id by LZString Base64
+ try {
+ var id = LZString.decompressFromBase64(noteId)
+ if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) }
+ } catch (err) {
+ return _callback(err, null)
+ }
+ },
+ parseNoteIdByShortId: function (_callback) {
+ // try to parse note id by shortId
+ try {
+ if (shortId.isValid(noteId)) {
+ Note.findOne({
+ where: {
+ shortid: noteId
+ }
+ }).then(function (note) {
+ if (!note) return _callback(null, null)
+ return callback(null, note.id)
+ }).catch(function (err) {
+ return _callback(err, null)
+ })
+ } else {
+ return _callback(null, null)
+ }
+ } catch (err) {
+ return _callback(err, null)
+ }
+ }
+ }, function (err, result) {
+ if (err) {
+ logger.error(err)
+ return callback(err, null)
+ }
+ return callback(null, null)
+ })
+ },
+ parseNoteInfo: function (body) {
+ var parsed = Note.extractMeta(body)
+ var $ = cheerio.load(md.render(parsed.markdown))
+ return {
+ title: Note.extractNoteTitle(parsed.meta, $),
+ tags: Note.extractNoteTags(parsed.meta, $)
+ }
+ },
+ parseNoteTitle: function (body) {
+ var parsed = Note.extractMeta(body)
+ var $ = cheerio.load(md.render(parsed.markdown))
+ return Note.extractNoteTitle(parsed.meta, $)
+ },
+ extractNoteTitle: function (meta, $) {
+ var title = ''
+ if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) {
+ title = meta.title
+ } else {
+ var h1s = $('h1')
+ if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s }
+ }
+ if (!title) title = 'Untitled'
+ return title
+ },
+ generateDescription: function (markdown) {
+ return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ')
+ },
+ decodeTitle: function (title) {
+ return title || 'Untitled'
+ },
+ generateWebTitle: function (title) {
+ title = !title || title === 'Untitled' ? 'HackMD - Collaborative markdown notes' : title + ' - HackMD'
+ return title
+ },
+ extractNoteTags: function (meta, $) {
+ var tags = []
+ var rawtags = []
+ if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) {
+ var metaTags = ('' + meta.tags).split(',')
+ for (let i = 0; i < metaTags.length; i++) {
+ var text = metaTags[i].trim()
+ if (text) rawtags.push(text)
+ }
+ } else {
+ var h6s = $('h6')
+ h6s.each(function (key, value) {
+ if (/^tags/gmi.test($(value).text())) {
+ var codes = $(value).find('code')
+ for (let i = 0; i < codes.length; i++) {
+ var text = S($(codes[i]).text().trim()).stripTags().s
+ if (text) rawtags.push(text)
+ }
+ }
+ })
+ }
+ for (let i = 0; i < rawtags.length; i++) {
+ var found = false
+ for (let j = 0; j < tags.length; j++) {
+ if (tags[j] === rawtags[i]) {
+ found = true
+ break
+ }
+ }
+ if (!found) { tags.push(rawtags[i]) }
+ }
+ return tags
+ },
+ extractMeta: function (content) {
+ var obj = null
+ try {
+ obj = metaMarked(content)
+ if (!obj.markdown) obj.markdown = ''
+ if (!obj.meta) obj.meta = {}
+ } catch (err) {
+ obj = {
+ markdown: content,
+ meta: {}
+ }
+ }
+ return obj
+ },
+ parseMeta: function (meta) {
+ var _meta = {}
+ if (meta) {
+ if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { _meta.title = meta.title }
+ if (meta.description && (typeof meta.description === 'string' || typeof meta.description === 'number')) { _meta.description = meta.description }
+ if (meta.robots && (typeof meta.robots === 'string' || typeof meta.robots === 'number')) { _meta.robots = meta.robots }
+ if (meta.GA && (typeof meta.GA === 'string' || typeof meta.GA === 'number')) { _meta.GA = meta.GA }
+ if (meta.disqus && (typeof meta.disqus === 'string' || typeof meta.disqus === 'number')) { _meta.disqus = meta.disqus }
+ if (meta.slideOptions && (typeof meta.slideOptions === 'object')) { _meta.slideOptions = meta.slideOptions }
+ }
+ return _meta
+ },
+ updateAuthorshipByOperation: function (operation, userId, authorships) {
+ var index = 0
+ var timestamp = Date.now()
+ for (let i = 0; i < operation.length; i++) {
+ var op = operation[i]
+ if (ot.TextOperation.isRetain(op)) {
+ index += op
+ } else if (ot.TextOperation.isInsert(op)) {
+ let opStart = index
+ let opEnd = index + op.length
+ var inserted = false
+ // authorship format: [userId, startPos, endPos, createdAt, updatedAt]
+ if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp])
+ else {
+ for (let j = 0; j < authorships.length; j++) {
+ let authorship = authorships[j]
+ if (!inserted) {
+ let nextAuthorship = authorships[j + 1] || -1
+ if ((nextAuthorship !== -1 && nextAuthorship[1] >= opEnd) || j >= authorships.length - 1) {
+ if (authorship[1] < opStart && authorship[2] > opStart) {
+ // divide
+ let postLength = authorship[2] - opStart
+ authorship[2] = opStart
+ authorship[4] = timestamp
+ authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp])
+ authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp])
+ j += 2
+ inserted = true
+ } else if (authorship[1] >= opStart) {
+ authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp])
+ j += 1
+ inserted = true
+ } else if (authorship[2] <= opStart) {
+ authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp])
+ j += 1
+ inserted = true
}
+ }
}
- return authorships;
- },
- transformPatchToOperations: function (patch, contentLength) {
- var operations = [];
- if (patch.length > 0) {
- // calculate original content length
- for (var j = patch.length - 1; j >= 0; j--) {
- var p = patch[j];
- for (var i = 0; i < p.diffs.length; i++) {
- var diff = p.diffs[i];
- switch(diff[0]) {
- case 1: // insert
- contentLength -= diff[1].length;
- break;
- case -1: // delete
- contentLength += diff[1].length;
- break;
- }
- }
- }
- // generate operations
- var bias = 0;
- var lengthBias = 0;
- for (var j = 0; j < patch.length; j++) {
- var operation = [];
- var p = patch[j];
- var currIndex = p.start1;
- var currLength = contentLength - bias;
- for (var i = 0; i < p.diffs.length; i++) {
- var diff = p.diffs[i];
- switch(diff[0]) {
- case 0: // retain
- if (i == 0) // first
- operation.push(currIndex + diff[1].length);
- else if (i != p.diffs.length - 1) // mid
- operation.push(diff[1].length);
- else // last
- operation.push(currLength + lengthBias - currIndex);
- currIndex += diff[1].length;
- break;
- case 1: // insert
- operation.push(diff[1]);
- lengthBias += diff[1].length;
- currIndex += diff[1].length;
- break;
- case -1: // delete
- operation.push(-diff[1].length);
- bias += diff[1].length;
- currIndex += diff[1].length;
- break;
- }
- }
- operations.push(operation);
- }
+ if (authorship[1] >= opStart) {
+ authorship[1] += op.length
+ authorship[2] += op.length
}
- return operations;
+ }
}
- },
- hooks: {
- beforeCreate: function (note, options, callback) {
- // if no content specified then use default note
- if (!note.content) {
- var body = null;
- var filePath = null;
- if (!note.alias) {
- filePath = config.defaultnotepath;
- } else {
- filePath = path.join(config.docspath, note.alias + '.md');
- }
- if (Note.checkFileExist(filePath)) {
- var fsCreatedTime = moment(fs.statSync(filePath).ctime);
- body = fs.readFileSync(filePath, 'utf8');
- note.title = Note.parseNoteTitle(body);
- note.content = body;
- if (filePath !== config.defaultnotepath) {
- note.createdAt = fsCreatedTime;
- }
- }
+ index += op.length
+ } else if (ot.TextOperation.isDelete(op)) {
+ let opStart = index
+ let opEnd = index - op
+ if (operation.length === 1) {
+ authorships = []
+ } else if (authorships.length > 0) {
+ for (let j = 0; j < authorships.length; j++) {
+ let authorship = authorships[j]
+ if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
+ authorships.splice(j, 1)
+ j -= 1
+ } else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
+ authorship[2] += op
+ authorship[4] = timestamp
+ } else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
+ authorship[2] = opStart
+ authorship[4] = timestamp
+ } else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
+ authorship[1] = opEnd
+ authorship[4] = timestamp
}
- // 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 = config.defaultpermission;
- } else {
- note.permission = "freely";
- }
+ if (authorship[1] >= opEnd) {
+ authorship[1] += op
+ authorship[2] += op
}
- return callback(null, note);
- },
- afterCreate: function (note, options, callback) {
- sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
- callback(err, note);
- });
+ }
+ }
+ index += op
+ }
+ }
+ // merge
+ for (let j = 0; j < authorships.length; j++) {
+ let authorship = authorships[j]
+ for (let k = j + 1; k < authorships.length; k++) {
+ let nextAuthorship = authorships[k]
+ if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
+ let minTimestamp = Math.min(authorship[3], nextAuthorship[3])
+ let maxTimestamp = Math.max(authorship[3], nextAuthorship[3])
+ authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp])
+ authorships.splice(k, 1)
+ j -= 1
+ break
+ }
+ }
+ }
+ // clear
+ for (let j = 0; j < authorships.length; j++) {
+ let authorship = authorships[j]
+ if (!authorship[0]) {
+ authorships.splice(j, 1)
+ j -= 1
+ }
+ }
+ return authorships
+ },
+ transformPatchToOperations: function (patch, contentLength) {
+ var operations = []
+ if (patch.length > 0) {
+ // calculate original content length
+ for (let j = patch.length - 1; j >= 0; j--) {
+ var p = patch[j]
+ for (let i = 0; i < p.diffs.length; i++) {
+ var diff = p.diffs[i]
+ switch (diff[0]) {
+ case 1: // insert
+ contentLength -= diff[1].length
+ break
+ case -1: // delete
+ contentLength += diff[1].length
+ break
+ }
+ }
+ }
+ // generate operations
+ var bias = 0
+ var lengthBias = 0
+ for (let j = 0; j < patch.length; j++) {
+ var operation = []
+ let p = patch[j]
+ var currIndex = p.start1
+ var currLength = contentLength - bias
+ for (let i = 0; i < p.diffs.length; i++) {
+ let diff = p.diffs[i]
+ switch (diff[0]) {
+ case 0: // retain
+ if (i === 0) {
+ // first
+ operation.push(currIndex + diff[1].length)
+ } else if (i !== p.diffs.length - 1) {
+ // mid
+ operation.push(diff[1].length)
+ } else {
+ // last
+ operation.push(currLength + lengthBias - currIndex)
+ }
+ currIndex += diff[1].length
+ break
+ case 1: // insert
+ operation.push(diff[1])
+ lengthBias += diff[1].length
+ currIndex += diff[1].length
+ break
+ case -1: // delete
+ operation.push(-diff[1].length)
+ bias += diff[1].length
+ currIndex += diff[1].length
+ break
+ }
}
+ operations.push(operation)
+ }
+ }
+ return operations
+ }
+ },
+ hooks: {
+ beforeCreate: function (note, options, callback) {
+ // if no content specified then use default note
+ if (!note.content) {
+ var body = null
+ let filePath = null
+ if (!note.alias) {
+ filePath = config.defaultnotepath
+ } else {
+ filePath = path.join(config.docspath, note.alias + '.md')
+ }
+ if (Note.checkFileExist(filePath)) {
+ var fsCreatedTime = moment(fs.statSync(filePath).ctime)
+ body = fs.readFileSync(filePath, 'utf8')
+ note.title = Note.parseNoteTitle(body)
+ note.content = body
+ if (filePath !== config.defaultnotepath) {
+ note.createdAt = fsCreatedTime
+ }
+ }
+ }
+ // 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 = config.defaultpermission
+ } else {
+ note.permission = 'freely'
+ }
}
- });
+ return callback(null, note)
+ },
+ afterCreate: function (note, options, callback) {
+ sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
+ callback(err, note)
+ })
+ }
+ }
+ })
- return Note;
-};
+ return Note
+}
diff --git a/lib/models/revision.js b/lib/models/revision.js
index c7360fed..d8dab30a 100644
--- a/lib/models/revision.js
+++ b/lib/models/revision.js
@@ -1,306 +1,306 @@
-"use strict";
-
// external modules
-var Sequelize = require("sequelize");
-var async = require('async');
-var moment = require('moment');
-var childProcess = require('child_process');
-var shortId = require('shortid');
+var Sequelize = require('sequelize')
+var async = require('async')
+var moment = require('moment')
+var childProcess = require('child_process')
+var shortId = require('shortid')
// core
-var config = require("../config.js");
-var logger = require("../logger.js");
+var config = require('../config.js')
+var logger = require('../logger.js')
-var dmpWorker = createDmpWorker();
-var dmpCallbackCache = {};
+var dmpWorker = createDmpWorker()
+var dmpCallbackCache = {}
-function createDmpWorker() {
- var worker = childProcess.fork("./lib/workers/dmpWorker.js", {
- stdio: 'ignore'
- });
- if (config.debug) logger.info('dmp worker process started');
- worker.on('message', function (data) {
- if (!data || !data.msg || !data.cacheKey) {
- return logger.error('dmp worker error: not enough data on message');
- }
- var cacheKey = data.cacheKey;
- switch(data.msg) {
- case 'error':
- dmpCallbackCache[cacheKey](data.error, null);
- break;
- case 'check':
- dmpCallbackCache[cacheKey](null, data.result);
- break;
- }
- delete dmpCallbackCache[cacheKey];
- });
- worker.on('close', function (code) {
- dmpWorker = null;
- if (config.debug) logger.info('dmp worker process exited with code ' + code);
- });
- return worker;
+function createDmpWorker () {
+ var worker = childProcess.fork('./lib/workers/dmpWorker.js', {
+ stdio: 'ignore'
+ })
+ if (config.debug) logger.info('dmp worker process started')
+ worker.on('message', function (data) {
+ if (!data || !data.msg || !data.cacheKey) {
+ return logger.error('dmp worker error: not enough data on message')
+ }
+ var cacheKey = data.cacheKey
+ switch (data.msg) {
+ case 'error':
+ dmpCallbackCache[cacheKey](data.error, null)
+ break
+ case 'check':
+ dmpCallbackCache[cacheKey](null, data.result)
+ break
+ }
+ delete dmpCallbackCache[cacheKey]
+ })
+ worker.on('close', function (code) {
+ dmpWorker = null
+ if (config.debug) logger.info('dmp worker process exited with code ' + code)
+ })
+ return worker
}
-function sendDmpWorker(data, callback) {
- if (!dmpWorker) dmpWorker = createDmpWorker();
- var cacheKey = Date.now() + '_' + shortId.generate();
- dmpCallbackCache[cacheKey] = callback;
- data = Object.assign(data, {
- cacheKey: cacheKey
- });
- dmpWorker.send(data);
+function sendDmpWorker (data, callback) {
+ if (!dmpWorker) dmpWorker = createDmpWorker()
+ var cacheKey = Date.now() + '_' + shortId.generate()
+ dmpCallbackCache[cacheKey] = callback
+ data = Object.assign(data, {
+ cacheKey: cacheKey
+ })
+ dmpWorker.send(data)
}
module.exports = function (sequelize, DataTypes) {
- var Revision = sequelize.define("Revision", {
- id: {
- type: DataTypes.UUID,
- primaryKey: true,
- defaultValue: Sequelize.UUIDV4
- },
- patch: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('patch'), "");
- },
- set: function (value) {
- this.setDataValue('patch', sequelize.stripNullByte(value));
- }
- },
- lastContent: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('lastContent'), "");
+ var Revision = sequelize.define('Revision', {
+ id: {
+ type: DataTypes.UUID,
+ primaryKey: true,
+ defaultValue: Sequelize.UUIDV4
+ },
+ patch: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('patch'), '')
+ },
+ set: function (value) {
+ this.setDataValue('patch', sequelize.stripNullByte(value))
+ }
+ },
+ lastContent: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('lastContent'), '')
+ },
+ set: function (value) {
+ this.setDataValue('lastContent', sequelize.stripNullByte(value))
+ }
+ },
+ content: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('content'), '')
+ },
+ set: function (value) {
+ this.setDataValue('content', sequelize.stripNullByte(value))
+ }
+ },
+ length: {
+ type: DataTypes.INTEGER
+ },
+ authorship: {
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse)
+ },
+ set: function (value) {
+ this.setDataValue('authorship', value ? JSON.stringify(value) : value)
+ }
+ }
+ }, {
+ classMethods: {
+ associate: function (models) {
+ Revision.belongsTo(models.Note, {
+ foreignKey: 'noteId',
+ as: 'note',
+ constraints: false
+ })
+ },
+ getNoteRevisions: function (note, callback) {
+ Revision.findAll({
+ where: {
+ noteId: note.id
+ },
+ order: '"createdAt" DESC'
+ }).then(function (revisions) {
+ var data = []
+ for (var i = 0, l = revisions.length; i < l; i++) {
+ var revision = revisions[i]
+ data.push({
+ time: moment(revision.createdAt).valueOf(),
+ length: revision.length
+ })
+ }
+ callback(null, data)
+ }).catch(function (err) {
+ callback(err, null)
+ })
+ },
+ getPatchedNoteRevisionByTime: function (note, time, callback) {
+ // find all revisions to prepare for all possible calculation
+ Revision.findAll({
+ where: {
+ noteId: note.id
+ },
+ order: '"createdAt" DESC'
+ }).then(function (revisions) {
+ if (revisions.length <= 0) return callback(null, null)
+ // measure target revision position
+ Revision.count({
+ where: {
+ noteId: note.id,
+ createdAt: {
+ $gte: time
+ }
},
- set: function (value) {
- this.setDataValue('lastContent', sequelize.stripNullByte(value));
+ order: '"createdAt" DESC'
+ }).then(function (count) {
+ if (count <= 0) return callback(null, null)
+ sendDmpWorker({
+ msg: 'get revision',
+ revisions: revisions,
+ count: count
+ }, callback)
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ },
+ checkAllNotesRevision: function (callback) {
+ Revision.saveAllNotesRevision(function (err, notes) {
+ if (err) return callback(err, null)
+ if (!notes || notes.length <= 0) {
+ return callback(null, notes)
+ } else {
+ Revision.checkAllNotesRevision(callback)
+ }
+ })
+ },
+ saveAllNotesRevision: function (callback) {
+ sequelize.models.Note.findAll({
+ // query all notes that need to save for revision
+ where: {
+ $and: [
+ {
+ lastchangeAt: {
+ $or: {
+ $eq: null,
+ $and: {
+ $ne: null,
+ $gt: sequelize.col('createdAt')
+ }
+ }
+ }
+ },
+ {
+ savedAt: {
+ $or: {
+ $eq: null,
+ $lt: sequelize.col('lastchangeAt')
+ }
+ }
+ }
+ ]
+ }
+ }).then(function (notes) {
+ if (notes.length <= 0) return callback(null, notes)
+ var savedNotes = []
+ async.each(notes, function (note, _callback) {
+ // revision saving policy: note not been modified for 5 mins or not save for 10 mins
+ if (note.lastchangeAt && note.savedAt) {
+ var lastchangeAt = moment(note.lastchangeAt)
+ var savedAt = moment(note.savedAt)
+ if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) {
+ savedNotes.push(note)
+ Revision.saveNoteRevision(note, _callback)
+ } else if (lastchangeAt.isAfter(savedAt.add(10, 'minutes'))) {
+ savedNotes.push(note)
+ Revision.saveNoteRevision(note, _callback)
+ } else {
+ return _callback(null, null)
+ }
+ } else {
+ savedNotes.push(note)
+ Revision.saveNoteRevision(note, _callback)
}
- },
- content: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('content'), "");
- },
- set: function (value) {
- this.setDataValue('content', sequelize.stripNullByte(value));
+ }, function (err) {
+ if (err) {
+ return callback(err, null)
}
- },
- length: {
- type: DataTypes.INTEGER
- },
- authorship: {
- type: DataTypes.TEXT,
- get: function () {
- return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
- },
- set: function (value) {
- this.setDataValue('authorship', value ? JSON.stringify(value) : value);
- }
- }
- }, {
- classMethods: {
- associate: function (models) {
- Revision.belongsTo(models.Note, {
- foreignKey: "noteId",
- as: "note",
- constraints: false
- });
- },
- getNoteRevisions: function (note, callback) {
- Revision.findAll({
- where: {
- noteId: note.id
- },
- order: '"createdAt" DESC'
- }).then(function (revisions) {
- var data = [];
- for (var i = 0, l = revisions.length; i < l; i++) {
- var revision = revisions[i];
- data.push({
- time: moment(revision.createdAt).valueOf(),
- length: revision.length
- });
- }
- callback(null, data);
- }).catch(function (err) {
- callback(err, null);
- });
- },
- getPatchedNoteRevisionByTime: function (note, time, callback) {
- // find all revisions to prepare for all possible calculation
- Revision.findAll({
- where: {
- noteId: note.id
- },
- order: '"createdAt" DESC'
- }).then(function (revisions) {
- if (revisions.length <= 0) return callback(null, null);
- // measure target revision position
- Revision.count({
- where: {
- noteId: note.id,
- createdAt: {
- $gte: time
- }
- },
- order: '"createdAt" DESC'
- }).then(function (count) {
- if (count <= 0) return callback(null, null);
- sendDmpWorker({
- msg: 'get revision',
- revisions: revisions,
- count: count
- }, callback);
- }).catch(function (err) {
- return callback(err, null);
- });
+ // return null when no notes need saving at this moment but have delayed tasks to be done
+ var result = ((savedNotes.length === 0) && (notes.length > savedNotes.length)) ? null : savedNotes
+ return callback(null, result)
+ })
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ },
+ saveNoteRevision: function (note, callback) {
+ Revision.findAll({
+ where: {
+ noteId: note.id
+ },
+ order: '"createdAt" DESC'
+ }).then(function (revisions) {
+ if (revisions.length <= 0) {
+ // if no revision available
+ Revision.create({
+ noteId: note.id,
+ lastContent: note.content,
+ length: note.content.length,
+ authorship: note.authorship
+ }).then(function (revision) {
+ Revision.finishSaveNoteRevision(note, revision, callback)
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ } else {
+ var latestRevision = revisions[0]
+ var lastContent = latestRevision.content || latestRevision.lastContent
+ var content = note.content
+ sendDmpWorker({
+ msg: 'create patch',
+ lastDoc: lastContent,
+ currDoc: content
+ }, function (err, patch) {
+ if (err) logger.error('save note revision error', err)
+ if (!patch) {
+ // if patch is empty (means no difference) then just update the latest revision updated time
+ latestRevision.changed('updatedAt', true)
+ latestRevision.update({
+ updatedAt: Date.now()
+ }).then(function (revision) {
+ Revision.finishSaveNoteRevision(note, revision, callback)
}).catch(function (err) {
- return callback(err, null);
- });
- },
- checkAllNotesRevision: function (callback) {
- Revision.saveAllNotesRevision(function (err, notes) {
- if (err) return callback(err, null);
- if (!notes || notes.length <= 0) {
- return callback(null, notes);
- } else {
- Revision.checkAllNotesRevision(callback);
- }
- });
- },
- saveAllNotesRevision: function (callback) {
- sequelize.models.Note.findAll({
- // query all notes that need to save for revision
- where: {
- $and: [
- {
- lastchangeAt: {
- $or: {
- $eq: null,
- $and: {
- $ne: null,
- $gt: sequelize.col('createdAt')
- }
- }
- }
- },
- {
- savedAt: {
- $or: {
- $eq: null,
- $lt: sequelize.col('lastchangeAt')
- }
- }
- }
- ]
- }
- }).then(function (notes) {
- if (notes.length <= 0) return callback(null, notes);
- var savedNotes = [];
- async.each(notes, function (note, _callback) {
- // revision saving policy: note not been modified for 5 mins or not save for 10 mins
- if (note.lastchangeAt && note.savedAt) {
- var lastchangeAt = moment(note.lastchangeAt);
- var savedAt = moment(note.savedAt);
- if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) {
- savedNotes.push(note);
- Revision.saveNoteRevision(note, _callback);
- } else if (lastchangeAt.isAfter(savedAt.add(10, 'minutes'))) {
- savedNotes.push(note);
- Revision.saveNoteRevision(note, _callback);
- } else {
- return _callback(null, null);
- }
- } else {
- savedNotes.push(note);
- Revision.saveNoteRevision(note, _callback);
- }
- }, function (err) {
- if (err) return callback(err, null);
- // return null when no notes need saving at this moment but have delayed tasks to be done
- var result = ((savedNotes.length == 0) && (notes.length > savedNotes.length)) ? null : savedNotes;
- return callback(null, result);
- });
+ return callback(err, null)
+ })
+ } else {
+ Revision.create({
+ noteId: note.id,
+ patch: patch,
+ content: note.content,
+ length: note.content.length,
+ authorship: note.authorship
+ }).then(function (revision) {
+ // clear last revision content to reduce db size
+ latestRevision.update({
+ content: null
+ }).then(function () {
+ Revision.finishSaveNoteRevision(note, revision, callback)
+ }).catch(function (err) {
+ return callback(err, null)
+ })
}).catch(function (err) {
- return callback(err, null);
- });
- },
- saveNoteRevision: function (note, callback) {
- Revision.findAll({
- where: {
- noteId: note.id
- },
- order: '"createdAt" DESC'
- }).then(function (revisions) {
- if (revisions.length <= 0) {
- // if no revision available
- Revision.create({
- noteId: note.id,
- lastContent: note.content,
- length: note.content.length,
- authorship: note.authorship
- }).then(function (revision) {
- Revision.finishSaveNoteRevision(note, revision, callback);
- }).catch(function (err) {
- return callback(err, null);
- });
- } else {
- var latestRevision = revisions[0];
- var lastContent = latestRevision.content || latestRevision.lastContent;
- var content = note.content;
- sendDmpWorker({
- msg: 'create patch',
- lastDoc: lastContent,
- currDoc: content,
- }, function (err, patch) {
- if (err) logger.error('save note revision error', err);
- if (!patch) {
- // if patch is empty (means no difference) then just update the latest revision updated time
- latestRevision.changed('updatedAt', true);
- latestRevision.update({
- updatedAt: Date.now()
- }).then(function (revision) {
- Revision.finishSaveNoteRevision(note, revision, callback);
- }).catch(function (err) {
- return callback(err, null);
- });
- } else {
- Revision.create({
- noteId: note.id,
- patch: patch,
- content: note.content,
- length: note.content.length,
- authorship: note.authorship
- }).then(function (revision) {
- // clear last revision content to reduce db size
- latestRevision.update({
- content: null
- }).then(function () {
- Revision.finishSaveNoteRevision(note, revision, callback);
- }).catch(function (err) {
- return callback(err, null);
- });
- }).catch(function (err) {
- return callback(err, null);
- });
- }
- });
- }
- }).catch(function (err) {
- return callback(err, null);
- });
- },
- finishSaveNoteRevision: function (note, revision, callback) {
- note.update({
- savedAt: revision.updatedAt
- }).then(function () {
- return callback(null, revision);
- }).catch(function (err) {
- return callback(err, null);
- });
- }
- }
- });
+ return callback(err, null)
+ })
+ }
+ })
+ }
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ },
+ finishSaveNoteRevision: function (note, revision, callback) {
+ note.update({
+ savedAt: revision.updatedAt
+ }).then(function () {
+ return callback(null, revision)
+ }).catch(function (err) {
+ return callback(err, null)
+ })
+ }
+ }
+ })
- return Revision;
-}; \ No newline at end of file
+ return Revision
+}
diff --git a/lib/models/temp.js b/lib/models/temp.js
index 6eeff153..e770bb3a 100644
--- a/lib/models/temp.js
+++ b/lib/models/temp.js
@@ -1,19 +1,17 @@
-"use strict";
-
-//external modules
-var shortId = require('shortid');
+// external modules
+var shortId = require('shortid')
module.exports = function (sequelize, DataTypes) {
- var Temp = sequelize.define("Temp", {
- id: {
- type: DataTypes.STRING,
- primaryKey: true,
- defaultValue: shortId.generate
- },
- data: {
- type: DataTypes.TEXT
- }
- });
-
- return Temp;
-}; \ No newline at end of file
+ var Temp = sequelize.define('Temp', {
+ id: {
+ type: DataTypes.STRING,
+ primaryKey: true,
+ defaultValue: shortId.generate
+ },
+ data: {
+ type: DataTypes.TEXT
+ }
+ })
+
+ return Temp
+}
diff --git a/lib/models/user.js b/lib/models/user.js
index dd93bf78..f7e533b7 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -1,149 +1,147 @@
-"use strict";
-
// external modules
-var md5 = require("blueimp-md5");
-var Sequelize = require("sequelize");
-var scrypt = require('scrypt');
+var md5 = require('blueimp-md5')
+var Sequelize = require('sequelize')
+var scrypt = require('scrypt')
// core
-var logger = require("../logger.js");
-var letterAvatars = require('../letter-avatars.js');
+var logger = require('../logger.js')
+var letterAvatars = require('../letter-avatars.js')
module.exports = function (sequelize, DataTypes) {
- var User = sequelize.define("User", {
- id: {
- type: DataTypes.UUID,
- primaryKey: true,
- defaultValue: Sequelize.UUIDV4
- },
- profileid: {
- type: DataTypes.STRING,
- unique: true
- },
- profile: {
- type: DataTypes.TEXT
- },
- history: {
- type: DataTypes.TEXT
- },
- accessToken: {
- type: DataTypes.STRING
- },
- refreshToken: {
- type: DataTypes.STRING
- },
- email: {
- type: Sequelize.TEXT,
- validate: {
- isEmail: true
- }
- },
- password: {
- type: Sequelize.TEXT,
- set: function(value) {
- var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString("hex");
- this.setDataValue('password', hash);
- }
+ var User = sequelize.define('User', {
+ id: {
+ type: DataTypes.UUID,
+ primaryKey: true,
+ defaultValue: Sequelize.UUIDV4
+ },
+ profileid: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ profile: {
+ type: DataTypes.TEXT
+ },
+ history: {
+ type: DataTypes.TEXT
+ },
+ accessToken: {
+ type: DataTypes.STRING
+ },
+ refreshToken: {
+ type: DataTypes.STRING
+ },
+ email: {
+ type: Sequelize.TEXT,
+ validate: {
+ isEmail: true
+ }
+ },
+ password: {
+ type: Sequelize.TEXT,
+ set: function (value) {
+ var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
+ this.setDataValue('password', hash)
+ }
+ }
+ }, {
+ instanceMethods: {
+ verifyPassword: function (attempt) {
+ if (scrypt.verifyKdfSync(new Buffer(this.password, 'hex'), attempt)) {
+ return this
+ } else {
+ return false
}
- }, {
- instanceMethods: {
- verifyPassword: function(attempt) {
- if (scrypt.verifyKdfSync(new Buffer(this.password, "hex"), attempt)) {
- return this;
- } else {
- return false;
- }
- }
- },
- classMethods: {
- associate: function (models) {
- User.hasMany(models.Note, {
- foreignKey: "ownerId",
- constraints: false
- });
- User.hasMany(models.Note, {
- foreignKey: "lastchangeuserId",
- constraints: false
- });
- },
- getProfile: function (user) {
- return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null);
- },
- parseProfile: function (profile) {
- try {
- var profile = JSON.parse(profile);
- } catch (err) {
- logger.error(err);
- profile = null;
- }
- if (profile) {
- profile = {
- name: profile.displayName || profile.username,
- photo: User.parsePhotoByProfile(profile),
- biggerphoto: User.parsePhotoByProfile(profile, true)
- }
- }
- return profile;
- },
- parsePhotoByProfile: function (profile, bigger) {
- var photo = null;
- switch (profile.provider) {
- case "facebook":
- 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';
- if (bigger) photo += '?size=original';
- else photo += '?size=bigger';
- break;
- case "github":
- photo = 'https://avatars.githubusercontent.com/u/' + profile.id;
- if (bigger) photo += '?s=400';
- else photo += '?s=96';
- break;
- case "gitlab":
- 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);
- if (bigger) photo += '?s=400';
- else photo += '?s=96';
- break;
- case "google":
- 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]);
- if (bigger) photo += '?s=400';
- else photo += '?s=96';
- } else {
- photo = letterAvatars(profile.username);
- }
- break;
- }
- return photo;
- },
- parseProfileByEmail: function (email) {
- var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email);
- return {
- name: email.substring(0, email.lastIndexOf("@")),
- photo: photoUrl += '?s=96',
- biggerphoto: photoUrl += '?s=400'
- };
+ }
+ },
+ classMethods: {
+ associate: function (models) {
+ User.hasMany(models.Note, {
+ foreignKey: 'ownerId',
+ constraints: false
+ })
+ User.hasMany(models.Note, {
+ foreignKey: 'lastchangeuserId',
+ constraints: false
+ })
+ },
+ getProfile: function (user) {
+ return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null)
+ },
+ parseProfile: function (profile) {
+ try {
+ profile = JSON.parse(profile)
+ } catch (err) {
+ logger.error(err)
+ profile = null
+ }
+ if (profile) {
+ profile = {
+ name: profile.displayName || profile.username,
+ photo: User.parsePhotoByProfile(profile),
+ biggerphoto: User.parsePhotoByProfile(profile, true)
+ }
+ }
+ return profile
+ },
+ parsePhotoByProfile: function (profile, bigger) {
+ var photo = null
+ switch (profile.provider) {
+ case 'facebook':
+ 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'
+ if (bigger) photo += '?size=original'
+ else photo += '?size=bigger'
+ break
+ case 'github':
+ photo = 'https://avatars.githubusercontent.com/u/' + profile.id
+ if (bigger) photo += '?s=400'
+ else photo += '?s=96'
+ break
+ case 'gitlab':
+ 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)
+ if (bigger) photo += '?s=400'
+ else photo += '?s=96'
+ break
+ case 'google':
+ 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])
+ if (bigger) photo += '?s=400'
+ else photo += '?s=96'
+ } else {
+ photo = letterAvatars(profile.username)
}
+ break
+ }
+ return photo
+ },
+ parseProfileByEmail: function (email) {
+ var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email)
+ return {
+ name: email.substring(0, email.lastIndexOf('@')),
+ photo: photoUrl + '?s=96',
+ biggerphoto: photoUrl + '?s=400'
}
- });
+ }
+ }
+ })
- return User;
-}; \ No newline at end of file
+ return User
+}
diff --git a/lib/realtime.js b/lib/realtime.js
index c1db6886..cff795c7 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -1,937 +1,924 @@
-//realtime
-//external modules
-var cookie = require('cookie');
-var cookieParser = require('cookie-parser');
-var url = require('url');
-var async = require('async');
-var LZString = require('lz-string');
-var randomcolor = require("randomcolor");
-var Chance = require('chance'),
- chance = new Chance();
-var moment = require('moment');
-
-//core
-var config = require("./config.js");
-var logger = require("./logger.js");
-var history = require("./history.js");
-var models = require("./models");
-
-//ot
-var ot = require("./ot/index.js");
-
-//public
+// realtime
+// external modules
+var cookie = require('cookie')
+var cookieParser = require('cookie-parser')
+var url = require('url')
+var async = require('async')
+var LZString = require('lz-string')
+var randomcolor = require('randomcolor')
+var Chance = require('chance')
+var chance = new Chance()
+var moment = require('moment')
+
+// core
+var config = require('./config.js')
+var logger = require('./logger.js')
+var history = require('./history.js')
+var models = require('./models')
+
+// ot
+var ot = require('./ot/index.js')
+
+// public
var realtime = {
- io: null,
- onAuthorizeSuccess: onAuthorizeSuccess,
- onAuthorizeFail: onAuthorizeFail,
- secure: secure,
- connection: connection,
- getStatus: getStatus,
- isReady: isReady
-};
-
-function onAuthorizeSuccess(data, accept) {
- accept();
+ io: null,
+ onAuthorizeSuccess: onAuthorizeSuccess,
+ onAuthorizeFail: onAuthorizeFail,
+ secure: secure,
+ connection: connection,
+ getStatus: getStatus,
+ isReady: isReady
}
-function onAuthorizeFail(data, message, error, accept) {
- accept(); //accept whether authorize or not to allow anonymous usage
+function onAuthorizeSuccess (data, accept) {
+ accept()
}
-//secure the origin by the cookie
-function secure(socket, next) {
- try {
- var handshakeData = socket.request;
- if (handshakeData.headers.cookie) {
- handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
- handshakeData.sessionID = cookieParser.signedCookie(handshakeData.cookie[config.sessionname], config.sessionsecret);
- if (handshakeData.sessionID &&
+function onAuthorizeFail (data, message, error, accept) {
+ accept() // accept whether authorize or not to allow anonymous usage
+}
+
+// secure the origin by the cookie
+function secure (socket, next) {
+ try {
+ var handshakeData = socket.request
+ if (handshakeData.headers.cookie) {
+ handshakeData.cookie = cookie.parse(handshakeData.headers.cookie)
+ handshakeData.sessionID = cookieParser.signedCookie(handshakeData.cookie[config.sessionname], config.sessionsecret)
+ if (handshakeData.sessionID &&
handshakeData.cookie[config.sessionname] &&
- handshakeData.cookie[config.sessionname] != handshakeData.sessionID) {
- if (config.debug)
- logger.info("AUTH success cookie: " + handshakeData.sessionID);
- return next();
- } else {
- next(new Error('AUTH failed: Cookie is invalid.'));
- }
- } else {
- next(new Error('AUTH failed: No cookie transmitted.'));
- }
- } catch (ex) {
- next(new Error("AUTH failed:" + JSON.stringify(ex)));
+ handshakeData.cookie[config.sessionname] !== handshakeData.sessionID) {
+ if (config.debug) { logger.info('AUTH success cookie: ' + handshakeData.sessionID) }
+ return next()
+ } else {
+ next(new Error('AUTH failed: Cookie is invalid.'))
+ }
+ } else {
+ next(new Error('AUTH failed: No cookie transmitted.'))
}
+ } catch (ex) {
+ next(new Error('AUTH failed:' + JSON.stringify(ex)))
+ }
}
-function emitCheck(note) {
- var out = {
- title: note.title,
- updatetime: note.updatetime,
- lastchangeuser: note.lastchangeuser,
- lastchangeuserprofile: note.lastchangeuserprofile,
- authors: note.authors,
- authorship: note.authorship
- };
- realtime.io.to(note.id).emit('check', out);
+function emitCheck (note) {
+ var out = {
+ title: note.title,
+ updatetime: note.updatetime,
+ lastchangeuser: note.lastchangeuser,
+ lastchangeuserprofile: note.lastchangeuserprofile,
+ authors: note.authors,
+ authorship: note.authorship
+ }
+ realtime.io.to(note.id).emit('check', out)
}
-//actions
-var users = {};
-var notes = {};
-//update when the note is dirty
-var updater = setInterval(function () {
- async.each(Object.keys(notes), function (key, callback) {
- var note = notes[key];
- if (note.server.isDirty) {
- if (config.debug) logger.info("updater found dirty note: " + key);
- note.server.isDirty = false;
- updateNote(note, function(err, _note) {
- // handle when note already been clean up
- if (!notes[key] || !notes[key].server) return callback(null, null);
- if (!_note) {
- realtime.io.to(note.id).emit('info', {
- code: 404
- });
- logger.error('note not found: ', note.id);
- }
- if (err || !_note) {
- for (var i = 0, l = note.socks.length; i < l; i++) {
- var sock = note.socks[i];
- if (typeof sock !== 'undefined' && sock) {
- setTimeout(function () {
- sock.disconnect(true);
- }, 0);
- }
- }
- return callback(err, null);
- }
- note.updatetime = moment(_note.lastchangeAt).valueOf();
- emitCheck(note);
- return callback(null, null);
- });
- } else {
- return callback(null, null);
- }
- }, function (err) {
- if (err) return logger.error('updater error', err);
- });
-}, 1000);
-function updateNote(note, callback) {
- models.Note.findOne({
- where: {
- id: note.id
+// actions
+var users = {}
+var notes = {}
+// update when the note is dirty
+setInterval(function () {
+ async.each(Object.keys(notes), function (key, callback) {
+ var note = notes[key]
+ if (note.server.isDirty) {
+ if (config.debug) logger.info('updater found dirty note: ' + key)
+ note.server.isDirty = false
+ updateNote(note, function (err, _note) {
+ // handle when note already been clean up
+ if (!notes[key] || !notes[key].server) return callback(null, null)
+ if (!_note) {
+ realtime.io.to(note.id).emit('info', {
+ code: 404
+ })
+ logger.error('note not found: ', note.id)
}
- }).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({
- where: {
- id: note.lastchangeuser
- }
- }).then(function (user) {
- if (!user) return callback(null, null);
- note.lastchangeuserprofile = models.User.getProfile(user);
- return finishUpdateNote(note, _note, callback);
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
- } else {
- return finishUpdateNote(note, _note, callback);
+ if (err || !_note) {
+ for (var i = 0, l = note.socks.length; i < l; i++) {
+ var sock = note.socks[i]
+ if (typeof sock !== 'undefined' && sock) {
+ setTimeout(function () {
+ sock.disconnect(true)
+ }, 0)
}
- } else {
- note.lastchangeuserprofile = null;
- return finishUpdateNote(note, _note, callback);
+ }
+ return callback(err, null)
}
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
+ note.updatetime = moment(_note.lastchangeAt).valueOf()
+ emitCheck(note)
+ return callback(null, null)
+ })
+ } else {
+ return callback(null, null)
+ }
+ }, function (err) {
+ if (err) return logger.error('updater error', err)
+ })
+}, 1000)
+
+function updateNote (note, callback) {
+ models.Note.findOne({
+ where: {
+ id: note.id
+ }
+ }).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({
+ where: {
+ id: note.lastchangeuser
+ }
+ }).then(function (user) {
+ if (!user) return callback(null, null)
+ note.lastchangeuserprofile = models.User.getProfile(user)
+ return finishUpdateNote(note, _note, callback)
+ }).catch(function (err) {
+ logger.error(err)
+ return callback(err, null)
+ })
+ } else {
+ return finishUpdateNote(note, _note, callback)
+ }
+ } else {
+ note.lastchangeuserprofile = null
+ return finishUpdateNote(note, _note, callback)
+ }
+ }).catch(function (err) {
+ logger.error(err)
+ return callback(err, null)
+ })
}
-function finishUpdateNote(note, _note, callback) {
- if (!note || !note.server) return callback(null, null);
- var body = note.server.document;
- var title = note.title = models.Note.parseNoteTitle(body);
- var values = {
- title: title,
- content: body,
- authorship: note.authorship,
- lastchangeuserId: note.lastchangeuser,
- lastchangeAt: Date.now()
- };
- _note.update(values).then(function (_note) {
- saverSleep = false;
- return callback(null, _note);
- }).catch(function (err) {
- logger.error(err);
- return callback(err, null);
- });
+
+function finishUpdateNote (note, _note, callback) {
+ if (!note || !note.server) return callback(null, null)
+ var body = note.server.document
+ var title = note.title = models.Note.parseNoteTitle(body)
+ var values = {
+ title: title,
+ content: body,
+ authorship: note.authorship,
+ lastchangeuserId: note.lastchangeuser,
+ lastchangeAt: Date.now()
+ }
+ _note.update(values).then(function (_note) {
+ saverSleep = false
+ return callback(null, _note)
+ }).catch(function (err) {
+ logger.error(err)
+ return callback(err, null)
+ })
}
-//clean when user not in any rooms or user not in connected list
-var cleaner = setInterval(function () {
- async.each(Object.keys(users), function (key, callback) {
- var socket = realtime.io.sockets.connected[key];
- if ((!socket && users[key]) ||
- (socket && (!socket.rooms || socket.rooms.length <= 0))) {
- if (config.debug)
- logger.info("cleaner found redundant user: " + key);
- if (!socket) {
- socket = {
- id: key
- };
- }
- disconnectSocketQueue.push(socket);
- disconnect(socket);
+
+// clean when user not in any rooms or user not in connected list
+setInterval(function () {
+ async.each(Object.keys(users), function (key, callback) {
+ var socket = realtime.io.sockets.connected[key]
+ if ((!socket && users[key]) ||
+ (socket && (!socket.rooms || socket.rooms.length <= 0))) {
+ if (config.debug) { logger.info('cleaner found redundant user: ' + key) }
+ if (!socket) {
+ socket = {
+ id: key
}
- return callback(null, null);
- }, function (err) {
- if (err) return logger.error('cleaner error', err);
- });
-}, 60000);
-var saverSleep = false;
+ }
+ disconnectSocketQueue.push(socket)
+ disconnect(socket)
+ }
+ return callback(null, null)
+ }, function (err) {
+ if (err) return logger.error('cleaner error', err)
+ })
+}, 60000)
+
+var saverSleep = false
// save note revision in interval
-var saver = setInterval(function () {
- if (saverSleep) return;
- models.Revision.saveAllNotesRevision(function (err, notes) {
- if (err) return logger.error('revision saver failed: ' + err);
- if (notes && notes.length <= 0) {
- saverSleep = true;
- return;
+setInterval(function () {
+ if (saverSleep) return
+ models.Revision.saveAllNotesRevision(function (err, notes) {
+ if (err) return logger.error('revision saver failed: ' + err)
+ if (notes && notes.length <= 0) {
+ saverSleep = true
+ }
+ })
+}, 60000 * 5)
+
+function getStatus (callback) {
+ models.Note.count().then(function (notecount) {
+ var distinctaddresses = []
+ var regaddresses = []
+ var distinctregaddresses = []
+ Object.keys(users).forEach(function (key) {
+ var user = users[key]
+ if (!user) return
+ let found = false
+ for (let i = 0; i < distinctaddresses.length; i++) {
+ if (user.address === distinctaddresses[i]) {
+ found = true
+ break
}
- });
-}, 60000 * 5);
-
-function getStatus(callback) {
- models.Note.count().then(function (notecount) {
- var distinctaddresses = [];
- var regaddresses = [];
- var distinctregaddresses = [];
- Object.keys(users).forEach(function (key) {
- var user = users[key];
- if (!user) return;
- var found = false;
- for (var i = 0; i < distinctaddresses.length; i++) {
- if (user.address == distinctaddresses[i]) {
- found = true;
- break;
- }
- }
- if (!found) {
- distinctaddresses.push(user.address);
- }
- if (user.login) {
- regaddresses.push(user.address);
- var found = false;
- for (var i = 0; i < distinctregaddresses.length; i++) {
- if (user.address == distinctregaddresses[i]) {
- found = true;
- break;
- }
- }
- if (!found) {
- distinctregaddresses.push(user.address);
- }
- }
- });
- models.User.count().then(function (regcount) {
- return callback ? callback({
- onlineNotes: Object.keys(notes).length,
- onlineUsers: Object.keys(users).length,
- distinctOnlineUsers: distinctaddresses.length,
- notesCount: notecount,
- registeredUsers: regcount,
- onlineRegisteredUsers: regaddresses.length,
- distinctOnlineRegisteredUsers: distinctregaddresses.length,
- isConnectionBusy: isConnectionBusy,
- connectionSocketQueueLength: connectionSocketQueue.length,
- isDisconnectBusy: isDisconnectBusy,
- disconnectSocketQueueLength: disconnectSocketQueue.length
- }) : null;
- }).catch(function (err) {
- return logger.error('count user failed: ' + err);
- });
+ }
+ if (!found) {
+ distinctaddresses.push(user.address)
+ }
+ if (user.login) {
+ regaddresses.push(user.address)
+ let found = false
+ for (let i = 0; i < distinctregaddresses.length; i++) {
+ if (user.address === distinctregaddresses[i]) {
+ found = true
+ break
+ }
+ }
+ if (!found) {
+ distinctregaddresses.push(user.address)
+ }
+ }
+ })
+ models.User.count().then(function (regcount) {
+ return callback ? callback({
+ onlineNotes: Object.keys(notes).length,
+ onlineUsers: Object.keys(users).length,
+ distinctOnlineUsers: distinctaddresses.length,
+ notesCount: notecount,
+ registeredUsers: regcount,
+ onlineRegisteredUsers: regaddresses.length,
+ distinctOnlineRegisteredUsers: distinctregaddresses.length,
+ isConnectionBusy: isConnectionBusy,
+ connectionSocketQueueLength: connectionSocketQueue.length,
+ isDisconnectBusy: isDisconnectBusy,
+ disconnectSocketQueueLength: disconnectSocketQueue.length
+ }) : null
}).catch(function (err) {
- return logger.error('count note failed: ' + err);
- });
+ return logger.error('count user failed: ' + err)
+ })
+ }).catch(function (err) {
+ return logger.error('count note failed: ' + err)
+ })
}
-function isReady() {
- return realtime.io
- && Object.keys(notes).length == 0 && Object.keys(users).length == 0
- && connectionSocketQueue.length == 0 && !isConnectionBusy
- && disconnectSocketQueue.length == 0 && !isDisconnectBusy;
+function isReady () {
+ return realtime.io &&
+ Object.keys(notes).length === 0 && Object.keys(users).length === 0 &&
+ connectionSocketQueue.length === 0 && !isConnectionBusy &&
+ disconnectSocketQueue.length === 0 && !isDisconnectBusy
}
-function extractNoteIdFromSocket(socket) {
- if (!socket || !socket.handshake || !socket.handshake.headers) {
- return false;
- }
- var referer = socket.handshake.headers.referer;
- if (!referer) {
- return false;
- }
- var hostUrl = url.parse(referer);
- var noteId = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1];
- return noteId;
+function extractNoteIdFromSocket (socket) {
+ if (!socket || !socket.handshake || !socket.handshake.headers) {
+ return false
+ }
+ var referer = socket.handshake.headers.referer
+ if (!referer) {
+ return false
+ }
+ var hostUrl = url.parse(referer)
+ var noteId = config.urlpath ? hostUrl.pathname.slice(config.urlpath.length + 1, hostUrl.pathname.length).split('/')[1] : hostUrl.pathname.split('/')[1]
+ return noteId
}
-function parseNoteIdFromSocket(socket, callback) {
- var noteId = extractNoteIdFromSocket(socket);
- if (!noteId) {
- return callback(null, null);
- }
- models.Note.parseNoteId(noteId, function (err, id) {
- if (err || !id) return callback(err, id);
- return callback(null, id);
- });
+function parseNoteIdFromSocket (socket, callback) {
+ var noteId = extractNoteIdFromSocket(socket)
+ if (!noteId) {
+ return callback(null, null)
+ }
+ models.Note.parseNoteId(noteId, function (err, id) {
+ if (err || !id) return callback(err, id)
+ return callback(null, id)
+ })
}
-function emitOnlineUsers(socket) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var users = [];
- Object.keys(notes[noteId].users).forEach(function (key) {
- var user = notes[noteId].users[key];
- if (user)
- users.push(buildUserOutData(user));
- });
- var out = {
- users: users
- };
- realtime.io.to(noteId).emit('online users', out);
+function emitOnlineUsers (socket) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var users = []
+ Object.keys(notes[noteId].users).forEach(function (key) {
+ var user = notes[noteId].users[key]
+ if (user) { users.push(buildUserOutData(user)) }
+ })
+ var out = {
+ users: users
+ }
+ realtime.io.to(noteId).emit('online users', out)
}
-function emitUserStatus(socket) {
- var noteId = socket.noteId;
- var user = users[socket.id];
- if (!noteId || !notes[noteId] || !user) return;
- var out = buildUserOutData(user);
- socket.broadcast.to(noteId).emit('user status', out);
+function emitUserStatus (socket) {
+ var noteId = socket.noteId
+ var user = users[socket.id]
+ if (!noteId || !notes[noteId] || !user) return
+ var out = buildUserOutData(user)
+ socket.broadcast.to(noteId).emit('user status', out)
}
-function emitRefresh(socket) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var note = notes[noteId];
- var out = {
- title: note.title,
- docmaxlength: config.documentmaxlength,
- owner: note.owner,
- ownerprofile: note.ownerprofile,
- lastchangeuser: note.lastchangeuser,
- lastchangeuserprofile: note.lastchangeuserprofile,
- authors: note.authors,
- authorship: note.authorship,
- permission: note.permission,
- createtime: note.createtime,
- updatetime: note.updatetime
- };
- socket.emit('refresh', out);
+function emitRefresh (socket) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var note = notes[noteId]
+ var out = {
+ title: note.title,
+ docmaxlength: config.documentmaxlength,
+ owner: note.owner,
+ ownerprofile: note.ownerprofile,
+ lastchangeuser: note.lastchangeuser,
+ lastchangeuserprofile: note.lastchangeuserprofile,
+ authors: note.authors,
+ authorship: note.authorship,
+ permission: note.permission,
+ createtime: note.createtime,
+ updatetime: note.updatetime
+ }
+ socket.emit('refresh', out)
}
-function isDuplicatedInSocketQueue(queue, socket) {
- for (var i = 0; i < queue.length; i++) {
- if (queue[i] && queue[i].id == socket.id) {
- return true;
- }
+function isDuplicatedInSocketQueue (queue, socket) {
+ for (var i = 0; i < queue.length; i++) {
+ if (queue[i] && queue[i].id === socket.id) {
+ return true
}
- return false;
+ }
+ return false
}
-function clearSocketQueue(queue, socket) {
- for (var i = 0; i < queue.length; i++) {
- if (!queue[i] || queue[i].id == socket.id) {
- queue.splice(i, 1);
- i--;
- }
+function clearSocketQueue (queue, socket) {
+ for (var i = 0; i < queue.length; i++) {
+ if (!queue[i] || queue[i].id === socket.id) {
+ queue.splice(i, 1)
+ i--
}
+ }
}
-function connectNextSocket() {
- setTimeout(function () {
- isConnectionBusy = false;
- if (connectionSocketQueue.length > 0) {
- startConnection(connectionSocketQueue[0]);
- }
- }, 1);
+function connectNextSocket () {
+ setTimeout(function () {
+ isConnectionBusy = false
+ if (connectionSocketQueue.length > 0) {
+ startConnection(connectionSocketQueue[0])
+ }
+ }, 1)
}
-function interruptConnection(socket, note, user) {
- if (note) delete note;
- if (user) delete user;
- if (socket)
- clearSocketQueue(connectionSocketQueue, socket);
- else
- connectionSocketQueue.shift();
- connectNextSocket();
+function interruptConnection (socket, noteId, socketId) {
+ if (notes[noteId]) delete notes[noteId]
+ if (users[socketId]) delete users[socketId]
+ if (socket) { clearSocketQueue(connectionSocketQueue, socket) } else { connectionSocketQueue.shift() }
+ connectNextSocket()
}
-function checkViewPermission(req, note) {
- if (note.permission == 'private') {
- if (req.user && req.user.logged_in && req.user.id == note.owner)
- return true;
- else
- return false;
- } else if (note.permission == 'limited' || note.permission == 'protected') {
- if(req.user && req.user.logged_in)
- return true;
- else
- return false;
- } else {
- return true;
- }
+function checkViewPermission (req, note) {
+ if (note.permission === 'private') {
+ if (req.user && req.user.logged_in && req.user.id === note.owner) { return true } else { return false }
+ } else if (note.permission === 'limited' || note.permission === 'protected') {
+ if (req.user && req.user.logged_in) { return true } else { return false }
+ } else {
+ return true
+ }
}
-var isConnectionBusy = false;
-var connectionSocketQueue = [];
-var isDisconnectBusy = false;
-var disconnectSocketQueue = [];
-
-function finishConnection(socket, note, user) {
- // if no valid info provided will drop the client
- if (!socket || !note || !user) {
- return interruptConnection(socket, note, user);
- }
- // check view permission
- if (!checkViewPermission(socket.request, note)) {
- interruptConnection(socket, note, user);
- return failConnection(403, 'connection forbidden', socket);
- }
- // update user color to author color
- if (note.authors[user.userid]) {
- user.color = users[socket.id].color = note.authors[user.userid].color;
- }
- note.users[socket.id] = user;
- note.socks.push(socket);
- note.server.addClient(socket);
- note.server.setName(socket, user.name);
- note.server.setColor(socket, user.color);
-
- // update user note history
- updateHistory(user.userid, note);
-
- emitOnlineUsers(socket);
- emitRefresh(socket);
-
- //clear finished socket in queue
- clearSocketQueue(connectionSocketQueue, socket);
- //seek for next socket
- connectNextSocket();
-
- if (config.debug) {
- var noteId = socket.noteId;
- logger.info('SERVER connected a client to [' + noteId + ']:');
- logger.info(JSON.stringify(user));
- //logger.info(notes);
- getStatus(function (data) {
- logger.info(JSON.stringify(data));
- });
- }
+var isConnectionBusy = false
+var connectionSocketQueue = []
+var isDisconnectBusy = false
+var disconnectSocketQueue = []
+
+function finishConnection (socket, noteId, socketId) {
+ // if no valid info provided will drop the client
+ if (!socket || !notes[noteId] || !users[socketId]) {
+ return interruptConnection(socket, noteId, socketId)
+ }
+ // check view permission
+ if (!checkViewPermission(socket.request, notes[noteId])) {
+ interruptConnection(socket, noteId, socketId)
+ return failConnection(403, 'connection forbidden', socket)
+ }
+ let note = notes[noteId]
+ let user = users[socketId]
+ // update user color to author color
+ if (note.authors[user.userid]) {
+ user.color = users[socket.id].color = note.authors[user.userid].color
+ }
+ note.users[socket.id] = user
+ note.socks.push(socket)
+ note.server.addClient(socket)
+ note.server.setName(socket, user.name)
+ note.server.setColor(socket, user.color)
+
+ // update user note history
+ updateHistory(user.userid, note)
+
+ emitOnlineUsers(socket)
+ emitRefresh(socket)
+
+ // clear finished socket in queue
+ clearSocketQueue(connectionSocketQueue, socket)
+ // seek for next socket
+ connectNextSocket()
+
+ if (config.debug) {
+ let noteId = socket.noteId
+ logger.info('SERVER connected a client to [' + noteId + ']:')
+ logger.info(JSON.stringify(user))
+ // logger.info(notes);
+ getStatus(function (data) {
+ logger.info(JSON.stringify(data))
+ })
+ }
}
-function startConnection(socket) {
- if (isConnectionBusy) return;
- isConnectionBusy = true;
+function startConnection (socket) {
+ if (isConnectionBusy) return
+ isConnectionBusy = true
+
+ var noteId = socket.noteId
+ if (!noteId) {
+ return failConnection(404, 'note id not found', socket)
+ }
+
+ if (!notes[noteId]) {
+ var include = [{
+ model: models.User,
+ as: 'owner'
+ }, {
+ model: models.User,
+ as: 'lastchangeuser'
+ }, {
+ model: models.Author,
+ as: 'authors',
+ include: [{
+ model: models.User,
+ as: 'user'
+ }]
+ }]
- var noteId = socket.noteId;
- if (!noteId) {
- return failConnection(404, 'note id not found', socket);
- }
+ models.Note.findOne({
+ where: {
+ id: noteId
+ },
+ include: include
+ }).then(function (note) {
+ if (!note) {
+ return failConnection(404, 'note not found', socket)
+ }
+ var owner = note.ownerId
+ var ownerprofile = note.owner ? models.User.getProfile(note.owner) : null
+
+ var lastchangeuser = note.lastchangeuserId
+ var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null
+
+ var body = note.content
+ var createtime = note.createdAt
+ var updatetime = note.lastchangeAt
+ var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback)
+
+ var authors = {}
+ for (var i = 0; i < note.authors.length; i++) {
+ var author = note.authors[i]
+ var profile = models.User.getProfile(author.user)
+ authors[author.userId] = {
+ userid: author.userId,
+ color: author.color,
+ photo: profile.photo,
+ name: profile.name
+ }
+ }
- if (!notes[noteId]) {
- var include = [{
- model: models.User,
- as: "owner"
- }, {
- model: models.User,
- as: "lastchangeuser"
- }, {
- model: models.Author,
- as: "authors",
- include: [{
- model: models.User,
- as: "user"
- }]
- }];
-
- models.Note.findOne({
- where: {
- id: noteId
- },
- include: include
- }).then(function (note) {
- if (!note) {
- return failConnection(404, 'note not found', socket);
- }
- var owner = note.ownerId;
- var ownerprofile = note.owner ? models.User.getProfile(note.owner) : null;
-
- var lastchangeuser = note.lastchangeuserId;
- var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null;
-
- var body = note.content;
- var createtime = note.createdAt;
- var updatetime = note.lastchangeAt;
- var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
-
- var authors = {};
- for (var i = 0; i < note.authors.length; i++) {
- var author = note.authors[i];
- var profile = models.User.getProfile(author.user);
- authors[author.userId] = {
- userid: author.userId,
- color: author.color,
- photo: profile.photo,
- name: profile.name
- };
- }
+ notes[noteId] = {
+ id: noteId,
+ alias: note.alias,
+ title: note.title,
+ owner: owner,
+ ownerprofile: ownerprofile,
+ permission: note.permission,
+ lastchangeuser: lastchangeuser,
+ lastchangeuserprofile: lastchangeuserprofile,
+ socks: [],
+ users: {},
+ tempUsers: {},
+ createtime: moment(createtime).valueOf(),
+ updatetime: moment(updatetime).valueOf(),
+ server: server,
+ authors: authors,
+ authorship: note.authorship
+ }
- notes[noteId] = {
- id: noteId,
- alias: note.alias,
- title: note.title,
- owner: owner,
- ownerprofile: ownerprofile,
- permission: note.permission,
- lastchangeuser: lastchangeuser,
- lastchangeuserprofile: lastchangeuserprofile,
- socks: [],
- users: {},
- tempUsers: {},
- createtime: moment(createtime).valueOf(),
- updatetime: moment(updatetime).valueOf(),
- server: server,
- authors: authors,
- authorship: note.authorship
- };
-
- return finishConnection(socket, notes[noteId], users[socket.id]);
- }).catch(function (err) {
- return failConnection(500, err, socket);
- });
- } else {
- return finishConnection(socket, notes[noteId], users[socket.id]);
- }
+ return finishConnection(socket, noteId, socket.id)
+ }).catch(function (err) {
+ return failConnection(500, err, socket)
+ })
+ } else {
+ return finishConnection(socket, noteId, socket.id)
+ }
}
-function failConnection(code, err, socket) {
- logger.error(err);
- // clear error socket in queue
- clearSocketQueue(connectionSocketQueue, socket);
- connectNextSocket();
- // emit error info
- socket.emit('info', {
- code: code
- });
- return socket.disconnect(true);
+function failConnection (code, err, socket) {
+ logger.error(err)
+ // clear error socket in queue
+ clearSocketQueue(connectionSocketQueue, socket)
+ connectNextSocket()
+ // emit error info
+ socket.emit('info', {
+ code: code
+ })
+ return socket.disconnect(true)
}
-function disconnect(socket) {
- if (isDisconnectBusy) return;
- isDisconnectBusy = true;
-
- if (config.debug) {
- logger.info("SERVER disconnected a client");
- logger.info(JSON.stringify(users[socket.id]));
+function disconnect (socket) {
+ if (isDisconnectBusy) return
+ isDisconnectBusy = true
+
+ if (config.debug) {
+ logger.info('SERVER disconnected a client')
+ logger.info(JSON.stringify(users[socket.id]))
+ }
+
+ if (users[socket.id]) {
+ delete users[socket.id]
+ }
+ var noteId = socket.noteId
+ var note = notes[noteId]
+ if (note) {
+ // delete user in users
+ if (note.users[socket.id]) {
+ delete note.users[socket.id]
}
-
- if (users[socket.id]) {
- delete users[socket.id];
- }
- var noteId = socket.noteId;
- var note = notes[noteId];
- if (note) {
- // delete user in users
- if (note.users[socket.id]) {
- delete note.users[socket.id];
- }
- // remove sockets in the note socks
- do {
- var index = note.socks.indexOf(socket);
- if (index != -1) {
- note.socks.splice(index, 1);
- }
- } while (index != -1);
- // remove note in notes if no user inside
- if (Object.keys(note.users).length <= 0) {
- if (note.server.isDirty) {
- updateNote(note, function (err, _note) {
- if (err) return logger.error('disconnect note failed: ' + err);
- // clear server before delete to avoid memory leaks
- note.server.document = "";
- note.server.operations = [];
- delete note.server;
- delete notes[noteId];
- if (config.debug) {
- //logger.info(notes);
- getStatus(function (data) {
- logger.info(JSON.stringify(data));
- });
- }
- });
- } else {
- delete note.server;
- delete notes[noteId];
- }
- }
- }
- emitOnlineUsers(socket);
-
- //clear finished socket in queue
- clearSocketQueue(disconnectSocketQueue, socket);
- //seek for next socket
- isDisconnectBusy = false;
- if (disconnectSocketQueue.length > 0)
- disconnect(disconnectSocketQueue[0]);
-
- if (config.debug) {
- //logger.info(notes);
- getStatus(function (data) {
- logger.info(JSON.stringify(data));
- });
+ // remove sockets in the note socks
+ do {
+ var index = note.socks.indexOf(socket)
+ if (index !== -1) {
+ note.socks.splice(index, 1)
+ }
+ } while (index !== -1)
+ // remove note in notes if no user inside
+ if (Object.keys(note.users).length <= 0) {
+ if (note.server.isDirty) {
+ updateNote(note, function (err, _note) {
+ if (err) return logger.error('disconnect note failed: ' + err)
+ // clear server before delete to avoid memory leaks
+ note.server.document = ''
+ note.server.operations = []
+ delete note.server
+ delete notes[noteId]
+ if (config.debug) {
+ // logger.info(notes);
+ getStatus(function (data) {
+ logger.info(JSON.stringify(data))
+ })
+ }
+ })
+ } else {
+ delete note.server
+ delete notes[noteId]
+ }
}
+ }
+ emitOnlineUsers(socket)
+
+ // clear finished socket in queue
+ clearSocketQueue(disconnectSocketQueue, socket)
+ // seek for next socket
+ isDisconnectBusy = false
+ if (disconnectSocketQueue.length > 0) { disconnect(disconnectSocketQueue[0]) }
+
+ if (config.debug) {
+ // logger.info(notes);
+ getStatus(function (data) {
+ logger.info(JSON.stringify(data))
+ })
+ }
}
-function buildUserOutData(user) {
- var out = {
- id: user.id,
- login: user.login,
- userid: user.userid,
- photo: user.photo,
- color: user.color,
- cursor: user.cursor,
- name: user.name,
- idle: user.idle,
- type: user.type
- };
- return out;
+function buildUserOutData (user) {
+ var out = {
+ id: user.id,
+ login: user.login,
+ userid: user.userid,
+ photo: user.photo,
+ color: user.color,
+ cursor: user.cursor,
+ name: user.name,
+ idle: user.idle,
+ type: user.type
+ }
+ return out
}
-function updateUserData(socket, user) {
- //retrieve user data from passport
- if (socket.request.user && socket.request.user.logged_in) {
- var profile = models.User.getProfile(socket.request.user);
- user.photo = profile.photo;
- user.name = profile.name;
- user.userid = socket.request.user.id;
- user.login = true;
- } else {
- user.userid = null;
- user.name = 'Guest ' + chance.last();
- user.login = false;
- }
+function updateUserData (socket, user) {
+ // retrieve user data from passport
+ if (socket.request.user && socket.request.user.logged_in) {
+ var profile = models.User.getProfile(socket.request.user)
+ user.photo = profile.photo
+ user.name = profile.name
+ user.userid = socket.request.user.id
+ user.login = true
+ } else {
+ user.userid = null
+ user.name = 'Guest ' + chance.last()
+ user.login = false
+ }
}
-function ifMayEdit(socket, callback) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var note = notes[noteId];
- var mayEdit = true;
- switch (note.permission) {
- case "freely":
- //not blocking anyone
- break;
- case "editable": case "limited":
- //only login user can change
- if (!socket.request.user || !socket.request.user.logged_in)
- mayEdit = false;
- break;
- case "locked": case "private": case "protected":
- //only owner can change
- if (!note.owner || note.owner != socket.request.user.id)
- mayEdit = false;
- break;
- }
- //if user may edit and this is a text operation
- if (socket.origin == 'operation' && mayEdit) {
- //save for the last change user id
- if (socket.request.user && socket.request.user.logged_in) {
- note.lastchangeuser = socket.request.user.id;
- } else {
- note.lastchangeuser = null;
- }
+function ifMayEdit (socket, callback) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var note = notes[noteId]
+ var mayEdit = true
+ switch (note.permission) {
+ case 'freely':
+ // not blocking anyone
+ break
+ case 'editable': case 'limited':
+ // only login user can change
+ if (!socket.request.user || !socket.request.user.logged_in) { mayEdit = false }
+ break
+ case 'locked': case 'private': case 'protected':
+ // only owner can change
+ if (!note.owner || note.owner !== socket.request.user.id) { mayEdit = false }
+ break
+ }
+ // if user may edit and this is a text operation
+ if (socket.origin === 'operation' && mayEdit) {
+ // save for the last change user id
+ if (socket.request.user && socket.request.user.logged_in) {
+ note.lastchangeuser = socket.request.user.id
+ } else {
+ note.lastchangeuser = null
}
- return callback(mayEdit);
+ }
+ return callback(mayEdit)
}
-function operationCallback(socket, operation) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var note = notes[noteId];
- var userId = null;
- // save authors
- if (socket.request.user && socket.request.user.logged_in) {
- var user = users[socket.id];
- if (!user) return;
- userId = socket.request.user.id;
- if (!note.authors[userId]) {
- models.Author.findOrCreate({
- where: {
- noteId: noteId,
- userId: userId
- },
- defaults: {
- noteId: noteId,
- userId: userId,
- color: user.color
- }
- }).spread(function (author, created) {
- if (author) {
- note.authors[author.userId] = {
- userid: author.userId,
- color: author.color,
- photo: user.photo,
- name: user.name
- };
- }
- }).catch(function (err) {
- return logger.error('operation callback failed: ' + err);
- });
+function operationCallback (socket, operation) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var note = notes[noteId]
+ var userId = null
+ // save authors
+ if (socket.request.user && socket.request.user.logged_in) {
+ var user = users[socket.id]
+ if (!user) return
+ userId = socket.request.user.id
+ if (!note.authors[userId]) {
+ models.Author.findOrCreate({
+ where: {
+ noteId: noteId,
+ userId: userId
+ },
+ defaults: {
+ noteId: noteId,
+ userId: userId,
+ color: user.color
+ }
+ }).spread(function (author, created) {
+ if (author) {
+ note.authors[author.userId] = {
+ userid: author.userId,
+ color: author.color,
+ photo: user.photo,
+ name: user.name
+ }
}
- note.tempUsers[userId] = Date.now();
+ }).catch(function (err) {
+ return logger.error('operation callback failed: ' + err)
+ })
}
- // save authorship - use timer here because it's an O(n) complexity algorithm
- setImmediate(function () {
- note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
- });
+ note.tempUsers[userId] = Date.now()
+ }
+ // 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 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) {
- if (config.maintenance) return;
- parseNoteIdFromSocket(socket, function (err, noteId) {
- if (err) {
- return failConnection(500, err, socket);
- }
- if (!noteId) {
- return failConnection(404, 'note id not found', socket);
- }
+function connection (socket) {
+ if (config.maintenance) return
+ parseNoteIdFromSocket(socket, function (err, noteId) {
+ if (err) {
+ return failConnection(500, err, socket)
+ }
+ if (!noteId) {
+ return failConnection(404, 'note id not found', socket)
+ }
- if (isDuplicatedInSocketQueue(socket, connectionSocketQueue)) return;
-
- // store noteId in this socket session
- socket.noteId = noteId;
-
- //initialize user data
- //random color
- var color = randomcolor();
- //make sure color not duplicated or reach max random count
- if (notes[noteId]) {
- var randomcount = 0;
- var maxrandomcount = 10;
- var found = false;
- do {
- Object.keys(notes[noteId].users).forEach(function (user) {
- if (user.color == color) {
- found = true;
- return;
- }
- });
- if (found) {
- color = randomcolor();
- randomcount++;
- }
- } while (found && randomcount < maxrandomcount);
+ if (isDuplicatedInSocketQueue(socket, connectionSocketQueue)) return
+
+ // store noteId in this socket session
+ socket.noteId = noteId
+
+ // initialize user data
+ // random color
+ var color = randomcolor()
+ // make sure color not duplicated or reach max random count
+ if (notes[noteId]) {
+ var randomcount = 0
+ var maxrandomcount = 10
+ var found = false
+ do {
+ Object.keys(notes[noteId].users).forEach(function (user) {
+ if (user.color === color) {
+ found = true
+ }
+ })
+ if (found) {
+ color = randomcolor()
+ randomcount++
}
- //create user data
- users[socket.id] = {
- id: socket.id,
- address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address,
- 'user-agent': socket.handshake.headers['user-agent'],
- color: color,
- cursor: null,
- login: false,
- userid: null,
- name: null,
- idle: false,
- type: null
- };
- updateUserData(socket, users[socket.id]);
-
- //start connection
- connectionSocketQueue.push(socket);
- startConnection(socket);
- });
-
- //received client refresh request
- socket.on('refresh', function () {
- emitRefresh(socket);
- });
-
- //received user status
- socket.on('user status', function (data) {
- var noteId = socket.noteId;
- var user = users[socket.id];
- if (!noteId || !notes[noteId] || !user) return;
- if (config.debug)
- logger.info('SERVER received [' + noteId + '] user status from [' + socket.id + ']: ' + JSON.stringify(data));
- if (data) {
- user.idle = data.idle;
- user.type = data.type;
- }
- emitUserStatus(socket);
- });
-
- //received note permission change request
- socket.on('permission', function (permission) {
- //need login to do more actions
- if (socket.request.user && socket.request.user.logged_in) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var note = notes[noteId];
- //Only owner can change permission
- if (note.owner && note.owner == socket.request.user.id) {
- if (permission == 'freely' && !config.allowanonymous) return;
- note.permission = permission;
- models.Note.update({
- permission: permission
- }, {
- where: {
- id: noteId
- }
- }).then(function (count) {
- if (!count) {
- return;
- }
- var out = {
- permission: permission
- };
- realtime.io.to(note.id).emit('permission', out);
- for (var i = 0, l = note.socks.length; i < l; i++) {
- var sock = note.socks[i];
- if (typeof sock !== 'undefined' && sock) {
- // check view permission
- if (!checkViewPermission(sock.request, note)) {
- sock.emit('info', {
- code: 403
- });
- setTimeout(function () {
- sock.disconnect(true);
- }, 0);
- }
- }
- }
- }).catch(function (err) {
- return logger.error('update note permission failed: ' + err);
- });
+ } while (found && randomcount < maxrandomcount)
+ }
+ // create user data
+ users[socket.id] = {
+ id: socket.id,
+ address: socket.handshake.headers['x-forwarded-for'] || socket.handshake.address,
+ 'user-agent': socket.handshake.headers['user-agent'],
+ color: color,
+ cursor: null,
+ login: false,
+ userid: null,
+ name: null,
+ idle: false,
+ type: null
+ }
+ updateUserData(socket, users[socket.id])
+
+ // start connection
+ connectionSocketQueue.push(socket)
+ startConnection(socket)
+ })
+
+ // received client refresh request
+ socket.on('refresh', function () {
+ emitRefresh(socket)
+ })
+
+ // received user status
+ socket.on('user status', function (data) {
+ var noteId = socket.noteId
+ var user = users[socket.id]
+ if (!noteId || !notes[noteId] || !user) return
+ if (config.debug) { logger.info('SERVER received [' + noteId + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)) }
+ if (data) {
+ user.idle = data.idle
+ user.type = data.type
+ }
+ emitUserStatus(socket)
+ })
+
+ // received note permission change request
+ socket.on('permission', function (permission) {
+ // need login to do more actions
+ if (socket.request.user && socket.request.user.logged_in) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var note = notes[noteId]
+ // Only owner can change permission
+ if (note.owner && note.owner === socket.request.user.id) {
+ if (permission === 'freely' && !config.allowanonymous) return
+ note.permission = permission
+ models.Note.update({
+ permission: permission
+ }, {
+ where: {
+ id: noteId
+ }
+ }).then(function (count) {
+ if (!count) {
+ return
+ }
+ var out = {
+ permission: permission
+ }
+ realtime.io.to(note.id).emit('permission', out)
+ for (var i = 0, l = note.socks.length; i < l; i++) {
+ var sock = note.socks[i]
+ if (typeof sock !== 'undefined' && sock) {
+ // check view permission
+ if (!checkViewPermission(sock.request, note)) {
+ sock.emit('info', {
+ code: 403
+ })
+ setTimeout(function () {
+ sock.disconnect(true)
+ }, 0)
+ }
}
- }
- });
-
- // delete a note
- socket.on('delete', function () {
- //need login to do more actions
- if (socket.request.user && socket.request.user.logged_in) {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var note = notes[noteId];
- //Only owner can delete note
- if (note.owner && note.owner == socket.request.user.id) {
- models.Note.destroy({
- where: {
- id: noteId
- }
- }).then(function (count) {
- if (!count) return;
- for (var i = 0, l = note.socks.length; i < l; i++) {
- var sock = note.socks[i];
- if (typeof sock !== 'undefined' && sock) {
- sock.emit('delete');
- setTimeout(function () {
- sock.disconnect(true);
- }, 0);
- }
- }
- }).catch(function (err) {
- return logger.error('delete note failed: ' + err);
- });
+ }
+ }).catch(function (err) {
+ return logger.error('update note permission failed: ' + err)
+ })
+ }
+ }
+ })
+
+ // delete a note
+ socket.on('delete', function () {
+ // need login to do more actions
+ if (socket.request.user && socket.request.user.logged_in) {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var note = notes[noteId]
+ // Only owner can delete note
+ if (note.owner && note.owner === socket.request.user.id) {
+ models.Note.destroy({
+ where: {
+ id: noteId
+ }
+ }).then(function (count) {
+ if (!count) return
+ for (var i = 0, l = note.socks.length; i < l; i++) {
+ var sock = note.socks[i]
+ if (typeof sock !== 'undefined' && sock) {
+ sock.emit('delete')
+ setTimeout(function () {
+ sock.disconnect(true)
+ }, 0)
}
- }
- });
-
- //reveiced when user logout or changed
- socket.on('user changed', function () {
- logger.info('user changed');
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var user = notes[noteId].users[socket.id];
- if (!user) return;
- updateUserData(socket, user);
- emitOnlineUsers(socket);
- });
-
- //received sync of online users request
- socket.on('online users', function () {
- var noteId = socket.noteId;
- if (!noteId || !notes[noteId]) return;
- var users = [];
- Object.keys(notes[noteId].users).forEach(function (key) {
- var user = notes[noteId].users[key];
- if (user)
- users.push(buildUserOutData(user));
- });
- var out = {
- users: users
- };
- socket.emit('online users', out);
- });
-
- //check version
- socket.on('version', function () {
- socket.emit('version', {
- version: config.version,
- minimumCompatibleVersion: config.minimumCompatibleVersion
- });
- });
-
- //received cursor focus
- socket.on('cursor focus', function (data) {
- var noteId = socket.noteId;
- var user = users[socket.id];
- if (!noteId || !notes[noteId] || !user) return;
- user.cursor = data;
- var out = buildUserOutData(user);
- socket.broadcast.to(noteId).emit('cursor focus', out);
- });
-
- //received cursor activity
- socket.on('cursor activity', function (data) {
- var noteId = socket.noteId;
- var user = users[socket.id];
- if (!noteId || !notes[noteId] || !user) return;
- user.cursor = data;
- var out = buildUserOutData(user);
- socket.broadcast.to(noteId).emit('cursor activity', out);
- });
-
- //received cursor blur
- socket.on('cursor blur', function () {
- var noteId = socket.noteId;
- var user = users[socket.id];
- if (!noteId || !notes[noteId] || !user) return;
- user.cursor = null;
- var out = {
- id: socket.id
- };
- socket.broadcast.to(noteId).emit('cursor blur', out);
- });
-
- //when a new client disconnect
- socket.on('disconnect', function () {
- if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return;
- disconnectSocketQueue.push(socket);
- disconnect(socket);
- });
+ }
+ }).catch(function (err) {
+ return logger.error('delete note failed: ' + err)
+ })
+ }
+ }
+ })
+
+ // reveiced when user logout or changed
+ socket.on('user changed', function () {
+ logger.info('user changed')
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var user = notes[noteId].users[socket.id]
+ if (!user) return
+ updateUserData(socket, user)
+ emitOnlineUsers(socket)
+ })
+
+ // received sync of online users request
+ socket.on('online users', function () {
+ var noteId = socket.noteId
+ if (!noteId || !notes[noteId]) return
+ var users = []
+ Object.keys(notes[noteId].users).forEach(function (key) {
+ var user = notes[noteId].users[key]
+ if (user) { users.push(buildUserOutData(user)) }
+ })
+ var out = {
+ users: users
+ }
+ socket.emit('online users', out)
+ })
+
+ // check version
+ socket.on('version', function () {
+ socket.emit('version', {
+ version: config.version,
+ minimumCompatibleVersion: config.minimumCompatibleVersion
+ })
+ })
+
+ // received cursor focus
+ socket.on('cursor focus', function (data) {
+ var noteId = socket.noteId
+ var user = users[socket.id]
+ if (!noteId || !notes[noteId] || !user) return
+ user.cursor = data
+ var out = buildUserOutData(user)
+ socket.broadcast.to(noteId).emit('cursor focus', out)
+ })
+
+ // received cursor activity
+ socket.on('cursor activity', function (data) {
+ var noteId = socket.noteId
+ var user = users[socket.id]
+ if (!noteId || !notes[noteId] || !user) return
+ user.cursor = data
+ var out = buildUserOutData(user)
+ socket.broadcast.to(noteId).emit('cursor activity', out)
+ })
+
+ // received cursor blur
+ socket.on('cursor blur', function () {
+ var noteId = socket.noteId
+ var user = users[socket.id]
+ if (!noteId || !notes[noteId] || !user) return
+ user.cursor = null
+ var out = {
+ id: socket.id
+ }
+ socket.broadcast.to(noteId).emit('cursor blur', out)
+ })
+
+ // when a new client disconnect
+ socket.on('disconnect', function () {
+ if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return
+ disconnectSocketQueue.push(socket)
+ disconnect(socket)
+ })
}
-module.exports = realtime; \ No newline at end of file
+module.exports = realtime
diff --git a/lib/response.js b/lib/response.js
index 585d1d54..31fa18b2 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -1,609 +1,601 @@
-//response
-//external modules
-var fs = require('fs');
-var path = require('path');
-var markdownpdf = require("markdown-pdf");
-var LZString = require('lz-string');
-var S = require('string');
-var shortId = require('shortid');
-var querystring = require('querystring');
-var request = require('request');
-var moment = require('moment');
+// response
+// external modules
+var fs = require('fs')
+var markdownpdf = require('markdown-pdf')
+var LZString = require('lz-string')
+var shortId = require('shortid')
+var querystring = require('querystring')
+var request = require('request')
+var moment = require('moment')
-//core
-var config = require("./config.js");
-var logger = require("./logger.js");
-var models = require("./models");
+// core
+var config = require('./config.js')
+var logger = require('./logger.js')
+var models = require('./models')
-//public
+// public
var response = {
- errorForbidden: function (res) {
- responseError(res, "403", "Forbidden", "oh no.");
- },
- errorNotFound: function (res) {
- responseError(res, "404", "Not Found", "oops.");
- },
- errorBadRequest: function (res) {
- responseError(res, "400", "Bad Request", "something not right.");
- },
- errorInternalError: function (res) {
- responseError(res, "500", "Internal Error", "wtf.");
- },
- errorServiceUnavailable: function (res) {
- res.status(503).send("I'm busy right now, try again later.");
- },
- newNote: newNote,
- showNote: showNote,
- showPublishNote: showPublishNote,
- showPublishSlide: showPublishSlide,
- showIndex: showIndex,
- noteActions: noteActions,
- publishNoteActions: publishNoteActions,
- publishSlideActions: publishSlideActions,
- githubActions: githubActions,
- gitlabActions: gitlabActions
-};
+ errorForbidden: function (res) {
+ responseError(res, '403', 'Forbidden', 'oh no.')
+ },
+ errorNotFound: function (res) {
+ responseError(res, '404', 'Not Found', 'oops.')
+ },
+ errorBadRequest: function (res) {
+ responseError(res, '400', 'Bad Request', 'something not right.')
+ },
+ errorInternalError: function (res) {
+ responseError(res, '500', 'Internal Error', 'wtf.')
+ },
+ errorServiceUnavailable: function (res) {
+ res.status(503).send("I'm busy right now, try again later.")
+ },
+ newNote: newNote,
+ showNote: showNote,
+ showPublishNote: showPublishNote,
+ showPublishSlide: showPublishSlide,
+ showIndex: showIndex,
+ noteActions: noteActions,
+ publishNoteActions: publishNoteActions,
+ publishSlideActions: publishSlideActions,
+ githubActions: githubActions,
+ gitlabActions: gitlabActions
+}
-function responseError(res, code, detail, msg) {
- res.status(code).render(config.errorpath, {
- url: config.serverurl,
- title: code + ' ' + detail + ' ' + msg,
- code: code,
- detail: detail,
- msg: msg,
- useCDN: config.usecdn
- });
+function responseError (res, code, detail, msg) {
+ res.status(code).render(config.errorpath, {
+ url: config.serverurl,
+ title: code + ' ' + detail + ' ' + msg,
+ code: code,
+ detail: detail,
+ msg: msg,
+ useCDN: config.usecdn
+ })
}
-function showIndex(req, res, next) {
- res.render(config.indexpath, {
- url: config.serverurl,
- useCDN: config.usecdn,
- allowAnonymous: config.allowanonymous,
- facebook: config.facebook,
- twitter: config.twitter,
- github: config.github,
- gitlab: config.gitlab,
- dropbox: config.dropbox,
- google: config.google,
- ldap: config.ldap,
- email: config.email,
- allowemailregister: config.allowemailregister,
- signin: req.isAuthenticated(),
- infoMessage: req.flash('info'),
- errorMessage: req.flash('error')
- });
+function showIndex (req, res, next) {
+ res.render(config.indexpath, {
+ url: config.serverurl,
+ useCDN: config.usecdn,
+ allowAnonymous: config.allowanonymous,
+ facebook: config.facebook,
+ twitter: config.twitter,
+ github: config.github,
+ gitlab: config.gitlab,
+ dropbox: config.dropbox,
+ google: config.google,
+ ldap: config.ldap,
+ email: config.email,
+ allowemailregister: config.allowemailregister,
+ signin: req.isAuthenticated(),
+ infoMessage: req.flash('info'),
+ errorMessage: req.flash('error')
+ })
}
-function responseHackMD(res, note) {
- var body = note.content;
- var extracted = models.Note.extractMeta(body);
- var meta = models.Note.parseMeta(extracted.meta);
- var title = models.Note.decodeTitle(note.title);
- title = models.Note.generateWebTitle(meta.title || title);
- res.set({
- 'Cache-Control': 'private', // only cache by client
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.render(config.hackmdpath, {
- url: config.serverurl,
- title: title,
- useCDN: config.usecdn,
- allowAnonymous: config.allowanonymous,
- facebook: config.facebook,
- twitter: config.twitter,
- github: config.github,
- gitlab: config.gitlab,
- dropbox: config.dropbox,
- google: config.google,
- ldap: config.ldap,
- email: config.email,
- allowemailregister: config.allowemailregister
- });
+function responseHackMD (res, note) {
+ var body = note.content
+ var extracted = models.Note.extractMeta(body)
+ var meta = models.Note.parseMeta(extracted.meta)
+ var title = models.Note.decodeTitle(note.title)
+ title = models.Note.generateWebTitle(meta.title || title)
+ res.set({
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.render(config.hackmdpath, {
+ url: config.serverurl,
+ title: title,
+ useCDN: config.usecdn,
+ allowAnonymous: config.allowanonymous,
+ facebook: config.facebook,
+ twitter: config.twitter,
+ github: config.github,
+ gitlab: config.gitlab,
+ dropbox: config.dropbox,
+ google: config.google,
+ ldap: config.ldap,
+ email: config.email,
+ allowemailregister: config.allowemailregister
+ })
}
-function newNote(req, res, next) {
- var owner = null;
- if (req.isAuthenticated()) {
- owner = req.user.id;
- } else if (!config.allowanonymous) {
- return response.errorForbidden(res);
- }
- models.Note.create({
- ownerId: owner,
- alias: req.alias ? req.alias : null
- }).then(function (note) {
- return res.redirect(config.serverurl + "/" + LZString.compressToBase64(note.id));
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
+function newNote (req, res, next) {
+ var owner = null
+ if (req.isAuthenticated()) {
+ owner = req.user.id
+ } else if (!config.allowanonymous) {
+ return response.errorForbidden(res)
+ }
+ models.Note.create({
+ ownerId: owner,
+ alias: req.alias ? req.alias : null
+ }).then(function (note) {
+ return res.redirect(config.serverurl + '/' + LZString.compressToBase64(note.id))
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
}
-function checkViewPermission(req, note) {
- if (note.permission == 'private') {
- if (!req.isAuthenticated() || note.ownerId != req.user.id)
- return false;
- else
- return true;
- } else if (note.permission == 'limited' || note.permission == 'protected') {
- if(!req.isAuthenticated())
- return false;
- else
- return true;
- } else {
- return true;
- }
+function checkViewPermission (req, note) {
+ if (note.permission === 'private') {
+ if (!req.isAuthenticated() || note.ownerId !== req.user.id) { return false } else { return true }
+ } else if (note.permission === 'limited' || note.permission === 'protected') {
+ if (!req.isAuthenticated()) { return false } else { return true }
+ } else {
+ return true
+ }
}
-function findNote(req, res, callback, include) {
- var noteId = req.params.noteId;
- var id = req.params.noteId || req.params.shortid;
- models.Note.parseNoteId(id, function (err, _id) {
- models.Note.findOne({
- where: {
- id: _id
- },
- include: include || null
- }).then(function (note) {
- if (!note) {
- if (config.allowfreeurl && noteId) {
- req.alias = noteId;
- return newNote(req, res);
- } else {
- return response.errorNotFound(res);
- }
- }
- if (!checkViewPermission(req, note)) {
- return response.errorForbidden(res);
- } else {
- return callback(note);
- }
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
- });
+function findNote (req, res, callback, include) {
+ var noteId = req.params.noteId
+ var id = req.params.noteId || req.params.shortid
+ models.Note.parseNoteId(id, function (err, _id) {
+ if (err) {
+ logger.log(err)
+ }
+ models.Note.findOne({
+ where: {
+ id: _id
+ },
+ include: include || null
+ }).then(function (note) {
+ if (!note) {
+ if (config.allowfreeurl && noteId) {
+ req.alias = noteId
+ return newNote(req, res)
+ } else {
+ return response.errorNotFound(res)
+ }
+ }
+ if (!checkViewPermission(req, note)) {
+ return response.errorForbidden(res)
+ } else {
+ return callback(note)
+ }
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
+ })
}
-function showNote(req, res, next) {
- findNote(req, res, function (note) {
- // force to use note id
- var noteId = req.params.noteId;
- var id = LZString.compressToBase64(note.id);
- if ((note.alias && noteId != note.alias) || (!note.alias && noteId != id))
- return res.redirect(config.serverurl + "/" + (note.alias || id));
- return responseHackMD(res, note);
- });
+function showNote (req, res, next) {
+ findNote(req, res, function (note) {
+ // force to use note id
+ var noteId = req.params.noteId
+ var id = LZString.compressToBase64(note.id)
+ if ((note.alias && noteId !== note.alias) || (!note.alias && noteId !== id)) { return res.redirect(config.serverurl + '/' + (note.alias || id)) }
+ return responseHackMD(res, note)
+ })
}
-function showPublishNote(req, res, next) {
- var include = [{
- model: models.User,
- as: "owner"
- }, {
- model: models.User,
- as: "lastchangeuser"
- }];
- findNote(req, res, function (note) {
- // force to use short id
- var shortid = req.params.shortid;
- if ((note.alias && shortid != note.alias) || (!note.alias && shortid != note.shortid))
- return res.redirect(config.serverurl + "/s/" + (note.alias || note.shortid));
- note.increment('viewcount').then(function (note) {
- if (!note) {
- return response.errorNotFound(res);
- }
- var body = note.content;
- var extracted = models.Note.extractMeta(body);
- markdown = extracted.markdown;
- meta = models.Note.parseMeta(extracted.meta);
- var createtime = note.createdAt;
- var updatetime = note.lastchangeAt;
- var title = models.Note.decodeTitle(note.title);
- title = models.Note.generateWebTitle(meta.title || title);
- var origin = config.serverurl;
- var data = {
- title: title,
- description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
- viewcount: note.viewcount,
- createtime: createtime,
- updatetime: updatetime,
- url: origin,
- body: body,
- useCDN: config.usecdn,
- owner: note.owner ? note.owner.id : null,
- ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
- lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
- lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
- robots: meta.robots || false, //default allow robots
- GA: meta.GA,
- disqus: meta.disqus
- };
- return renderPublish(data, res);
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
- }, include);
+function showPublishNote (req, res, next) {
+ var include = [{
+ model: models.User,
+ as: 'owner'
+ }, {
+ model: models.User,
+ as: 'lastchangeuser'
+ }]
+ findNote(req, res, function (note) {
+ // force to use short id
+ var shortid = req.params.shortid
+ if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
+ return res.redirect(config.serverurl + '/s/' + (note.alias || note.shortid))
+ }
+ note.increment('viewcount').then(function (note) {
+ if (!note) {
+ return response.errorNotFound(res)
+ }
+ var body = note.content
+ var extracted = models.Note.extractMeta(body)
+ var markdown = extracted.markdown
+ var meta = models.Note.parseMeta(extracted.meta)
+ var createtime = note.createdAt
+ var updatetime = note.lastchangeAt
+ var title = models.Note.decodeTitle(note.title)
+ title = models.Note.generateWebTitle(meta.title || title)
+ var origin = config.serverurl
+ var data = {
+ title: title,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
+ viewcount: note.viewcount,
+ createtime: createtime,
+ updatetime: updatetime,
+ url: origin,
+ body: body,
+ useCDN: config.usecdn,
+ owner: note.owner ? note.owner.id : null,
+ ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
+ lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
+ lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
+ robots: meta.robots || false, // default allow robots
+ GA: meta.GA,
+ disqus: meta.disqus
+ }
+ return renderPublish(data, res)
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
+ }, include)
}
-function renderPublish(data, res) {
- res.set({
- 'Cache-Control': 'private' // only cache by client
- });
- res.render(config.prettypath, data);
+function renderPublish (data, res) {
+ res.set({
+ 'Cache-Control': 'private' // only cache by client
+ })
+ res.render(config.prettypath, data)
}
-function actionPublish(req, res, note) {
- res.redirect(config.serverurl + "/s/" + (note.alias || note.shortid));
+function actionPublish (req, res, note) {
+ res.redirect(config.serverurl + '/s/' + (note.alias || note.shortid))
}
-function actionSlide(req, res, note) {
- res.redirect(config.serverurl + "/p/" + (note.alias || note.shortid));
+function actionSlide (req, res, note) {
+ res.redirect(config.serverurl + '/p/' + (note.alias || note.shortid))
}
-function actionDownload(req, res, note) {
- var body = note.content;
- var title = models.Note.decodeTitle(note.title);
- var filename = title;
- filename = encodeURIComponent(filename);
- res.set({
- 'Access-Control-Allow-Origin': '*', //allow CORS as API
- 'Access-Control-Allow-Headers': 'Range',
- 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
- 'Content-Type': 'text/markdown; charset=UTF-8',
- 'Cache-Control': 'private',
- 'Content-disposition': 'attachment; filename=' + filename + '.md',
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.send(body);
+function actionDownload (req, res, note) {
+ var body = note.content
+ var title = models.Note.decodeTitle(note.title)
+ var filename = title
+ filename = encodeURIComponent(filename)
+ res.set({
+ 'Access-Control-Allow-Origin': '*', // allow CORS as API
+ 'Access-Control-Allow-Headers': 'Range',
+ 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
+ 'Content-Type': 'text/markdown; charset=UTF-8',
+ 'Cache-Control': 'private',
+ 'Content-disposition': 'attachment; filename=' + filename + '.md',
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.send(body)
}
-function actionInfo(req, res, note) {
- var body = note.content;
- var extracted = models.Note.extractMeta(body);
- var markdown = extracted.markdown;
- var meta = models.Note.parseMeta(extracted.meta);
- var createtime = note.createdAt;
- var updatetime = note.lastchangeAt;
- var title = models.Note.decodeTitle(note.title);
- var data = {
- title: meta.title || title,
- description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
- viewcount: note.viewcount,
- createtime: createtime,
- updatetime: updatetime
- };
- res.set({
- 'Access-Control-Allow-Origin': '*', //allow CORS as API
- 'Access-Control-Allow-Headers': 'Range',
- 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
- 'Cache-Control': 'private', // only cache by client
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.send(data);
+function actionInfo (req, res, note) {
+ var body = note.content
+ var extracted = models.Note.extractMeta(body)
+ var markdown = extracted.markdown
+ var meta = models.Note.parseMeta(extracted.meta)
+ var createtime = note.createdAt
+ var updatetime = note.lastchangeAt
+ var title = models.Note.decodeTitle(note.title)
+ var data = {
+ title: meta.title || title,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
+ viewcount: note.viewcount,
+ createtime: createtime,
+ updatetime: updatetime
+ }
+ res.set({
+ 'Access-Control-Allow-Origin': '*', // allow CORS as API
+ 'Access-Control-Allow-Headers': 'Range',
+ 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.send(data)
}
-function actionPDF(req, res, note) {
- var body = note.content;
- var extracted = models.Note.extractMeta(body);
- var title = models.Note.decodeTitle(note.title);
+function actionPDF (req, res, note) {
+ var body = note.content
+ var extracted = models.Note.extractMeta(body)
+ var title = models.Note.decodeTitle(note.title)
- if (!fs.existsSync(config.tmppath)) {
- fs.mkdirSync(config.tmppath);
- }
- var path = config.tmppath + '/' + Date.now() + '.pdf';
- markdownpdf().from.string(extracted.markdown).to(path, function () {
- var stream = fs.createReadStream(path);
- var filename = title;
- // Be careful of special characters
- filename = encodeURIComponent(filename);
- // Ideally this should strip them
- res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"');
- res.setHeader('Cache-Control', 'private');
- res.setHeader('Content-Type', 'application/pdf; charset=UTF-8');
- res.setHeader('X-Robots-Tag', 'noindex, nofollow'); // prevent crawling
- stream.pipe(res);
- fs.unlink(path);
- });
+ if (!fs.existsSync(config.tmppath)) {
+ fs.mkdirSync(config.tmppath)
+ }
+ var path = config.tmppath + '/' + Date.now() + '.pdf'
+ markdownpdf().from.string(extracted.markdown).to(path, function () {
+ var stream = fs.createReadStream(path)
+ var filename = title
+ // Be careful of special characters
+ filename = encodeURIComponent(filename)
+ // Ideally this should strip them
+ res.setHeader('Content-disposition', 'attachment; filename="' + filename + '.pdf"')
+ res.setHeader('Cache-Control', 'private')
+ res.setHeader('Content-Type', 'application/pdf; charset=UTF-8')
+ res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
+ stream.pipe(res)
+ fs.unlink(path)
+ })
}
-function actionGist(req, res, note) {
- var data = {
- client_id: config.github.clientID,
- redirect_uri: config.serverurl + '/auth/github/callback/' + LZString.compressToBase64(note.id) + '/gist',
- scope: "gist",
- state: shortId.generate()
- };
- var query = querystring.stringify(data);
- res.redirect("https://github.com/login/oauth/authorize?" + query);
+function actionGist (req, res, note) {
+ var data = {
+ client_id: config.github.clientID,
+ redirect_uri: config.serverurl + '/auth/github/callback/' + LZString.compressToBase64(note.id) + '/gist',
+ scope: 'gist',
+ state: shortId.generate()
+ }
+ var query = querystring.stringify(data)
+ res.redirect('https://github.com/login/oauth/authorize?' + query)
}
-function actionRevision(req, res, note) {
- var actionId = req.params.actionId;
- if (actionId) {
- var time = moment(parseInt(actionId));
- if (time.isValid()) {
- models.Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) {
- if (err) {
- logger.error(err);
- return response.errorInternalError(res);
- }
- if (!content) {
- return response.errorNotFound(res);
- }
- res.set({
- 'Access-Control-Allow-Origin': '*', //allow CORS as API
- 'Access-Control-Allow-Headers': 'Range',
- 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
- 'Cache-Control': 'private', // only cache by client
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.send(content);
- });
- } else {
- return response.errorNotFound(res);
+function actionRevision (req, res, note) {
+ var actionId = req.params.actionId
+ if (actionId) {
+ var time = moment(parseInt(actionId))
+ if (time.isValid()) {
+ models.Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) {
+ if (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
}
+ if (!content) {
+ return response.errorNotFound(res)
+ }
+ res.set({
+ 'Access-Control-Allow-Origin': '*', // allow CORS as API
+ 'Access-Control-Allow-Headers': 'Range',
+ 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.send(content)
+ })
} else {
- models.Revision.getNoteRevisions(note, function (err, data) {
- if (err) {
- logger.error(err);
- return response.errorInternalError(res);
- }
- var out = {
- revision: data
- };
- res.set({
- 'Access-Control-Allow-Origin': '*', //allow CORS as API
- 'Access-Control-Allow-Headers': 'Range',
- 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
- 'Cache-Control': 'private', // only cache by client
- 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
- });
- res.send(out);
- });
+ return response.errorNotFound(res)
}
+ } else {
+ models.Revision.getNoteRevisions(note, function (err, data) {
+ if (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ }
+ var out = {
+ revision: data
+ }
+ res.set({
+ 'Access-Control-Allow-Origin': '*', // allow CORS as API
+ 'Access-Control-Allow-Headers': 'Range',
+ 'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
+ 'Cache-Control': 'private', // only cache by client
+ 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
+ })
+ res.send(out)
+ })
+ }
}
-function noteActions(req, res, next) {
- var noteId = req.params.noteId;
- findNote(req, res, function (note) {
- var action = req.params.action;
- switch (action) {
- case "publish":
- case "pretty": //pretty deprecated
- actionPublish(req, res, note);
- break;
- case "slide":
- actionSlide(req, res, note);
- break;
- case "download":
- actionDownload(req, res, note);
- break;
- case "info":
- actionInfo(req, res, note);
- break;
- case "pdf":
- actionPDF(req, res, note);
- break;
- case "gist":
- actionGist(req, res, note);
- break;
- case "revision":
- actionRevision(req, res, note);
- break;
- default:
- return res.redirect(config.serverurl + '/' + noteId);
- break;
- }
- });
+function noteActions (req, res, next) {
+ var noteId = req.params.noteId
+ findNote(req, res, function (note) {
+ var action = req.params.action
+ switch (action) {
+ case 'publish':
+ case 'pretty': // pretty deprecated
+ actionPublish(req, res, note)
+ break
+ case 'slide':
+ actionSlide(req, res, note)
+ break
+ case 'download':
+ actionDownload(req, res, note)
+ break
+ case 'info':
+ actionInfo(req, res, note)
+ break
+ case 'pdf':
+ actionPDF(req, res, note)
+ break
+ case 'gist':
+ actionGist(req, res, note)
+ break
+ case 'revision':
+ actionRevision(req, res, note)
+ break
+ default:
+ return res.redirect(config.serverurl + '/' + noteId)
+ }
+ })
}
-function publishNoteActions(req, res, next) {
- findNote(req, res, function (note) {
- var action = req.params.action;
- switch (action) {
- case "edit":
- res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)));
- break;
- default:
- res.redirect(config.serverurl + '/s/' + note.shortid);
- break;
- }
- });
+function publishNoteActions (req, res, next) {
+ findNote(req, res, function (note) {
+ var action = req.params.action
+ switch (action) {
+ case 'edit':
+ res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)))
+ break
+ default:
+ res.redirect(config.serverurl + '/s/' + note.shortid)
+ break
+ }
+ })
}
-function publishSlideActions(req, res, next) {
- findNote(req, res, function (note) {
- var action = req.params.action;
- switch (action) {
- case "edit":
- res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)));
- break;
- default:
- res.redirect(config.serverurl + '/p/' + note.shortid);
- break;
- }
- });
+function publishSlideActions (req, res, next) {
+ findNote(req, res, function (note) {
+ var action = req.params.action
+ switch (action) {
+ case 'edit':
+ res.redirect(config.serverurl + '/' + (note.alias ? note.alias : LZString.compressToBase64(note.id)))
+ break
+ default:
+ res.redirect(config.serverurl + '/p/' + note.shortid)
+ break
+ }
+ })
}
-function githubActions(req, res, next) {
- var noteId = req.params.noteId;
- findNote(req, res, function (note) {
- var action = req.params.action;
- switch (action) {
- case "gist":
- githubActionGist(req, res, note);
- break;
- default:
- res.redirect(config.serverurl + '/' + noteId);
- break;
- }
- });
+function githubActions (req, res, next) {
+ var noteId = req.params.noteId
+ findNote(req, res, function (note) {
+ var action = req.params.action
+ switch (action) {
+ case 'gist':
+ githubActionGist(req, res, note)
+ break
+ default:
+ res.redirect(config.serverurl + '/' + noteId)
+ break
+ }
+ })
}
-function githubActionGist(req, res, note) {
- var code = req.query.code;
- var state = req.query.state;
- if (!code || !state) {
- return response.errorForbidden(res);
- } else {
- var data = {
- client_id: config.github.clientID,
- client_secret: config.github.clientSecret,
- code: code,
- state: state
- }
- var auth_url = 'https://github.com/login/oauth/access_token';
- request({
- url: auth_url,
- method: "POST",
- json: data
- }, function (error, httpResponse, body) {
- if (!error && httpResponse.statusCode == 200) {
- var access_token = body.access_token;
- if (access_token) {
- var content = note.content;
- var title = models.Note.decodeTitle(note.title);
- var filename = title.replace('/', ' ') + '.md';
- var gist = {
- "files": {}
- };
- gist.files[filename] = {
- "content": content
- };
- var gist_url = "https://api.github.com/gists";
- request({
- url: gist_url,
- headers: {
- 'User-Agent': 'HackMD',
- 'Authorization': 'token ' + access_token
- },
- method: "POST",
- json: gist
- }, function (error, httpResponse, body) {
- if (!error && httpResponse.statusCode == 201) {
- res.setHeader('referer', '');
- res.redirect(body.html_url);
- } else {
- return response.errorForbidden(res);
- }
- });
- } else {
- return response.errorForbidden(res);
- }
+function githubActionGist (req, res, note) {
+ var code = req.query.code
+ var state = req.query.state
+ if (!code || !state) {
+ return response.errorForbidden(res)
+ } else {
+ var data = {
+ client_id: config.github.clientID,
+ client_secret: config.github.clientSecret,
+ code: code,
+ state: state
+ }
+ var authUrl = 'https://github.com/login/oauth/access_token'
+ request({
+ url: authUrl,
+ method: 'POST',
+ json: data
+ }, function (error, httpResponse, body) {
+ if (!error && httpResponse.statusCode === 200) {
+ var accessToken = body.access_token
+ if (accessToken) {
+ var content = note.content
+ var title = models.Note.decodeTitle(note.title)
+ var filename = title.replace('/', ' ') + '.md'
+ var gist = {
+ 'files': {}
+ }
+ gist.files[filename] = {
+ 'content': content
+ }
+ var gistUrl = 'https://api.github.com/gists'
+ request({
+ url: gistUrl,
+ headers: {
+ 'User-Agent': 'HackMD',
+ 'Authorization': 'token ' + accessToken
+ },
+ method: 'POST',
+ json: gist
+ }, function (error, httpResponse, body) {
+ if (!error && httpResponse.statusCode === 201) {
+ res.setHeader('referer', '')
+ res.redirect(body.html_url)
} else {
- return response.errorForbidden(res);
+ return response.errorForbidden(res)
}
- })
- }
+ })
+ } else {
+ return response.errorForbidden(res)
+ }
+ } else {
+ return response.errorForbidden(res)
+ }
+ })
+ }
}
-function gitlabActions(req, res, next) {
- var noteId = req.params.noteId;
- findNote(req, res, function (note) {
- var action = req.params.action;
- switch (action) {
- case "projects":
- gitlabActionProjects(req, res, note);
- break;
- default:
- res.redirect(config.serverurl + '/' + noteId);
- break;
- }
- });
+function gitlabActions (req, res, next) {
+ var noteId = req.params.noteId
+ findNote(req, res, function (note) {
+ var action = req.params.action
+ switch (action) {
+ case 'projects':
+ gitlabActionProjects(req, res, note)
+ break
+ default:
+ res.redirect(config.serverurl + '/' + noteId)
+ break
+ }
+ })
}
-function gitlabActionProjects(req, res, note) {
- if (req.isAuthenticated()) {
- models.User.findOne({
- where: {
- id: req.user.id
- }
- }).then(function (user) {
- if (!user)
- return response.errorNotFound(res);
- var ret = { baseURL: config.gitlab.baseURL };
- ret.accesstoken = user.accessToken;
- ret.profileid = user.profileid;
- request(
+function gitlabActionProjects (req, res, note) {
+ if (req.isAuthenticated()) {
+ models.User.findOne({
+ where: {
+ id: req.user.id
+ }
+ }).then(function (user) {
+ if (!user) { return response.errorNotFound(res) }
+ var ret = { baseURL: config.gitlab.baseURL }
+ ret.accesstoken = user.accessToken
+ ret.profileid = user.profileid
+ request(
config.gitlab.baseURL + '/api/v3/projects?access_token=' + user.accessToken,
- function(error, httpResponse, body) {
- if (!error && httpResponse.statusCode == 200) {
- ret.projects = JSON.parse(body);
- return res.send(ret);
- } else {
- return res.send(ret);
- }
+ function (error, httpResponse, body) {
+ if (!error && httpResponse.statusCode === 200) {
+ ret.projects = JSON.parse(body)
+ return res.send(ret)
+ } else {
+ return res.send(ret)
+ }
}
- );
- }).catch(function (err) {
- logger.error('gitlab action projects failed: ' + err);
- return response.errorInternalError(res);
- });
- } else {
- return response.errorForbidden(res);
- }
+ )
+ }).catch(function (err) {
+ logger.error('gitlab action projects failed: ' + err)
+ return response.errorInternalError(res)
+ })
+ } else {
+ return response.errorForbidden(res)
+ }
}
-function showPublishSlide(req, res, next) {
- var include = [{
- model: models.User,
- as: "owner"
- }, {
- model: models.User,
- as: "lastchangeuser"
- }];
- findNote(req, res, function (note) {
- // force to use short id
- var shortid = req.params.shortid;
- if ((note.alias && shortid != note.alias) || (!note.alias && shortid != note.shortid))
- return res.redirect(config.serverurl + "/p/" + (note.alias || note.shortid));
- note.increment('viewcount').then(function (note) {
- if (!note) {
- return response.errorNotFound(res);
- }
- var body = note.content;
- var extracted = models.Note.extractMeta(body);
- markdown = extracted.markdown;
- meta = models.Note.parseMeta(extracted.meta);
- var createtime = note.createdAt;
- var updatetime = note.lastchangeAt;
- var title = models.Note.decodeTitle(note.title);
- title = models.Note.generateWebTitle(meta.title || title);
- var origin = config.serverurl;
- var data = {
- title: title,
- description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
- viewcount: note.viewcount,
- createtime: createtime,
- updatetime: updatetime,
- url: origin,
- body: markdown,
- meta: JSON.stringify(extracted.meta),
- useCDN: config.usecdn,
- owner: note.owner ? note.owner.id : null,
- ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
- lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
- lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
- robots: meta.robots || false, //default allow robots
- GA: meta.GA,
- disqus: meta.disqus
- };
- return renderPublishSlide(data, res);
- }).catch(function (err) {
- logger.error(err);
- return response.errorInternalError(res);
- });
- }, include);
+function showPublishSlide (req, res, next) {
+ var include = [{
+ model: models.User,
+ as: 'owner'
+ }, {
+ model: models.User,
+ as: 'lastchangeuser'
+ }]
+ findNote(req, res, function (note) {
+ // force to use short id
+ var shortid = req.params.shortid
+ if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) { return res.redirect(config.serverurl + '/p/' + (note.alias || note.shortid)) }
+ note.increment('viewcount').then(function (note) {
+ if (!note) {
+ return response.errorNotFound(res)
+ }
+ var body = note.content
+ var extracted = models.Note.extractMeta(body)
+ var markdown = extracted.markdown
+ var meta = models.Note.parseMeta(extracted.meta)
+ var createtime = note.createdAt
+ var updatetime = note.lastchangeAt
+ var title = models.Note.decodeTitle(note.title)
+ title = models.Note.generateWebTitle(meta.title || title)
+ var origin = config.serverurl
+ var data = {
+ title: title,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
+ viewcount: note.viewcount,
+ createtime: createtime,
+ updatetime: updatetime,
+ url: origin,
+ body: markdown,
+ meta: JSON.stringify(extracted.meta),
+ useCDN: config.usecdn,
+ owner: note.owner ? note.owner.id : null,
+ ownerprofile: note.owner ? models.User.getProfile(note.owner) : null,
+ lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
+ lastchangeuserprofile: note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null,
+ robots: meta.robots || false, // default allow robots
+ GA: meta.GA,
+ disqus: meta.disqus
+ }
+ return renderPublishSlide(data, res)
+ }).catch(function (err) {
+ logger.error(err)
+ return response.errorInternalError(res)
+ })
+ }, include)
}
-function renderPublishSlide(data, res) {
- res.set({
- 'Cache-Control': 'private' // only cache by client
- });
- res.render(config.slidepath, data);
+function renderPublishSlide (data, res) {
+ res.set({
+ 'Cache-Control': 'private' // only cache by client
+ })
+ res.render(config.slidepath, data)
}
-module.exports = response;
+module.exports = response
diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js
index 8e69636e..6a1da981 100644
--- a/lib/workers/dmpWorker.js
+++ b/lib/workers/dmpWorker.js
@@ -1,140 +1,137 @@
// external modules
-var DiffMatchPatch = require('diff-match-patch');
-var dmp = new DiffMatchPatch();
+var DiffMatchPatch = require('diff-match-patch')
+var dmp = new DiffMatchPatch()
// core
-var config = require("../config.js");
-var logger = require("../logger.js");
+var config = require('../config.js')
+var logger = require('../logger.js')
-process.on('message', function(data) {
- if (!data || !data.msg || !data.cacheKey) {
- return logger.error('dmp worker error: not enough data');
- }
- switch (data.msg) {
- case 'create patch':
- if (!data.hasOwnProperty('lastDoc') || !data.hasOwnProperty('currDoc')) {
- return logger.error('dmp worker error: not enough data on create patch');
- }
- try {
- var patch = createPatch(data.lastDoc, data.currDoc);
- process.send({
- msg: 'check',
- result: patch,
- cacheKey: data.cacheKey
- });
- } catch (err) {
- logger.error('dmp worker error', err);
- process.send({
- msg: 'error',
- error: err,
- cacheKey: data.cacheKey
- });
- }
- break;
- case 'get revision':
- if (!data.hasOwnProperty('revisions') || !data.hasOwnProperty('count')) {
- return logger.error('dmp worker error: not enough data on get revision');
- }
- try {
- var result = getRevision(data.revisions, data.count);
- process.send({
- msg: 'check',
- result: result,
- cacheKey: data.cacheKey
- });
- } catch (err) {
- logger.error('dmp worker error', err);
- process.send({
- msg: 'error',
- error: err,
- cacheKey: data.cacheKey
- });
- }
- break;
- }
-});
+process.on('message', function (data) {
+ if (!data || !data.msg || !data.cacheKey) {
+ return logger.error('dmp worker error: not enough data')
+ }
+ switch (data.msg) {
+ case 'create patch':
+ if (!data.hasOwnProperty('lastDoc') || !data.hasOwnProperty('currDoc')) {
+ return logger.error('dmp worker error: not enough data on create patch')
+ }
+ try {
+ var patch = createPatch(data.lastDoc, data.currDoc)
+ process.send({
+ msg: 'check',
+ result: patch,
+ cacheKey: data.cacheKey
+ })
+ } catch (err) {
+ logger.error('dmp worker error', err)
+ process.send({
+ msg: 'error',
+ error: err,
+ cacheKey: data.cacheKey
+ })
+ }
+ break
+ case 'get revision':
+ if (!data.hasOwnProperty('revisions') || !data.hasOwnProperty('count')) {
+ return logger.error('dmp worker error: not enough data on get revision')
+ }
+ try {
+ var result = getRevision(data.revisions, data.count)
+ process.send({
+ msg: 'check',
+ result: result,
+ cacheKey: data.cacheKey
+ })
+ } catch (err) {
+ logger.error('dmp worker error', err)
+ process.send({
+ msg: 'error',
+ error: err,
+ cacheKey: data.cacheKey
+ })
+ }
+ break
+ }
+})
-function createPatch(lastDoc, currDoc) {
- var ms_start = (new Date()).getTime();
- var diff = dmp.diff_main(lastDoc, currDoc);
- var patch = dmp.patch_make(lastDoc, diff);
- patch = dmp.patch_toText(patch);
- var ms_end = (new Date()).getTime();
- if (config.debug) {
- logger.info(patch);
- logger.info((ms_end - ms_start) + 'ms');
- }
- return patch;
+function createPatch (lastDoc, currDoc) {
+ var msStart = (new Date()).getTime()
+ var diff = dmp.diff_main(lastDoc, currDoc)
+ var patch = dmp.patch_make(lastDoc, diff)
+ patch = dmp.patch_toText(patch)
+ var msEnd = (new Date()).getTime()
+ if (config.debug) {
+ logger.info(patch)
+ logger.info((msEnd - msStart) + 'ms')
+ }
+ return patch
}
-function getRevision(revisions, count) {
- var ms_start = (new Date()).getTime();
- var startContent = null;
- var lastPatch = [];
- var applyPatches = [];
- var authorship = [];
- if (count <= Math.round(revisions.length / 2)) {
- // start from top to target
- for (var i = 0; i < count; i++) {
- var revision = revisions[i];
- if (i == 0) {
- startContent = revision.content || revision.lastContent;
- }
- if (i != count - 1) {
- var patch = dmp.patch_fromText(revision.patch);
- applyPatches = applyPatches.concat(patch);
- }
- lastPatch = revision.patch;
- authorship = revision.authorship;
- }
- // swap DIFF_INSERT and DIFF_DELETE to achieve unpatching
- for (var i = 0, l = applyPatches.length; i < l; i++) {
- for (var j = 0, m = applyPatches[i].diffs.length; j < m; j++) {
- var diff = applyPatches[i].diffs[j];
- if (diff[0] == DiffMatchPatch.DIFF_INSERT)
- diff[0] = DiffMatchPatch.DIFF_DELETE;
- else if (diff[0] == DiffMatchPatch.DIFF_DELETE)
- diff[0] = DiffMatchPatch.DIFF_INSERT;
- }
- }
- } else {
- // start from bottom to target
- var l = revisions.length - 1;
- for (var i = l; i >= count - 1; i--) {
- var revision = revisions[i];
- if (i == l) {
- startContent = revision.lastContent;
- authorship = revision.authorship;
- }
- if (revision.patch) {
- var patch = dmp.patch_fromText(revision.patch);
- applyPatches = applyPatches.concat(patch);
- }
- lastPatch = revision.patch;
- authorship = revision.authorship;
- }
+function getRevision (revisions, count) {
+ var msStart = (new Date()).getTime()
+ var startContent = null
+ var lastPatch = []
+ var applyPatches = []
+ var authorship = []
+ if (count <= Math.round(revisions.length / 2)) {
+ // start from top to target
+ for (let i = 0; i < count; i++) {
+ let revision = revisions[i]
+ if (i === 0) {
+ startContent = revision.content || revision.lastContent
+ }
+ if (i !== count - 1) {
+ let patch = dmp.patch_fromText(revision.patch)
+ applyPatches = applyPatches.concat(patch)
+ }
+ lastPatch = revision.patch
+ authorship = revision.authorship
}
- try {
- var finalContent = dmp.patch_apply(applyPatches, startContent)[0];
- } catch (err) {
- throw new Error(err);
+ // swap DIFF_INSERT and DIFF_DELETE to achieve unpatching
+ for (let i = 0, l = applyPatches.length; i < l; i++) {
+ for (let j = 0, m = applyPatches[i].diffs.length; j < m; j++) {
+ var diff = applyPatches[i].diffs[j]
+ if (diff[0] === DiffMatchPatch.DIFF_INSERT) { diff[0] = DiffMatchPatch.DIFF_DELETE } else if (diff[0] === DiffMatchPatch.DIFF_DELETE) { diff[0] = DiffMatchPatch.DIFF_INSERT }
+ }
}
- var data = {
- content: finalContent,
- patch: dmp.patch_fromText(lastPatch),
- authorship: authorship
- };
- var ms_end = (new Date()).getTime();
- if (config.debug) {
- logger.info((ms_end - ms_start) + 'ms');
+ } else {
+ // start from bottom to target
+ var l = revisions.length - 1
+ for (var i = l; i >= count - 1; i--) {
+ let revision = revisions[i]
+ if (i === l) {
+ startContent = revision.lastContent
+ authorship = revision.authorship
+ }
+ if (revision.patch) {
+ let patch = dmp.patch_fromText(revision.patch)
+ applyPatches = applyPatches.concat(patch)
+ }
+ lastPatch = revision.patch
+ authorship = revision.authorship
}
- return data;
+ }
+ try {
+ var finalContent = dmp.patch_apply(applyPatches, startContent)[0]
+ } catch (err) {
+ throw new Error(err)
+ }
+ var data = {
+ content: finalContent,
+ patch: dmp.patch_fromText(lastPatch),
+ authorship: authorship
+ }
+ var msEnd = (new Date()).getTime()
+ if (config.debug) {
+ logger.info((msEnd - msStart) + 'ms')
+ }
+ return data
}
// log uncaught exception
process.on('uncaughtException', function (err) {
- logger.error('An uncaught exception has occured.');
- logger.error(err);
- logger.error('Process will exit now.');
- process.exit(1);
-}); \ No newline at end of file
+ logger.error('An uncaught exception has occured.')
+ logger.error(err)
+ logger.error('Process will exit now.')
+ process.exit(1)
+})
diff --git a/locales/ru.json b/locales/ru.json
index a1a95aaf..f87f7c69 100644
--- a/locales/ru.json
+++ b/locales/ru.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.": "Лучший способ, чтобы записывать и делиться своими знаниями markdown.",
+ "Best way to write and share your knowledge in markdown.": "Лучший способ записывать свои знания и делиться ими в формате markdown.",
"Intro": "Введение",
"History": "История",
"New guest note": "Новая гостевая заметка",
@@ -101,4 +101,4 @@
"OR": "ИЛИ",
"Export to Snippet": "Экспорт фрагмента кода",
"Select Visibility Level": "Выберите уровень видимости"
-} \ No newline at end of file
+}
diff --git a/package.json b/package.json
index a179d93e..05789321 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
"main": "app.js",
"license": "MIT",
"scripts": {
- "test": "npm run-script lint",
- "lint": "eslint .",
+ "test": "npm run-script standard",
+ "standard": "node ./node_modules/standard/bin/cmd.js",
"dev": "webpack --config webpack.config.js --progress --colors --watch",
"build": "webpack --config webpack.production.js --progress --colors",
"postinstall": "bin/heroku",
@@ -152,7 +152,6 @@
"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",
@@ -165,8 +164,15 @@
"optimize-css-assets-webpack-plugin": "^1.3.0",
"script-loader": "^0.7.0",
"style-loader": "^0.13.1",
+ "standard": "^9.0.1",
"url-loader": "^0.5.7",
"webpack": "^1.14.0",
"webpack-parallel-uglify-plugin": "^0.2.0"
+ },
+ "standard": {
+ "ignore": [
+ "lib/ot",
+ "public/vendor"
+ ]
}
}
diff --git a/public/js/cover.js b/public/js/cover.js
index bc6e73f9..a45a1c13 100644
--- a/public/js/cover.js
+++ b/public/js/cover.js
@@ -1,7 +1,10 @@
-require('./locale');
+/* eslint-env browser, jquery */
+/* global moment, serverurl */
-require('../css/cover.css');
-require('../css/site.css');
+require('./locale')
+
+require('../css/cover.css')
+require('../css/site.css')
import {
checkIfAuth,
@@ -9,7 +12,7 @@ import {
getLoginState,
resetCheckAuth,
setloginStateChangeEvent
-} from './lib/common/login';
+} from './lib/common/login'
import {
clearDuplicatedHistory,
@@ -23,411 +26,403 @@ import {
removeHistory,
saveHistory,
saveStorageHistoryToServer
-} from './history';
+} from './history'
-import { saveAs } from 'file-saver';
-import List from 'list.js';
-import S from 'string';
+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-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\
- <div class="content">\
- <h4 class="text"></h4>\
- <p>\
- <i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>\
- <br>\
- <i class="timestamp" style="display:none;"></i>\
- <i class="time"></i>\
- </p>\
- <p class="tags"></p>\
- </div>\
- </div>\
- </a>\
- </li>',
- page: 18,
- plugins: [
- ListPagination({
- outerWindow: 1
- })
- ]
-};
-const historyList = new List('history', options);
-
-migrateHistoryFromTempCallback = pageInit;
-setloginStateChangeEvent(pageInit);
-
-pageInit();
-
-function pageInit() {
- checkIfAuth(
+ 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-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>' +
+ '<div class="content">' +
+ '<h4 class="text"></h4>' +
+ '<p>' +
+ '<i><i class="fa fa-clock-o"></i> visited </i><i class="fromNow"></i>' +
+ '<br>' +
+ '<i class="timestamp" style="display:none;"></i>' +
+ '<i class="time"></i>' +
+ '</p>' +
+ '<p class="tags"></p>' +
+ '</div>' +
+ '</div>' +
+ '</a>' +
+ '</li>',
+ page: 18,
+ plugins: [
+ window.ListPagination({
+ outerWindow: 1
+ })
+ ]
+}
+const historyList = new List('history', options)
+
+window.migrateHistoryFromTempCallback = pageInit
+setloginStateChangeEvent(pageInit)
+
+pageInit()
+
+function pageInit () {
+ checkIfAuth(
data => {
- $('.ui-signin').hide();
- $('.ui-or').hide();
- $('.ui-welcome').show();
- if (data.photo) $('.ui-avatar').prop('src', data.photo).show();
- else $('.ui-avatar').prop('src', '').hide();
- $('.ui-name').html(data.name);
- $('.ui-signout').show();
- $(".ui-history").click();
- parseServerToHistory(historyList, parseHistoryCallback);
+ $('.ui-signin').hide()
+ $('.ui-or').hide()
+ $('.ui-welcome').show()
+ if (data.photo) $('.ui-avatar').prop('src', data.photo).show()
+ else $('.ui-avatar').prop('src', '').hide()
+ $('.ui-name').html(data.name)
+ $('.ui-signout').show()
+ $('.ui-history').click()
+ parseServerToHistory(historyList, parseHistoryCallback)
},
() => {
- $('.ui-signin').show();
- $('.ui-or').show();
- $('.ui-welcome').hide();
- $('.ui-avatar').prop('src', '').hide();
- $('.ui-name').html('');
- $('.ui-signout').hide();
- parseStorageToHistory(historyList, parseHistoryCallback);
+ $('.ui-signin').show()
+ $('.ui-or').show()
+ $('.ui-welcome').hide()
+ $('.ui-avatar').prop('src', '').hide()
+ $('.ui-name').html('')
+ $('.ui-signout').hide()
+ parseStorageToHistory(historyList, parseHistoryCallback)
}
- );
+ )
}
-$(".masthead-nav li").click(function () {
- $(this).siblings().removeClass("active");
- $(this).addClass("active");
-});
+$('.masthead-nav li').click(function () {
+ $(this).siblings().removeClass('active')
+ $(this).addClass('active')
+})
// prevent empty link change hash
$('a[href="#"]').click(function (e) {
- e.preventDefault();
-});
-
-$(".ui-home").click(function (e) {
- if (!$("#home").is(':visible')) {
- $(".section:visible").hide();
- $("#home").fadeIn();
- }
-});
-
-$(".ui-history").click(() => {
- if (!$("#history").is(':visible')) {
- $(".section:visible").hide();
- $("#history").fadeIn();
- }
-});
-
-function checkHistoryList() {
- if ($("#history-list").children().length > 0) {
- $('.pagination').show();
- $(".ui-nohistory").hide();
- $(".ui-import-from-browser").hide();
- } else if ($("#history-list").children().length == 0) {
- $('.pagination').hide();
- $(".ui-nohistory").slideDown();
- getStorageHistory(data => {
- if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) {
- $(".ui-import-from-browser").slideDown();
- }
- });
- }
+ e.preventDefault()
+})
+
+$('.ui-home').click(function (e) {
+ if (!$('#home').is(':visible')) {
+ $('.section:visible').hide()
+ $('#home').fadeIn()
+ }
+})
+
+$('.ui-history').click(() => {
+ if (!$('#history').is(':visible')) {
+ $('.section:visible').hide()
+ $('#history').fadeIn()
+ }
+})
+
+function checkHistoryList () {
+ if ($('#history-list').children().length > 0) {
+ $('.pagination').show()
+ $('.ui-nohistory').hide()
+ $('.ui-import-from-browser').hide()
+ } else if ($('#history-list').children().length === 0) {
+ $('.pagination').hide()
+ $('.ui-nohistory').slideDown()
+ getStorageHistory(data => {
+ if (data && data.length > 0 && getLoginState() && historyList.items.length === 0) {
+ $('.ui-import-from-browser').slideDown()
+ }
+ })
+ }
}
-function parseHistoryCallback(list, notehistory) {
- checkHistoryList();
- //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;
- }
- }
+function parseHistoryCallback (list, notehistory) {
+ checkHistoryList()
+ // 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
}
- });
+ }
+ }
+ })
// parse filter 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 (let j = 0; j < tags.length; j++) {
- //push info filtertags if not found
- let found = false;
- if (filtertags.includes(tags[j]))
- found = true;
- if (!found)
- filtertags.push(tags[j]);
- }
- }
+ 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 (let j = 0; j < tags.length; j++) {
+ // push info filtertags if not found
+ let found = false
+ if (filtertags.includes(tags[j])) { found = true }
+ if (!found) { filtertags.push(tags[j]) }
+ }
}
- buildTagsFilter(filtertags);
+ }
+ buildTagsFilter(filtertags)
}
// update items whenever list updated
historyList.on('updated', e => {
- for (let i = 0, l = e.items.length; i < l; i++) {
- const item = e.items[i];
- if (item.visible()) {
- 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}`);
- //parse pinned
- if (values.pinned) {
- pin.addClass('active');
- } else {
- pin.removeClass('active');
- }
- //parse tags
- const tags = values.tags;
- if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
- 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>`);
- }
- tagsEl.html(labels.join(' '));
- }
+ for (let i = 0, l = e.items.length; i < l; i++) {
+ const item = e.items[i]
+ if (item.visible()) {
+ 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}`)
+ // parse pinned
+ if (values.pinned) {
+ pin.addClass('active')
+ } else {
+ pin.removeClass('active')
+ }
+ // parse tags
+ const tags = values.tags
+ if (tags && tags.length > 0 && tagsEl.children().length <= 0) {
+ 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>`)
}
+ tagsEl.html(labels.join(' '))
+ }
}
- $(".ui-history-close").off('click');
- $(".ui-history-close").on('click', historyCloseClick);
- $(".ui-history-pin").off('click');
- $(".ui-history-pin").on('click', historyPinClick);
-});
-
-function historyCloseClick(e) {
- e.preventDefault();
- 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}`);
- clearHistory = false;
- deleteId = id;
+ }
+ $('.ui-history-close').off('click')
+ $('.ui-history-close').on('click', historyCloseClick)
+ $('.ui-history-pin').off('click')
+ $('.ui-history-pin').on('click', historyPinClick)
+})
+
+function historyCloseClick (e) {
+ e.preventDefault()
+ 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}`)
+ clearHistory = false
+ deleteId = id
}
-function historyPinClick(e) {
- e.preventDefault();
- 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;
- } else {
- pinned = false;
- item._values.pinned = false;
- }
- checkIfAuth(() => {
- postHistoryToServer(id, {
- pinned
- }, (err, result) => {
- if (!err) {
- if (pinned)
- $this.addClass('active');
- else
- $this.removeClass('active');
- }
- });
- }, () => {
- getHistory(notehistory => {
- for(let i = 0; i < notehistory.length; i++) {
- if (notehistory[i].id == id) {
- notehistory[i].pinned = pinned;
- break;
- }
- }
- saveHistory(notehistory);
- if (pinned)
- $this.addClass('active');
- else
- $this.removeClass('active');
- });
- });
+function historyPinClick (e) {
+ e.preventDefault()
+ 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
+ } else {
+ pinned = false
+ item._values.pinned = false
+ }
+ checkIfAuth(() => {
+ postHistoryToServer(id, {
+ pinned
+ }, (err, result) => {
+ if (!err) {
+ if (pinned) { $this.addClass('active') } else { $this.removeClass('active') }
+ }
+ })
+ }, () => {
+ getHistory(notehistory => {
+ for (let i = 0; i < notehistory.length; i++) {
+ if (notehistory[i].id === id) {
+ notehistory[i].pinned = pinned
+ break
+ }
+ }
+ saveHistory(notehistory)
+ if (pinned) { $this.addClass('active') } else { $this.removeClass('active') }
+ })
+ })
}
-//auto update item fromNow every minutes
-setInterval(updateItemFromNow, 60000);
+// auto update item fromNow every minutes
+setInterval(updateItemFromNow, 60000)
-function updateItemFromNow() {
- 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());
- }
+function updateItemFromNow () {
+ 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())
+ }
}
-var clearHistory = false;
-var deleteId = null;
-
-function deleteHistory() {
- checkIfAuth(() => {
- deleteServerHistory(deleteId, (err, result) => {
- if (!err) {
- if (clearHistory) {
- historyList.clear();
- checkHistoryList();
- } else {
- historyList.remove('id', deleteId);
- checkHistoryList();
- }
- }
- $('.delete-modal').modal('hide');
- deleteId = null;
- clearHistory = false;
- });
- }, () => {
+var clearHistory = false
+var deleteId = null
+
+function deleteHistory () {
+ checkIfAuth(() => {
+ deleteServerHistory(deleteId, (err, result) => {
+ if (!err) {
if (clearHistory) {
- saveHistory([]);
- historyList.clear();
- checkHistoryList();
- deleteId = null;
+ historyList.clear()
+ checkHistoryList()
} else {
- if (!deleteId) return;
- getHistory(notehistory => {
- const newnotehistory = removeHistory(deleteId, notehistory);
- saveHistory(newnotehistory);
- historyList.remove('id', deleteId);
- checkHistoryList();
- deleteId = null;
- });
+ historyList.remove('id', deleteId)
+ checkHistoryList()
}
- $('.delete-modal').modal('hide');
- clearHistory = false;
- });
+ }
+ $('.delete-modal').modal('hide')
+ deleteId = null
+ clearHistory = false
+ })
+ }, () => {
+ if (clearHistory) {
+ saveHistory([])
+ historyList.clear()
+ checkHistoryList()
+ deleteId = null
+ } else {
+ if (!deleteId) return
+ getHistory(notehistory => {
+ const newnotehistory = removeHistory(deleteId, notehistory)
+ saveHistory(newnotehistory)
+ historyList.remove('id', deleteId)
+ checkHistoryList()
+ deleteId = null
+ })
+ }
+ $('.delete-modal').modal('hide')
+ clearHistory = false
+ })
}
-$(".ui-delete-modal-confirm").click(() => {
- deleteHistory();
-});
-
-$(".ui-import-from-browser").click(() => {
- saveStorageHistoryToServer(() => {
- parseStorageToHistory(historyList, parseHistoryCallback);
- });
-});
-
-$(".ui-save-history").click(() => {
+$('.ui-delete-modal-confirm').click(() => {
+ deleteHistory()
+})
+
+$('.ui-import-from-browser').click(() => {
+ saveStorageHistoryToServer(() => {
+ parseStorageToHistory(historyList, parseHistoryCallback)
+ })
+})
+
+$('.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')}`, true)
+ })
+})
+
+$('.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(data => {
- const history = JSON.stringify(data);
- const blob = new Blob([history], {
- type: "application/json;charset=utf-8"
- });
- saveAs(blob, `hackmd_history_${moment().format('YYYYMMDDHHmmss')}`, true);
- });
-});
-
-$(".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(data => {
- let mergedata = data.concat(notehistory);
- mergedata = clearDuplicatedHistory(mergedata);
- saveHistory(mergedata);
- parseHistory(historyList, parseHistoryCallback);
- });
- $(".ui-open-history").replaceWith($(".ui-open-history").val('').clone(true));
- };
- reader.readAsText(file);
-});
-
-$(".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(() => {
- const lastTags = $(".ui-use-tags").select2('val');
- $(".ui-use-tags").select2('val', '');
- historyList.filter();
- const lastKeyword = $('.search').val();
- $('.search').val('');
- historyList.search();
- $('#history-list').slideUp('fast');
- $('.pagination').hide();
-
- resetCheckAuth();
- historyList.clear();
- parseHistory(historyList, (list, notehistory) => {
- parseHistoryCallback(list, notehistory);
- $(".ui-use-tags").select2('val', lastTags);
- $(".ui-use-tags").trigger('change');
- historyList.search(lastKeyword);
- $('.search').val(lastKeyword);
- checkHistoryList();
- $('#history-list').slideDown('fast');
- });
-});
-
-$(".ui-logout").click(() => {
- clearLoginState();
- location.href = `${serverurl}/logout`;
-});
-
-let filtertags = [];
-$(".ui-use-tags").select2({
- placeholder: $(".ui-use-tags").attr('placeholder'),
- multiple: true,
- data() {
- return {
- results: filtertags
- };
+ let mergedata = data.concat(notehistory)
+ mergedata = clearDuplicatedHistory(mergedata)
+ saveHistory(mergedata)
+ parseHistory(historyList, parseHistoryCallback)
+ })
+ $('.ui-open-history').replaceWith($('.ui-open-history').val('').clone(true))
+ }
+ reader.readAsText(file)
+})
+
+$('.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(() => {
+ const lastTags = $('.ui-use-tags').select2('val')
+ $('.ui-use-tags').select2('val', '')
+ historyList.filter()
+ const lastKeyword = $('.search').val()
+ $('.search').val('')
+ historyList.search()
+ $('#history-list').slideUp('fast')
+ $('.pagination').hide()
+
+ resetCheckAuth()
+ historyList.clear()
+ parseHistory(historyList, (list, notehistory) => {
+ parseHistoryCallback(list, notehistory)
+ $('.ui-use-tags').select2('val', lastTags)
+ $('.ui-use-tags').trigger('change')
+ historyList.search(lastKeyword)
+ $('.search').val(lastKeyword)
+ checkHistoryList()
+ $('#history-list').slideDown('fast')
+ })
+})
+
+$('.ui-logout').click(() => {
+ clearLoginState()
+ location.href = `${serverurl}/logout`
+})
+
+let filtertags = []
+$('.ui-use-tags').select2({
+ placeholder: $('.ui-use-tags').attr('placeholder'),
+ multiple: true,
+ data () {
+ return {
+ results: filtertags
}
-});
-$('.select2-input').css('width', 'inherit');
-buildTagsFilter([]);
-
-function buildTagsFilter(tags) {
- for (let i = 0; i < tags.length; i++)
- tags[i] = {
- id: i,
- text: S(tags[i]).unescapeHTML().s
- };
- filtertags = tags;
-}
-$(".ui-use-tags").on('change', function () {
- 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(item => {
- const values = item.values();
- if (!values.tags) return false;
- let found = false;
- for (let i = 0; i < tags.length; i++) {
- if (values.tags.includes(tags[i])) {
- found = true;
- break;
- }
- }
- return found;
- });
- } else {
- historyList.filter();
+ }
+})
+$('.select2-input').css('width', 'inherit')
+buildTagsFilter([])
+
+function buildTagsFilter (tags) {
+ for (let i = 0; i < tags.length; i++) {
+ tags[i] = {
+ id: i,
+ text: S(tags[i]).unescapeHTML().s
}
- checkHistoryList();
-});
+ }
+ filtertags = tags
+}
+$('.ui-use-tags').on('change', function () {
+ 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(item => {
+ const values = item.values()
+ if (!values.tags) return false
+ let found = false
+ for (let i = 0; i < tags.length; i++) {
+ if (values.tags.includes(tags[i])) {
+ found = true
+ break
+ }
+ }
+ return found
+ })
+ } else {
+ historyList.filter()
+ }
+ checkHistoryList()
+})
$('.search').keyup(() => {
- checkHistoryList();
-});
+ checkHistoryList()
+})
diff --git a/public/js/extra.js b/public/js/extra.js
index a3e840d2..844d52c6 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -1,1150 +1,1140 @@
-require('prismjs/themes/prism.css');
-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');
-
-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");
-
-//auto update last change
-window.createtime = null;
-window.lastchangetime = null;
+/* eslint-env browser, jquery */
+/* global moment, serverurl */
+
+require('prismjs/themes/prism.css')
+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')
+
+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')
+
+// auto update last change
+window.createtime = null
+window.lastchangetime = null
window.lastchangeui = {
- status: $(".ui-status-lastchange"),
- time: $(".ui-lastchange"),
- user: $(".ui-lastchangeuser"),
- nouser: $(".ui-no-lastchangeuser")
+ status: $('.ui-status-lastchange'),
+ time: $('.ui-lastchange'),
+ user: $('.ui-lastchangeuser'),
+ nouser: $('.ui-no-lastchangeuser')
}
-const ownerui = $(".ui-owner");
+const ownerui = $('.ui-owner')
-export function updateLastChange() {
- if (!lastchangeui) return;
- if (createtime) {
- if (createtime && !lastchangetime) {
- lastchangeui.status.text('created');
- } else {
- lastchangeui.status.text('changed');
- }
- const time = lastchangetime || createtime;
- lastchangeui.time.html(moment(time).fromNow());
- lastchangeui.time.attr('title', moment(time).format('llll'));
+export function updateLastChange () {
+ if (!window.lastchangeui) return
+ if (window.createtime) {
+ if (window.createtime && !window.lastchangetime) {
+ window.lastchangeui.status.text('created')
+ } else {
+ window.lastchangeui.status.text('changed')
}
+ const time = window.lastchangetime || window.createtime
+ window.lastchangeui.time.html(moment(time).fromNow())
+ window.lastchangeui.time.attr('title', moment(time).format('llll'))
+ }
}
-setInterval(updateLastChange, 60000);
-
-window.lastchangeuser = null;
-window.lastchangeuserprofile = null;
-
-export function updateLastChangeUser() {
- if (lastchangeui) {
- if (lastchangeuser && lastchangeuserprofile) {
- const icon = lastchangeui.user.children('i');
- icon.attr('title', lastchangeuserprofile.name).tooltip('fixTitle');
- if (lastchangeuserprofile.photo)
- icon.attr('style', `background-image:url(${lastchangeuserprofile.photo})`);
- lastchangeui.user.show();
- lastchangeui.nouser.hide();
- } else {
- lastchangeui.user.hide();
- lastchangeui.nouser.show();
- }
+setInterval(updateLastChange, 60000)
+
+window.lastchangeuser = null
+window.lastchangeuserprofile = null
+
+export function updateLastChangeUser () {
+ if (window.lastchangeui) {
+ if (window.lastchangeuser && window.lastchangeuserprofile) {
+ const icon = window.lastchangeui.user.children('i')
+ icon.attr('title', window.lastchangeuserprofile.name).tooltip('fixTitle')
+ if (window.lastchangeuserprofile.photo) { icon.attr('style', `background-image:url(${window.lastchangeuserprofile.photo})`) }
+ window.lastchangeui.user.show()
+ window.lastchangeui.nouser.hide()
+ } else {
+ window.lastchangeui.user.hide()
+ window.lastchangeui.nouser.show()
}
+ }
}
-window.owner = null;
-window.ownerprofile = null;
-
-export function updateOwner() {
- if (ownerui) {
- if (owner && ownerprofile && owner !== lastchangeuser) {
- const icon = ownerui.children('i');
- icon.attr('title', ownerprofile.name).tooltip('fixTitle');
- const styleString = `background-image:url(${ownerprofile.photo})`;
- if (ownerprofile.photo && icon.attr('style') !== styleString)
- icon.attr('style', styleString);
- ownerui.show();
- } else {
- ownerui.hide();
- }
+window.owner = null
+window.ownerprofile = null
+
+export function updateOwner () {
+ if (window.ownerui) {
+ if (window.owner && window.ownerprofile && window.owner !== window.lastchangeuser) {
+ const icon = ownerui.children('i')
+ icon.attr('title', window.ownerprofile.name).tooltip('fixTitle')
+ const styleString = `background-image:url(${window.ownerprofile.photo})`
+ if (window.ownerprofile.photo && icon.attr('style') !== styleString) { icon.attr('style', styleString) }
+ ownerui.show()
+ } else {
+ ownerui.hide()
}
+ }
}
-//get title
-function getTitle(view) {
- let title = "";
- if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) {
- title = md.meta.title;
+// get title
+function getTitle (view) {
+ let title = ''
+ if (md && md.meta && md.meta.title && (typeof md.meta.title === 'string' || typeof md.meta.title === 'number')) {
+ title = md.meta.title
+ } else {
+ const h1s = view.find('h1')
+ if (h1s.length > 0) {
+ title = h1s.first().text()
} else {
- const h1s = view.find("h1");
- if (h1s.length > 0) {
- title = h1s.first().text();
- } else {
- title = null;
- }
+ title = null
}
- return title;
+ }
+ return title
}
-//render title
-export function renderTitle(view) {
- let title = getTitle(view);
- if (title) {
- title += ' - HackMD';
- } else {
- title = 'HackMD - Collaborative markdown notes';
- }
- return title;
+// render title
+export function renderTitle (view) {
+ let title = getTitle(view)
+ if (title) {
+ title += ' - HackMD'
+ } else {
+ title = 'HackMD - Collaborative markdown notes'
+ }
+ return title
}
-//render filename
-export function renderFilename(view) {
- let filename = getTitle(view);
- if (!filename) {
- filename = 'Untitled';
- }
- return filename;
+// render filename
+export function renderFilename (view) {
+ let filename = getTitle(view)
+ if (!filename) {
+ filename = 'Untitled'
+ }
+ return filename
}
// render tags
-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")) {
- const metaTags = (`${md.meta.tags}`).split(',');
- for (var i = 0; i < metaTags.length; i++) {
- const text = metaTags[i].trim();
- if (text) rawtags.push(text);
- }
- } else {
- view.find('h6').each((key, value) => {
- if (/^tags/gmi.test($(value).text())) {
- const codes = $(value).find("code");
- for (let i = 0; i < codes.length; i++) {
- const text = codes[i].innerHTML.trim();
- if (text) rawtags.push(text);
- }
- }
- });
+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')) {
+ const metaTags = (`${md.meta.tags}`).split(',')
+ for (let i = 0; i < metaTags.length; i++) {
+ const text = metaTags[i].trim()
+ if (text) rawtags.push(text)
}
- for (var i = 0; i < rawtags.length; i++) {
- let found = false;
- for (let j = 0; j < tags.length; j++) {
- if (tags[j] == rawtags[i]) {
- found = true;
- break;
- }
+ } else {
+ view.find('h6').each((key, value) => {
+ if (/^tags/gmi.test($(value).text())) {
+ const codes = $(value).find('code')
+ for (let i = 0; i < codes.length; i++) {
+ const text = codes[i].innerHTML.trim()
+ if (text) rawtags.push(text)
}
- if (!found)
- tags.push(rawtags[i]);
+ }
+ })
+ }
+ for (let i = 0; i < rawtags.length; i++) {
+ let found = false
+ for (let j = 0; j < tags.length; j++) {
+ if (tags[j] === rawtags[i]) {
+ found = true
+ break
+ }
}
- return tags;
+ if (!found) { tags.push(rawtags[i]) }
+ }
+ return tags
}
-function slugifyWithUTF8(text) {
- let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s;
- newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, '');
- return newText;
+function slugifyWithUTF8 (text) {
+ let newText = S(text.toLowerCase()).trim().stripTags().dasherize().s
+ newText = newText.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '')
+ return newText
}
-export function isValidURL(str) {
- const 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
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
- '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
- if (!pattern.test(str)) {
- return false;
- } else {
- return true;
- }
+ '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
+ if (!pattern.test(str)) {
+ return false
+ } else {
+ return true
+ }
}
-//parse meta
-export function parseMeta(md, edit, view, toc, tocAffix) {
- let lang = null;
- let dir = null;
- let breaks = true;
- if (md && md.meta) {
- const meta = md.meta;
- lang = meta.lang;
- dir = meta.dir;
- breaks = meta.breaks;
- }
- //text language
- if (lang && typeof lang == "string") {
- view.attr('lang', lang);
- toc.attr('lang', lang);
- tocAffix.attr('lang', lang);
- if (edit)
- edit.attr('lang', lang);
- } else {
- view.removeAttr('lang');
- toc.removeAttr('lang');
- tocAffix.removeAttr('lang');
- if (edit)
- edit.removeAttr('lang', lang);
- }
- //text direction
- if (dir && typeof dir == "string") {
- view.attr('dir', dir);
- toc.attr('dir', dir);
- tocAffix.attr('dir', dir);
- } else {
- view.removeAttr('dir');
- toc.removeAttr('dir');
- tocAffix.removeAttr('dir');
- }
- //breaks
- if (typeof breaks === 'boolean' && !breaks) {
- md.options.breaks = false;
- } else {
- md.options.breaks = true;
- }
+// parse meta
+export function parseMeta (md, edit, view, toc, tocAffix) {
+ let lang = null
+ let dir = null
+ let breaks = true
+ if (md && md.meta) {
+ const meta = md.meta
+ lang = meta.lang
+ dir = meta.dir
+ breaks = meta.breaks
+ }
+ // text language
+ if (lang && typeof lang === 'string') {
+ view.attr('lang', lang)
+ toc.attr('lang', lang)
+ tocAffix.attr('lang', lang)
+ if (edit) { edit.attr('lang', lang) }
+ } else {
+ view.removeAttr('lang')
+ toc.removeAttr('lang')
+ tocAffix.removeAttr('lang')
+ if (edit) { edit.removeAttr('lang', lang) }
+ }
+ // text direction
+ if (dir && typeof dir === 'string') {
+ view.attr('dir', dir)
+ toc.attr('dir', dir)
+ tocAffix.attr('dir', dir)
+ } else {
+ view.removeAttr('dir')
+ toc.removeAttr('dir')
+ tocAffix.removeAttr('dir')
+ }
+ // breaks
+ if (typeof breaks === 'boolean' && !breaks) {
+ md.options.breaks = false
+ } else {
+ md.options.breaks = true
+ }
}
-window.viewAjaxCallback = null;
-
-//regex for extra tags
-const spaceregex = /\s*/;
-const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/;
-let coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/;
-coloregex = new RegExp(coloregex.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");
-
-function replaceExtraTags(html) {
- html = html.replace(coloregex, '<span class="color" data-color="$1"></span>');
- html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>');
- html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>');
- html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>');
- return html;
+window.viewAjaxCallback = null
+
+// regex for extra tags
+const spaceregex = /\s*/
+const notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/
+let coloregex = /\[color=([#|(|)|\s|,|\w]*?)\]/
+coloregex = new RegExp(coloregex.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')
+
+function replaceExtraTags (html) {
+ html = html.replace(coloregex, '<span class="color" data-color="$1"></span>')
+ html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>')
+ html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>')
+ html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>')
+ return html
}
-if (typeof mermaid !== 'undefined' && mermaid) mermaid.startOnLoad = false;
+if (typeof window.mermaid !== 'undefined' && window.mermaid) window.mermaid.startOnLoad = false
-//dynamic event or object binding here
-export function finishView(view) {
- //todo list
- const lis = view.find('li.raw').removeClass("raw").sortByDepth().toArray();
+// dynamic event or object binding here
+export function finishView (view) {
+ // todo list
+ 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;
- 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>`);
- 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
- const tag_color = $(li).closest('ul').find(".color");
- tag_color.each((key, value) => {
- $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'));
- });
+ 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]
}
-
- //youtube
- view.find("div.youtube.raw").removeClass("raw")
+ html = replaceExtraTags(html)
+ li.innerHTML = html
+ let disabled = 'disabled'
+ if (typeof editor !== 'undefined' && window.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>`)
+ li.setAttribute('class', 'task-list-item')
+ }
+ if (typeof editor !== 'undefined' && window.havePermission()) { $(li).find('input').change(toggleTodoEvent) }
+ // color tag in list will convert it to tag icon with color
+ const tagColor = $(li).closest('ul').find('.color')
+ tagColor.each((key, value) => {
+ $(value).addClass('fa fa-tag').css('color', $(value).attr('data-color'))
+ })
+ }
+
+ // youtube
+ view.find('div.youtube.raw').removeClass('raw')
.click(function () {
- imgPlayiframe(this, '//www.youtube.com/embed/');
- });
- //vimeo
- view.find("div.vimeo.raw").removeClass("raw")
+ imgPlayiframe(this, '//www.youtube.com/embed/')
+ })
+ // vimeo
+ view.find('div.vimeo.raw').removeClass('raw')
.click(function () {
- imgPlayiframe(this, '//player.vimeo.com/video/');
+ imgPlayiframe(this, '//player.vimeo.com/video/')
})
.each((key, value) => {
- $.ajax({
- type: 'GET',
- url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
- jsonp: 'callback',
- dataType: 'jsonp',
- 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((key, value) => {
- if ($(value).children().length == 0)
- $(value).gist(viewAjaxCallback);
- });
- //sequence diagram
- const sequences = view.find("div.sequence-diagram.raw").removeClass("raw");
- sequences.each((key, value) => {
- try {
- var $value = $(value);
- const $ele = $(value).parent().parent();
-
- const sequence = $value;
- sequence.sequenceDiagram({
- theme: 'simple'
- });
-
- $ele.addClass('sequence-diagram');
- $value.children().unwrap().unwrap();
- 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();
- $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
- console.warn(err);
- }
- });
- //flowchart
- const flow = view.find("div.flow-chart.raw").removeClass("raw");
- flow.each((key, value) => {
- try {
- var $value = $(value);
- const $ele = $(value).parent().parent();
-
- const chart = flowchart.parse($value.text());
- $value.html('');
- chart.drawSVG(value, {
- 'line-width': 2,
- 'fill': 'none',
- 'font-size': '16px',
- 'font-family': "'Andale Mono', monospace"
- });
-
- $ele.addClass('flow-chart');
- $value.children().unwrap().unwrap();
- } catch (err) {
- $value.unwrap();
- $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
- console.warn(err);
- }
- });
- //graphviz
- var graphvizs = view.find("div.graphviz.raw").removeClass("raw");
- graphvizs.each(function (key, value) {
- try {
- 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) {
- $value.unwrap();
- $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
- console.warn(err);
- }
- });
- //mermaid
- const mermaids = view.find("div.mermaid.raw").removeClass("raw");
- mermaids.each((key, value) => {
- try {
- var $value = $(value);
- const $ele = $(value).closest('pre');
-
- let mermaidError = null;
- mermaid.parseError = (err, hash) => {
- mermaidError = err;
- };
-
- if (mermaidAPI.parse($value.text())) {
- $ele.addClass('mermaid');
- $ele.html($value.text());
- mermaid.init(undefined, $ele);
- } else {
- throw new Error(mermaidError);
+ $.ajax({
+ type: 'GET',
+ url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
+ jsonp: 'callback',
+ dataType: 'jsonp',
+ success (data) {
+ const thumbnailSrc = data[0].thumbnail_large
+ const image = `<img src="${thumbnailSrc}" />`
+ $(value).prepend(image)
+ if (window.viewAjaxCallback) window.viewAjaxCallback()
}
- } catch (err) {
- $value.unwrap();
- $value.parent().append('<div class="alert alert-warning">' + err + '</div>');
- console.warn(err);
- }
- });
- //image href new window(emoji not included)
- const images = view.find("img.raw[src]").removeClass("raw");
- images.each((key, value) => {
+ })
+ })
+ // gist
+ view.find('code[data-gist-id]').each((key, value) => {
+ if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
+ })
+ // sequence diagram
+ const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
+ sequences.each((key, value) => {
+ try {
+ var $value = $(value)
+ const $ele = $(value).parent().parent()
+
+ const sequence = $value
+ sequence.sequenceDiagram({
+ theme: 'simple'
+ })
+
+ $ele.addClass('sequence-diagram')
+ $value.children().unwrap().unwrap()
+ 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()
+ $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+ console.warn(err)
+ }
+ })
+ // flowchart
+ const flow = view.find('div.flow-chart.raw').removeClass('raw')
+ flow.each((key, value) => {
+ try {
+ var $value = $(value)
+ const $ele = $(value).parent().parent()
+
+ const chart = window.flowchart.parse($value.text())
+ $value.html('')
+ chart.drawSVG(value, {
+ 'line-width': 2,
+ 'fill': 'none',
+ 'font-size': '16px',
+ 'font-family': "'Andale Mono', monospace"
+ })
+
+ $ele.addClass('flow-chart')
+ $value.children().unwrap().unwrap()
+ } catch (err) {
+ $value.unwrap()
+ $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+ console.warn(err)
+ }
+ })
+ // graphviz
+ var graphvizs = view.find('div.graphviz.raw').removeClass('raw')
+ graphvizs.each(function (key, value) {
+ try {
+ 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) {
+ $value.unwrap()
+ $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+ console.warn(err)
+ }
+ })
+ // mermaid
+ const mermaids = view.find('div.mermaid.raw').removeClass('raw')
+ mermaids.each((key, value) => {
+ try {
+ var $value = $(value)
+ const $ele = $(value).closest('pre')
+
+ let mermaidError = null
+ window.mermaid.parseError = (err, hash) => {
+ mermaidError = err
+ }
+
+ if (window.mermaidAPI.parse($value.text())) {
+ $ele.addClass('mermaid')
+ $ele.html($value.text())
+ window.mermaid.init(undefined, $ele)
+ } else {
+ throw new Error(mermaidError)
+ }
+ } catch (err) {
+ $value.unwrap()
+ $value.parent().append('<div class="alert alert-warning">' + err + '</div>')
+ console.warn(err)
+ }
+ })
+ // image href new window(emoji not included)
+ const images = view.find('img.raw[src]').removeClass('raw')
+ images.each((key, value) => {
// if it's already wrapped by link, then ignore
- const $value = $(value);
- $value[0].onload = e => {
- if(viewAjaxCallback) viewAjaxCallback();
- };
- });
- //blockquote
- 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
- 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")
+ const $value = $(value)
+ $value[0].onload = e => {
+ if (window.viewAjaxCallback) window.viewAjaxCallback()
+ }
+ })
+ // blockquote
+ const blockquote = view.find('blockquote.raw').removeClass('raw')
+ const blockquoteP = blockquote.find('p')
+ blockquoteP.each((key, value) => {
+ let html = $(value).html()
+ html = replaceExtraTags(html)
+ $(value).html(html)
+ })
+ // color tag in blockquote will change its left border color
+ const blockquoteColor = blockquote.find('.color')
+ blockquoteColor.each((key, value) => {
+ $(value).closest('blockquote').css('border-left-color', $(value).attr('data-color'))
+ })
+ // slideshare
+ view.find('div.slideshare.raw').removeClass('raw')
.each((key, value) => {
- $.ajax({
- type: 'GET',
- url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
- jsonp: 'callback',
- dataType: 'jsonp',
- 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();
- }
- });
- });
- //speakerdeck
- view.find("div.speakerdeck.raw").removeClass("raw")
+ $.ajax({
+ type: 'GET',
+ url: `//www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
+ jsonp: 'callback',
+ dataType: 'jsonp',
+ 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 (window.viewAjaxCallback) window.viewAjaxCallback()
+ }
+ })
+ })
+ // speakerdeck
+ view.find('div.speakerdeck.raw').removeClass('raw')
.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}'`,
- format: "json"
- },
- dataType: "jsonp",
- success(data) {
- if (!data.query || !data.query.results) return;
- const json = data.query.results.json;
- const html = json.html;
- var ratio = json.height / json.width;
- $(value).html(html);
- const iframe = $(value).children('iframe');
- const src = iframe.attr('src');
- if (src.indexOf('//') == 0)
- 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}%`);
- $(value).html(inner);
- if(viewAjaxCallback) viewAjaxCallback();
- }
- });
- });
- //pdf
- view.find("div.pdf.raw").removeClass("raw")
+ 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}'`,
+ format: 'json'
+ },
+ dataType: 'jsonp',
+ success (data) {
+ if (!data.query || !data.query.results) return
+ const json = data.query.results.json
+ const html = json.html
+ var ratio = json.height / json.width
+ $(value).html(html)
+ const iframe = $(value).children('iframe')
+ const src = iframe.attr('src')
+ if (src.indexOf('//') === 0) { iframe.attr('src', `https:${src}`) }
+ const inner = $('<div class="inner"></div>').append(iframe)
+ const height = iframe.attr('height')
+ const width = iframe.attr('width')
+ ratio = (height / width) * 100
+ inner.css('padding-bottom', `${ratio}%`)
+ $(value).html(inner)
+ if (window.viewAjaxCallback) window.viewAjaxCallback()
+ }
+ })
+ })
+ // pdf
+ view.find('div.pdf.raw').removeClass('raw')
.each(function (key, value) {
- const url = $(value).attr('data-pdfurl');
- const inner = $('<div></div>');
- $(this).append(inner);
- PDFObject.embed(url, inner, {
- height: '400px'
- });
- });
- //syntax highlighting
- view.find("code.raw").removeClass("raw")
+ const url = $(value).attr('data-pdfurl')
+ const inner = $('<div></div>')
+ $(this).append(inner)
+ PDFObject.embed(url, inner, {
+ height: '400px'
+ })
+ })
+ // syntax highlighting
+ view.find('code.raw').removeClass('raw')
.each((key, value) => {
- const langDiv = $(value);
- if (langDiv.length > 0) {
- 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) {
- var result = {
- value: code
- };
- } else if (reallang == "haskell" || reallang == "go" || reallang == "typescript" || reallang == "jsx") {
- code = S(code).unescapeHTML().s;
- var result = {
- value: Prism.highlight(code, Prism.languages[reallang])
- };
- } else if (reallang == "tiddlywiki" || reallang == "mediawiki") {
- code = S(code).unescapeHTML().s;
- var result = {
- value: Prism.highlight(code, Prism.languages.wiki)
- };
- } else {
- code = S(code).unescapeHTML().s;
- const languages = hljs.listLanguages();
- if (!languages.includes(reallang)) {
- var result = hljs.highlightAuto(code);
- } else {
- var result = hljs.highlight(reallang, code);
- }
- }
- if (codeDiv.length > 0) codeDiv.html(result.value);
- else langDiv.html(result.value);
+ const langDiv = $(value)
+ if (langDiv.length > 0) {
+ 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()
+ var result
+ if (!reallang) {
+ result = {
+ value: code
+ }
+ } else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx') {
+ code = S(code).unescapeHTML().s
+ result = {
+ value: Prism.highlight(code, Prism.languages[reallang])
+ }
+ } else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
+ code = S(code).unescapeHTML().s
+ result = {
+ value: Prism.highlight(code, Prism.languages.wiki)
+ }
+ } else {
+ code = S(code).unescapeHTML().s
+ const languages = hljs.listLanguages()
+ if (!languages.includes(reallang)) {
+ result = hljs.highlightAuto(code)
+ } else {
+ result = hljs.highlight(reallang, code)
+ }
}
- });
- //mathjax
- const mathjaxdivs = view.find('span.mathjax.raw').removeClass("raw").toArray();
- try {
- if (mathjaxdivs.length > 1) {
- MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs]);
- MathJax.Hub.Queue(viewAjaxCallback);
- } else if (mathjaxdivs.length > 0) {
- MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[0]]);
- MathJax.Hub.Queue(viewAjaxCallback);
- }
- } catch (err) {
- console.warn(err);
+ if (codeDiv.length > 0) codeDiv.html(result.value)
+ else langDiv.html(result.value)
+ }
+ })
+ // mathjax
+ const mathjaxdivs = view.find('span.mathjax.raw').removeClass('raw').toArray()
+ try {
+ if (mathjaxdivs.length > 1) {
+ window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs])
+ window.MathJax.Hub.Queue(window.viewAjaxCallback)
+ } else if (mathjaxdivs.length > 0) {
+ window.MathJax.Hub.Queue(['Typeset', window.MathJax.Hub, mathjaxdivs[0]])
+ window.MathJax.Hub.Queue(window.viewAjaxCallback)
}
- //render title
- document.title = renderTitle(view);
+ } catch (err) {
+ console.warn(err)
+ }
+ // render title
+ document.title = renderTitle(view)
}
-//only static transform should be here
-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
- const linenumberdivs = result.find('.gutter.linenumber').toArray();
- for (let i = 0; i < linenumberdivs.length; i++) {
- if ($(linenumberdivs[i]).hasClass('continue')) {
- 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);
- });
- }
- }
- // show yaml meta paring error
- if (md.metaError) {
- var warning = result.find('div#meta-error');
- if (warning && warning.length > 0) {
- warning.text(md.metaError)
- } else {
- warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>')
- result.prepend(warning);
- }
+// only static transform should be here
+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
+ const linenumberdivs = result.find('.gutter.linenumber').toArray()
+ for (let i = 0; i < linenumberdivs.length; i++) {
+ if ($(linenumberdivs[i]).hasClass('continue')) {
+ 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)
+ })
+ }
+ }
+ // show yaml meta paring error
+ if (md.metaError) {
+ var warning = result.find('div#meta-error')
+ if (warning && warning.length > 0) {
+ warning.text(md.metaError)
+ } else {
+ warning = $('<div id="meta-error" class="alert alert-warning">' + md.metaError + '</div>')
+ result.prepend(warning)
}
- return result;
+ }
+ return result
}
-window.postProcess = postProcess;
-
-function generateCleanHTML(view) {
- const src = view.clone();
- const eles = src.find('*');
- //remove syncscroll parts
- eles.removeClass('part');
- src.find('*[class=""]').removeAttr('class');
- eles.removeAttr('data-startline data-endline');
- src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
- //remove gist content
- src.find("code[data-gist-id]").children().remove();
- //disable todo list
- src.find("input.task-list-item-checkbox").attr('disabled', '');
- //replace emoji image path
- 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`);
- });
- //replace video to iframe
- 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) {
- const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
- iframe.attr('src', url + id);
- iframe.attr('style', style);
- $(value).html(iframe);
- }
- });
- return src;
+window.postProcess = postProcess
+
+function generateCleanHTML (view) {
+ const src = view.clone()
+ const eles = src.find('*')
+ // remove syncscroll parts
+ eles.removeClass('part')
+ src.find('*[class=""]').removeAttr('class')
+ eles.removeAttr('data-startline data-endline')
+ src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
+ // remove gist content
+ src.find('code[data-gist-id]').children().remove()
+ // disable todo list
+ src.find('input.task-list-item-checkbox').attr('disabled', '')
+ // replace emoji image path
+ 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`)
+ })
+ // replace video to iframe
+ 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) {
+ const iframe = $('<iframe frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>')
+ iframe.attr('src', url + id)
+ iframe.attr('style', style)
+ $(value).html(iframe)
+ }
+ })
+ return src
}
-export function exportToRawHTML(view) {
- const filename = `${renderFilename(ui.area.markdown)}.html`;
- const src = generateCleanHTML(view);
- $(src).find('a.anchor').remove();
- const html = src[0].outerHTML;
- const blob = new Blob([html], {
- type: "text/html;charset=utf-8"
- });
- saveAs(blob, filename, true);
+export function exportToRawHTML (view) {
+ const filename = `${renderFilename(window.ui.area.markdown)}.html`
+ const src = generateCleanHTML(view)
+ $(src).find('a.anchor').remove()
+ const html = src[0].outerHTML
+ const blob = new Blob([html], {
+ type: 'text/html;charset=utf-8'
+ })
+ saveAs(blob, filename, true)
}
-//extract markdown body to html and compile to template
-export function exportToHTML(view) {
- const title = renderTitle(ui.area.markdown);
- const filename = `${renderFilename(ui.area.markdown)}.html`;
- const src = generateCleanHTML(view);
- //generate toc
- const toc = $('#ui-toc').clone();
- toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll');
- 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`, css => {
- $.get(`${serverurl}/views/html.hbs`, data => {
- const template = Handlebars.compile(data);
- const context = {
- url: serverurl,
- 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
- };
- const html = template(context);
+// extract markdown body to html and compile to template
+export function exportToHTML (view) {
+ const title = renderTitle(window.ui.area.markdown)
+ const filename = `${renderFilename(window.ui.area.markdown)}.html`
+ const src = generateCleanHTML(view)
+ // generate toc
+ const toc = $('#ui-toc').clone()
+ toc.find('*').removeClass('active').find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
+ 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`, css => {
+ $.get(`${serverurl}/views/html.hbs`, data => {
+ const template = window.Handlebars.compile(data)
+ const context = {
+ url: serverurl,
+ 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
+ }
+ const html = template(context)
// console.log(html);
- const blob = new Blob([html], {
- type: "text/html;charset=utf-8"
- });
- saveAs(blob, filename, true);
- });
- });
+ const blob = new Blob([html], {
+ type: 'text/html;charset=utf-8'
+ })
+ saveAs(blob, filename, true)
+ })
+ })
}
-//jQuery sortByDepth
+// jQuery sortByDepth
$.fn.sortByDepth = function () {
- const ar = this.map(function () {
- return {
- length: $(this).parents().length,
- elt: this
- }
- }).get();
-
- const result = [];
- let i = ar.length;
- ar.sort((a, b) => a.length - b.length);
- while (i--) {
- result.push(ar[i].elt);
- }
- return $(result);
-};
-
-function toggleTodoEvent(e) {
- 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) {
- let checked = null;
- if (matches[1] == 'x')
- checked = true;
- else if (matches[1] == ' ')
- checked = false;
- const replacements = matches[0].match(/(^[>\s]*[\-\+\*]\s\[)([x ])(\])/);
- editor.replaceRange(checked ? ' ' : 'x', {
- line: startline,
- ch: replacements[1].length
- }, {
- line: startline,
- ch: replacements[1].length + 1
- }, '+input');
+ const ar = this.map(function () {
+ return {
+ length: $(this).parents().length,
+ elt: this
}
+ }).get()
+
+ const result = []
+ let i = ar.length
+ ar.sort((a, b) => a.length - b.length)
+ while (i--) {
+ result.push(ar[i].elt)
+ }
+ return $(result)
}
-//remove hash
-function removeHash() {
- history.pushState("", document.title, window.location.pathname + window.location.search);
+function toggleTodoEvent (e) {
+ const startline = $(this).closest('li').attr('data-startline') - 1
+ const line = window.editor.getLine(startline)
+ const matches = line.match(/^[>\s]*[-+*]\s\[([x ])\]/)
+ if (matches && matches.length >= 2) {
+ let checked = null
+ if (matches[1] === 'x') { checked = true } else if (matches[1] === ' ') { checked = false }
+ const replacements = matches[0].match(/(^[>\s]*[-+*]\s\[)([x ])(\])/)
+ window.editor.replaceRange(checked ? ' ' : 'x', {
+ line: startline,
+ ch: replacements[1].length
+ }, {
+ line: startline,
+ ch: replacements[1].length + 1
+ }, '+input')
+ }
}
-let tocExpand = false;
+// remove hash
+function removeHash () {
+ history.pushState('', document.title, window.location.pathname + window.location.search)
+}
-function checkExpandToggle() {
- const toc = $('.ui-toc-dropdown .toc');
- const toggle = $('.expand-toggle');
- if (!tocExpand) {
- toc.removeClass('expand');
- toggle.text('Expand all');
- } else {
- toc.addClass('expand');
- toggle.text('Collapse all');
- }
+let tocExpand = false
+
+function checkExpandToggle () {
+ const toc = $('.ui-toc-dropdown .toc')
+ const toggle = $('.expand-toggle')
+ if (!tocExpand) {
+ toc.removeClass('expand')
+ toggle.text('Expand all')
+ } else {
+ toc.addClass('expand')
+ toggle.text('Collapse all')
+ }
}
-//toc
-export function generateToc(id) {
- const target = $(`#${id}`);
- target.html('');
- new Toc('doc', {
- 'level': 3,
- 'top': -1,
- 'class': 'toc',
- 'ulClass': 'nav',
- 'targetId': id,
- 'process': getHeaderContent
- });
- if (target.text() == 'undefined')
- target.html('');
- 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(e => {
- e.preventDefault();
- e.stopPropagation();
- tocExpand = !tocExpand;
- checkExpandToggle();
- });
- backtotop.click(e => {
- e.preventDefault();
- e.stopPropagation();
- if (scrollToTop)
- scrollToTop();
- removeHash();
- });
- gotobottom.click(e => {
- e.preventDefault();
- e.stopPropagation();
- if (scrollToBottom)
- scrollToBottom();
- removeHash();
- });
- tocMenu.append(toggle).append(backtotop).append(gotobottom);
- target.append(tocMenu);
+// toc
+export function generateToc (id) {
+ const target = $(`#${id}`)
+ target.html('')
+ /* eslint-disable no-unused-vars */
+ var toc = new window.Toc('doc', {
+ 'level': 3,
+ 'top': -1,
+ 'class': 'toc',
+ 'ulClass': 'nav',
+ 'targetId': id,
+ 'process': getHeaderContent
+ })
+ /* eslint-enable no-unsed-vars */
+ if (target.text() === 'undefined') { target.html('') }
+ 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(e => {
+ e.preventDefault()
+ e.stopPropagation()
+ tocExpand = !tocExpand
+ checkExpandToggle()
+ })
+ backtotop.click(e => {
+ e.preventDefault()
+ e.stopPropagation()
+ if (window.scrollToTop) { window.scrollToTop() }
+ removeHash()
+ })
+ gotobottom.click(e => {
+ e.preventDefault()
+ e.stopPropagation()
+ if (window.scrollToBottom) { window.scrollToBottom() }
+ removeHash()
+ })
+ tocMenu.append(toggle).append(backtotop).append(gotobottom)
+ target.append(tocMenu)
}
-//smooth all hash trigger scrolling
-export function smoothHashScroll() {
- const hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray();
+// smooth all hash trigger scrolling
+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) {
+ for (const element of hashElements) {
+ const $element = $(element)
+ const hash = element.hash
+ if (hash) {
+ $element.on('click', function (e) {
// store hash
- const hash = decodeURIComponent(this.hash);
+ const hash = decodeURIComponent(this.hash)
// escape special characters in jquery selector
- const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, "\\$1"));
+ const $hash = $(hash.replace(/(:|\.|\[|\]|,)/g, '\\$1'))
// return if no element been selected
- if ($hash.length <= 0) return;
+ if ($hash.length <= 0) return
// prevent default anchor click behavior
- e.preventDefault();
+ e.preventDefault()
// animate
- $('body, html').stop(true, true).animate({
- scrollTop: $hash.offset().top
- }, 100, "linear", () => {
+ $('body, html').stop(true, true).animate({
+ scrollTop: $hash.offset().top
+ }, 100, 'linear', () => {
// when done, add hash to url
// (default click behaviour)
- window.location.hash = hash;
- });
- });
- $element.attr('smoothhashscroll', '');
- }
+ window.location.hash = hash
+ })
+ })
+ $element.attr('smoothhashscroll', '')
}
+ }
}
-function imgPlayiframe(element, src) {
- if (!$(element).attr("data-videoid")) return;
- 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);
+function imgPlayiframe (element, src) {
+ if (!$(element).attr('data-videoid')) return
+ 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)
}
const anchorForId = id => {
- const anchor = document.createElement("a");
- anchor.className = "anchor hidden-xs";
- anchor.href = `#${id}`;
- anchor.innerHTML = "<span class=\"octicon octicon-link\"></span>";
- anchor.title = id;
- return anchor;
-};
+ const anchor = document.createElement('a')
+ anchor.className = 'anchor hidden-xs'
+ anchor.href = `#${id}`
+ anchor.innerHTML = '<span class="octicon octicon-link"></span>'
+ anchor.title = id
+ return anchor
+}
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
- const id = slugifyWithUTF8(getHeaderContent(header));
- header.id = id;
- }
- header.insertBefore(anchorForId(header.id), header.firstChild);
- }
+ 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
+ const id = slugifyWithUTF8(getHeaderContent(header))
+ header.id = id
+ }
+ header.insertBefore(anchorForId(header.id), header.firstChild)
}
-};
+ }
+}
-export function autoLinkify(view) {
- const contentBlock = view[0];
- if (!contentBlock) {
- return;
- }
- for (let level = 1; level <= 6; level++) {
- linkifyAnchors(level, contentBlock);
- }
+export function autoLinkify (view) {
+ const contentBlock = view[0]
+ if (!contentBlock) {
+ return
+ }
+ for (let level = 1; level <= 6; level++) {
+ linkifyAnchors(level, contentBlock)
+ }
}
-function getHeaderContent(header) {
- const headerHTML = $(header).clone();
- headerHTML.find('.MathJax_Preview').remove();
- headerHTML.find('.MathJax').remove();
- return headerHTML[0].innerHTML;
+function getHeaderContent (header) {
+ const headerHTML = $(header).clone()
+ headerHTML.find('.MathJax_Preview').remove()
+ headerHTML.find('.MathJax').remove()
+ return headerHTML[0].innerHTML
}
-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;
- const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray();
- for (let j = 0; j < duplicatedHeaders.length; j++) {
- if (duplicatedHeaders[j] != headers[i]) {
- const newId = id + j;
- const $duplicatedHeader = $(duplicatedHeaders[j]);
- $duplicatedHeader.attr('id', newId);
- const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`);
- $headerLink.attr('href', `#${newId}`);
- $headerLink.attr('title', newId);
- }
- }
+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
+ const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
+ for (let j = 0; j < duplicatedHeaders.length; j++) {
+ if (duplicatedHeaders[j] !== headers[i]) {
+ const newId = id + j
+ const $duplicatedHeader = $(duplicatedHeaders[j])
+ $duplicatedHeader.attr('id', newId)
+ const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`)
+ $headerLink.attr('href', `#${newId}`)
+ $headerLink.attr('title', newId)
+ }
}
+ }
}
-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);
- const target = $(`#${id}`);
- target.html('');
- new Toc('doc', {
- 'level': 3,
- 'top': -1,
- 'class': 'toc',
- 'targetId': id,
- 'process': getHeaderContent
- });
- if (target.text() == 'undefined')
- target.html('');
- target.replaceWith(target.html());
- }
+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)
+ const target = $(`#${id}`)
+ target.html('')
+ /* eslint-disable no-unused-vars */
+ var toc = new window.Toc('doc', {
+ 'level': 3,
+ 'top': -1,
+ 'class': 'toc',
+ 'targetId': id,
+ 'process': getHeaderContent
+ })
+ /* eslint-enable no-unused-vars */
+ if (target.text() === 'undefined') { target.html('') }
+ target.replaceWith(target.html())
+ }
}
-export function scrollToHash() {
- const hash = location.hash;
- location.hash = "";
- location.hash = hash;
+export function scrollToHash () {
+ const hash = location.hash
+ location.hash = ''
+ location.hash = hash
}
-function highlightRender(code, lang) {
- if (!lang || /no(-?)highlight|plain|text/.test(lang))
- return;
- code = S(code).escapeHTML().s
- if (lang == 'sequence') {
- return `<div class="sequence-diagram raw">${code}</div>`;
- } else if (lang == 'flow') {
- return `<div class="flow-chart raw">${code}</div>`;
- } else if (lang == 'graphviz') {
- return `<div class="graphviz raw">${code}</div>`;
- } else if (lang == 'mermaid') {
- return `<div class="mermaid raw">${code}</div>`;
+function highlightRender (code, lang) {
+ if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
+ code = S(code).escapeHTML().s
+ if (lang === 'sequence') {
+ return `<div class="sequence-diagram raw">${code}</div>`
+ } else if (lang === 'flow') {
+ return `<div class="flow-chart raw">${code}</div>`
+ } else if (lang === 'graphviz') {
+ return `<div class="graphviz raw">${code}</div>`
+ } else if (lang === 'mermaid') {
+ return `<div class="mermaid raw">${code}</div>`
+ }
+ const result = {
+ value: code
+ }
+ const showlinenumbers = /=$|=\d+$|=\+$/.test(lang)
+ if (showlinenumbers) {
+ let startnumber = 1
+ const matches = lang.match(/=(\d+)$/)
+ if (matches) { startnumber = parseInt(matches[1]) }
+ 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>`
}
- const result = {
- value: code
- };
- const showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang);
- if (showlinenumbers) {
- let startnumber = 1;
- const matches = lang.match(/\=(\d+)$/);
- if (matches)
- startnumber = parseInt(matches[1]);
- 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>`;
- }
- 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;
+ 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
}
-import markdownit from 'markdown-it';
-import markdownitContainer from 'markdown-it-container';
+import markdownit from 'markdown-it'
+import markdownitContainer from 'markdown-it-container'
export let md = markdownit('default', {
- html: true,
- breaks: true,
- langPrefix: "",
- linkify: true,
- typographer: true,
- highlight: highlightRender
-});
-window.md = md;
-
-md.use(require('markdown-it-abbr'));
-md.use(require('markdown-it-footnote'));
-md.use(require('markdown-it-deflist'));
-md.use(require('markdown-it-mark'));
-md.use(require('markdown-it-ins'));
-md.use(require('markdown-it-sub'));
-md.use(require('markdown-it-sup'));
+ html: true,
+ breaks: true,
+ langPrefix: '',
+ linkify: true,
+ typographer: true,
+ highlight: highlightRender
+})
+window.md = md
+
+md.use(require('markdown-it-abbr'))
+md.use(require('markdown-it-footnote'))
+md.use(require('markdown-it-deflist'))
+md.use(require('markdown-it-mark'))
+md.use(require('markdown-it-ins'))
+md.use(require('markdown-it-sub'))
+md.use(require('markdown-it-sup'))
md.use(require('markdown-it-mathjax')({
- beforeMath: '<span class="mathjax raw">',
- afterMath: '</span>',
- beforeInlineMath: '<span class="mathjax raw">\\(',
- afterInlineMath: '\\)</span>',
- beforeDisplayMath: '<span class="mathjax raw">\\[',
- afterDisplayMath: '\\]</span>'
-}));
-md.use(require('markdown-it-imsize'));
+ beforeMath: '<span class="mathjax raw">',
+ afterMath: '</span>',
+ beforeInlineMath: '<span class="mathjax raw">\\(',
+ afterInlineMath: '\\)</span>',
+ beforeDisplayMath: '<span class="mathjax raw">\\[',
+ afterDisplayMath: '\\]</span>'
+}))
+md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-emoji'), {
- shortcuts: {}
-});
-
-emojify.setConfig({
- blacklist: {
- elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'],
- classes: ['no-emojify']
- },
- img_dir: `${serverurl}/build/emojify.js/dist/images/basic`,
- ignore_emoticons: true
-});
-
-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(...arguments);
+ shortcuts: {}
+})
+
+window.emojify.setConfig({
+ blacklist: {
+ elements: ['script', 'textarea', 'a', 'pre', 'code', 'svg'],
+ classes: ['no-emojify']
+ },
+ img_dir: `${serverurl}/build/emojify.js/dist/images/basic`,
+ ignore_emoticons: true
+})
+
+md.renderer.rules.emoji = (token, idx) => window.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(...arguments)
}
-md.use(markdownitContainer, 'success', { render: renderContainer });
-md.use(markdownitContainer, 'info', { render: renderContainer });
-md.use(markdownitContainer, 'warning', { render: renderContainer });
-md.use(markdownitContainer, 'danger', { render: renderContainer });
+md.use(markdownitContainer, 'success', { render: renderContainer })
+md.use(markdownitContainer, 'info', { render: renderContainer })
+md.use(markdownitContainer, 'warning', { render: renderContainer })
+md.use(markdownitContainer, 'danger', { render: renderContainer })
md.renderer.rules.image = function (tokens, idx, options, env, self) {
- tokens[idx].attrJoin('class', 'raw');
- return self.renderToken(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ return self.renderToken(...arguments)
+}
md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) {
- tokens[idx].attrJoin('class', 'raw');
- return self.renderToken(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ return self.renderToken(...arguments)
+}
md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
- tokens[idx].attrJoin('class', 'raw');
- return self.renderToken(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ return self.renderToken(...arguments)
+}
md.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
- tokens[idx].attrJoin('class', 'raw');
- return self.renderToken(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ return self.renderToken(...arguments)
+}
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];
- if (/\!$/.test(info)) token.attrJoin('class', 'wrap');
- token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!$/, ''));
- token.attrJoin('class', 'hljs');
- token.attrJoin('class', 'raw');
- }
-
- if (options.highlight) {
- highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content);
- } else {
- highlighted = md.utils.escapeHtml(token.content);
- }
-
- if (highlighted.indexOf('<pre') === 0) {
- return `${highlighted}\n`;
- }
-
- return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`;
-};
+ 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]
+ if (/!$/.test(info)) token.attrJoin('class', 'wrap')
+ token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!$/, ''))
+ token.attrJoin('class', 'hljs')
+ token.attrJoin('class', 'raw')
+ }
+
+ if (options.highlight) {
+ highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
+ } else {
+ highlighted = md.utils.escapeHtml(token.content)
+ }
+
+ if (highlighted.indexOf('<pre') === 0) {
+ return `${highlighted}\n`
+ }
+
+ return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
+}
/* Defined regex markdown it plugins */
-import Plugin from 'markdown-it-regexp';
+import Plugin from 'markdown-it-regexp'
-//youtube
+// youtube
const youtubePlugin = new Plugin(
// regexp to match
/{%youtube\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const videoid = match[1];
- if (!videoid) return;
- const div = $('<div class="youtube raw"></div>');
- div.attr('data-videoid', videoid);
- const thumbnail_src = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`;
- const image = `<img src="${thumbnail_src}" />`;
- div.append(image);
- const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
- div.append(icon);
- return div[0].outerHTML;
+ const videoid = match[1]
+ if (!videoid) return
+ const div = $('<div class="youtube raw"></div>')
+ div.attr('data-videoid', videoid)
+ const thumbnailSrc = `//img.youtube.com/vi/${videoid}/hqdefault.jpg`
+ const image = `<img src="${thumbnailSrc}" />`
+ div.append(image)
+ const icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'
+ div.append(icon)
+ return div[0].outerHTML
}
-);
-//vimeo
+)
+// vimeo
const vimeoPlugin = new Plugin(
// regexp to match
/{%vimeo\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const videoid = match[1];
- if (!videoid) return;
- const div = $('<div class="vimeo raw"></div>');
- div.attr('data-videoid', videoid);
- const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
- div.append(icon);
- return div[0].outerHTML;
+ const videoid = match[1]
+ if (!videoid) return
+ const div = $('<div class="vimeo raw"></div>')
+ div.attr('data-videoid', videoid)
+ const icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'
+ div.append(icon)
+ return div[0].outerHTML
}
-);
-//gist
+)
+// gist
const gistPlugin = new Plugin(
// regexp to match
/{%gist\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const gistid = match[1];
- const code = `<code data-gist-id="${gistid}"/>`;
- return code;
+ const gistid = match[1]
+ const code = `<code data-gist-id="${gistid}"/>`
+ return code
}
-);
-//TOC
+)
+// TOC
const tocPlugin = new Plugin(
// regexp to match
/^\[TOC\]$/i,
(match, utils) => '<div class="toc"></div>'
-);
-//slideshare
+)
+// slideshare
const slidesharePlugin = new Plugin(
// regexp to match
/{%slideshare\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const slideshareid = match[1];
- const div = $('<div class="slideshare raw"></div>');
- div.attr('data-slideshareid', slideshareid);
- return div[0].outerHTML;
+ const slideshareid = match[1]
+ const div = $('<div class="slideshare raw"></div>')
+ div.attr('data-slideshareid', slideshareid)
+ return div[0].outerHTML
}
-);
-//speakerdeck
+)
+// speakerdeck
const speakerdeckPlugin = new Plugin(
// regexp to match
/{%speakerdeck\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const speakerdeckid = match[1];
- const div = $('<div class="speakerdeck raw"></div>');
- div.attr('data-speakerdeckid', speakerdeckid);
- return div[0].outerHTML;
+ const speakerdeckid = match[1]
+ const div = $('<div class="speakerdeck raw"></div>')
+ div.attr('data-speakerdeckid', speakerdeckid)
+ return div[0].outerHTML
}
-);
-//pdf
+)
+// pdf
const pdfPlugin = new Plugin(
// regexp to match
/{%pdf\s*([\d\D]*?)\s*%}/,
(match, utils) => {
- const pdfurl = match[1];
- if (!isValidURL(pdfurl)) return match[0];
- const div = $('<div class="pdf raw"></div>');
- div.attr('data-pdfurl', pdfurl);
- return div[0].outerHTML;
+ const pdfurl = match[1]
+ if (!isValidURL(pdfurl)) return match[0]
+ const div = $('<div class="pdf raw"></div>')
+ div.attr('data-pdfurl', pdfurl)
+ return div[0].outerHTML
}
-);
+)
-//yaml meta, from https://github.com/eugeneware/remarkable-meta
-function get(state, line) {
- const pos = state.bMarks[line];
- const max = state.eMarks[line];
- return state.src.substr(pos, max - pos);
+// yaml meta, from https://github.com/eugeneware/remarkable-meta
+function get (state, line) {
+ const pos = state.bMarks[line]
+ const max = state.eMarks[line]
+ return state.src.substr(pos, max - pos)
}
-function meta(state, start, end, silent) {
- if (start !== 0 || state.blkIndent !== 0) return false;
- if (state.tShift[start] < 0) return false;
- if (!get(state, start).match(/^---$/)) return false;
-
- const data = [];
- for (var line = start + 1; line < end; line++) {
- const str = get(state, line);
- if (str.match(/^(\.{3}|-{3})$/)) break;
- if (state.tShift[line] < 0) break;
- data.push(str);
- }
-
- if (line >= end) return false;
-
- try {
- md.meta = jsyaml.safeLoad(data.join('\n')) || {};
- delete md.metaError;
- } catch(err) {
- md.metaError = err;
- console.warn(err);
- return false;
- }
-
- state.line = line + 1;
-
- return true;
+function meta (state, start, end, silent) {
+ if (start !== 0 || state.blkIndent !== 0) return false
+ if (state.tShift[start] < 0) return false
+ if (!get(state, start).match(/^---$/)) return false
+
+ const data = []
+ for (var line = start + 1; line < end; line++) {
+ const str = get(state, line)
+ if (str.match(/^(\.{3}|-{3})$/)) break
+ if (state.tShift[line] < 0) break
+ data.push(str)
+ }
+
+ if (line >= end) return false
+
+ try {
+ md.meta = window.jsyaml.safeLoad(data.join('\n')) || {}
+ delete md.metaError
+ } catch (err) {
+ md.metaError = err
+ console.warn(err)
+ return false
+ }
+
+ state.line = line + 1
+
+ return true
}
-function metaPlugin(md) {
- md.meta = md.meta || {};
- md.block.ruler.before('code', 'meta', meta, {
- alt: []
- });
+function metaPlugin (md) {
+ md.meta = md.meta || {}
+ md.block.ruler.before('code', 'meta', meta, {
+ alt: []
+ })
}
-md.use(metaPlugin);
-md.use(youtubePlugin);
-md.use(vimeoPlugin);
-md.use(gistPlugin);
-md.use(tocPlugin);
-md.use(slidesharePlugin);
-md.use(speakerdeckPlugin);
-md.use(pdfPlugin);
+md.use(metaPlugin)
+md.use(youtubePlugin)
+md.use(vimeoPlugin)
+md.use(gistPlugin)
+md.use(tocPlugin)
+md.use(slidesharePlugin)
+md.use(speakerdeckPlugin)
+md.use(pdfPlugin)
export default {
md
-};
+}
diff --git a/public/js/google-drive-picker.js b/public/js/google-drive-picker.js
index 94aa77ff..5006cd25 100644
--- a/public/js/google-drive-picker.js
+++ b/public/js/google-drive-picker.js
@@ -1,119 +1,118 @@
-/**!
+/** !
* Google Drive File Picker Example
* By Daniel Lo Nigro (http://dan.cx/)
*/
-(function() {
- /**
- * Initialise a Google Driver file picker
- */
- var FilePicker = window.FilePicker = function(options) {
- // Config
- this.apiKey = options.apiKey;
- this.clientId = options.clientId;
-
- // Elements
- this.buttonEl = options.buttonEl;
-
- // Events
- this.onSelect = options.onSelect;
- this.buttonEl.on('click', this.open.bind(this));
-
- // Disable the button until the API loads, as it won't work properly until then.
- this.buttonEl.prop('disabled', true);
+(function () {
+ /**
+ * Initialise a Google Driver file picker
+ */
+ var FilePicker = window.FilePicker = function (options) {
+ // Config
+ this.apiKey = options.apiKey
+ this.clientId = options.clientId
- // Load the drive API
- gapi.client.setApiKey(this.apiKey);
- gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this));
- google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) });
- }
+ // Elements
+ this.buttonEl = options.buttonEl
- FilePicker.prototype = {
- /**
- * Open the file picker.
- */
- open: function() {
- // Check if the user has already authenticated
- var token = gapi.auth.getToken();
- if (token) {
- this._showPicker();
- } else {
- // The user has not yet authenticated with Google
- // We need to do the authentication before displaying the Drive picker.
- this._doAuth(false, function() { this._showPicker(); }.bind(this));
- }
- },
-
- /**
- * Show the file picker once authentication has been done.
- * @private
- */
- _showPicker: function() {
- var accessToken = gapi.auth.getToken().access_token;
- 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).
- setAppId(this.clientId).
- setOAuthToken(accessToken).
- setCallback(this._pickerCallback.bind(this)).
- build().
- setVisible(true);
- },
-
- /**
- * Called when a file has been selected in the Google Drive file picker.
- * @private
- */
- _pickerCallback: function(data) {
- if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
- var file = data[google.picker.Response.DOCUMENTS][0],
- id = file[google.picker.Document.ID],
- request = gapi.client.drive.files.get({
- fileId: id
- });
-
- request.execute(this._fileGetCallback.bind(this));
- }
- },
- /**
- * Called when file details have been retrieved from Google Drive.
- * @private
- */
- _fileGetCallback: function(file) {
- if (this.onSelect) {
- this.onSelect(file);
- }
- },
-
- /**
- * Called when the Google Drive file picker API has finished loading.
- * @private
- */
- _pickerApiLoaded: function() {
- this.buttonEl.prop('disabled', false);
- },
-
- /**
- * Called when the Google Drive API has finished loading.
- * @private
- */
- _driveApiLoaded: function() {
- this._doAuth(true);
- },
-
- /**
- * Authenticate with Google Drive via the Google JavaScript API.
- * @private
- */
- _doAuth: function(immediate, callback) {
- gapi.auth.authorize({
- client_id: this.clientId,
- scope: 'https://www.googleapis.com/auth/drive.readonly',
- immediate: immediate
- }, callback ? callback : function() {});
- }
- };
-}());
+ // Events
+ this.onSelect = options.onSelect
+ this.buttonEl.on('click', this.open.bind(this))
+
+ // Disable the button until the API loads, as it won't work properly until then.
+ this.buttonEl.prop('disabled', true)
+
+ // Load the drive API
+ window.gapi.client.setApiKey(this.apiKey)
+ window.gapi.client.load('drive', 'v2', this._driveApiLoaded.bind(this))
+ window.google.load('picker', '1', { callback: this._pickerApiLoaded.bind(this) })
+ }
+
+ FilePicker.prototype = {
+ /**
+ * Open the file picker.
+ */
+ open: function () {
+ // Check if the user has already authenticated
+ var token = window.gapi.auth.getToken()
+ if (token) {
+ this._showPicker()
+ } else {
+ // The user has not yet authenticated with Google
+ // We need to do the authentication before displaying the Drive picker.
+ this._doAuth(false, function () { this._showPicker() }.bind(this))
+ }
+ },
+
+ /**
+ * Show the file picker once authentication has been done.
+ * @private
+ */
+ _showPicker: function () {
+ var accessToken = window.gapi.auth.getToken().access_token
+ var view = new window.google.picker.DocsView()
+ view.setMimeTypes('text/markdown,text/html')
+ view.setIncludeFolders(true)
+ view.setOwnedByMe(true)
+ this.picker = new window.google.picker.PickerBuilder()
+ .enableFeature(window.google.picker.Feature.NAV_HIDDEN)
+ .addView(view)
+ .setAppId(this.clientId)
+ .setOAuthToken(accessToken)
+ .setCallback(this._pickerCallback.bind(this))
+ .build()
+ .setVisible(true)
+ },
+
+ /**
+ * Called when a file has been selected in the Google Drive file picker.
+ * @private
+ */
+ _pickerCallback: function (data) {
+ if (data[window.google.picker.Response.ACTION] === window.google.picker.Action.PICKED) {
+ var file = data[window.google.picker.Response.DOCUMENTS][0]
+ var id = file[window.google.picker.Document.ID]
+ var request = window.gapi.client.drive.files.get({
+ fileId: id
+ })
+ request.execute(this._fileGetCallback.bind(this))
+ }
+ },
+ /**
+ * Called when file details have been retrieved from Google Drive.
+ * @private
+ */
+ _fileGetCallback: function (file) {
+ if (this.onSelect) {
+ this.onSelect(file)
+ }
+ },
+
+ /**
+ * Called when the Google Drive file picker API has finished loading.
+ * @private
+ */
+ _pickerApiLoaded: function () {
+ this.buttonEl.prop('disabled', false)
+ },
+
+ /**
+ * Called when the Google Drive API has finished loading.
+ * @private
+ */
+ _driveApiLoaded: function () {
+ this._doAuth(true)
+ },
+
+ /**
+ * Authenticate with Google Drive via the Google JavaScript API.
+ * @private
+ */
+ _doAuth: function (immediate, callback) {
+ window.gapi.auth.authorize({
+ client_id: this.clientId,
+ scope: 'https://www.googleapis.com/auth/drive.readonly',
+ immediate: immediate
+ }, callback || function () {})
+ }
+ }
+}())
diff --git a/public/js/google-drive-upload.js b/public/js/google-drive-upload.js
index eabc5b7f..6c0e8a62 100644
--- a/public/js/google-drive-upload.js
+++ b/public/js/google-drive-upload.js
@@ -1,30 +1,31 @@
+/* eslint-env browser, jquery */
/**
* Helper for implementing retries with backoff. Initial retry
* delay is 1 second, increasing by 2x (+jitter) for subsequent retries
*
* @constructor
*/
-var RetryHandler = function() {
- this.interval = 1000; // Start at one second
- this.maxInterval = 60 * 1000; // Don't wait longer than a minute
-};
+var RetryHandler = function () {
+ this.interval = 1000 // Start at one second
+ this.maxInterval = 60 * 1000 // Don't wait longer than a minute
+}
/**
* Invoke the function after waiting
*
* @param {function} fn Function to invoke
*/
-RetryHandler.prototype.retry = function(fn) {
- setTimeout(fn, this.interval);
- this.interval = this.nextInterval_();
-};
+RetryHandler.prototype.retry = function (fn) {
+ setTimeout(fn, this.interval)
+ this.interval = this.nextInterval_()
+}
/**
* Reset the counter (e.g. after successful request.)
*/
-RetryHandler.prototype.reset = function() {
- this.interval = 1000;
-};
+RetryHandler.prototype.reset = function () {
+ this.interval = 1000
+}
/**
* Calculate the next wait time.
@@ -32,10 +33,10 @@ RetryHandler.prototype.reset = function() {
*
* @private
*/
-RetryHandler.prototype.nextInterval_ = function() {
- var interval = this.interval * 2 + this.getRandomInt_(0, 1000);
- return Math.min(interval, this.maxInterval);
-};
+RetryHandler.prototype.nextInterval_ = function () {
+ var interval = this.interval * 2 + this.getRandomInt_(0, 1000)
+ return Math.min(interval, this.maxInterval)
+}
/**
* Get a random int in the range of min to max. Used to add jitter to wait times.
@@ -44,10 +45,9 @@ RetryHandler.prototype.nextInterval_ = function() {
* @param {number} max Upper bounds
* @private
*/
-RetryHandler.prototype.getRandomInt_ = function(min, max) {
- return Math.floor(Math.random() * (max - min + 1) + min);
-};
-
+RetryHandler.prototype.getRandomInt_ = function (min, max) {
+ return Math.floor(Math.random() * (max - min + 1) + min)
+}
/**
* Helper class for resumable uploads using XHR/CORS. Can upload any Blob-like item, whether
@@ -75,116 +75,115 @@ RetryHandler.prototype.getRandomInt_ = function(min, max) {
* @param {function} [options.onProgress] Callback for status for the in-progress upload
* @param {function} [options.onError] Callback if upload fails
*/
-var MediaUploader = function(options) {
- var noop = function() {};
- this.file = options.file;
- this.contentType = options.contentType || this.file.type || 'application/octet-stream';
+var MediaUploader = function (options) {
+ var noop = function () {}
+ this.file = options.file
+ this.contentType = options.contentType || this.file.type || 'application/octet-stream'
this.metadata = options.metadata || {
'title': this.file.name,
'mimeType': this.contentType
- };
- this.token = options.token;
- this.onComplete = options.onComplete || noop;
- this.onProgress = options.onProgress || noop;
- this.onError = options.onError || noop;
- this.offset = options.offset || 0;
- this.chunkSize = options.chunkSize || 0;
- this.retryHandler = new RetryHandler();
+ }
+ this.token = options.token
+ this.onComplete = options.onComplete || noop
+ this.onProgress = options.onProgress || noop
+ this.onError = options.onError || noop
+ this.offset = options.offset || 0
+ this.chunkSize = options.chunkSize || 0
+ this.retryHandler = new RetryHandler()
- this.url = options.url;
+ this.url = options.url
if (!this.url) {
- var params = options.params || {};
- params.uploadType = 'resumable';
- this.url = this.buildUrl_(options.fileId, params, options.baseUrl);
+ var params = options.params || {}
+ params.uploadType = 'resumable'
+ this.url = this.buildUrl_(options.fileId, params, options.baseUrl)
}
- this.httpMethod = options.fileId ? 'PUT' : 'POST';
-};
+ this.httpMethod = options.fileId ? 'PUT' : 'POST'
+}
/**
* Initiate the upload.
*/
-MediaUploader.prototype.upload = function() {
- var self = this;
- var xhr = new XMLHttpRequest();
+MediaUploader.prototype.upload = function () {
+ var xhr = new XMLHttpRequest()
- xhr.open(this.httpMethod, this.url, true);
- xhr.setRequestHeader('Authorization', 'Bearer ' + this.token);
- xhr.setRequestHeader('Content-Type', 'application/json');
- xhr.setRequestHeader('X-Upload-Content-Length', this.file.size);
- xhr.setRequestHeader('X-Upload-Content-Type', this.contentType);
+ xhr.open(this.httpMethod, this.url, true)
+ xhr.setRequestHeader('Authorization', 'Bearer ' + this.token)
+ xhr.setRequestHeader('Content-Type', 'application/json')
+ xhr.setRequestHeader('X-Upload-Content-Length', this.file.size)
+ xhr.setRequestHeader('X-Upload-Content-Type', this.contentType)
- xhr.onload = function(e) {
+ xhr.onload = function (e) {
if (e.target.status < 400) {
- var location = e.target.getResponseHeader('Location');
- this.url = location;
- this.sendFile_();
+ var location = e.target.getResponseHeader('Location')
+ this.url = location
+ this.sendFile_()
} else {
- this.onUploadError_(e);
+ this.onUploadError_(e)
}
- }.bind(this);
- xhr.onerror = this.onUploadError_.bind(this);
- xhr.send(JSON.stringify(this.metadata));
-};
+ }.bind(this)
+ xhr.onerror = this.onUploadError_.bind(this)
+ xhr.send(JSON.stringify(this.metadata))
+}
/**
* Send the actual file content.
*
* @private
*/
-MediaUploader.prototype.sendFile_ = function() {
- var content = this.file;
- var end = this.file.size;
+MediaUploader.prototype.sendFile_ = function () {
+ var content = this.file
+ var end = this.file.size
if (this.offset || this.chunkSize) {
// Only bother to slice the file if we're either resuming or uploading in chunks
if (this.chunkSize) {
- end = Math.min(this.offset + this.chunkSize, this.file.size);
+ end = Math.min(this.offset + this.chunkSize, this.file.size)
}
- content = content.slice(this.offset, end);
+ content = content.slice(this.offset, end)
}
- var xhr = new XMLHttpRequest();
- xhr.open('PUT', this.url, true);
- xhr.setRequestHeader('Content-Type', this.contentType);
- xhr.setRequestHeader('Content-Range', "bytes " + this.offset + "-" + (end - 1) + "/" + this.file.size);
- xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
+ var xhr = new XMLHttpRequest()
+ xhr.open('PUT', this.url, true)
+ xhr.setRequestHeader('Content-Type', this.contentType)
+ xhr.setRequestHeader('Content-Range', 'bytes ' + this.offset + '-' + (end - 1) + '/' + this.file.size)
+ xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
if (xhr.upload) {
- xhr.upload.addEventListener('progress', this.onProgress);
+ xhr.upload.addEventListener('progress', this.onProgress)
}
- xhr.onload = this.onContentUploadSuccess_.bind(this);
- xhr.onerror = this.onContentUploadError_.bind(this);
- xhr.send(content);
-};
+ xhr.onload = this.onContentUploadSuccess_.bind(this)
+ xhr.onerror = this.onContentUploadError_.bind(this)
+ xhr.send(content)
+}
/**
* Query for the state of the file for resumption.
*
* @private
*/
-MediaUploader.prototype.resume_ = function() {
- var xhr = new XMLHttpRequest();
- xhr.open('PUT', this.url, true);
- xhr.setRequestHeader('Content-Range', "bytes */" + this.file.size);
- xhr.setRequestHeader('X-Upload-Content-Type', this.file.type);
+MediaUploader.prototype.resume_ = function () {
+ var xhr = new XMLHttpRequest()
+ xhr.open('PUT', this.url, true)
+ xhr.setRequestHeader('Content-Range', 'bytes */' + this.file.size)
+ xhr.setRequestHeader('X-Upload-Content-Type', this.file.type)
if (xhr.upload) {
- xhr.upload.addEventListener('progress', this.onProgress);
+ xhr.upload.addEventListener('progress', this.onProgress)
}
- xhr.onload = this.onContentUploadSuccess_.bind(this);
- xhr.onerror = this.onContentUploadError_.bind(this);
- xhr.send();
-};
+ xhr.onload = this.onContentUploadSuccess_.bind(this)
+ xhr.onerror = this.onContentUploadError_.bind(this)
+ xhr.send()
+}
/**
* Extract the last saved range if available in the request.
*
* @param {XMLHttpRequest} xhr Request object
*/
-MediaUploader.prototype.extractRange_ = function(xhr) {
- var range = xhr.getResponseHeader('Range');
+MediaUploader.prototype.extractRange_ = function (xhr) {
+ var range = xhr.getResponseHeader('Range')
if (range) {
- this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
+ this.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1
}
-};
+}
/**
* Handle successful responses for uploads. Depending on the context,
@@ -194,17 +193,17 @@ MediaUploader.prototype.extractRange_ = function(xhr) {
* @private
* @param {object} e XHR event
*/
-MediaUploader.prototype.onContentUploadSuccess_ = function(e) {
- if (e.target.status == 200 || e.target.status == 201) {
- this.onComplete(e.target.response);
- } else if (e.target.status == 308) {
- this.extractRange_(e.target);
- this.retryHandler.reset();
- this.sendFile_();
+MediaUploader.prototype.onContentUploadSuccess_ = function (e) {
+ if (e.target.status === 200 || e.target.status === 201) {
+ this.onComplete(e.target.response)
+ } else if (e.target.status === 308) {
+ this.extractRange_(e.target)
+ this.retryHandler.reset()
+ this.sendFile_()
} else {
- this.onContentUploadError_(e);
+ this.onContentUploadError_(e)
}
-};
+}
/**
* Handles errors for uploads. Either retries or aborts depending
@@ -213,13 +212,13 @@ MediaUploader.prototype.onContentUploadSuccess_ = function(e) {
* @private
* @param {object} e XHR event
*/
-MediaUploader.prototype.onContentUploadError_ = function(e) {
+MediaUploader.prototype.onContentUploadError_ = function (e) {
if (e.target.status && e.target.status < 500) {
- this.onError(e.target.response);
+ this.onError(e.target.response)
} else {
- this.retryHandler.retry(this.resume_.bind(this));
+ this.retryHandler.retry(this.resume_.bind(this))
}
-};
+}
/**
* Handles errors for the initial request.
@@ -227,9 +226,9 @@ MediaUploader.prototype.onContentUploadError_ = function(e) {
* @private
* @param {object} e XHR event
*/
-MediaUploader.prototype.onUploadError_ = function(e) {
- this.onError(e.target.response); // TODO - Retries for initial upload
-};
+MediaUploader.prototype.onUploadError_ = function (e) {
+ this.onError(e.target.response) // TODO - Retries for initial upload
+}
/**
* Construct a query string from a hash/object
@@ -238,12 +237,12 @@ MediaUploader.prototype.onUploadError_ = function(e) {
* @param {object} [params] Key/value pairs for query string
* @return {string} query string
*/
-MediaUploader.prototype.buildQuery_ = function(params) {
- params = params || {};
- return Object.keys(params).map(function(key) {
- return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
- }).join('&');
-};
+MediaUploader.prototype.buildQuery_ = function (params) {
+ params = params || {}
+ return Object.keys(params).map(function (key) {
+ return encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
+ }).join('&')
+}
/**
* Build the drive upload URL
@@ -253,16 +252,16 @@ MediaUploader.prototype.buildQuery_ = function(params) {
* @param {object} [params] Query parameters
* @return {string} URL
*/
-MediaUploader.prototype.buildUrl_ = function(id, params, baseUrl) {
- var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/';
+MediaUploader.prototype.buildUrl_ = function (id, params, baseUrl) {
+ var url = baseUrl || 'https://www.googleapis.com/upload/drive/v2/files/'
if (id) {
- url += id;
+ url += id
}
- var query = this.buildQuery_(params);
+ var query = this.buildQuery_(params)
if (query) {
- url += '?' + query;
+ url += '?' + query
}
- return url;
-};
+ return url
+}
-window.MediaUploader = MediaUploader;
+window.MediaUploader = MediaUploader
diff --git a/public/js/history.js b/public/js/history.js
index 34b2cba7..e14b80d8 100644
--- a/public/js/history.js
+++ b/public/js/history.js
@@ -1,372 +1,328 @@
-import store from 'store';
-import S from 'string';
+/* eslint-env browser, jquery */
+/* global serverurl, Cookies, moment */
+
+import store from 'store'
+import S from 'string'
import {
checkIfAuth
-} from './lib/common/login';
+} from './lib/common/login'
import {
urlpath
-} from './lib/config';
+} from './lib/config'
-window.migrateHistoryFromTempCallback = null;
+window.migrateHistoryFromTempCallback = null
-migrateHistoryFromTemp();
+migrateHistoryFromTemp()
-function migrateHistoryFromTemp() {
- if (url('#tempid')) {
- $.get(`${serverurl}/temp`, {
- tempid: url('#tempid')
- })
- .done(data => {
- if (data && data.temp) {
- getStorageHistory(olddata => {
- if (!olddata || olddata.length == 0) {
- saveHistoryToStorage(JSON.parse(data.temp));
- }
- });
- }
- })
- .always(() => {
- let hash = location.hash.split('#')[1];
- hash = hash.split('&');
- for (let i = 0; i < hash.length; i++)
- if (hash[i].indexOf('tempid') == 0) {
- hash.splice(i, 1);
- i--;
- }
- hash = hash.join('&');
- location.hash = hash;
- if (migrateHistoryFromTempCallback)
- migrateHistoryFromTempCallback();
- });
- }
+function migrateHistoryFromTemp () {
+ if (window.url('#tempid')) {
+ $.get(`${serverurl}/temp`, {
+ tempid: window.url('#tempid')
+ })
+ .done(data => {
+ if (data && data.temp) {
+ getStorageHistory(olddata => {
+ if (!olddata || olddata.length === 0) {
+ saveHistoryToStorage(JSON.parse(data.temp))
+ }
+ })
+ }
+ })
+ .always(() => {
+ let hash = location.hash.split('#')[1]
+ hash = hash.split('&')
+ for (let i = 0; i < hash.length; i++) {
+ if (hash[i].indexOf('tempid') === 0) {
+ hash.splice(i, 1)
+ i--
+ }
+ }
+ hash = hash.join('&')
+ location.hash = hash
+ if (window.migrateHistoryFromTempCallback) { window.migrateHistoryFromTempCallback() }
+ })
+ }
}
-export function saveHistory(notehistory) {
- checkIfAuth(
+export function saveHistory (notehistory) {
+ checkIfAuth(
() => {
- saveHistoryToServer(notehistory);
+ saveHistoryToServer(notehistory)
},
() => {
- saveHistoryToStorage(notehistory);
+ saveHistoryToStorage(notehistory)
}
- );
+ )
}
-function saveHistoryToStorage(notehistory) {
- if (store.enabled)
- store.set('notehistory', JSON.stringify(notehistory));
- else
- saveHistoryToCookie(notehistory);
+function saveHistoryToStorage (notehistory) {
+ if (store.enabled) { store.set('notehistory', JSON.stringify(notehistory)) } else { saveHistoryToCookie(notehistory) }
}
-function saveHistoryToCookie(notehistory) {
- Cookies.set('notehistory', notehistory, {
- expires: 365
- });
+function saveHistoryToCookie (notehistory) {
+ Cookies.set('notehistory', notehistory, {
+ expires: 365
+ })
}
-function saveHistoryToServer(notehistory) {
- $.post(`${serverurl}/history`, {
- history: JSON.stringify(notehistory)
- });
+function saveHistoryToServer (notehistory) {
+ $.post(`${serverurl}/history`, {
+ history: JSON.stringify(notehistory)
+ })
}
-function saveCookieHistoryToStorage(callback) {
- store.set('notehistory', Cookies.get('notehistory'));
- callback();
-}
-
-export function saveStorageHistoryToServer(callback) {
- const data = store.get('notehistory');
- if (data) {
- $.post(`${serverurl}/history`, {
- history: data
- })
- .done(data => {
- callback(data);
- });
- }
-}
-
-function saveCookieHistoryToServer(callback) {
+export function saveStorageHistoryToServer (callback) {
+ const data = store.get('notehistory')
+ if (data) {
$.post(`${serverurl}/history`, {
- history: Cookies.get('notehistory')
- })
- .done(data => {
- callback(data);
- });
+ history: data
+ })
+ .done(data => {
+ callback(data)
+ })
+ }
}
-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) {
- 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];
- }
- found = true;
- break;
- }
+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) {
+ 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]
}
- if (!found)
- newnotehistory.push(notehistory[i]);
+ found = true
+ break
+ }
}
- return newnotehistory;
+ if (!found) { newnotehistory.push(notehistory[i]) }
+ }
+ return newnotehistory
}
-function addHistory(id, text, time, tags, pinned, notehistory) {
+function addHistory (id, text, time, tags, pinned, notehistory) {
// only add when note id exists
- if (id) {
- notehistory.push({
- id,
- text,
- time,
- tags,
- pinned
- });
- }
- return notehistory;
+ if (id) {
+ notehistory.push({
+ id,
+ text,
+ time,
+ tags,
+ pinned
+ })
+ }
+ return notehistory
}
-export function removeHistory(id, notehistory) {
- for (let i = 0; i < notehistory.length; i++) {
- if (notehistory[i].id == id) {
- notehistory.splice(i, 1);
- i -= 1;
- }
+export function removeHistory (id, notehistory) {
+ for (let i = 0; i < notehistory.length; i++) {
+ if (notehistory[i].id === id) {
+ notehistory.splice(i, 1)
+ i -= 1
}
- return notehistory;
+ }
+ return notehistory
}
-//used for inner
-export function writeHistory(title, tags) {
- checkIfAuth(
+// used for inner
+export function writeHistory (title, tags) {
+ checkIfAuth(
() => {
// no need to do this anymore, this will count from server-side
// writeHistoryToServer(title, tags);
},
() => {
- writeHistoryToStorage(title, tags);
+ writeHistoryToStorage(title, tags)
}
- );
+ )
}
-function writeHistoryToServer(title, tags) {
- $.get(`${serverurl}/history`)
- .done(data => {
- try {
- if (data.history) {
- var notehistory = data.history;
- } else {
- var notehistory = [];
- }
- } catch (err) {
- var notehistory = [];
- }
- if (!notehistory)
- notehistory = [];
-
- const newnotehistory = generateHistory(title, tags, notehistory);
- saveHistoryToServer(newnotehistory);
- })
- .fail((xhr, status, error) => {
- console.error(xhr.responseText);
- });
+function writeHistoryToCookie (title, tags) {
+ var notehistory
+ try {
+ notehistory = Cookies.getJSON('notehistory')
+ } catch (err) {
+ notehistory = []
+ }
+ if (!notehistory) { notehistory = [] }
+ const newnotehistory = generateHistory(title, tags, notehistory)
+ saveHistoryToCookie(newnotehistory)
}
-function writeHistoryToCookie(title, tags) {
- try {
- var notehistory = Cookies.getJSON('notehistory');
- } catch (err) {
- var notehistory = [];
- }
- if (!notehistory)
- notehistory = [];
-
- const newnotehistory = generateHistory(title, tags, notehistory);
- saveHistoryToCookie(newnotehistory);
-}
-
-function writeHistoryToStorage(title, tags) {
- if (store.enabled) {
- let data = store.get('notehistory');
- if (data) {
- if (typeof data == "string")
- data = JSON.parse(data);
- var notehistory = data;
- } else
- var notehistory = [];
- if (!notehistory)
- notehistory = [];
-
- const newnotehistory = generateHistory(title, tags, notehistory);
- saveHistoryToStorage(newnotehistory);
+function writeHistoryToStorage (title, tags) {
+ if (store.enabled) {
+ let data = store.get('notehistory')
+ var notehistory
+ if (data) {
+ if (typeof data === 'string') { data = JSON.parse(data) }
+ notehistory = data
} else {
- writeHistoryToCookie(title, tags);
+ notehistory = []
}
+ if (!notehistory) { notehistory = [] }
+
+ const newnotehistory = generateHistory(title, tags, notehistory)
+ saveHistoryToStorage(newnotehistory)
+ } else {
+ writeHistoryToCookie(title, tags)
+ }
}
if (!Array.isArray) {
- Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]';
+ Array.isArray = arg => Object.prototype.toString.call(arg) === '[object Array]'
}
-function renderHistory(title, tags) {
- //console.debug(tags);
- const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1];
- return {
- id,
- text: title,
- time: moment().valueOf(),
- tags
- };
+function renderHistory (title, tags) {
+ // console.debug(tags);
+ const id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]
+ return {
+ id,
+ text: title,
+ time: moment().valueOf(),
+ tags
+ }
}
-function generateHistory(title, tags, notehistory) {
- 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;
- }
+function generateHistory (title, tags, notehistory) {
+ 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);
- return notehistory;
+ }
+ notehistory = removeHistory(info.id, notehistory)
+ notehistory = addHistory(info.id, info.text, info.time, info.tags, pinned, notehistory)
+ notehistory = clearDuplicatedHistory(notehistory)
+ return notehistory
}
-//used for outer
-export function getHistory(callback) {
- checkIfAuth(
+// used for outer
+export function getHistory (callback) {
+ checkIfAuth(
() => {
- getServerHistory(callback);
+ getServerHistory(callback)
},
() => {
- getStorageHistory(callback);
+ getStorageHistory(callback)
}
- );
+ )
}
-function getServerHistory(callback) {
- $.get(`${serverurl}/history`)
+function getServerHistory (callback) {
+ $.get(`${serverurl}/history`)
.done(data => {
- if (data.history) {
- callback(data.history);
- }
+ if (data.history) {
+ callback(data.history)
+ }
})
.fail((xhr, status, error) => {
- console.error(xhr.responseText);
- });
+ console.error(xhr.responseText)
+ })
}
-function getCookieHistory(callback) {
- callback(Cookies.getJSON('notehistory'));
+function getCookieHistory (callback) {
+ callback(Cookies.getJSON('notehistory'))
}
-export function getStorageHistory(callback) {
- if (store.enabled) {
- let data = store.get('notehistory');
- if (data) {
- if (typeof data == "string")
- data = JSON.parse(data);
- callback(data);
- } else
- getCookieHistory(callback);
- } else {
- getCookieHistory(callback);
- }
+export function getStorageHistory (callback) {
+ if (store.enabled) {
+ let data = store.get('notehistory')
+ if (data) {
+ if (typeof data === 'string') { data = JSON.parse(data) }
+ callback(data)
+ } else { getCookieHistory(callback) }
+ } else {
+ getCookieHistory(callback)
+ }
}
-export function parseHistory(list, callback) {
- checkIfAuth(
+export function parseHistory (list, callback) {
+ checkIfAuth(
() => {
- parseServerToHistory(list, callback);
+ parseServerToHistory(list, callback)
},
() => {
- parseStorageToHistory(list, callback);
+ parseStorageToHistory(list, callback)
}
- );
+ )
}
-export function parseServerToHistory(list, callback) {
- $.get(`${serverurl}/history`)
+export function parseServerToHistory (list, callback) {
+ $.get(`${serverurl}/history`)
.done(data => {
- if (data.history) {
- parseToHistory(list, data.history, callback);
- }
+ if (data.history) {
+ parseToHistory(list, data.history, callback)
+ }
})
.fail((xhr, status, error) => {
- console.error(xhr.responseText);
- });
+ console.error(xhr.responseText)
+ })
}
-function parseCookieToHistory(list, callback) {
- const notehistory = Cookies.getJSON('notehistory');
- parseToHistory(list, notehistory, callback);
+function parseCookieToHistory (list, callback) {
+ const notehistory = Cookies.getJSON('notehistory')
+ parseToHistory(list, notehistory, callback)
}
-export function parseStorageToHistory(list, callback) {
- if (store.enabled) {
- let data = store.get('notehistory');
- if (data) {
- if (typeof data == "string")
- data = JSON.parse(data);
- parseToHistory(list, data, callback);
- } else
- parseCookieToHistory(list, callback);
- } else {
- parseCookieToHistory(list, callback);
- }
+export function parseStorageToHistory (list, callback) {
+ if (store.enabled) {
+ let data = store.get('notehistory')
+ if (data) {
+ if (typeof data === 'string') { data = JSON.parse(data) }
+ parseToHistory(list, data, callback)
+ } else { parseCookieToHistory(list, callback) }
+ } else {
+ parseCookieToHistory(list, callback)
+ }
}
-function parseToHistory(list, notehistory, callback) {
- if (!callback) return;
- else if (!list || !notehistory) callback(list, notehistory);
- else if (notehistory && notehistory.length > 0) {
- for (let i = 0; i < notehistory.length; i++) {
- //parse time to timestamp and fromNow
- const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
- notehistory[i].timestamp = timestamp.valueOf();
- notehistory[i].fromNow = timestamp.fromNow();
- notehistory[i].time = timestamp.format('llll');
+function parseToHistory (list, notehistory, callback) {
+ if (!callback) return
+ else if (!list || !notehistory) callback(list, notehistory)
+ else if (notehistory && notehistory.length > 0) {
+ for (let i = 0; i < notehistory.length; i++) {
+ // parse time to timestamp and fromNow
+ const timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'))
+ notehistory[i].timestamp = timestamp.valueOf()
+ notehistory[i].fromNow = timestamp.fromNow()
+ notehistory[i].time = timestamp.format('llll')
// prevent XSS
- notehistory[i].text = S(notehistory[i].text).escapeHTML().s;
- notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : [];
+ notehistory[i].text = S(notehistory[i].text).escapeHTML().s
+ notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
// add to list
- if (notehistory[i].id && list.get('id', notehistory[i].id).length == 0)
- list.add(notehistory[i]);
- }
+ if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
}
- callback(list, notehistory);
+ }
+ callback(list, notehistory)
}
-export function postHistoryToServer(noteId, data, callback) {
- $.post(`${serverurl}/history/${noteId}`, data)
+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);
- });
+ console.error(xhr.responseText)
+ return callback(error, null)
+ })
}
-export function deleteServerHistory(noteId, callback) {
- $.ajax({
- url: `${serverurl}/history${noteId ? '/' + noteId : ""}`,
- type: 'DELETE'
- })
+export function deleteServerHistory (noteId, callback) {
+ $.ajax({
+ url: `${serverurl}/history${noteId ? '/' + noteId : ''}`,
+ type: 'DELETE'
+ })
.done(result => callback(null, result))
.fail((xhr, status, error) => {
- console.error(xhr.responseText);
- return callback(error, null);
- });
+ console.error(xhr.responseText)
+ return callback(error, null)
+ })
}
diff --git a/public/js/htmlExport.js b/public/js/htmlExport.js
index 1c2c5eb9..1a873aca 100644
--- a/public/js/htmlExport.js
+++ b/public/js/htmlExport.js
@@ -1,6 +1,6 @@
-require('../css/github-extract.css');
-require('../css/markdown.css');
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/google-font.css');
-require('../css/site.css');
+require('../css/github-extract.css')
+require('../css/markdown.css')
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/google-font.css')
+require('../css/site.css')
diff --git a/public/js/index.js b/public/js/index.js
index f0c476ef..e672a68d 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1,26 +1,30 @@
-/* jquery and jquery plugins */
-require('../vendor/showup/showup');
+/* eslint-env browser, jquery */
+/* global CodeMirror, Cookies, moment, editor, ui, Spinner,
+ modeType, Idle, serverurl, key, gapi, Dropbox, FilePicker
+ ot, MediaUploader, hex2rgb, num_loaded, Visibility */
-require('../css/index.css');
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/site.css');
+require('../vendor/showup/showup')
-require('highlight.js/styles/github-gist.css');
+require('../css/index.css')
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/site.css')
-var toMarkdown = require('to-markdown');
+require('highlight.js/styles/github-gist.css')
-var saveAs = require('file-saver').saveAs;
-var randomColor = require('randomcolor');
+var toMarkdown = require('to-markdown')
-var _ = require("lodash");
+var saveAs = require('file-saver').saveAs
+var randomColor = require('randomcolor')
-var List = require('list.js');
+var _ = require('lodash')
+
+var List = require('list.js')
import {
checkLoginStateChanged,
setloginStateChangeEvent
-} from './lib/common/login';
+} from './lib/common/login'
import {
debug,
@@ -31,7 +35,7 @@ import {
noteurl,
urlpath,
version
-} from './lib/config';
+} from './lib/config'
import {
autoLinkify,
@@ -53,14 +57,14 @@ import {
updateLastChange,
updateLastChangeUser,
updateOwner
-} from './extra';
+} from './extra'
import {
clearMap,
setupSyncAreas,
syncScrollToEdit,
syncScrollToView
-} from './syncscroll';
+} from './syncscroll'
import {
writeHistory,
@@ -68,4007 +72,3856 @@ import {
getHistory,
saveHistory,
removeHistory
-} from './history';
+} from './history'
-var renderer = require('./render');
-var preventXSS = renderer.preventXSS;
+var renderer = require('./render')
+var preventXSS = renderer.preventXSS
-var defaultTextHeight = 20;
-var viewportMargin = 20;
-var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-var defaultEditorMode = 'gfm';
+var defaultTextHeight = 20
+var viewportMargin = 20
+var mac = CodeMirror.keyMap['default'] === CodeMirror.keyMap.macDefault
+var defaultEditorMode = 'gfm'
var defaultExtraKeys = {
- "F10": function (cm) {
- cm.setOption("fullScreen", !cm.getOption("fullScreen"));
- },
- "Esc": function (cm) {
- if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass;
- else if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
- },
- "Cmd-S": function () {
- return false;
- },
- "Ctrl-S": function () {
- return false;
- },
- "Enter": "newlineAndIndentContinueMarkdownList",
- "Tab": function (cm) {
- var tab = '\t';
- var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" ");
- //auto indent whole line when in list or blockquote
- var cursor = cm.getCursor();
- var line = cm.getLine(cursor.line);
- var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/;
- var match;
- var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1;
- if (multiple) {
- cm.execCommand('defaultTab');
- } else if ((match = regex.exec(line)) !== null) {
- var ch = match[1].length;
- var pos = {
- line: cursor.line,
- ch: ch
- };
- if (cm.getOption('indentWithTabs'))
- cm.replaceRange(tab, pos, pos, '+input');
- else
- cm.replaceRange(spaces, pos, pos, '+input');
- } else {
- if (cm.getOption('indentWithTabs'))
- cm.execCommand('defaultTab');
- else {
- cm.replaceSelection(spaces);
- }
- }
- },
- "Cmd-Left": "goLineLeftSmart",
- "Cmd-Right": "goLineRight",
- "Ctrl-C": function (cm) {
- if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand("copy");
- else return CodeMirror.Pass;
- },
- "Ctrl-*": function (cm) {
- wrapTextWith(cm, '*');
- },
- "Shift-Ctrl-8": function (cm) {
- wrapTextWith(cm, '*');
- },
- "Ctrl-_": function (cm) {
- wrapTextWith(cm, '_');
- },
- "Shift-Ctrl--": function (cm) {
- wrapTextWith(cm, '_');
- },
- "Ctrl-~": function (cm) {
- wrapTextWith(cm, '~');
- },
- "Shift-Ctrl-`": function (cm) {
- wrapTextWith(cm, '~');
- },
- "Ctrl-^": function (cm) {
- wrapTextWith(cm, '^');
- },
- "Shift-Ctrl-6": function (cm) {
- wrapTextWith(cm, '^');
- },
- "Ctrl-+": function (cm) {
- wrapTextWith(cm, '+');
- },
- "Shift-Ctrl-=": function (cm) {
- wrapTextWith(cm, '+');
- },
- "Ctrl-=": function (cm) {
- wrapTextWith(cm, '=');
- },
- "Shift-Ctrl-Backspace": function (cm) {
- wrapTextWith(cm, 'Backspace');
- }
-};
-
-var wrapSymbols = ['*', '_', '~', '^', '+', '='];
-
-function wrapTextWith(cm, symbol) {
- if (!cm.getSelection()) {
- return CodeMirror.Pass;
+ 'F10': function (cm) {
+ cm.setOption('fullScreen', !cm.getOption('fullScreen'))
+ },
+ 'Esc': function (cm) {
+ if (cm.getOption('keyMap').substr(0, 3) === 'vim') return CodeMirror.Pass
+ else if (cm.getOption('fullScreen')) cm.setOption('fullScreen', false)
+ },
+ 'Cmd-S': function () {
+ return false
+ },
+ 'Ctrl-S': function () {
+ return false
+ },
+ 'Enter': 'newlineAndIndentContinueMarkdownList',
+ 'Tab': function (cm) {
+ var tab = '\t'
+ var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ')
+ // auto indent whole line when in list or blockquote
+ var cursor = cm.getCursor()
+ var line = cm.getLine(cursor.line)
+ var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
+ var match
+ var multiple = cm.getSelection().split('\n').length > 1 || cm.getSelections().length > 1
+ if (multiple) {
+ cm.execCommand('defaultTab')
+ } else if ((match = regex.exec(line)) !== null) {
+ var ch = match[1].length
+ var pos = {
+ line: cursor.line,
+ ch: ch
+ }
+ if (cm.getOption('indentWithTabs')) { cm.replaceRange(tab, pos, pos, '+input') } else { cm.replaceRange(spaces, pos, pos, '+input') }
} else {
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (!range.empty()) {
- var from = range.from(), to = range.to();
- if (symbol !== 'Backspace') {
- cm.replaceRange(symbol, to, to, '+input');
- cm.replaceRange(symbol, from, from, '+input');
+ if (cm.getOption('indentWithTabs')) { cm.execCommand('defaultTab') } else {
+ cm.replaceSelection(spaces)
+ }
+ }
+ },
+ 'Cmd-Left': 'goLineLeftSmart',
+ 'Cmd-Right': 'goLineRight',
+ 'Ctrl-C': function (cm) {
+ if (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand('copy')
+ else return CodeMirror.Pass
+ },
+ 'Ctrl-*': function (cm) {
+ wrapTextWith(cm, '*')
+ },
+ 'Shift-Ctrl-8': function (cm) {
+ wrapTextWith(cm, '*')
+ },
+ 'Ctrl-_': function (cm) {
+ wrapTextWith(cm, '_')
+ },
+ 'Shift-Ctrl--': function (cm) {
+ wrapTextWith(cm, '_')
+ },
+ 'Ctrl-~': function (cm) {
+ wrapTextWith(cm, '~')
+ },
+ 'Shift-Ctrl-`': function (cm) {
+ wrapTextWith(cm, '~')
+ },
+ 'Ctrl-^': function (cm) {
+ wrapTextWith(cm, '^')
+ },
+ 'Shift-Ctrl-6': function (cm) {
+ wrapTextWith(cm, '^')
+ },
+ 'Ctrl-+': function (cm) {
+ wrapTextWith(cm, '+')
+ },
+ 'Shift-Ctrl-=': function (cm) {
+ wrapTextWith(cm, '+')
+ },
+ 'Ctrl-=': function (cm) {
+ wrapTextWith(cm, '=')
+ },
+ 'Shift-Ctrl-Backspace': function (cm) {
+ wrapTextWith(cm, 'Backspace')
+ }
+}
+
+var wrapSymbols = ['*', '_', '~', '^', '+', '=']
+
+function wrapTextWith (cm, symbol) {
+ if (!cm.getSelection()) {
+ return CodeMirror.Pass
+ } else {
+ var ranges = cm.listSelections()
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i]
+ if (!range.empty()) {
+ var from = range.from()
+ var to = range.to()
+ if (symbol !== 'Backspace') {
+ cm.replaceRange(symbol, to, to, '+input')
+ cm.replaceRange(symbol, from, from, '+input')
// workaround selection range not correct after add symbol
- var _ranges = cm.listSelections();
- var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
- var headIndex = editor.indexFromPos(_ranges[i].head);
- if (anchorIndex > headIndex) {
- _ranges[i].anchor.ch--;
- } else {
- _ranges[i].head.ch--;
- }
- cm.setSelections(_ranges);
- } else {
- var preEndPos = {
- line: to.line,
- ch: to.ch + 1
- };
- var preText = cm.getRange(to, preEndPos);
- var preIndex = wrapSymbols.indexOf(preText);
- var postEndPos = {
- line: from.line,
- ch: from.ch - 1
- };
- var postText = cm.getRange(postEndPos, from);
- var postIndex = wrapSymbols.indexOf(postText);
+ var _ranges = cm.listSelections()
+ var anchorIndex = window.editor.indexFromPos(_ranges[i].anchor)
+ var headIndex = window.editor.indexFromPos(_ranges[i].head)
+ if (anchorIndex > headIndex) {
+ _ranges[i].anchor.ch--
+ } else {
+ _ranges[i].head.ch--
+ }
+ cm.setSelections(_ranges)
+ } else {
+ var preEndPos = {
+ line: to.line,
+ ch: to.ch + 1
+ }
+ var preText = cm.getRange(to, preEndPos)
+ var preIndex = wrapSymbols.indexOf(preText)
+ var postEndPos = {
+ line: from.line,
+ ch: from.ch - 1
+ }
+ var postText = cm.getRange(postEndPos, from)
+ var postIndex = wrapSymbols.indexOf(postText)
// check if surround symbol are list in array and matched
- if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
- cm.replaceRange("", to, preEndPos, '+delete');
- cm.replaceRange("", postEndPos, from, '+delete');
- }
- }
- }
+ if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
+ cm.replaceRange('', to, preEndPos, '+delete')
+ cm.replaceRange('', postEndPos, from, '+delete')
+ }
}
+ }
}
+ }
}
-var idleTime = 300000; //5 mins
-var updateViewDebounce = 100;
-var cursorMenuThrottle = 50;
-var cursorActivityDebounce = 50;
-var cursorAnimatePeriod = 100;
-var supportContainers = ['success', 'info', 'warning', 'danger'];
-var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go'];
-var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid'];
+var idleTime = 300000 // 5 mins
+var updateViewDebounce = 100
+var cursorMenuThrottle = 50
+var cursorActivityDebounce = 50
+var cursorAnimatePeriod = 100
+var supportContainers = ['success', 'info', 'warning', 'danger']
+var supportCodeModes = ['javascript', 'typescript', 'jsx', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'haskell', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki', 'go']
+var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']
var supportHeaders = [
- {
- text: '# h1',
- search: '#'
- },
- {
- text: '## h2',
- search: '##'
- },
- {
- text: '### h3',
- search: '###'
- },
- {
- text: '#### h4',
- search: '####'
- },
- {
- text: '##### h5',
- search: '#####'
- },
- {
- text: '###### h6',
- search: '######'
- },
- {
- text: '###### tags: `example`',
- search: '###### tags:'
- }
-];
+ {
+ text: '# h1',
+ search: '#'
+ },
+ {
+ text: '## h2',
+ search: '##'
+ },
+ {
+ text: '### h3',
+ search: '###'
+ },
+ {
+ text: '#### h4',
+ search: '####'
+ },
+ {
+ text: '##### h5',
+ search: '#####'
+ },
+ {
+ text: '###### h6',
+ search: '######'
+ },
+ {
+ text: '###### tags: `example`',
+ search: '###### tags:'
+ }
+]
var supportReferrals = [
- {
- text: '[reference link]',
- search: '[]'
- },
- {
- text: '[reference]: https:// "title"',
- search: '[]:'
- },
- {
- text: '[^footnote link]',
- search: '[^]'
- },
- {
- text: '[^footnote reference]: https:// "title"',
- search: '[^]:'
- },
- {
- text: '^[inline footnote]',
- search: '^[]'
- },
- {
- text: '[link text][reference]',
- search: '[][]'
- },
- {
- text: '[link text](https:// "title")',
- search: '[]()'
- },
- {
- text: '![image alt][reference]',
- search: '![][]'
- },
- {
- text: '![image alt](https:// "title")',
- search: '![]()'
- },
- {
- text: '![image alt](https:// "title" =WidthxHeight)',
- search: '![]()'
- },
- {
- text: '[TOC]',
- search: '[]'
- }
-];
+ {
+ text: '[reference link]',
+ search: '[]'
+ },
+ {
+ text: '[reference]: https:// "title"',
+ search: '[]:'
+ },
+ {
+ text: '[^footnote link]',
+ search: '[^]'
+ },
+ {
+ text: '[^footnote reference]: https:// "title"',
+ search: '[^]:'
+ },
+ {
+ text: '^[inline footnote]',
+ search: '^[]'
+ },
+ {
+ text: '[link text][reference]',
+ search: '[][]'
+ },
+ {
+ text: '[link text](https:// "title")',
+ search: '[]()'
+ },
+ {
+ text: '![image alt][reference]',
+ search: '![][]'
+ },
+ {
+ text: '![image alt](https:// "title")',
+ search: '![]()'
+ },
+ {
+ text: '![image alt](https:// "title" =WidthxHeight)',
+ search: '![]()'
+ },
+ {
+ text: '[TOC]',
+ search: '[]'
+ }
+]
var supportExternals = [
- {
- text: '{%youtube youtubeid %}',
- search: 'youtube'
- },
- {
- text: '{%vimeo vimeoid %}',
- search: 'vimeo'
- },
- {
- text: '{%gist gistid %}',
- search: 'gist'
- },
- {
- text: '{%slideshare slideshareid %}',
- search: 'slideshare'
- },
- {
- text: '{%speakerdeck speakerdeckid %}',
- search: 'speakerdeck'
- },
- {
- text: '{%pdf pdfurl %}',
- search: 'pdf'
- }
-];
+ {
+ text: '{%youtube youtubeid %}',
+ search: 'youtube'
+ },
+ {
+ text: '{%vimeo vimeoid %}',
+ search: 'vimeo'
+ },
+ {
+ text: '{%gist gistid %}',
+ search: 'gist'
+ },
+ {
+ text: '{%slideshare slideshareid %}',
+ search: 'slideshare'
+ },
+ {
+ text: '{%speakerdeck speakerdeckid %}',
+ search: 'speakerdeck'
+ },
+ {
+ text: '{%pdf pdfurl %}',
+ search: 'pdf'
+ }
+]
var supportExtraTags = [
- {
- text: '[name tag]',
- search: '[]',
- command: function () {
- return '[name=' + personalInfo.name + ']';
- },
- },
- {
- text: '[time tag]',
- search: '[]',
- command: function () {
- return '[time=' + moment().format('llll') + ']';
- },
- },
- {
- text: '[my color tag]',
- search: '[]',
- command: function () {
- return '[color=' + personalInfo.color + ']';
- }
- },
- {
- text: '[random color tag]',
- search: '[]',
- command: function () {
- var color = randomColor();
- return '[color=' + color + ']';
- }
- }
-];
+ {
+ text: '[name tag]',
+ search: '[]',
+ command: function () {
+ return '[name=' + window.personalInfo.name + ']'
+ }
+ },
+ {
+ text: '[time tag]',
+ search: '[]',
+ command: function () {
+ return '[time=' + moment().format('llll') + ']'
+ }
+ },
+ {
+ text: '[my color tag]',
+ search: '[]',
+ command: function () {
+ return '[color=' + window.personalInfo.color + ']'
+ }
+ },
+ {
+ text: '[random color tag]',
+ search: '[]',
+ command: function () {
+ var color = randomColor()
+ return '[color=' + color + ']'
+ }
+ }
+]
window.modeType = {
- edit: {
- name: "edit"
- },
- view: {
- name: "view"
- },
- both: {
- name: "both"
- }
-};
+ edit: {
+ name: 'edit'
+ },
+ view: {
+ name: 'view'
+ },
+ both: {
+ name: 'both'
+ }
+}
var statusType = {
- connected: {
- msg: "CONNECTED",
- label: "label-warning",
- fa: "fa-wifi"
- },
- online: {
- msg: "ONLINE",
- label: "label-primary",
- fa: "fa-users"
- },
- offline: {
- msg: "OFFLINE",
- label: "label-danger",
- fa: "fa-plug"
- }
-};
-var defaultMode = modeType.view;
-
-//global vars
-window.loaded = false;
-window.needRefresh = false;
-window.isDirty = false;
-window.editShown = false;
-window.visibleXS = false;
-window.visibleSM = false;
-window.visibleMD = false;
-window.visibleLG = false;
-window.isTouchDevice = 'ontouchstart' in document.documentElement;
-window.currentMode = defaultMode;
-window.currentStatus = statusType.offline;
+ connected: {
+ msg: 'CONNECTED',
+ label: 'label-warning',
+ fa: 'fa-wifi'
+ },
+ online: {
+ msg: 'ONLINE',
+ label: 'label-primary',
+ fa: 'fa-users'
+ },
+ offline: {
+ msg: 'OFFLINE',
+ label: 'label-danger',
+ fa: 'fa-plug'
+ }
+}
+var defaultMode = modeType.view
+
+// global vars
+window.loaded = false
+window.needRefresh = false
+window.isDirty = false
+window.editShown = false
+window.visibleXS = false
+window.visibleSM = false
+window.visibleMD = false
+window.visibleLG = false
+window.isTouchDevice = 'ontouchstart' in document.documentElement
+window.currentMode = defaultMode
+window.currentStatus = statusType.offline
window.lastInfo = {
- needRestore: false,
- cursor: null,
- scroll: null,
- edit: {
- scroll: {
- left: null,
- top: null
- },
- cursor: {
- line: null,
- ch: null
- },
- selections: null
+ needRestore: false,
+ cursor: null,
+ scroll: null,
+ edit: {
+ scroll: {
+ left: null,
+ top: null
},
- view: {
- scroll: {
- left: null,
- top: null
- }
+ cursor: {
+ line: null,
+ ch: null
},
- history: null
-};
-window.personalInfo = {};
-window.onlineUsers = [];
+ selections: null
+ },
+ view: {
+ scroll: {
+ left: null,
+ top: null
+ }
+ },
+ history: null
+}
+window.personalInfo = {}
+window.onlineUsers = []
window.fileTypes = {
- "pl": "perl",
- "cgi": "perl",
- "js": "javascript",
- "php": "php",
- "sh": "bash",
- "rb": "ruby",
- "html": "html",
- "py": "python"
-};
-
-//editor settings
-var textit = document.getElementById("textit");
-if (!textit) throw new Error("There was no textit area!");
+ 'pl': 'perl',
+ 'cgi': 'perl',
+ 'js': 'javascript',
+ 'php': 'php',
+ 'sh': 'bash',
+ 'rb': 'ruby',
+ 'html': 'html',
+ 'py': 'python'
+}
+
+// editor settings
+var textit = document.getElementById('textit')
+if (!textit) throw new Error('There was no textit area!')
window.editor = CodeMirror.fromTextArea(textit, {
- mode: defaultEditorMode,
- backdrop: defaultEditorMode,
- keyMap: "sublime",
- viewportMargin: viewportMargin,
- styleActiveLine: true,
- lineNumbers: true,
- lineWrapping: true,
- showCursorWhenSelecting: true,
- highlightSelectionMatches: true,
- indentUnit: 4,
- continueComments: "Enter",
- theme: "one-dark",
- inputStyle: "textarea",
- matchBrackets: true,
- autoCloseBrackets: true,
- matchTags: {
- bothTags: true
- },
- autoCloseTags: true,
- foldGutter: true,
- gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"],
- extraKeys: defaultExtraKeys,
- flattenSpans: true,
- addModeClass: true,
- readOnly: true,
- autoRefresh: true,
- otherCursors: true,
- placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
-});
-var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
-defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
-
-var statusBarTemplate = null;
-var statusBar = null;
-var statusPanel = null;
-var statusCursor = null;
-var statusFile = null;
-var statusIndicators = null;
-var statusLength = null;
-var statusKeymap = null;
-var statusIndent = null;
-var statusTheme = null;
-var statusSpellcheck = null;
-var statusPreferences = null;
-
-function getStatusBarTemplate(callback) {
- $.get(serverurl + '/views/statusbar.html', function (template) {
- statusBarTemplate = template;
- if (callback) callback();
- });
-}
-getStatusBarTemplate();
-
-function addStatusBar() {
- if (!statusBarTemplate) {
- getStatusBarTemplate(addStatusBar);
- return;
- }
- statusBar = $(statusBarTemplate);
- statusCursor = statusBar.find('.status-cursor');
- statusFile = statusBar.find('.status-file');
- statusIndicators = statusBar.find('.status-indicators');
- statusIndent = statusBar.find('.status-indent');
- statusKeymap = statusBar.find('.status-keymap');
- statusLength = statusBar.find('.status-length');
- statusTheme = statusBar.find('.status-theme');
- statusSpellcheck = statusBar.find('.status-spellcheck');
- statusPreferences = statusBar.find('.status-preferences');
- statusPanel = editor.addPanel(statusBar[0], {
- position: "bottom"
- });
-
- setIndent();
- setKeymap();
- setTheme();
- setSpellcheck();
- setPreferences();
-}
-
-function setIndent() {
- var cookieIndentType = Cookies.get('indent_type');
- var cookieTabSize = parseInt(Cookies.get('tab_size'));
- var cookieSpaceUnits = parseInt(Cookies.get('space_units'));
- if (cookieIndentType) {
- if (cookieIndentType == 'tab') {
- editor.setOption('indentWithTabs', true);
- if (cookieTabSize)
- editor.setOption('indentUnit', cookieTabSize);
- } else if (cookieIndentType == 'space') {
- editor.setOption('indentWithTabs', false);
- if (cookieSpaceUnits)
- editor.setOption('indentUnit', cookieSpaceUnits);
- }
- }
- if (cookieTabSize)
- editor.setOption('tabSize', cookieTabSize);
-
- var type = statusIndicators.find('.indent-type');
- var widthLabel = statusIndicators.find('.indent-width-label');
- var widthInput = statusIndicators.find('.indent-width-input');
-
- function setType() {
- if (editor.getOption('indentWithTabs')) {
- Cookies.set('indent_type', 'tab', {
- expires: 365
- });
- type.text('Tab Size:');
- } else {
- Cookies.set('indent_type', 'space', {
- expires: 365
- });
- type.text('Spaces:');
- }
- }
- setType();
-
- function setUnit() {
- var unit = editor.getOption('indentUnit');
- if (editor.getOption('indentWithTabs')) {
- Cookies.set('tab_size', unit, {
- expires: 365
- });
- } else {
- Cookies.set('space_units', unit, {
- expires: 365
- });
- }
- widthLabel.text(unit);
+ mode: defaultEditorMode,
+ backdrop: defaultEditorMode,
+ keyMap: 'sublime',
+ viewportMargin: viewportMargin,
+ styleActiveLine: true,
+ lineNumbers: true,
+ lineWrapping: true,
+ showCursorWhenSelecting: true,
+ highlightSelectionMatches: true,
+ indentUnit: 4,
+ continueComments: 'Enter',
+ theme: 'one-dark',
+ inputStyle: 'textarea',
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ matchTags: {
+ bothTags: true
+ },
+ autoCloseTags: true,
+ foldGutter: true,
+ gutters: ['CodeMirror-linenumbers', 'authorship-gutters', 'CodeMirror-foldgutter'],
+ extraKeys: defaultExtraKeys,
+ flattenSpans: true,
+ addModeClass: true,
+ readOnly: true,
+ autoRefresh: true,
+ otherCursors: true,
+ placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
+})
+var inlineAttach = window.inlineAttachment.editors.codemirror4.attach(editor)
+defaultTextHeight = parseInt($('.CodeMirror').css('line-height'))
+
+var statusBarTemplate = null
+var statusBar = null
+var statusCursor = null
+var statusFile = null
+var statusIndicators = null
+var statusLength = null
+var statusTheme = null
+var statusSpellcheck = null
+
+function getStatusBarTemplate (callback) {
+ $.get(serverurl + '/views/statusbar.html', function (template) {
+ statusBarTemplate = template
+ if (callback) callback()
+ })
+}
+getStatusBarTemplate()
+
+function addStatusBar () {
+ if (!statusBarTemplate) {
+ getStatusBarTemplate(addStatusBar)
+ return
+ }
+ statusBar = $(statusBarTemplate)
+ statusCursor = statusBar.find('.status-cursor')
+ statusFile = statusBar.find('.status-file')
+ statusIndicators = statusBar.find('.status-indicators')
+ statusBar.find('.status-indent')
+ statusBar.find('.status-keymap')
+ statusLength = statusBar.find('.status-length')
+ statusTheme = statusBar.find('.status-theme')
+ statusSpellcheck = statusBar.find('.status-spellcheck')
+ statusBar.find('.status-preferences')
+ editor.addPanel(statusBar[0], {
+ position: 'bottom'
+ })
+
+ setIndent()
+ setKeymap()
+ setTheme()
+ setSpellcheck()
+ setPreferences()
+}
+
+function setIndent () {
+ var cookieIndentType = Cookies.get('indent_type')
+ var cookieTabSize = parseInt(Cookies.get('tab_size'))
+ var cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+ if (cookieIndentType) {
+ if (cookieIndentType === 'tab') {
+ editor.setOption('indentWithTabs', true)
+ if (cookieTabSize) { editor.setOption('indentUnit', cookieTabSize) }
+ } else if (cookieIndentType === 'space') {
+ editor.setOption('indentWithTabs', false)
+ if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
+ }
+ }
+ if (cookieTabSize) { editor.setOption('tabSize', cookieTabSize) }
+
+ var type = statusIndicators.find('.indent-type')
+ var widthLabel = statusIndicators.find('.indent-width-label')
+ var widthInput = statusIndicators.find('.indent-width-input')
+
+ function setType () {
+ if (editor.getOption('indentWithTabs')) {
+ Cookies.set('indent_type', 'tab', {
+ expires: 365
+ })
+ type.text('Tab Size:')
+ } else {
+ Cookies.set('indent_type', 'space', {
+ expires: 365
+ })
+ type.text('Spaces:')
+ }
+ }
+ setType()
+
+ function setUnit () {
+ var unit = editor.getOption('indentUnit')
+ if (editor.getOption('indentWithTabs')) {
+ Cookies.set('tab_size', unit, {
+ expires: 365
+ })
+ } else {
+ Cookies.set('space_units', unit, {
+ expires: 365
+ })
+ }
+ widthLabel.text(unit)
+ }
+ setUnit()
+
+ type.click(function () {
+ if (editor.getOption('indentWithTabs')) {
+ editor.setOption('indentWithTabs', false)
+ cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+ if (cookieSpaceUnits) { editor.setOption('indentUnit', cookieSpaceUnits) }
+ } else {
+ editor.setOption('indentWithTabs', true)
+ cookieTabSize = parseInt(Cookies.get('tab_size'))
+ if (cookieTabSize) {
+ editor.setOption('indentUnit', cookieTabSize)
+ editor.setOption('tabSize', cookieTabSize)
+ }
+ }
+ setType()
+ setUnit()
+ })
+ widthLabel.click(function () {
+ if (widthLabel.is(':visible')) {
+ widthLabel.addClass('hidden')
+ widthInput.removeClass('hidden')
+ widthInput.val(editor.getOption('indentUnit'))
+ widthInput.select()
+ } else {
+ widthLabel.removeClass('hidden')
+ widthInput.addClass('hidden')
+ }
+ })
+ widthInput.on('change', function () {
+ var val = parseInt(widthInput.val())
+ if (!val) val = editor.getOption('indentUnit')
+ if (val < 1) val = 1
+ else if (val > 10) val = 10
+
+ if (editor.getOption('indentWithTabs')) {
+ editor.setOption('tabSize', val)
+ }
+ editor.setOption('indentUnit', val)
+ setUnit()
+ })
+ widthInput.on('blur', function () {
+ widthLabel.removeClass('hidden')
+ widthInput.addClass('hidden')
+ })
+}
+
+function setKeymap () {
+ var cookieKeymap = Cookies.get('keymap')
+ if (cookieKeymap) { editor.setOption('keyMap', cookieKeymap) }
+
+ var label = statusIndicators.find('.ui-keymap-label')
+ var sublime = statusIndicators.find('.ui-keymap-sublime')
+ var emacs = statusIndicators.find('.ui-keymap-emacs')
+ var vim = statusIndicators.find('.ui-keymap-vim')
+
+ function setKeymapLabel () {
+ var keymap = editor.getOption('keyMap')
+ Cookies.set('keymap', keymap, {
+ expires: 365
+ })
+ label.text(keymap)
+ restoreOverrideEditorKeymap()
+ setOverrideBrowserKeymap()
+ }
+ setKeymapLabel()
+
+ sublime.click(function () {
+ editor.setOption('keyMap', 'sublime')
+ setKeymapLabel()
+ })
+ emacs.click(function () {
+ editor.setOption('keyMap', 'emacs')
+ setKeymapLabel()
+ })
+ vim.click(function () {
+ editor.setOption('keyMap', 'vim')
+ setKeymapLabel()
+ })
+}
+
+function setTheme () {
+ var cookieTheme = Cookies.get('theme')
+ if (cookieTheme) {
+ editor.setOption('theme', cookieTheme)
+ }
+
+ var themeToggle = statusTheme.find('.ui-theme-toggle')
+ themeToggle.click(function () {
+ var theme = editor.getOption('theme')
+ if (theme === 'one-dark') {
+ theme = 'default'
+ } else {
+ theme = 'one-dark'
}
- setUnit();
-
- type.click(function () {
- if (editor.getOption('indentWithTabs')) {
- editor.setOption('indentWithTabs', false);
- cookieSpaceUnits = parseInt(Cookies.get('space_units'));
- if (cookieSpaceUnits)
- editor.setOption('indentUnit', cookieSpaceUnits)
- } else {
- editor.setOption('indentWithTabs', true);
- cookieTabSize = parseInt(Cookies.get('tab_size'));
- if (cookieTabSize) {
- editor.setOption('indentUnit', cookieTabSize);
- editor.setOption('tabSize', cookieTabSize);
- }
- }
- setType();
- setUnit();
- });
- widthLabel.click(function () {
- if (widthLabel.is(':visible')) {
- widthLabel.addClass('hidden');
- widthInput.removeClass('hidden');
- widthInput.val(editor.getOption('indentUnit'));
- widthInput.select();
- } else {
- widthLabel.removeClass('hidden');
- widthInput.addClass('hidden');
- }
- });
- widthInput.on('change', function () {
- var val = parseInt(widthInput.val());
- if (!val) val = editor.getOption('indentUnit');
- if (val < 1) val = 1;
- else if (val > 10) val = 10;
-
- if (editor.getOption('indentWithTabs')) {
- editor.setOption('tabSize', val);
- }
- editor.setOption('indentUnit', val);
- setUnit();
- });
- widthInput.on('blur', function () {
- widthLabel.removeClass('hidden');
- widthInput.addClass('hidden');
- });
-}
-
-function setKeymap() {
- var cookieKeymap = Cookies.get('keymap');
- if (cookieKeymap)
- editor.setOption('keyMap', cookieKeymap);
-
- var label = statusIndicators.find('.ui-keymap-label');
- var sublime = statusIndicators.find('.ui-keymap-sublime');
- var emacs = statusIndicators.find('.ui-keymap-emacs');
- var vim = statusIndicators.find('.ui-keymap-vim');
-
- function setKeymapLabel() {
- var keymap = editor.getOption('keyMap');
- Cookies.set('keymap', keymap, {
- expires: 365
- });
- label.text(keymap);
- restoreOverrideEditorKeymap();
- setOverrideBrowserKeymap();
- }
- setKeymapLabel();
-
- sublime.click(function () {
- editor.setOption('keyMap', 'sublime');
- setKeymapLabel();
- });
- emacs.click(function () {
- editor.setOption('keyMap', 'emacs');
- setKeymapLabel();
- });
- vim.click(function () {
- editor.setOption('keyMap', 'vim');
- setKeymapLabel();
- });
-}
-
-function setTheme() {
- var cookieTheme = Cookies.get('theme');
- if (cookieTheme) {
- editor.setOption('theme', cookieTheme);
- }
-
- var themeToggle = statusTheme.find('.ui-theme-toggle');
- themeToggle.click(function () {
- var theme = editor.getOption('theme');
- if (theme == "one-dark") {
- theme = "default";
- } else {
- theme = "one-dark";
- }
- editor.setOption('theme', theme);
- Cookies.set('theme', theme, {
- expires: 365
- });
- checkTheme();
- });
- function checkTheme() {
- var theme = editor.getOption('theme');
- if (theme == "one-dark") {
- themeToggle.removeClass('active');
- } else {
- themeToggle.addClass('active');
- }
+ editor.setOption('theme', theme)
+ Cookies.set('theme', theme, {
+ expires: 365
+ })
+ checkTheme()
+ })
+ function checkTheme () {
+ var theme = editor.getOption('theme')
+ if (theme === 'one-dark') {
+ themeToggle.removeClass('active')
+ } else {
+ themeToggle.addClass('active')
}
- checkTheme();
+ }
+ checkTheme()
}
-function setSpellcheck() {
- var cookieSpellcheck = Cookies.get('spellcheck');
- if (cookieSpellcheck) {
- var mode = null;
- if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
- mode = 'spell-checker';
- } else {
- mode = defaultEditorMode;
- }
- if (mode && mode !== editor.getOption('mode')) {
- editor.setOption('mode', mode);
- }
- }
-
- var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle');
- spellcheckToggle.click(function () {
- var mode = editor.getOption('mode');
- if (mode == defaultEditorMode) {
- mode = "spell-checker";
- } else {
- mode = defaultEditorMode;
- }
- if (mode && mode !== editor.getOption('mode')) {
- editor.setOption('mode', mode);
- }
- Cookies.set('spellcheck', (mode == "spell-checker"), {
- expires: 365
- });
- checkSpellcheck();
- });
- function checkSpellcheck() {
- var mode = editor.getOption('mode');
- if (mode == defaultEditorMode) {
- spellcheckToggle.removeClass('active');
- } else {
- spellcheckToggle.addClass('active');
- }
+function setSpellcheck () {
+ var cookieSpellcheck = Cookies.get('spellcheck')
+ if (cookieSpellcheck) {
+ var mode = null
+ if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
+ mode = 'spell-checker'
+ } else {
+ mode = defaultEditorMode
}
- checkSpellcheck();
-
- //workaround spellcheck might not activate beacuse the ajax loading
- if (num_loaded < 2) {
- var spellcheckTimer = setInterval(function () {
- if (num_loaded >= 2) {
- if (editor.getOption('mode') == "spell-checker")
- editor.setOption('mode', "spell-checker");
- clearInterval(spellcheckTimer);
- }
- }, 100);
+ if (mode && mode !== editor.getOption('mode')) {
+ editor.setOption('mode', mode)
}
-}
+ }
-var jumpToAddressBarKeymapName = mac ? "Cmd-L" : "Ctrl-L";
-var jumpToAddressBarKeymapValue = null;
-function resetEditorKeymapToBrowserKeymap() {
- var keymap = editor.getOption('keyMap');
- if (!jumpToAddressBarKeymapValue) {
- jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
- delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
- }
-}
-function restoreOverrideEditorKeymap() {
- var keymap = editor.getOption('keyMap');
- if (jumpToAddressBarKeymapValue) {
- CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue;
- jumpToAddressBarKeymapValue = null;
- }
-}
-function setOverrideBrowserKeymap() {
- var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
- if(overrideBrowserKeymap.is(":checked")) {
- Cookies.set('preferences-override-browser-keymap', true, {
- expires: 365
- });
- restoreOverrideEditorKeymap();
+ var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle')
+ spellcheckToggle.click(function () {
+ var mode = editor.getOption('mode')
+ if (mode === defaultEditorMode) {
+ mode = 'spell-checker'
} else {
- Cookies.remove('preferences-override-browser-keymap');
- resetEditorKeymapToBrowserKeymap();
+ mode = defaultEditorMode
}
-}
-
-function setPreferences() {
- var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]');
- var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap');
- if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === "true") {
- overrideBrowserKeymap.prop('checked', true);
- } else {
- overrideBrowserKeymap.prop('checked', false);
+ if (mode && mode !== editor.getOption('mode')) {
+ editor.setOption('mode', mode)
}
- setOverrideBrowserKeymap();
-
- overrideBrowserKeymap.change(function() {
- setOverrideBrowserKeymap();
- });
-}
-
-var selection = null;
-
-function updateStatusBar() {
- if (!statusBar) return;
- var cursor = editor.getCursor();
- var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1);
- if (selection) {
- var anchor = selection.anchor;
- var head = selection.head;
- var start = head.line <= anchor.line ? head : anchor;
- var end = head.line >= anchor.line ? head : anchor;
- var selectionText = ' — Selected ';
- var selectionCharCount = Math.abs(head.ch - anchor.ch);
- // borrow from brackets EditorStatusBar.js
- if (start.line !== end.line) {
- var lines = end.line - start.line + 1;
- if (end.ch === 0) {
- lines--;
- }
- selectionText += lines + ' lines';
- } else if (selectionCharCount > 0)
- selectionText += selectionCharCount + ' columns';
- if (start.line !== end.line || selectionCharCount > 0)
- cursorText += selectionText;
- }
- statusCursor.text(cursorText);
- var fileText = ' — ' + editor.lineCount() + ' Lines';
- statusFile.text(fileText);
- var docLength = editor.getValue().length;
- statusLength.text('Length ' + docLength);
- if (docLength > (docmaxlength * 0.95)) {
- statusLength.css('color', 'red');
- statusLength.attr('title', 'Your almost reach note max length limit.');
- } else if (docLength > (docmaxlength * 0.8)) {
- statusLength.css('color', 'orange');
- statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
+ Cookies.set('spellcheck', (mode === 'spell-checker'), {
+ expires: 365
+ })
+ checkSpellcheck()
+ })
+ function checkSpellcheck () {
+ var mode = editor.getOption('mode')
+ if (mode === defaultEditorMode) {
+ spellcheckToggle.removeClass('active')
} else {
- statusLength.css('color', 'white');
- statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
- }
-}
-
-//ui vars
+ spellcheckToggle.addClass('active')
+ }
+ }
+ checkSpellcheck()
+
+ // workaround spellcheck might not activate beacuse the ajax loading
+ /* eslint-disable camelcase */
+ if (num_loaded < 2) {
+ var spellcheckTimer = setInterval(function () {
+ if (num_loaded >= 2) {
+ if (editor.getOption('mode') === 'spell-checker') { editor.setOption('mode', 'spell-checker') }
+ clearInterval(spellcheckTimer)
+ }
+ }, 100)
+ }
+ /* eslint-endable camelcase */
+}
+
+var jumpToAddressBarKeymapName = mac ? 'Cmd-L' : 'Ctrl-L'
+var jumpToAddressBarKeymapValue = null
+function resetEditorKeymapToBrowserKeymap () {
+ var keymap = editor.getOption('keyMap')
+ if (!jumpToAddressBarKeymapValue) {
+ jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+ delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+ }
+}
+function restoreOverrideEditorKeymap () {
+ var keymap = editor.getOption('keyMap')
+ if (jumpToAddressBarKeymapValue) {
+ CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue
+ jumpToAddressBarKeymapValue = null
+ }
+}
+function setOverrideBrowserKeymap () {
+ var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
+ if (overrideBrowserKeymap.is(':checked')) {
+ Cookies.set('preferences-override-browser-keymap', true, {
+ expires: 365
+ })
+ restoreOverrideEditorKeymap()
+ } else {
+ Cookies.remove('preferences-override-browser-keymap')
+ resetEditorKeymapToBrowserKeymap()
+ }
+}
+
+function setPreferences () {
+ var overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
+ var cookieOverrideBrowserKeymap = Cookies.get('preferences-override-browser-keymap')
+ if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
+ overrideBrowserKeymap.prop('checked', true)
+ } else {
+ overrideBrowserKeymap.prop('checked', false)
+ }
+ setOverrideBrowserKeymap()
+
+ overrideBrowserKeymap.change(function () {
+ setOverrideBrowserKeymap()
+ })
+}
+
+var selection = null
+
+function updateStatusBar () {
+ if (!statusBar) return
+ var cursor = editor.getCursor()
+ var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1)
+ if (selection) {
+ var anchor = selection.anchor
+ var head = selection.head
+ var start = head.line <= anchor.line ? head : anchor
+ var end = head.line >= anchor.line ? head : anchor
+ var selectionText = ' — Selected '
+ var selectionCharCount = Math.abs(head.ch - anchor.ch)
+ // borrow from brackets EditorStatusBar.js
+ if (start.line !== end.line) {
+ var lines = end.line - start.line + 1
+ if (end.ch === 0) {
+ lines--
+ }
+ selectionText += lines + ' lines'
+ } else if (selectionCharCount > 0) { selectionText += selectionCharCount + ' columns' }
+ if (start.line !== end.line || selectionCharCount > 0) { cursorText += selectionText }
+ }
+ statusCursor.text(cursorText)
+ var fileText = ' — ' + editor.lineCount() + ' Lines'
+ statusFile.text(fileText)
+ var docLength = editor.getValue().length
+ statusLength.text('Length ' + docLength)
+ if (docLength > (docmaxlength * 0.95)) {
+ statusLength.css('color', 'red')
+ statusLength.attr('title', 'Your almost reach note max length limit.')
+ } else if (docLength > (docmaxlength * 0.8)) {
+ statusLength.css('color', 'orange')
+ statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.')
+ } else {
+ statusLength.css('color', 'white')
+ statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.')
+ }
+}
+
+// ui vars
window.ui = {
- spinner: $(".ui-spinner"),
- content: $(".ui-content"),
- toolbar: {
- shortStatus: $(".ui-short-status"),
- status: $(".ui-status"),
- new: $(".ui-new"),
- publish: $(".ui-publish"),
- extra: {
- revision: $(".ui-extra-revision"),
- slide: $(".ui-extra-slide")
- },
- download: {
- markdown: $(".ui-download-markdown"),
- html: $(".ui-download-html"),
- rawhtml: $(".ui-download-raw-html"),
- pdf: $(".ui-download-pdf-beta"),
- },
- export: {
- dropbox: $(".ui-save-dropbox"),
- googleDrive: $(".ui-save-google-drive"),
- gist: $(".ui-save-gist"),
- snippet: $(".ui-save-snippet")
- },
- import: {
- dropbox: $(".ui-import-dropbox"),
- googleDrive: $(".ui-import-google-drive"),
- gist: $(".ui-import-gist"),
- snippet: $(".ui-import-snippet"),
- clipboard: $(".ui-import-clipboard")
- },
- mode: $(".ui-mode"),
- edit: $(".ui-edit"),
- view: $(".ui-view"),
- both: $(".ui-both"),
- uploadImage: $(".ui-upload-image")
+ spinner: $('.ui-spinner'),
+ content: $('.ui-content'),
+ toolbar: {
+ shortStatus: $('.ui-short-status'),
+ status: $('.ui-status'),
+ new: $('.ui-new'),
+ publish: $('.ui-publish'),
+ extra: {
+ revision: $('.ui-extra-revision'),
+ slide: $('.ui-extra-slide')
},
- infobar: {
- lastchange: $(".ui-lastchange"),
- lastchangeuser: $(".ui-lastchangeuser"),
- nolastchangeuser: $(".ui-no-lastchangeuser"),
- permission: {
- permission: $(".ui-permission"),
- label: $(".ui-permission-label"),
- freely: $(".ui-permission-freely"),
- editable: $(".ui-permission-editable"),
- locked: $(".ui-permission-locked"),
- private: $(".ui-permission-private"),
- limited: $(".ui-permission-limited"),
- protected: $(".ui-permission-protected")
- },
- delete: $(".ui-delete-note")
+ download: {
+ markdown: $('.ui-download-markdown'),
+ html: $('.ui-download-html'),
+ rawhtml: $('.ui-download-raw-html'),
+ pdf: $('.ui-download-pdf-beta')
},
- toc: {
- toc: $('.ui-toc'),
- affix: $('.ui-affix-toc'),
- label: $('.ui-toc-label'),
- dropdown: $('.ui-toc-dropdown')
+ export: {
+ dropbox: $('.ui-save-dropbox'),
+ googleDrive: $('.ui-save-google-drive'),
+ gist: $('.ui-save-gist'),
+ snippet: $('.ui-save-snippet')
},
- area: {
- edit: $(".ui-edit-area"),
- view: $(".ui-view-area"),
- codemirror: $(".ui-edit-area .CodeMirror"),
- codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
- codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
- codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
- markdown: $(".ui-view-area .markdown-body"),
- resize: {
- handle: $('.ui-resizable-handle'),
- syncToggle: $('.ui-sync-toggle')
- }
+ import: {
+ dropbox: $('.ui-import-dropbox'),
+ googleDrive: $('.ui-import-google-drive'),
+ gist: $('.ui-import-gist'),
+ snippet: $('.ui-import-snippet'),
+ clipboard: $('.ui-import-clipboard')
},
- modal: {
- snippetImportProjects: $("#snippetImportModalProjects"),
- snippetImportSnippets: $("#snippetImportModalSnippets"),
- revision: $("#revisionModal")
- }
-};
-
-//page actions
+ mode: $('.ui-mode'),
+ edit: $('.ui-edit'),
+ view: $('.ui-view'),
+ both: $('.ui-both'),
+ uploadImage: $('.ui-upload-image')
+ },
+ infobar: {
+ lastchange: $('.ui-lastchange'),
+ lastchangeuser: $('.ui-lastchangeuser'),
+ nolastchangeuser: $('.ui-no-lastchangeuser'),
+ permission: {
+ permission: $('.ui-permission'),
+ label: $('.ui-permission-label'),
+ freely: $('.ui-permission-freely'),
+ editable: $('.ui-permission-editable'),
+ locked: $('.ui-permission-locked'),
+ private: $('.ui-permission-private'),
+ limited: $('.ui-permission-limited'),
+ protected: $('.ui-permission-protected')
+ },
+ delete: $('.ui-delete-note')
+ },
+ toc: {
+ toc: $('.ui-toc'),
+ affix: $('.ui-affix-toc'),
+ label: $('.ui-toc-label'),
+ dropdown: $('.ui-toc-dropdown')
+ },
+ area: {
+ edit: $('.ui-edit-area'),
+ view: $('.ui-view-area'),
+ codemirror: $('.ui-edit-area .CodeMirror'),
+ codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'),
+ codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'),
+ codemirrorSizerInner: $('.ui-edit-area .CodeMirror .CodeMirror-sizer > div'),
+ markdown: $('.ui-view-area .markdown-body'),
+ resize: {
+ handle: $('.ui-resizable-handle'),
+ syncToggle: $('.ui-sync-toggle')
+ }
+ },
+ modal: {
+ snippetImportProjects: $('#snippetImportModalProjects'),
+ snippetImportSnippets: $('#snippetImportModalSnippets'),
+ revision: $('#revisionModal')
+ }
+}
+
+// page actions
var opts = {
- lines: 11, // The number of lines to draw
- length: 20, // The length of each line
- width: 2, // The line thickness
- radius: 30, // The radius of the inner circle
- corners: 0, // Corner roundness (0..1)
- rotate: 0, // The rotation offset
- direction: 1, // 1: clockwise, -1: counterclockwise
- color: '#000', // #rgb or #rrggbb or array of colors
- speed: 1.1, // Rounds per second
- trail: 60, // Afterglow percentage
- shadow: false, // Whether to render a shadow
- hwaccel: true, // Whether to use hardware acceleration
- className: 'spinner', // The CSS class to assign to the spinner
- zIndex: 2e9, // The z-index (defaults to 2000000000)
- top: '50%', // Top position relative to parent
- left: '50%' // Left position relative to parent
-};
-var spinner = new Spinner(opts).spin(ui.spinner[0]);
-
-//idle
+ lines: 11, // The number of lines to draw
+ length: 20, // The length of each line
+ width: 2, // The line thickness
+ radius: 30, // The radius of the inner circle
+ corners: 0, // Corner roundness (0..1)
+ rotate: 0, // The rotation offset
+ direction: 1, // 1: clockwise, -1: counterclockwise
+ color: '#000', // #rgb or #rrggbb or array of colors
+ speed: 1.1, // Rounds per second
+ trail: 60, // Afterglow percentage
+ shadow: false, // Whether to render a shadow
+ hwaccel: true, // Whether to use hardware acceleration
+ className: 'spinner', // The CSS class to assign to the spinner
+ zIndex: 2e9, // The z-index (defaults to 2000000000)
+ top: '50%', // Top position relative to parent
+ left: '50%' // Left position relative to parent
+}
+
+/* eslint-disable no-unused-vars */
+var spinner = new Spinner(opts).spin(ui.spinner[0])
+/* eslint-enable no-unused-vars */
+
+// idle
var idle = new Idle({
- onAway: function () {
- idle.isAway = true;
- emitUserStatus();
- updateOnlineStatus();
- },
- onAwayBack: function () {
- idle.isAway = false;
- emitUserStatus();
- updateOnlineStatus();
- setHaveUnreadChanges(false);
- updateTitleReminder();
- },
- awayTimeout: idleTime
-});
+ onAway: function () {
+ idle.isAway = true
+ emitUserStatus()
+ updateOnlineStatus()
+ },
+ onAwayBack: function () {
+ idle.isAway = false
+ emitUserStatus()
+ updateOnlineStatus()
+ setHaveUnreadChanges(false)
+ updateTitleReminder()
+ },
+ awayTimeout: idleTime
+})
ui.area.codemirror.on('touchstart', function () {
- idle.onActive();
-});
+ idle.onActive()
+})
-var haveUnreadChanges = false;
+var haveUnreadChanges = false
-function setHaveUnreadChanges(bool) {
- if (!loaded) return;
- if (bool && (idle.isAway || Visibility.hidden())) {
- haveUnreadChanges = true;
- } else if (!bool && !idle.isAway && !Visibility.hidden()) {
- haveUnreadChanges = false;
- }
+function setHaveUnreadChanges (bool) {
+ if (!window.loaded) return
+ if (bool && (idle.isAway || Visibility.hidden())) {
+ haveUnreadChanges = true
+ } else if (!bool && !idle.isAway && !Visibility.hidden()) {
+ haveUnreadChanges = false
+ }
}
-function updateTitleReminder() {
- if (!loaded) return;
- if (haveUnreadChanges) {
- document.title = '• ' + renderTitle(ui.area.markdown);
- } else {
- document.title = renderTitle(ui.area.markdown);
- }
+function updateTitleReminder () {
+ if (!window.loaded) return
+ if (haveUnreadChanges) {
+ document.title = '• ' + renderTitle(ui.area.markdown)
+ } else {
+ document.title = renderTitle(ui.area.markdown)
+ }
}
-function setRefreshModal(status) {
- $('#refreshModal').modal('show');
- $('#refreshModal').find('.modal-body > div').hide();
- $('#refreshModal').find('.' + status).show();
+function setRefreshModal (status) {
+ $('#refreshModal').modal('show')
+ $('#refreshModal').find('.modal-body > div').hide()
+ $('#refreshModal').find('.' + status).show()
}
-function setNeedRefresh() {
- needRefresh = true;
- editor.setOption('readOnly', true);
- socket.disconnect();
- showStatus(statusType.offline);
+function setNeedRefresh () {
+ window.needRefresh = true
+ editor.setOption('readOnly', true)
+ socket.disconnect()
+ showStatus(statusType.offline)
}
setloginStateChangeEvent(function () {
- setRefreshModal('user-state-changed');
- setNeedRefresh();
-});
+ setRefreshModal('user-state-changed')
+ setNeedRefresh()
+})
-//visibility
-var wasFocus = false;
+// visibility
+var wasFocus = false
Visibility.change(function (e, state) {
- var hidden = Visibility.hidden();
- if (hidden) {
- if (editorHasFocus()) {
- wasFocus = true;
- editor.getInputField().blur();
- }
- } else {
- if (wasFocus) {
- if (!visibleXS) {
- editor.focus();
- editor.refresh();
- }
- wasFocus = false;
- }
- setHaveUnreadChanges(false);
- }
- updateTitleReminder();
-});
+ var hidden = Visibility.hidden()
+ if (hidden) {
+ if (editorHasFocus()) {
+ wasFocus = true
+ editor.getInputField().blur()
+ }
+ } else {
+ if (wasFocus) {
+ if (!window.visibleXS) {
+ editor.focus()
+ editor.refresh()
+ }
+ wasFocus = false
+ }
+ setHaveUnreadChanges(false)
+ }
+ updateTitleReminder()
+})
-//when page ready
+// when page ready
$(document).ready(function () {
- idle.checkAway();
- checkResponsive();
- //if in smaller screen, we don't need advanced scrollbar
- var scrollbarStyle;
- if (visibleXS) {
- scrollbarStyle = 'native';
- } else {
- scrollbarStyle = 'overlay';
- }
- if (scrollbarStyle != editor.getOption('scrollbarStyle')) {
- editor.setOption('scrollbarStyle', scrollbarStyle);
- clearMap();
- }
- checkEditorStyle();
+ idle.checkAway()
+ checkResponsive()
+ // if in smaller screen, we don't need advanced scrollbar
+ var scrollbarStyle
+ if (window.visibleXS) {
+ scrollbarStyle = 'native'
+ } else {
+ scrollbarStyle = 'overlay'
+ }
+ if (scrollbarStyle !== editor.getOption('scrollbarStyle')) {
+ editor.setOption('scrollbarStyle', scrollbarStyle)
+ clearMap()
+ }
+ checkEditorStyle()
/* we need this only on touch devices */
- if (isTouchDevice) {
+ if (window.isTouchDevice) {
/* cache dom references */
- var $body = jQuery('body');
+ var $body = jQuery('body')
/* bind events */
- $(document)
+ $(document)
.on('focus', 'textarea, input', function () {
- $body.addClass('fixfixed');
+ $body.addClass('fixfixed')
})
.on('blur', 'textarea, input', function () {
- $body.removeClass('fixfixed');
- });
- }
- //showup
- $().showUp('.navbar', {
- upClass: 'navbar-hide',
- downClass: 'navbar-show'
- });
- //tooltip
- $('[data-toggle="tooltip"]').tooltip();
+ $body.removeClass('fixfixed')
+ })
+ }
+ // showup
+ $().showUp('.navbar', {
+ upClass: 'navbar-hide',
+ downClass: 'navbar-show'
+ })
+ // tooltip
+ $('[data-toggle="tooltip"]').tooltip()
// shortcuts
// allow on all tags
- key.filter = function (e) { return true; };
- key('ctrl+alt+e', function (e) {
- changeMode(modeType.edit);
- });
- key('ctrl+alt+v', function (e) {
- changeMode(modeType.view);
- });
- key('ctrl+alt+b', function (e) {
- changeMode(modeType.both);
- });
+ key.filter = function (e) { return true }
+ key('ctrl+alt+e', function (e) {
+ changeMode(modeType.edit)
+ })
+ key('ctrl+alt+v', function (e) {
+ changeMode(modeType.view)
+ })
+ key('ctrl+alt+b', function (e) {
+ changeMode(modeType.both)
+ })
// toggle-dropdown
- $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
- e.stopPropagation();
- });
-});
-//when page resize
+ $(document).on('click', '.toggle-dropdown .dropdown-menu', function (e) {
+ e.stopPropagation()
+ })
+})
+// when page resize
$(window).resize(function () {
- checkLayout();
- checkEditorStyle();
- checkTocStyle();
- checkCursorMenu();
- windowResize();
-});
-//when page unload
+ checkLayout()
+ checkEditorStyle()
+ checkTocStyle()
+ checkCursorMenu()
+ windowResize()
+})
+// when page unload
$(window).on('unload', function () {
- //updateHistoryInner();
-});
+ // updateHistoryInner();
+})
$(window).on('error', function () {
- //setNeedRefresh();
-});
-
-setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown);
+ // setNeedRefresh();
+})
-function autoSyncscroll() {
- if (editorHasFocus()) {
- syncScrollToView();
+setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown)
+
+function autoSyncscroll () {
+ if (editorHasFocus()) {
+ syncScrollToView()
+ } else {
+ syncScrollToEdit()
+ }
+}
+
+var windowResizeDebounce = 200
+var windowResize = _.debounce(windowResizeInner, windowResizeDebounce)
+
+function windowResizeInner (callback) {
+ checkLayout()
+ checkResponsive()
+ checkEditorStyle()
+ checkTocStyle()
+ checkCursorMenu()
+ // refresh editor
+ if (window.loaded) {
+ if (editor.getOption('scrollbarStyle') === 'native') {
+ setTimeout(function () {
+ clearMap()
+ autoSyncscroll()
+ updateScrollspy()
+ if (callback && typeof callback === 'function') { callback() }
+ }, 1)
} else {
- syncScrollToEdit();
- }
-}
-
-var windowResizeDebounce = 200;
-var windowResize = _.debounce(windowResizeInner, windowResizeDebounce);
-
-function windowResizeInner(callback) {
- checkLayout();
- checkResponsive();
- checkEditorStyle();
- checkTocStyle();
- checkCursorMenu();
- //refresh editor
- if (loaded) {
- if (editor.getOption('scrollbarStyle') === 'native') {
- setTimeout(function () {
- clearMap();
- autoSyncscroll();
- updateScrollspy();
- if (callback && typeof callback === 'function')
- callback();
- }, 1);
- } else {
// force it load all docs at once to prevent scroll knob blink
- editor.setOption('viewportMargin', Infinity);
- setTimeout(function () {
- clearMap();
- autoSyncscroll();
- editor.setOption('viewportMargin', viewportMargin);
- //add or update user cursors
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id != personalInfo.id)
- buildCursor(onlineUsers[i]);
- }
- updateScrollspy();
- if (callback && typeof callback === 'function')
- callback();
- }, 1);
+ editor.setOption('viewportMargin', Infinity)
+ setTimeout(function () {
+ clearMap()
+ autoSyncscroll()
+ editor.setOption('viewportMargin', viewportMargin)
+ // add or update user cursors
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id !== window.personalInfo.id) { buildCursor(window.onlineUsers[i]) }
}
+ updateScrollspy()
+ if (callback && typeof callback === 'function') { callback() }
+ }, 1)
}
+ }
}
-function checkLayout() {
- var navbarHieght = $('.navbar').outerHeight();
- $('body').css('padding-top', navbarHieght + 'px');
+function checkLayout () {
+ var navbarHieght = $('.navbar').outerHeight()
+ $('body').css('padding-top', navbarHieght + 'px')
}
-function editorHasFocus() {
- return $(editor.getInputField()).is(":focus");
+function editorHasFocus () {
+ return $(editor.getInputField()).is(':focus')
}
-//768-792px have a gap
-function checkResponsive() {
- visibleXS = $(".visible-xs").is(":visible");
- visibleSM = $(".visible-sm").is(":visible");
- visibleMD = $(".visible-md").is(":visible");
- visibleLG = $(".visible-lg").is(":visible");
+// 768-792px have a gap
+function checkResponsive () {
+ window.visibleXS = $('.visible-xs').is(':visible')
+ window.visibleSM = $('.visible-sm').is(':visible')
+ window.visibleMD = $('.visible-md').is(':visible')
+ window.visibleLG = $('.visible-lg').is(':visible')
- if (visibleXS && currentMode == modeType.both)
- if (editorHasFocus())
- changeMode(modeType.edit);
- else
- changeMode(modeType.view);
+ if (window.visibleXS && window.currentMode === modeType.both) {
+ if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) }
+ }
- emitUserStatus();
+ emitUserStatus()
}
-var lastEditorWidth = 0;
-var previousFocusOnEditor = null;
+var lastEditorWidth = 0
+var previousFocusOnEditor = null
-function checkEditorStyle() {
- var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height();
+function checkEditorStyle () {
+ var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height()
// set editor height and min height based on scrollbar style and mode
- var scrollbarStyle = editor.getOption('scrollbarStyle');
- if (scrollbarStyle == 'overlay' || currentMode == modeType.both) {
- ui.area.codemirrorScroll.css('height', desireHeight + 'px');
- ui.area.codemirrorScroll.css('min-height', '');
- checkEditorScrollbar();
- } else if (scrollbarStyle == 'native') {
- ui.area.codemirrorScroll.css('height', '');
- ui.area.codemirrorScroll.css('min-height', desireHeight + 'px');
- }
+ var scrollbarStyle = editor.getOption('scrollbarStyle')
+ if (scrollbarStyle === 'overlay' || window.currentMode === modeType.both) {
+ ui.area.codemirrorScroll.css('height', desireHeight + 'px')
+ ui.area.codemirrorScroll.css('min-height', '')
+ checkEditorScrollbar()
+ } else if (scrollbarStyle === 'native') {
+ ui.area.codemirrorScroll.css('height', '')
+ ui.area.codemirrorScroll.css('min-height', desireHeight + 'px')
+ }
// workaround editor will have wrong doc height when editor height changed
- editor.setSize(null, ui.area.edit.height());
- //make editor resizable
- if (!ui.area.resize.handle.length) {
- ui.area.edit.resizable({
- handles: 'e',
- maxWidth: $(window).width() * 0.7,
- minWidth: $(window).width() * 0.2,
- create: function (e, ui) {
- $(this).parent().on('resize', function (e) {
- e.stopPropagation();
- });
- },
- start: function (e) {
- editor.setOption('viewportMargin', Infinity);
- },
- resize: function (e) {
- ui.area.resize.syncToggle.stop(true, true).show();
- checkTocStyle();
- },
- stop: function (e) {
- lastEditorWidth = ui.area.edit.width();
+ editor.setSize(null, ui.area.edit.height())
+ // make editor resizable
+ if (!ui.area.resize.handle.length) {
+ ui.area.edit.resizable({
+ handles: 'e',
+ maxWidth: $(window).width() * 0.7,
+ minWidth: $(window).width() * 0.2,
+ create: function (e, ui) {
+ $(this).parent().on('resize', function (e) {
+ e.stopPropagation()
+ })
+ },
+ start: function (e) {
+ editor.setOption('viewportMargin', Infinity)
+ },
+ resize: function (e) {
+ ui.area.resize.syncToggle.stop(true, true).show()
+ checkTocStyle()
+ },
+ stop: function (e) {
+ lastEditorWidth = ui.area.edit.width()
// workaround that scroll event bindings
- preventSyncScrollToView = 2;
- preventSyncScrollToEdit = true;
- editor.setOption('viewportMargin', viewportMargin);
- if (editorHasFocus()) {
- windowResizeInner(function () {
- ui.area.codemirrorScroll.scroll();
- });
- } else {
- windowResizeInner(function () {
- ui.area.view.scroll();
- });
- }
- checkEditorScrollbar();
- }
- });
- ui.area.resize.handle = $('.ui-resizable-handle');
- }
- if (!ui.area.resize.syncToggle.length) {
- ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>');
- ui.area.resize.syncToggle.hover(function () {
- previousFocusOnEditor = editorHasFocus();
- }, function () {
- previousFocusOnEditor = null;
- });
- ui.area.resize.syncToggle.click(function () {
- syncscroll = !syncscroll;
- checkSyncToggle();
- });
- ui.area.resize.handle.append(ui.area.resize.syncToggle);
- ui.area.resize.syncToggle.hide();
- ui.area.resize.handle.hover(function () {
- ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100);
- }, function () {
- ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300);
- });
- }
-}
-
-function checkSyncToggle() {
- if (syncscroll) {
- if (previousFocusOnEditor) {
- preventSyncScrollToView = false;
- syncScrollToView();
+ window.preventSyncScrollToView = 2
+ window.preventSyncScrollToEdit = true
+ editor.setOption('viewportMargin', viewportMargin)
+ if (editorHasFocus()) {
+ windowResizeInner(function () {
+ ui.area.codemirrorScroll.scroll()
+ })
} else {
- preventSyncScrollToEdit = false;
- syncScrollToEdit();
+ windowResizeInner(function () {
+ ui.area.view.scroll()
+ })
}
- ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link');
+ checkEditorScrollbar()
+ }
+ })
+ ui.area.resize.handle = $('.ui-resizable-handle')
+ }
+ if (!ui.area.resize.syncToggle.length) {
+ ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>')
+ ui.area.resize.syncToggle.hover(function () {
+ previousFocusOnEditor = editorHasFocus()
+ }, function () {
+ previousFocusOnEditor = null
+ })
+ ui.area.resize.syncToggle.click(function () {
+ window.syncscroll = !window.syncscroll
+ checkSyncToggle()
+ })
+ ui.area.resize.handle.append(ui.area.resize.syncToggle)
+ ui.area.resize.syncToggle.hide()
+ ui.area.resize.handle.hover(function () {
+ ui.area.resize.syncToggle.stop(true, true).delay(200).fadeIn(100)
+ }, function () {
+ ui.area.resize.syncToggle.stop(true, true).delay(300).fadeOut(300)
+ })
+ }
+}
+
+function checkSyncToggle () {
+ if (window.syncscroll) {
+ if (previousFocusOnEditor) {
+ window.preventSyncScrollToView = false
+ syncScrollToView()
} else {
- ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
+ window.preventSyncScrollToEdit = false
+ syncScrollToEdit()
}
+ ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link')
+ } else {
+ ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink')
+ }
}
var checkEditorScrollbar = _.debounce(function () {
- editor.operation(checkEditorScrollbarInner);
-}, 50);
+ editor.operation(checkEditorScrollbarInner)
+}, 50)
-function checkEditorScrollbarInner() {
+function checkEditorScrollbarInner () {
// workaround simple scroll bar knob
// will get wrong position when editor height changed
- var scrollInfo = editor.getScrollInfo();
- editor.scrollTo(null, scrollInfo.top - 1);
- editor.scrollTo(null, scrollInfo.top);
-}
-
-function checkTocStyle() {
- //toc right
- var paddingRight = parseFloat(ui.area.markdown.css('padding-right'));
- var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight));
- ui.toc.toc.css('right', right + 'px');
- //affix toc left
- var newbool;
- var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2;
- //for ipad or wider device
- if (rightMargin >= 133) {
- newbool = true;
- var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2;
- var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin;
- ui.toc.affix.css('left', left + 'px');
- ui.toc.affix.css('width', rightMargin + 'px');
- } else {
- newbool = false;
- }
- //toc scrollspy
- ui.toc.toc.removeClass('scrollspy-body, scrollspy-view');
- ui.toc.affix.removeClass('scrollspy-body, scrollspy-view');
- if (currentMode == modeType.both) {
- ui.toc.toc.addClass('scrollspy-view');
- ui.toc.affix.addClass('scrollspy-view');
- } else if (currentMode != modeType.both && !newbool) {
- ui.toc.toc.addClass('scrollspy-body');
- ui.toc.affix.addClass('scrollspy-body');
- } else {
- ui.toc.toc.addClass('scrollspy-view');
- ui.toc.affix.addClass('scrollspy-body');
- }
- if (newbool != enoughForAffixToc) {
- enoughForAffixToc = newbool;
- generateScrollspy();
- }
-}
-
-function showStatus(type, num) {
- currentStatus = type;
- var shortStatus = ui.toolbar.shortStatus;
- var status = ui.toolbar.status;
- var label = $('<span class="label"></span>');
- var fa = $('<i class="fa"></i>');
- var msg = "";
- var shortMsg = "";
-
- shortStatus.html("");
- status.html("");
-
- switch (currentStatus) {
- case statusType.connected:
- label.addClass(statusType.connected.label);
- fa.addClass(statusType.connected.fa);
- msg = statusType.connected.msg;
- break;
- case statusType.online:
- label.addClass(statusType.online.label);
- fa.addClass(statusType.online.fa);
- shortMsg = num;
- msg = num + " " + statusType.online.msg;
- break;
- case statusType.offline:
- label.addClass(statusType.offline.label);
- fa.addClass(statusType.offline.fa);
- msg = statusType.offline.msg;
- break;
- }
-
- label.append(fa);
- var shortLabel = label.clone();
-
- shortLabel.append(" " + shortMsg);
- shortStatus.append(shortLabel);
-
- label.append(" " + msg);
- status.append(label);
-}
-
-function toggleMode() {
- switch (currentMode) {
- case modeType.edit:
- changeMode(modeType.view);
- break;
- case modeType.view:
- changeMode(modeType.edit);
- break;
- case modeType.both:
- changeMode(modeType.view);
- break;
- }
-}
-
-var lastMode = null;
-
-function changeMode(type) {
+ var scrollInfo = editor.getScrollInfo()
+ editor.scrollTo(null, scrollInfo.top - 1)
+ editor.scrollTo(null, scrollInfo.top)
+}
+
+function checkTocStyle () {
+ // toc right
+ var paddingRight = parseFloat(ui.area.markdown.css('padding-right'))
+ var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight))
+ ui.toc.toc.css('right', right + 'px')
+ // affix toc left
+ var newbool
+ var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2
+ // for ipad or wider device
+ if (rightMargin >= 133) {
+ newbool = true
+ var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2
+ var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin
+ ui.toc.affix.css('left', left + 'px')
+ ui.toc.affix.css('width', rightMargin + 'px')
+ } else {
+ newbool = false
+ }
+ // toc scrollspy
+ ui.toc.toc.removeClass('scrollspy-body, scrollspy-view')
+ ui.toc.affix.removeClass('scrollspy-body, scrollspy-view')
+ if (window.currentMode === modeType.both) {
+ ui.toc.toc.addClass('scrollspy-view')
+ ui.toc.affix.addClass('scrollspy-view')
+ } else if (window.currentMode !== modeType.both && !newbool) {
+ ui.toc.toc.addClass('scrollspy-body')
+ ui.toc.affix.addClass('scrollspy-body')
+ } else {
+ ui.toc.toc.addClass('scrollspy-view')
+ ui.toc.affix.addClass('scrollspy-body')
+ }
+ if (newbool !== enoughForAffixToc) {
+ enoughForAffixToc = newbool
+ generateScrollspy()
+ }
+}
+
+function showStatus (type, num) {
+ window.currentStatus = type
+ var shortStatus = ui.toolbar.shortStatus
+ var status = ui.toolbar.status
+ var label = $('<span class="label"></span>')
+ var fa = $('<i class="fa"></i>')
+ var msg = ''
+ var shortMsg = ''
+
+ shortStatus.html('')
+ status.html('')
+
+ switch (window.currentStatus) {
+ case statusType.connected:
+ label.addClass(statusType.connected.label)
+ fa.addClass(statusType.connected.fa)
+ msg = statusType.connected.msg
+ break
+ case statusType.online:
+ label.addClass(statusType.online.label)
+ fa.addClass(statusType.online.fa)
+ shortMsg = num
+ msg = num + ' ' + statusType.online.msg
+ break
+ case statusType.offline:
+ label.addClass(statusType.offline.label)
+ fa.addClass(statusType.offline.fa)
+ msg = statusType.offline.msg
+ break
+ }
+
+ label.append(fa)
+ var shortLabel = label.clone()
+
+ shortLabel.append(' ' + shortMsg)
+ shortStatus.append(shortLabel)
+
+ label.append(' ' + msg)
+ status.append(label)
+}
+
+function toggleMode () {
+ switch (window.currentMode) {
+ case modeType.edit:
+ changeMode(modeType.view)
+ break
+ case modeType.view:
+ changeMode(modeType.edit)
+ break
+ case modeType.both:
+ changeMode(modeType.view)
+ break
+ }
+}
+
+var lastMode = null
+
+function changeMode (type) {
// lock navbar to prevent it hide after changeMode
- lockNavbar();
- saveInfo();
- if (type) {
- lastMode = currentMode;
- currentMode = type;
- }
- var responsiveClass = "col-lg-6 col-md-6 col-sm-6";
- var scrollClass = "ui-scrollable";
- ui.area.codemirror.removeClass(scrollClass);
- ui.area.edit.removeClass(responsiveClass);
- ui.area.view.removeClass(scrollClass);
- ui.area.view.removeClass(responsiveClass);
- switch (currentMode) {
- case modeType.edit:
- ui.area.edit.show();
- ui.area.view.hide();
- if (!editShown) {
- editor.refresh();
- editShown = true;
- }
- break;
- case modeType.view:
- ui.area.edit.hide();
- ui.area.view.show();
- break;
- case modeType.both:
- ui.area.codemirror.addClass(scrollClass);
- ui.area.edit.addClass(responsiveClass).show();
- ui.area.view.addClass(scrollClass);
- ui.area.view.show();
- break;
- }
+ lockNavbar()
+ saveInfo()
+ if (type) {
+ lastMode = window.currentMode
+ window.currentMode = type
+ }
+ var responsiveClass = 'col-lg-6 col-md-6 col-sm-6'
+ var scrollClass = 'ui-scrollable'
+ ui.area.codemirror.removeClass(scrollClass)
+ ui.area.edit.removeClass(responsiveClass)
+ ui.area.view.removeClass(scrollClass)
+ ui.area.view.removeClass(responsiveClass)
+ switch (window.currentMode) {
+ case modeType.edit:
+ ui.area.edit.show()
+ ui.area.view.hide()
+ if (!window.editShown) {
+ editor.refresh()
+ window.editShown = true
+ }
+ break
+ case modeType.view:
+ ui.area.edit.hide()
+ ui.area.view.show()
+ break
+ case modeType.both:
+ ui.area.codemirror.addClass(scrollClass)
+ ui.area.edit.addClass(responsiveClass).show()
+ ui.area.view.addClass(scrollClass)
+ ui.area.view.show()
+ break
+ }
// save mode to url
- if (history.replaceState && loaded) history.replaceState(null, "", serverurl + '/' + noteid + '?' + currentMode.name);
- if (currentMode == modeType.view) {
- editor.getInputField().blur();
- }
- if (currentMode == modeType.edit || currentMode == modeType.both) {
- ui.toolbar.uploadImage.fadeIn();
- //add and update status bar
- if (!statusBar) {
- addStatusBar();
- updateStatusBar();
- }
- //work around foldGutter might not init properly
- editor.setOption('foldGutter', false);
- editor.setOption('foldGutter', true);
- } else {
- ui.toolbar.uploadImage.fadeOut();
- }
- if (currentMode != modeType.edit) {
- $(document.body).css('background-color', 'white');
- updateView();
- } else {
- $(document.body).css('background-color', ui.area.codemirror.css('background-color'));
- }
- //check resizable editor style
- if (currentMode == modeType.both) {
- if (lastEditorWidth > 0)
- ui.area.edit.css('width', lastEditorWidth + 'px');
- else
- ui.area.edit.css('width', '');
- ui.area.resize.handle.show();
- } else {
- ui.area.edit.css('width', '');
- ui.area.resize.handle.hide();
- }
-
- windowResizeInner();
-
- restoreInfo();
-
- if (lastMode == modeType.view && currentMode == modeType.both) {
- preventSyncScrollToView = 2;
- syncScrollToEdit(null, true);
- }
-
- if (lastMode == modeType.edit && currentMode == modeType.both) {
- preventSyncScrollToEdit = 2;
- syncScrollToView(null, true);
- }
-
- if (lastMode == modeType.both && currentMode != modeType.both) {
- preventSyncScrollToView = false;
- preventSyncScrollToEdit = false;
- }
-
- if (lastMode != modeType.edit && currentMode == modeType.edit) {
- editor.refresh();
- }
-
- $(document.body).scrollspy('refresh');
- ui.area.view.scrollspy('refresh');
-
- ui.toolbar.both.removeClass("active");
- ui.toolbar.edit.removeClass("active");
- ui.toolbar.view.removeClass("active");
- var modeIcon = ui.toolbar.mode.find('i');
- modeIcon.removeClass('fa-pencil').removeClass('fa-eye');
- if (ui.area.edit.is(":visible") && ui.area.view.is(":visible")) { //both
- ui.toolbar.both.addClass("active");
- modeIcon.addClass('fa-eye');
- } else if (ui.area.edit.is(":visible")) { //edit
- ui.toolbar.edit.addClass("active");
- modeIcon.addClass('fa-eye');
- } else if (ui.area.view.is(":visible")) { //view
- ui.toolbar.view.addClass("active");
- modeIcon.addClass('fa-pencil');
- }
- unlockNavbar();
-}
-
-function lockNavbar() {
- $('.navbar').addClass('locked');
+ if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + window.currentMode.name)
+ if (window.currentMode === modeType.view) {
+ editor.getInputField().blur()
+ }
+ if (window.currentMode === modeType.edit || window.currentMode === modeType.both) {
+ ui.toolbar.uploadImage.fadeIn()
+ // add and update status bar
+ if (!statusBar) {
+ addStatusBar()
+ updateStatusBar()
+ }
+ // work around foldGutter might not init properly
+ editor.setOption('foldGutter', false)
+ editor.setOption('foldGutter', true)
+ } else {
+ ui.toolbar.uploadImage.fadeOut()
+ }
+ if (window.currentMode !== modeType.edit) {
+ $(document.body).css('background-color', 'white')
+ updateView()
+ } else {
+ $(document.body).css('background-color', ui.area.codemirror.css('background-color'))
+ }
+ // check resizable editor style
+ if (window.currentMode === modeType.both) {
+ if (lastEditorWidth > 0) { ui.area.edit.css('width', lastEditorWidth + 'px') } else { ui.area.edit.css('width', '') }
+ ui.area.resize.handle.show()
+ } else {
+ ui.area.edit.css('width', '')
+ ui.area.resize.handle.hide()
+ }
+
+ windowResizeInner()
+
+ restoreInfo()
+
+ if (lastMode === modeType.view && window.currentMode === modeType.both) {
+ window.preventSyncScrollToView = 2
+ syncScrollToEdit(null, true)
+ }
+
+ if (lastMode === modeType.edit && window.currentMode === modeType.both) {
+ window.preventSyncScrollToEdit = 2
+ syncScrollToView(null, true)
+ }
+
+ if (lastMode === modeType.both && window.currentMode !== modeType.both) {
+ window.preventSyncScrollToView = false
+ window.preventSyncScrollToEdit = false
+ }
+
+ if (lastMode !== modeType.edit && window.currentMode === modeType.edit) {
+ editor.refresh()
+ }
+
+ $(document.body).scrollspy('refresh')
+ ui.area.view.scrollspy('refresh')
+
+ ui.toolbar.both.removeClass('active')
+ ui.toolbar.edit.removeClass('active')
+ ui.toolbar.view.removeClass('active')
+ var modeIcon = ui.toolbar.mode.find('i')
+ modeIcon.removeClass('fa-pencil').removeClass('fa-eye')
+ if (ui.area.edit.is(':visible') && ui.area.view.is(':visible')) { // both
+ ui.toolbar.both.addClass('active')
+ modeIcon.addClass('fa-eye')
+ } else if (ui.area.edit.is(':visible')) { // edit
+ ui.toolbar.edit.addClass('active')
+ modeIcon.addClass('fa-eye')
+ } else if (ui.area.view.is(':visible')) { // view
+ ui.toolbar.view.addClass('active')
+ modeIcon.addClass('fa-pencil')
+ }
+ unlockNavbar()
+}
+
+function lockNavbar () {
+ $('.navbar').addClass('locked')
}
var unlockNavbar = _.debounce(function () {
- $('.navbar').removeClass('locked');
-}, 200);
-
-function closestIndex(arr, closestTo) {
- var closest = Math.max.apply(null, arr); //Get the highest number in arr in case it match nothing.
- var index = 0;
- for (var i = 0; i < arr.length; i++) { //Loop the array
- if (arr[i] >= closestTo && arr[i] < closest) {
- closest = arr[i]; //Check if it's higher than your number, but lower than your closest value
- index = i;
- }
- }
- return index; // return the value
-}
+ $('.navbar').removeClass('locked')
+}, 200)
-function showMessageModal(title, header, href, text, success) {
- var modal = $('.message-modal');
- modal.find('.modal-title').html(title);
- modal.find('.modal-body h5').html(header);
- if (href)
- modal.find('.modal-body a').attr('href', href).text(text);
- else
- modal.find('.modal-body a').removeAttr('href').text(text);
- modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
- if (success)
- modal.find('.modal-footer button').addClass('btn-success');
- else
- modal.find('.modal-footer button').addClass('btn-danger');
- modal.modal('show');
+function showMessageModal (title, header, href, text, success) {
+ var modal = $('.message-modal')
+ modal.find('.modal-title').html(title)
+ modal.find('.modal-body h5').html(header)
+ if (href) { modal.find('.modal-body a').attr('href', href).text(text) } else { modal.find('.modal-body a').removeAttr('href').text(text) }
+ modal.find('.modal-footer button').removeClass('btn-default btn-success btn-danger')
+ if (success) { modal.find('.modal-footer button').addClass('btn-success') } else { modal.find('.modal-footer button').addClass('btn-danger') }
+ modal.modal('show')
}
// check if dropbox app key is set and load scripts
if (DROPBOX_APP_KEY) {
- $('<script>')
+ $('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://www.dropbox.com/static/api/2/dropins.js')
.attr('id', 'dropboxjs')
.attr('data-app-key', DROPBOX_APP_KEY)
.prop('async', true)
.prop('defer', true)
- .appendTo('body');
+ .appendTo('body')
} else {
- ui.toolbar.import.dropbox.hide();
- ui.toolbar.export.dropbox.hide();
+ ui.toolbar.import.dropbox.hide()
+ ui.toolbar.export.dropbox.hide()
}
// check if google api key and client id are set and load scripts
if (GOOGLE_API_KEY && GOOGLE_CLIENT_ID) {
- $('<script>')
+ $('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://www.google.com/jsapi?callback=onGoogleAPILoaded')
.prop('async', true)
.prop('defer', true)
- .appendTo('body');
+ .appendTo('body')
} else {
- ui.toolbar.import.googleDrive.hide();
- ui.toolbar.export.googleDrive.hide();
+ ui.toolbar.import.googleDrive.hide()
+ ui.toolbar.export.googleDrive.hide()
}
-function onGoogleAPILoaded() {
- $('<script>')
+function onGoogleAPILoaded () {
+ $('<script>')
.attr('type', 'text/javascript')
.attr('src', 'https://apis.google.com/js/client:plusone.js?onload=onGoogleClientLoaded')
.prop('async', true)
.prop('defer', true)
- .appendTo('body');
+ .appendTo('body')
}
-window.onGoogleAPILoaded = onGoogleAPILoaded;
+window.onGoogleAPILoaded = onGoogleAPILoaded
-//button actions
-//share
-ui.toolbar.publish.attr("href", noteurl + "/publish");
+// button actions
+// share
+ui.toolbar.publish.attr('href', noteurl + '/publish')
// extra
-//slide
-ui.toolbar.extra.slide.attr("href", noteurl + "/slide");
-//download
-//markdown
+// slide
+ui.toolbar.extra.slide.attr('href', noteurl + '/slide')
+// download
+// markdown
ui.toolbar.download.markdown.click(function (e) {
- e.preventDefault();
- e.stopPropagation();
- var filename = renderFilename(ui.area.markdown) + '.md';
- var markdown = editor.getValue();
- var blob = new Blob([markdown], {
- type: "text/markdown;charset=utf-8"
- });
- saveAs(blob, filename, true);
-});
-//html
+ e.preventDefault()
+ e.stopPropagation()
+ var filename = renderFilename(ui.area.markdown) + '.md'
+ var markdown = editor.getValue()
+ var blob = new Blob([markdown], {
+ type: 'text/markdown;charset=utf-8'
+ })
+ saveAs(blob, filename, true)
+})
+// html
ui.toolbar.download.html.click(function (e) {
- e.preventDefault();
- e.stopPropagation();
- exportToHTML(ui.area.markdown);
-});
+ e.preventDefault()
+ e.stopPropagation()
+ exportToHTML(ui.area.markdown)
+})
// raw html
ui.toolbar.download.rawhtml.click(function (e) {
- e.preventDefault();
- e.stopPropagation();
- exportToRawHTML(ui.area.markdown);
-});
-//pdf
-ui.toolbar.download.pdf.attr("download", "").attr("href", noteurl + "/pdf");
-//export to dropbox
+ e.preventDefault()
+ e.stopPropagation()
+ exportToRawHTML(ui.area.markdown)
+})
+// pdf
+ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf')
+// export to dropbox
ui.toolbar.export.dropbox.click(function () {
- var filename = renderFilename(ui.area.markdown) + '.md';
- var options = {
- files: [
- {
- 'url': noteurl + "/download",
- 'filename': filename
- }
- ],
- error: function (errorMessage) {
- console.error(errorMessage);
- }
- };
- Dropbox.save(options);
-});
-function uploadToGoogleDrive(accessToken) {
- ui.spinner.show();
- var filename = renderFilename(ui.area.markdown) + '.md';
- var markdown = editor.getValue();
- var blob = new Blob([markdown], {
- type: "text/markdown;charset=utf-8"
- });
- blob.name = filename;
- var uploader = new MediaUploader({
- file: blob,
- token: accessToken,
- onComplete: function (data) {
- data = JSON.parse(data);
- showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true);
- ui.spinner.hide();
- },
- onError: function (data) {
- var modal = $('.export-modal');
- showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false);
- ui.spinner.hide();
- }
- });
- uploader.upload();
-}
-function googleApiAuth(immediate, callback) {
- gapi.auth.authorize(
- {
- 'client_id': GOOGLE_CLIENT_ID,
- 'scope': 'https://www.googleapis.com/auth/drive.file',
- 'immediate': immediate
- }, callback ? callback : function () { });
-}
-function onGoogleClientLoaded() {
- googleApiAuth(true);
- buildImportFromGoogleDrive();
-}
-window.onGoogleClientLoaded = onGoogleClientLoaded;
+ var filename = renderFilename(ui.area.markdown) + '.md'
+ var options = {
+ files: [
+ {
+ 'url': noteurl + '/download',
+ 'filename': filename
+ }
+ ],
+ error: function (errorMessage) {
+ console.error(errorMessage)
+ }
+ }
+ Dropbox.save(options)
+})
+function uploadToGoogleDrive (accessToken) {
+ ui.spinner.show()
+ var filename = renderFilename(ui.area.markdown) + '.md'
+ var markdown = editor.getValue()
+ var blob = new Blob([markdown], {
+ type: 'text/markdown;charset=utf-8'
+ })
+ blob.name = filename
+ var uploader = new MediaUploader({
+ file: blob,
+ token: accessToken,
+ onComplete: function (data) {
+ data = JSON.parse(data)
+ showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Complete!', data.alternateLink, 'Click here to view your file', true)
+ ui.spinner.hide()
+ },
+ onError: function (data) {
+ showMessageModal('<i class="fa fa-cloud-upload"></i> Export to Google Drive', 'Export Error :(', '', data, false)
+ ui.spinner.hide()
+ }
+ })
+ uploader.upload()
+}
+function googleApiAuth (immediate, callback) {
+ gapi.auth.authorize(
+ {
+ 'client_id': GOOGLE_CLIENT_ID,
+ 'scope': 'https://www.googleapis.com/auth/drive.file',
+ 'immediate': immediate
+ }, callback || function () { })
+}
+function onGoogleClientLoaded () {
+ googleApiAuth(true)
+ buildImportFromGoogleDrive()
+}
+window.onGoogleClientLoaded = onGoogleClientLoaded
// export to google drive
ui.toolbar.export.googleDrive.click(function (e) {
- var token = gapi.auth.getToken();
- if (token) {
- uploadToGoogleDrive(token.access_token);
- } else {
- googleApiAuth(false, function (result) {
- uploadToGoogleDrive(result.access_token);
- });
- }
-});
-//export to gist
-ui.toolbar.export.gist.attr("href", noteurl + "/gist");
-//export to snippet
-ui.toolbar.export.snippet.click(function() {
- ui.spinner.show();
- $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
+ var token = gapi.auth.getToken()
+ if (token) {
+ uploadToGoogleDrive(token.access_token)
+ } else {
+ googleApiAuth(false, function (result) {
+ uploadToGoogleDrive(result.access_token)
+ })
+ }
+})
+// export to gist
+ui.toolbar.export.gist.attr('href', noteurl + '/gist')
+// export to snippet
+ui.toolbar.export.snippet.click(function () {
+ ui.spinner.show()
+ $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
.done(function (data) {
- $("#snippetExportModalAccessToken").val(data.accesstoken);
- $("#snippetExportModalBaseURL").val(data.baseURL);
- $("#snippetExportModalLoading").hide();
- $("#snippetExportModal").modal('toggle');
- $("#snippetExportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>');
- if (data.projects) {
- data.projects.sort(function(a,b) {
- return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0);
- });
- data.projects.forEach(function(project) {
- if (!project.snippets_enabled
- || (project.permissions.project_access === null && project.permissions.group_access === null)
- || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20))
- {
- return;
- }
- $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetExportModalProjects");
- });
- $("#snippetExportModalProjects").prop('disabled', false);
- }
- $("#snippetExportModalLoading").hide();
+ $('#snippetExportModalAccessToken').val(data.accesstoken)
+ $('#snippetExportModalBaseURL').val(data.baseURL)
+ $('#snippetExportModalLoading').hide()
+ $('#snippetExportModal').modal('toggle')
+ $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>')
+ if (data.projects) {
+ data.projects.sort(function (a, b) {
+ return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0)
+ })
+ data.projects.forEach(function (project) {
+ if (!project.snippets_enabled ||
+ (project.permissions.project_access === null && project.permissions.group_access === null) ||
+ (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) {
+ return
+ }
+ $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetExportModalProjects')
+ })
+ $('#snippetExportModalProjects').prop('disabled', false)
+ }
+ $('#snippetExportModalLoading').hide()
})
.fail(function (data) {
- showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false);
+ showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false)
})
.always(function () {
- ui.spinner.hide();
- });
-});
-//import from dropbox
+ ui.spinner.hide()
+ })
+})
+// import from dropbox
ui.toolbar.import.dropbox.click(function () {
- var options = {
- success: function (files) {
- ui.spinner.show();
- var url = files[0].link;
- importFromUrl(url);
- },
- linkType: "direct",
- multiselect: false,
- extensions: ['.md', '.html']
- };
- Dropbox.choose(options);
-});
+ var options = {
+ success: function (files) {
+ ui.spinner.show()
+ var url = files[0].link
+ importFromUrl(url)
+ },
+ linkType: 'direct',
+ multiselect: false,
+ extensions: ['.md', '.html']
+ }
+ Dropbox.choose(options)
+})
// import from google drive
-var picker = null;
-function buildImportFromGoogleDrive() {
- picker = new FilePicker({
- apiKey: GOOGLE_API_KEY,
- clientId: GOOGLE_CLIENT_ID,
- buttonEl: ui.toolbar.import.googleDrive,
- onSelect: function (file) {
- if (file.downloadUrl) {
- ui.spinner.show();
- var accessToken = gapi.auth.getToken().access_token;
- $.ajax({
- type: 'GET',
- beforeSend: function (request) {
- request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
- },
- url: file.downloadUrl,
- success: function (data) {
- if (file.fileExtension == 'html')
- parseToEditor(data);
- else
- replaceAll(data);
- },
- error: function (data) {
- showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false);
- },
- complete: function () {
- ui.spinner.hide();
- }
- });
- }
- }
- });
+function buildImportFromGoogleDrive () {
+ FilePicker({
+ apiKey: GOOGLE_API_KEY,
+ clientId: GOOGLE_CLIENT_ID,
+ buttonEl: ui.toolbar.import.googleDrive,
+ onSelect: function (file) {
+ if (file.downloadUrl) {
+ ui.spinner.show()
+ var accessToken = gapi.auth.getToken().access_token
+ $.ajax({
+ type: 'GET',
+ beforeSend: function (request) {
+ request.setRequestHeader('Authorization', 'Bearer ' + accessToken)
+ },
+ url: file.downloadUrl,
+ success: function (data) {
+ if (file.fileExtension === 'html') { parseToEditor(data) } else { replaceAll(data) }
+ },
+ error: function (data) {
+ showMessageModal('<i class="fa fa-cloud-download"></i> Import from Google Drive', 'Import failed :(', '', data, false)
+ },
+ complete: function () {
+ ui.spinner.hide()
+ }
+ })
+ }
+ }
+ })
}
-//import from gist
+// import from gist
ui.toolbar.import.gist.click(function () {
- //na
-});
-//import from snippet
+ // na
+})
+// import from snippet
ui.toolbar.import.snippet.click(function () {
- ui.spinner.show();
- $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
+ ui.spinner.show()
+ $.get(serverurl + '/auth/gitlab/callback/' + noteid + '/projects')
.done(function (data) {
- $("#snippetImportModalAccessToken").val(data.accesstoken);
- $("#snippetImportModalBaseURL").val(data.baseURL);
- $("#snippetImportModalContent").prop('disabled', false);
- $("#snippetImportModalConfirm").prop('disabled', false);
- $("#snippetImportModalLoading").hide();
- $("#snippetImportModal").modal('toggle');
- $("#snippetImportModalProjects").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>');
- if (data.projects) {
- data.projects.sort(function(a,b) {
- return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0);
- });
- data.projects.forEach(function(project) {
- if (!project.snippets_enabled
- || (project.permissions.project_access === null && project.permissions.group_access === null)
- || (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20))
- {
- return;
- }
- $('<option>').val(project.id).text(project.path_with_namespace).appendTo("#snippetImportModalProjects");
- });
- $("#snippetImportModalProjects").prop('disabled', false);
- }
- $("#snippetImportModalLoading").hide();
+ $('#snippetImportModalAccessToken').val(data.accesstoken)
+ $('#snippetImportModalBaseURL').val(data.baseURL)
+ $('#snippetImportModalContent').prop('disabled', false)
+ $('#snippetImportModalConfirm').prop('disabled', false)
+ $('#snippetImportModalLoading').hide()
+ $('#snippetImportModal').modal('toggle')
+ $('#snippetImportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>')
+ if (data.projects) {
+ data.projects.sort(function (a, b) {
+ return (a.path_with_namespace < b.path_with_namespace) ? -1 : ((a.path_with_namespace > b.path_with_namespace) ? 1 : 0)
+ })
+ data.projects.forEach(function (project) {
+ if (!project.snippets_enabled ||
+ (project.permissions.project_access === null && project.permissions.group_access === null) ||
+ (project.permissions.project_access !== null && project.permissions.project_access.access_level < 20)) {
+ return
+ }
+ $('<option>').val(project.id).text(project.path_with_namespace).appendTo('#snippetImportModalProjects')
+ })
+ $('#snippetImportModalProjects').prop('disabled', false)
+ }
+ $('#snippetImportModalLoading').hide()
})
.fail(function (data) {
- showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false);
+ showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Unable to fetch gitlab parameters :(', '', '', false)
})
.always(function () {
- ui.spinner.hide();
- });
-});
-//import from clipboard
+ ui.spinner.hide()
+ })
+})
+// import from clipboard
ui.toolbar.import.clipboard.click(function () {
- //na
-});
-//upload image
+ // na
+})
+// upload image
ui.toolbar.uploadImage.bind('change', function (e) {
- var files = e.target.files || e.dataTransfer.files;
- e.dataTransfer = {};
- e.dataTransfer.files = files;
- inlineAttach.onDrop(e);
-});
-//toc
+ var files = e.target.files || e.dataTransfer.files
+ e.dataTransfer = {}
+ e.dataTransfer.files = files
+ inlineAttach.onDrop(e)
+})
+// toc
ui.toc.dropdown.click(function (e) {
- e.stopPropagation();
-});
+ e.stopPropagation()
+})
// prevent empty link change hash
$('a[href="#"]').click(function (e) {
- e.preventDefault();
-});
-
-//modal actions
-var revisions = [];
-var revisionViewer = null;
-var revisionInsert = [];
-var revisionDelete = [];
-var revisionInsertAnnotation = null;
-var revisionDeleteAnnotation = null;
-var revisionList = ui.modal.revision.find('.ui-revision-list');
-var revision = null;
-var revisionTime = null;
+ e.preventDefault()
+})
+
+// modal actions
+var revisions = []
+var revisionViewer = null
+var revisionInsert = []
+var revisionDelete = []
+var revisionInsertAnnotation = null
+var revisionDeleteAnnotation = null
+var revisionList = ui.modal.revision.find('.ui-revision-list')
+var revision = null
+var revisionTime = null
ui.modal.revision.on('show.bs.modal', function (e) {
- $.get(noteurl + '/revision')
- .done(function(data) {
- parseRevisions(data.revision);
- initRevisionViewer();
+ $.get(noteurl + '/revision')
+ .done(function (data) {
+ parseRevisions(data.revision)
+ initRevisionViewer()
})
- .fail(function(err) {
-
+ .fail(function (err) {
+ if (debug) {
+ console.log(err)
+ }
})
- .always(function() {
- //na
- });
-});
-function checkRevisionViewer() {
- if (revisionViewer) {
- var container = $(revisionViewer.display.wrapper).parent();
- $(revisionViewer.display.scroller).css('height', container.height() + 'px');
- revisionViewer.refresh();
- }
-}
-ui.modal.revision.on('shown.bs.modal', checkRevisionViewer);
-$(window).resize(checkRevisionViewer);
-function parseRevisions(_revisions) {
- if (_revisions.length != revisions) {
- revisions = _revisions;
- var lastRevision = null;
- if (revisionList.children().length > 0) {
- lastRevision = revisionList.find('.active').attr('data-revision-time');
- }
- revisionList.html('');
- for (var i = 0; i < revisions.length; i++) {
- var revision = revisions[i];
- var item = $('<a href="#" class="list-group-item"></a>');
- item.attr('data-revision-time', revision.time);
- if (lastRevision == revision.time) item.addClass('active');
- var itemHeading = $('<h5 class="list-group-item-heading"></h5>');
- itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll'));
- var itemText = $('<p class="list-group-item-text"></p>');
- itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length);
- item.append(itemHeading).append(itemText);
- item.click(function (e) {
- var time = $(this).attr('data-revision-time');
- selectRevision(time);
- });
- revisionList.append(item);
- }
- if (!lastRevision) {
- selectRevision(revisions[0].time);
- }
- }
-}
-function selectRevision(time) {
- if (time == revisionTime) return;
- $.get(noteurl + '/revision/' + time)
- .done(function(data) {
- revision = data;
- revisionTime = time;
- var lastScrollInfo = revisionViewer.getScrollInfo();
- revisionList.children().removeClass('active');
- revisionList.find('[data-revision-time="' + time + '"]').addClass('active');
- var content = revision.content;
- revisionViewer.setValue(content);
- revisionViewer.scrollTo(null, lastScrollInfo.top);
- revisionInsert = [];
- revisionDelete = [];
+ .always(function () {
+ // na
+ })
+})
+function checkRevisionViewer () {
+ if (revisionViewer) {
+ var container = $(revisionViewer.display.wrapper).parent()
+ $(revisionViewer.display.scroller).css('height', container.height() + 'px')
+ revisionViewer.refresh()
+ }
+}
+ui.modal.revision.on('shown.bs.modal', checkRevisionViewer)
+$(window).resize(checkRevisionViewer)
+function parseRevisions (_revisions) {
+ if (_revisions.length !== revisions) {
+ revisions = _revisions
+ var lastRevision = null
+ if (revisionList.children().length > 0) {
+ lastRevision = revisionList.find('.active').attr('data-revision-time')
+ }
+ revisionList.html('')
+ for (var i = 0; i < revisions.length; i++) {
+ var revision = revisions[i]
+ var item = $('<a href="#" class="list-group-item"></a>')
+ item.attr('data-revision-time', revision.time)
+ if (lastRevision === revision.time) item.addClass('active')
+ var itemHeading = $('<h5 class="list-group-item-heading"></h5>')
+ itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll'))
+ var itemText = $('<p class="list-group-item-text"></p>')
+ itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length)
+ item.append(itemHeading).append(itemText)
+ item.click(function (e) {
+ var time = $(this).attr('data-revision-time')
+ selectRevision(time)
+ })
+ revisionList.append(item)
+ }
+ if (!lastRevision) {
+ selectRevision(revisions[0].time)
+ }
+ }
+}
+function selectRevision (time) {
+ if (time === revisionTime) return
+ $.get(noteurl + '/revision/' + time)
+ .done(function (data) {
+ revision = data
+ revisionTime = time
+ var lastScrollInfo = revisionViewer.getScrollInfo()
+ revisionList.children().removeClass('active')
+ revisionList.find('[data-revision-time="' + time + '"]').addClass('active')
+ var content = revision.content
+ revisionViewer.setValue(content)
+ revisionViewer.scrollTo(null, lastScrollInfo.top)
+ revisionInsert = []
+ revisionDelete = []
// mark the text which have been insert or delete
- if (revision.patch.length > 0) {
- var bias = 0;
- for (var j = 0; j < revision.patch.length; j++) {
- var patch = revision.patch[j];
- var currIndex = patch.start1 + bias;
- for (var i = 0; i < patch.diffs.length; i++) {
- var diff = patch.diffs[i];
+ if (revision.patch.length > 0) {
+ var bias = 0
+ for (var j = 0; j < revision.patch.length; j++) {
+ var patch = revision.patch[j]
+ var currIndex = patch.start1 + bias
+ for (var i = 0; i < patch.diffs.length; i++) {
+ var diff = patch.diffs[i]
// ignore if diff only contains line breaks
- if ((diff[1].match(new RegExp("\n", "g")) || []).length == diff[1].length) continue;
- switch(diff[0]) {
- case 0: // retain
- currIndex += diff[1].length;
- break;
- case 1: // insert
- var prePos = revisionViewer.posFromIndex(currIndex);
- var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
- revisionInsert.push({
- from: prePos,
- to: postPos
- });
- revisionViewer.markText(prePos, postPos, {
- css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
- });
- currIndex += diff[1].length;
- break;
- case -1: // delete
- var prePos = revisionViewer.posFromIndex(currIndex);
- revisionViewer.replaceRange(diff[1], prePos);
- var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
- revisionDelete.push({
- from: prePos,
- to: postPos
- });
- revisionViewer.markText(prePos, postPos, {
- css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;'
- });
- bias += diff[1].length;
- currIndex += diff[1].length;
- break;
- }
- }
+ if ((diff[1].match(/\n/g) || []).length === diff[1].length) continue
+ var prePos
+ var postPos
+ switch (diff[0]) {
+ case 0: // retain
+ currIndex += diff[1].length
+ break
+ case 1: // insert
+ prePos = revisionViewer.posFromIndex(currIndex)
+ postPos = revisionViewer.posFromIndex(currIndex + diff[1].length)
+ revisionInsert.push({
+ from: prePos,
+ to: postPos
+ })
+ revisionViewer.markText(prePos, postPos, {
+ css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
+ })
+ currIndex += diff[1].length
+ break
+ case -1: // delete
+ prePos = revisionViewer.posFromIndex(currIndex)
+ revisionViewer.replaceRange(diff[1], prePos)
+ postPos = revisionViewer.posFromIndex(currIndex + diff[1].length)
+ revisionDelete.push({
+ from: prePos,
+ to: postPos
+ })
+ revisionViewer.markText(prePos, postPos, {
+ css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;'
+ })
+ bias += diff[1].length
+ currIndex += diff[1].length
+ break
}
+ }
}
- revisionInsertAnnotation.update(revisionInsert);
- revisionDeleteAnnotation.update(revisionDelete);
+ }
+ revisionInsertAnnotation.update(revisionInsert)
+ revisionDeleteAnnotation.update(revisionDelete)
})
- .fail(function(err) {
-
+ .fail(function (err) {
+ if (debug) {
+ console.log(err)
+ }
+ })
+ .always(function () {
+ // na
})
- .always(function() {
- //na
- });
-}
-function initRevisionViewer() {
- if (revisionViewer) return;
- var revisionViewerTextArea = document.getElementById("revisionViewer");
- revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
- mode: defaultEditorMode,
- viewportMargin: viewportMargin,
- lineNumbers: true,
- lineWrapping: true,
- showCursorWhenSelecting: true,
- inputStyle: "textarea",
- gutters: ["CodeMirror-linenumbers"],
- flattenSpans: true,
- addModeClass: true,
- readOnly: true,
- autoRefresh: true,
- scrollbarStyle: 'overlay'
- });
- revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-insert-match" });
- revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className:"CodeMirror-delete-match" });
- checkRevisionViewer();
+}
+function initRevisionViewer () {
+ if (revisionViewer) return
+ var revisionViewerTextArea = document.getElementById('revisionViewer')
+ revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
+ mode: defaultEditorMode,
+ viewportMargin: viewportMargin,
+ lineNumbers: true,
+ lineWrapping: true,
+ showCursorWhenSelecting: true,
+ inputStyle: 'textarea',
+ gutters: ['CodeMirror-linenumbers'],
+ flattenSpans: true,
+ addModeClass: true,
+ readOnly: true,
+ autoRefresh: true,
+ scrollbarStyle: 'overlay'
+ })
+ revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-insert-match' })
+ revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-delete-match' })
+ checkRevisionViewer()
}
$('#revisionModalDownload').click(function () {
- if (!revision) return;
- var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md';
- var blob = new Blob([revision.content], {
- type: "text/markdown;charset=utf-8"
- });
- saveAs(blob, filename, true);
-});
+ if (!revision) return
+ var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md'
+ var blob = new Blob([revision.content], {
+ type: 'text/markdown;charset=utf-8'
+ })
+ saveAs(blob, filename, true)
+})
$('#revisionModalRevert').click(function () {
- if (!revision) return;
- editor.setValue(revision.content);
- ui.modal.revision.modal('hide');
-});
-//snippet projects
-ui.modal.snippetImportProjects.change(function() {
- var accesstoken = $("#snippetImportModalAccessToken").val(),
- baseURL = $("#snippetImportModalBaseURL").val(),
- project = $("#snippetImportModalProjects").val();
-
- $("#snippetImportModalLoading").show();
- $("#snippetImportModalContent").val('/projects/' + project);
- $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken)
- .done(function(data) {
- $("#snippetImportModalSnippets").find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>');
- data.forEach(function(snippet) {
- $('<option>').val(snippet.id).text(snippet.title).appendTo($("#snippetImportModalSnippets"));
- });
- $("#snippetImportModalLoading").hide();
- $("#snippetImportModalSnippets").prop('disabled', false);
+ if (!revision) return
+ editor.setValue(revision.content)
+ ui.modal.revision.modal('hide')
+})
+// snippet projects
+ui.modal.snippetImportProjects.change(function () {
+ var accesstoken = $('#snippetImportModalAccessToken').val()
+ var baseURL = $('#snippetImportModalBaseURL').val()
+ var project = $('#snippetImportModalProjects').val()
+
+ $('#snippetImportModalLoading').show()
+ $('#snippetImportModalContent').val('/projects/' + project)
+ $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken)
+ .done(function (data) {
+ $('#snippetImportModalSnippets').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>')
+ data.forEach(function (snippet) {
+ $('<option>').val(snippet.id).text(snippet.title).appendTo($('#snippetImportModalSnippets'))
+ })
+ $('#snippetImportModalLoading').hide()
+ $('#snippetImportModalSnippets').prop('disabled', false)
})
- .fail(function(err) {
-
+ .fail(function (err) {
+ if (debug) {
+ console.log(err)
+ }
})
- .always(function() {
- //na
- });
-});
-//snippet snippets
-ui.modal.snippetImportSnippets.change(function() {
- var project = $("#snippetImportModalProjects").val(),
- snippet = $("#snippetImportModalSnippets").val();
-
- $("#snippetImportModalContent").val($("#snippetImportModalContent").val() + '/snippets/' + snippet);
-})
-
-function scrollToTop() {
- if (currentMode == modeType.both) {
- if (editor.getScrollInfo().top != 0)
- editor.scrollTo(0, 0);
- else
- ui.area.view.animate({
- scrollTop: 0
- }, 100, "linear");
- } else {
- $('body, html').stop(true, true).animate({
- scrollTop: 0
- }, 100, "linear");
- }
-}
-
-function scrollToBottom() {
- if (currentMode == modeType.both) {
- var scrollInfo = editor.getScrollInfo();
- var scrollHeight = scrollInfo.height;
- if (scrollInfo.top != scrollHeight)
- editor.scrollTo(0, scrollHeight * 2);
- else
- ui.area.view.animate({
- scrollTop: ui.area.view[0].scrollHeight
- }, 100, "linear");
- } else {
- $('body, html').stop(true, true).animate({
- scrollTop: $(document.body)[0].scrollHeight
- }, 100, "linear");
- }
-}
-
-window.scrollToTop = scrollToTop;
-window.scrollToBottom = scrollToBottom;
-
-var enoughForAffixToc = true;
-
-//scrollspy
-function generateScrollspy() {
- $(document.body).scrollspy({
- target: '.scrollspy-body'
- });
- ui.area.view.scrollspy({
- target: '.scrollspy-view'
- });
- $(document.body).scrollspy('refresh');
- ui.area.view.scrollspy('refresh');
- if (enoughForAffixToc) {
- ui.toc.toc.hide();
- ui.toc.affix.show();
- } else {
- ui.toc.affix.hide();
- ui.toc.toc.show();
- }
- //$(document.body).scroll();
- //ui.area.view.scroll();
-}
+ .always(function () {
+ // na
+ })
+})
+// snippet snippets
+ui.modal.snippetImportSnippets.change(function () {
+ var snippet = $('#snippetImportModalSnippets').val()
+ $('#snippetImportModalContent').val($('#snippetImportModalContent').val() + '/snippets/' + snippet)
+})
-function updateScrollspy() {
- var headers = ui.area.markdown.find('h1, h2, h3').toArray();
- var headerMap = [];
- for (var i = 0; i < headers.length; i++) {
- headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top')));
- }
- applyScrollspyActive($(window).scrollTop(), headerMap, headers,
- $('.scrollspy-body'), 0);
- var offset = ui.area.view.scrollTop() - ui.area.view.offset().top;
- applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers,
- $('.scrollspy-view'), offset - 10);
-}
-
-function applyScrollspyActive(top, headerMap, headers, target, offset) {
- var index = 0;
- for (var i = headerMap.length - 1; i >= 0; i--) {
- if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) {
- index = i;
- break;
- }
- }
- var header = $(headers[index]);
- var active = target.find('a[href="#' + header.attr('id') + '"]');
- active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active');
+function scrollToTop () {
+ if (window.currentMode === modeType.both) {
+ if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else {
+ ui.area.view.animate({
+ scrollTop: 0
+ }, 100, 'linear')
+ }
+ } else {
+ $('body, html').stop(true, true).animate({
+ scrollTop: 0
+ }, 100, 'linear')
+ }
+}
+
+function scrollToBottom () {
+ if (window.currentMode === modeType.both) {
+ var scrollInfo = editor.getScrollInfo()
+ var scrollHeight = scrollInfo.height
+ if (scrollInfo.top !== scrollHeight) { editor.scrollTo(0, scrollHeight * 2) } else {
+ ui.area.view.animate({
+ scrollTop: ui.area.view[0].scrollHeight
+ }, 100, 'linear')
+ }
+ } else {
+ $('body, html').stop(true, true).animate({
+ scrollTop: $(document.body)[0].scrollHeight
+ }, 100, 'linear')
+ }
+}
+
+window.scrollToTop = scrollToTop
+window.scrollToBottom = scrollToBottom
+
+var enoughForAffixToc = true
+
+// scrollspy
+function generateScrollspy () {
+ $(document.body).scrollspy({
+ target: '.scrollspy-body'
+ })
+ ui.area.view.scrollspy({
+ target: '.scrollspy-view'
+ })
+ $(document.body).scrollspy('refresh')
+ ui.area.view.scrollspy('refresh')
+ if (enoughForAffixToc) {
+ ui.toc.toc.hide()
+ ui.toc.affix.show()
+ } else {
+ ui.toc.affix.hide()
+ ui.toc.toc.show()
+ }
+ // $(document.body).scroll();
+ // ui.area.view.scroll();
+}
+
+function updateScrollspy () {
+ var headers = ui.area.markdown.find('h1, h2, h3').toArray()
+ var headerMap = []
+ for (var i = 0; i < headers.length; i++) {
+ headerMap.push($(headers[i]).offset().top - parseInt($(headers[i]).css('margin-top')))
+ }
+ applyScrollspyActive($(window).scrollTop(), headerMap, headers,
+ $('.scrollspy-body'), 0)
+ var offset = ui.area.view.scrollTop() - ui.area.view.offset().top
+ applyScrollspyActive(ui.area.view.scrollTop(), headerMap, headers,
+ $('.scrollspy-view'), offset - 10)
+}
+
+function applyScrollspyActive (top, headerMap, headers, target, offset) {
+ var index = 0
+ for (var i = headerMap.length - 1; i >= 0; i--) {
+ if (top >= (headerMap[i] + offset) && headerMap[i + 1] && top < (headerMap[i + 1] + offset)) {
+ index = i
+ break
+ }
+ }
+ var header = $(headers[index])
+ var active = target.find('a[href="#' + header.attr('id') + '"]')
+ active.closest('li').addClass('active').parent().closest('li').addClass('active').parent().closest('li').addClass('active')
}
// clipboard modal
-//fix for wrong autofocus
+// fix for wrong autofocus
$('#clipboardModal').on('shown.bs.modal', function () {
- $('#clipboardModal').blur();
-});
-$("#clipboardModalClear").click(function () {
- $("#clipboardModalContent").html('');
-});
-$("#clipboardModalConfirm").click(function () {
- var data = $("#clipboardModalContent").html();
- if (data) {
- parseToEditor(data);
- $('#clipboardModal').modal('hide');
- $("#clipboardModalContent").html('');
- }
-});
+ $('#clipboardModal').blur()
+})
+$('#clipboardModalClear').click(function () {
+ $('#clipboardModalContent').html('')
+})
+$('#clipboardModalConfirm').click(function () {
+ var data = $('#clipboardModalContent').html()
+ if (data) {
+ parseToEditor(data)
+ $('#clipboardModal').modal('hide')
+ $('#clipboardModalContent').html('')
+ }
+})
// refresh modal
$('#refreshModalRefresh').click(function () {
- location.reload(true);
-});
+ location.reload(true)
+})
// gist import modal
-$("#gistImportModalClear").click(function () {
- $("#gistImportModalContent").val('');
-});
-$("#gistImportModalConfirm").click(function () {
- var gisturl = $("#gistImportModalContent").val();
- if (!gisturl) return;
- $('#gistImportModal').modal('hide');
- $("#gistImportModalContent").val('');
- if (!isValidURL(gisturl)) {
- showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false);
- return;
+$('#gistImportModalClear').click(function () {
+ $('#gistImportModalContent').val('')
+})
+$('#gistImportModalConfirm').click(function () {
+ var gisturl = $('#gistImportModalContent').val()
+ if (!gisturl) return
+ $('#gistImportModal').modal('hide')
+ $('#gistImportModalContent').val('')
+ if (!isValidURL(gisturl)) {
+ showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid URL :(', '', '', false)
+ } else {
+ var hostname = window.url('hostname', gisturl)
+ if (hostname !== 'gist.github.com') {
+ showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false)
} else {
- var hostname = url('hostname', gisturl)
- if (hostname !== 'gist.github.com') {
- showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', '', false);
- } else {
- ui.spinner.show();
- $.get('https://api.github.com/gists/' + url('-1', gisturl))
+ ui.spinner.show()
+ $.get('https://api.github.com/gists/' + window.url('-1', gisturl))
.done(function (data) {
- if (data.files) {
- var contents = "";
- Object.keys(data.files).forEach(function (key) {
- contents += key;
- contents += '\n---\n';
- contents += data.files[key].content;
- contents += '\n\n';
- });
- replaceAll(contents);
- } else {
- showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false);
- }
+ if (data.files) {
+ var contents = ''
+ Object.keys(data.files).forEach(function (key) {
+ contents += key
+ contents += '\n---\n'
+ contents += data.files[key].content
+ contents += '\n\n'
+ })
+ replaceAll(contents)
+ } else {
+ showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false)
+ }
})
.fail(function (data) {
- showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false);
+ showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Not a valid Gist URL :(', '', JSON.stringify(data), false)
})
.always(function () {
- ui.spinner.hide();
- });
- }
+ ui.spinner.hide()
+ })
}
-});
+ }
+})
// snippet import modal
-$("#snippetImportModalClear").click(function () {
- $("#snippetImportModalContent").val('');
- $("#snippetImportModalProjects").val('init');
- $("#snippetImportModalSnippets").val('init');
- $("#snippetImportModalSnippets").prop('disabled', true);
-});
-$("#snippetImportModalConfirm").click(function () {
- var snippeturl = $("#snippetImportModalContent").val();
- if (!snippeturl) return;
- $('#snippetImportModal').modal('hide');
- $("#snippetImportModalContent").val('');
- if (!/^.+\/snippets\/.+$/.test(snippeturl)) {
- showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false);
- } else {
- ui.spinner.show();
- var accessToken = '?access_token=' + $("#snippetImportModalAccessToken").val();
- var fullURL = $("#snippetImportModalBaseURL").val() + '/api/v3' + snippeturl;
- $.get(fullURL + accessToken)
- .done(function(data) {
- var content = '# ' + (data.title || "Snippet Import");
- var fileInfo = data.file_name.split('.');
- fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : "md";
- $.get(fullURL + '/raw' + accessToken)
+$('#snippetImportModalClear').click(function () {
+ $('#snippetImportModalContent').val('')
+ $('#snippetImportModalProjects').val('init')
+ $('#snippetImportModalSnippets').val('init')
+ $('#snippetImportModalSnippets').prop('disabled', true)
+})
+$('#snippetImportModalConfirm').click(function () {
+ var snippeturl = $('#snippetImportModalContent').val()
+ if (!snippeturl) return
+ $('#snippetImportModal').modal('hide')
+ $('#snippetImportModalContent').val('')
+ if (!/^.+\/snippets\/.+$/.test(snippeturl)) {
+ showMessageModal('<i class="fa fa-github"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', '', false)
+ } else {
+ ui.spinner.show()
+ var accessToken = '?access_token=' + $('#snippetImportModalAccessToken').val()
+ var fullURL = $('#snippetImportModalBaseURL').val() + '/api/v3' + snippeturl
+ $.get(fullURL + accessToken)
+ .done(function (data) {
+ var content = '# ' + (data.title || 'Snippet Import')
+ var fileInfo = data.file_name.split('.')
+ fileInfo[1] = (fileInfo[1]) ? fileInfo[1] : 'md'
+ $.get(fullURL + '/raw' + accessToken)
.done(function (raw) {
- if (raw) {
- content += "\n\n";
- if (fileInfo[1] != "md") {
- content += "```" + fileTypes[fileInfo[1]] + "\n";
- }
- content += raw;
- if (fileInfo[1] != "md") {
- content += "\n```";
- }
- replaceAll(content);
+ if (raw) {
+ content += '\n\n'
+ if (fileInfo[1] !== 'md') {
+ content += '```' + window.fileTypes[fileInfo[1]] + '\n'
+ }
+ content += raw
+ if (fileInfo[1] !== 'md') {
+ content += '\n```'
}
+ replaceAll(content)
+ }
})
.fail(function (data) {
- showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false);
+ showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false)
})
.always(function () {
- ui.spinner.hide();
- });
+ ui.spinner.hide()
+ })
})
.fail(function (data) {
- showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false);
- });
- }
-});
-
-//snippet export modal
-$("#snippetExportModalConfirm").click(function() {
- var accesstoken = $("#snippetExportModalAccessToken").val(),
- baseURL = $("#snippetExportModalBaseURL").val(),
- data = {
- title: $("#snippetExportModalTitle").val(),
- file_name: $("#snippetExportModalFileName").val(),
- code: editor.getValue(),
- visibility_level: $("#snippetExportModalVisibility").val()
- };
- if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$("#snippetExportModalProjects").val()) return;
- $("#snippetExportModalLoading").show();
- var fullURL = baseURL + '/api/v3/projects/' + $("#snippetExportModalProjects").val() + '/snippets?access_token=' + accesstoken;
- $.post(fullURL
+ showMessageModal('<i class="fa fa-gitlab"></i> Import from Snippet', 'Not a valid Snippet URL :(', '', JSON.stringify(data), false)
+ })
+ }
+})
+
+// snippet export modal
+$('#snippetExportModalConfirm').click(function () {
+ var accesstoken = $('#snippetExportModalAccessToken').val()
+ var baseURL = $('#snippetExportModalBaseURL').val()
+ var data = {
+ title: $('#snippetExportModalTitle').val(),
+ file_name: $('#snippetExportModalFileName').val(),
+ code: editor.getValue(),
+ visibility_level: $('#snippetExportModalVisibility').val()
+ }
+ if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return
+ $('#snippetExportModalLoading').show()
+ var fullURL = baseURL + '/api/v3/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken
+ $.post(fullURL
, data
- , function(ret) {
- $("#snippetExportModalLoading").hide();
- $('#snippetExportModal').modal('hide');
- var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $("#snippetExportModalProjects").val() + "']").text() + '/snippets/' + ret.id;
- showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true);
+ , function (ret) {
+ $('#snippetExportModalLoading').hide()
+ $('#snippetExportModal').modal('hide')
+ var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $('#snippetExportModalProjects').val() + "']").text() + '/snippets/' + ret.id
+ showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true)
}
, 'json'
- );
-});
-
-function parseToEditor(data) {
- var parsed = toMarkdown(data);
- if (parsed)
- replaceAll(parsed);
-}
-
-function replaceAll(data) {
- editor.replaceRange(data, {
- line: 0,
- ch: 0
- }, {
- line: editor.lastLine(),
- ch: editor.lastLine().length
- }, '+input');
-}
-
-function importFromUrl(url) {
- //console.log(url);
- if (!url) return;
- if (!isValidURL(url)) {
- showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false);
- return;
- }
- $.ajax({
- method: "GET",
- url: url,
- success: function (data) {
- var extension = url.split('.').pop();
- if (extension == 'html')
- parseToEditor(data);
- else
- replaceAll(data);
- },
- error: function (data) {
- showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false);
- },
- complete: function () {
- ui.spinner.hide();
- }
- });
+ )
+})
+
+function parseToEditor (data) {
+ var parsed = toMarkdown(data)
+ if (parsed) { replaceAll(parsed) }
+}
+
+function replaceAll (data) {
+ editor.replaceRange(data, {
+ line: 0,
+ ch: 0
+ }, {
+ line: editor.lastLine(),
+ ch: editor.lastLine().length
+ }, '+input')
+}
+
+function importFromUrl (url) {
+ // console.log(url);
+ if (!url) return
+ if (!isValidURL(url)) {
+ showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false)
+ return
+ }
+ $.ajax({
+ method: 'GET',
+ url: url,
+ success: function (data) {
+ var extension = url.split('.').pop()
+ if (extension === 'html') { parseToEditor(data) } else { replaceAll(data) }
+ },
+ error: function (data) {
+ showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Import failed :(', '', JSON.stringify(data), false)
+ },
+ complete: function () {
+ ui.spinner.hide()
+ }
+ })
}
-//mode
+// mode
ui.toolbar.mode.click(function () {
- toggleMode();
-});
-//edit
+ toggleMode()
+})
+// edit
ui.toolbar.edit.click(function () {
- changeMode(modeType.edit);
-});
-//view
+ changeMode(modeType.edit)
+})
+// view
ui.toolbar.view.click(function () {
- changeMode(modeType.view);
-});
-//both
+ changeMode(modeType.view)
+})
+// both
ui.toolbar.both.click(function () {
- changeMode(modeType.both);
-});
-//permission
-//freely
+ changeMode(modeType.both)
+})
+// permission
+// freely
ui.infobar.permission.freely.click(function () {
- emitPermission("freely");
-});
-//editable
+ emitPermission('freely')
+})
+// editable
ui.infobar.permission.editable.click(function () {
- emitPermission("editable");
-});
-//locked
+ emitPermission('editable')
+})
+// locked
ui.infobar.permission.locked.click(function () {
- emitPermission("locked");
-});
-//private
+ emitPermission('locked')
+})
+// private
ui.infobar.permission.private.click(function () {
- emitPermission("private");
-});
-//limited
-ui.infobar.permission.limited.click(function() {
- emitPermission("limited");
-});
-//protected
-ui.infobar.permission.protected.click(function() {
- emitPermission("protected");
-});
+ emitPermission('private')
+})
+// limited
+ui.infobar.permission.limited.click(function () {
+ emitPermission('limited')
+})
+// protected
+ui.infobar.permission.protected.click(function () {
+ emitPermission('protected')
+})
// delete note
ui.infobar.delete.click(function () {
- $('.delete-modal').modal('show');
-});
+ $('.delete-modal').modal('show')
+})
$('.ui-delete-modal-confirm').click(function () {
- socket.emit('delete');
-});
-
-function emitPermission(_permission) {
- if (_permission != permission) {
- socket.emit('permission', _permission);
- }
-}
-
-function updatePermission(newPermission) {
- if (permission != newPermission) {
- permission = newPermission;
- if (loaded) refreshView();
- }
- var label = null;
- var title = null;
- switch (permission) {
- case "freely":
- label = '<i class="fa fa-leaf"></i> Freely';
- title = "Anyone can edit";
- break;
- case "editable":
- label = '<i class="fa fa-shield"></i> Editable';
- title = "Signed people can edit";
- break;
- case "limited":
- label = '<i class="fa fa-id-card"></i> Limited';
- title = "Signed people can edit (forbid guest)"
- break;
- case "locked":
- label = '<i class="fa fa-lock"></i> Locked';
- title = "Only owner can edit";
- break;
- case "protected":
- label = '<i class="fa fa-umbrella"></i> Protected';
- title = "Only owner can edit (forbid guest)";
- break;
- case "private":
- label = '<i class="fa fa-hand-stop-o"></i> Private';
- title = "Only owner can view & edit";
- break;
- }
- if (personalInfo.userid && owner && personalInfo.userid == owner) {
- label += ' <i class="fa fa-caret-down"></i>';
- ui.infobar.permission.label.removeClass('disabled');
- } else {
- ui.infobar.permission.label.addClass('disabled');
- }
- ui.infobar.permission.label.html(label).attr('title', title);
-}
-
-function havePermission() {
- var bool = false;
- switch (permission) {
- case "freely":
- bool = true;
- break;
- case "editable":
- case "limited":
- if (!personalInfo.login) {
- bool = false;
- } else {
- bool = true;
- }
- break;
- case "locked":
- case "private":
- case "protected":
- if (!owner || personalInfo.userid != owner) {
- bool = false;
- } else {
- bool = true;
- }
- break;
- }
- return bool;
+ socket.emit('delete')
+})
+
+function emitPermission (_permission) {
+ if (_permission !== permission) {
+ socket.emit('permission', _permission)
+ }
+}
+
+function updatePermission (newPermission) {
+ if (permission !== newPermission) {
+ permission = newPermission
+ if (window.loaded) refreshView()
+ }
+ var label = null
+ var title = null
+ switch (permission) {
+ case 'freely':
+ label = '<i class="fa fa-leaf"></i> Freely'
+ title = 'Anyone can edit'
+ break
+ case 'editable':
+ label = '<i class="fa fa-shield"></i> Editable'
+ title = 'Signed people can edit'
+ break
+ case 'limited':
+ label = '<i class="fa fa-id-card"></i> Limited'
+ title = 'Signed people can edit (forbid guest)'
+ break
+ case 'locked':
+ label = '<i class="fa fa-lock"></i> Locked'
+ title = 'Only owner can edit'
+ break
+ case 'protected':
+ label = '<i class="fa fa-umbrella"></i> Protected'
+ title = 'Only owner can edit (forbid guest)'
+ break
+ case 'private':
+ label = '<i class="fa fa-hand-stop-o"></i> Private'
+ title = 'Only owner can view & edit'
+ break
+ }
+ if (window.personalInfo.userid && window.owner && window.personalInfo.userid === window.owner) {
+ label += ' <i class="fa fa-caret-down"></i>'
+ ui.infobar.permission.label.removeClass('disabled')
+ } else {
+ ui.infobar.permission.label.addClass('disabled')
+ }
+ ui.infobar.permission.label.html(label).attr('title', title)
+}
+
+function havePermission () {
+ var bool = false
+ switch (permission) {
+ case 'freely':
+ bool = true
+ break
+ case 'editable':
+ case 'limited':
+ if (!window.personalInfo.login) {
+ bool = false
+ } else {
+ bool = true
+ }
+ break
+ case 'locked':
+ case 'private':
+ case 'protected':
+ if (!window.owner || window.personalInfo.userid !== window.owner) {
+ bool = false
+ } else {
+ bool = true
+ }
+ break
+ }
+ return bool
}
// global module workaround
-window.havePermission = havePermission;
+window.havePermission = havePermission
-//socket.io actions
-var io = require("socket.io-client");
+// socket.io actions
+var io = require('socket.io-client')
var socket = io.connect({
- path: urlpath ? '/' + urlpath + '/socket.io/' : '',
- timeout: 5000, //5 secs to timeout,
- reconnectionAttempts: 20 // retry 20 times on connect failed
-});
-//overwrite original event for checking login state
-var on = socket.on;
+ path: urlpath ? '/' + urlpath + '/socket.io/' : '',
+ timeout: 5000, // 5 secs to timeout,
+ reconnectionAttempts: 20 // retry 20 times on connect failed
+})
+// overwrite original event for checking login state
+var on = socket.on
socket.on = function () {
- if (!checkLoginStateChanged() && !needRefresh)
- return on.apply(socket, arguments);
-};
-var emit = socket.emit;
+ if (!checkLoginStateChanged() && !window.needRefresh) { return on.apply(socket, arguments) }
+}
+var emit = socket.emit
socket.emit = function () {
- if (!checkLoginStateChanged() && !needRefresh)
- emit.apply(socket, arguments);
-};
+ if (!checkLoginStateChanged() && !window.needRefresh) { emit.apply(socket, arguments) }
+}
socket.on('info', function (data) {
- console.error(data);
- switch (data.code) {
- case 403:
- location.href = serverurl + "/403";
- break;
- case 404:
- location.href = serverurl + "/404";
- break;
- case 500:
- location.href = serverurl + "/500";
- break;
- }
-});
+ console.error(data)
+ switch (data.code) {
+ case 403:
+ location.href = serverurl + '/403'
+ break
+ case 404:
+ location.href = serverurl + '/404'
+ break
+ case 500:
+ location.href = serverurl + '/500'
+ break
+ }
+})
socket.on('error', function (data) {
- console.error(data);
- if (data.message && data.message.indexOf('AUTH failed') === 0)
- location.href = serverurl + "/403";
-});
+ console.error(data)
+ if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' }
+})
socket.on('delete', function () {
- if (personalInfo.login) {
- deleteServerHistory(noteid, function (err, data) {
- if (!err) location.href = serverurl;
- });
- } else {
- getHistory(function (notehistory) {
- var newnotehistory = removeHistory(noteid, notehistory);
- saveHistory(newnotehistory);
- location.href = serverurl;
- });
- }
-});
-var retryTimer = null;
+ if (window.personalInfo.login) {
+ deleteServerHistory(noteid, function (err, data) {
+ if (!err) location.href = serverurl
+ })
+ } else {
+ getHistory(function (notehistory) {
+ var newnotehistory = removeHistory(noteid, notehistory)
+ saveHistory(newnotehistory)
+ location.href = serverurl
+ })
+ }
+})
+var retryTimer = null
socket.on('maintenance', function () {
- cmClient.revision = -1;
-});
+ cmClient.revision = -1
+})
socket.on('disconnect', function (data) {
- showStatus(statusType.offline);
- if (loaded) {
- saveInfo();
- lastInfo.history = editor.getHistory();
- }
- if (!editor.getOption('readOnly'))
- editor.setOption('readOnly', true);
- if (!retryTimer) {
- retryTimer = setInterval(function () {
- if (!needRefresh) socket.connect();
- }, 1000);
- }
-});
+ showStatus(statusType.offline)
+ if (window.loaded) {
+ saveInfo()
+ window.lastInfo.history = editor.getHistory()
+ }
+ if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) }
+ if (!retryTimer) {
+ retryTimer = setInterval(function () {
+ if (!window.needRefresh) socket.connect()
+ }, 1000)
+ }
+})
socket.on('reconnect', function (data) {
- //sync back any change in offline
- emitUserStatus(true);
- cursorActivity();
- socket.emit('online users');
-});
+ // sync back any change in offline
+ emitUserStatus(true)
+ cursorActivity()
+ socket.emit('online users')
+})
socket.on('connect', function (data) {
- clearInterval(retryTimer);
- retryTimer = null;
- personalInfo['id'] = socket.id;
- showStatus(statusType.connected);
- socket.emit('version');
-});
+ clearInterval(retryTimer)
+ retryTimer = null
+ window.personalInfo['id'] = socket.id
+ showStatus(statusType.connected)
+ socket.emit('version')
+})
socket.on('version', function (data) {
- if (version != data.version) {
- if (version < data.minimumCompatibleVersion) {
- setRefreshModal('incompatible-version');
- setNeedRefresh();
- } else {
- setRefreshModal('new-version');
- }
- }
-});
-var authors = [];
-var authorship = [];
-var authorshipMarks = {};
-var authorMarks = {}; // temp variable
-var addTextMarkers = []; // temp variable
-function updateInfo(data) {
- //console.log(data);
- if (data.hasOwnProperty('createtime') && createtime !== data.createtime) {
- createtime = data.createtime;
- updateLastChange();
- }
- if (data.hasOwnProperty('updatetime') && lastchangetime !== data.updatetime) {
- lastchangetime = data.updatetime;
- updateLastChange();
- }
- if (data.hasOwnProperty('owner') && owner !== data.owner) {
- owner = data.owner;
- ownerprofile = data.ownerprofile;
- updateOwner();
- }
- if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) {
- lastchangeuser = data.lastchangeuser;
- lastchangeuserprofile = data.lastchangeuserprofile;
- updateLastChangeUser();
- updateOwner();
- }
- if (data.hasOwnProperty('authors') && authors !== data.authors) {
- authors = data.authors;
- }
- if (data.hasOwnProperty('authorship') && authorship !== data.authorship) {
- authorship = data.authorship;
- updateAuthorship();
+ if (version !== data.version) {
+ if (version < data.minimumCompatibleVersion) {
+ setRefreshModal('incompatible-version')
+ setNeedRefresh()
+ } else {
+ setRefreshModal('new-version')
}
+ }
+})
+var authors = []
+var authorship = []
+var authorMarks = {} // temp variable
+var addTextMarkers = [] // temp variable
+function updateInfo (data) {
+ // console.log(data);
+ if (data.hasOwnProperty('createtime') && window.createtime !== data.createtime) {
+ window.createtime = data.createtime
+ updateLastChange()
+ }
+ if (data.hasOwnProperty('updatetime') && window.lastchangetime !== data.updatetime) {
+ window.lastchangetime = data.updatetime
+ updateLastChange()
+ }
+ if (data.hasOwnProperty('owner') && window.owner !== data.owner) {
+ window.owner = data.owner
+ window.ownerprofile = data.ownerprofile
+ updateOwner()
+ }
+ if (data.hasOwnProperty('lastchangeuser') && window.lastchangeuser !== data.lastchangeuser) {
+ window.lastchangeuser = data.lastchangeuser
+ window.lastchangeuserprofile = data.lastchangeuserprofile
+ updateLastChangeUser()
+ updateOwner()
+ }
+ if (data.hasOwnProperty('authors') && authors !== data.authors) {
+ authors = data.authors
+ }
+ if (data.hasOwnProperty('authorship') && authorship !== data.authorship) {
+ authorship = data.authorship
+ updateAuthorship()
+ }
}
var updateAuthorship = _.debounce(function () {
- editor.operation(updateAuthorshipInner);
-}, 50);
-function initMark() {
- return {
- gutter: {
- userid: null,
- timestamp: null
- },
- textmarkers: []
- };
-}
-function initMarkAndCheckGutter(mark, author, timestamp) {
- if (!mark) mark = initMark();
- if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) {
- mark.gutter.userid = author.userid;
- mark.gutter.timestamp = timestamp;
- }
- return mark;
-}
-var gutterStylePrefix = "border-left: 3px solid ";
-var gutterStylePostfix = "; height: " + defaultTextHeight + "px; margin-left: 3px;";
-var textMarkderStylePrefix = "background-image: linear-gradient(to top, ";
-var textMarkderStylePostfix = " 1px, transparent 1px);";
+ editor.operation(updateAuthorshipInner)
+}, 50)
+function initMark () {
+ return {
+ gutter: {
+ userid: null,
+ timestamp: null
+ },
+ textmarkers: []
+ }
+}
+function initMarkAndCheckGutter (mark, author, timestamp) {
+ if (!mark) mark = initMark()
+ if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) {
+ mark.gutter.userid = author.userid
+ mark.gutter.timestamp = timestamp
+ }
+ return mark
+}
+var gutterStylePrefix = 'border-left: 3px solid '
+var gutterStylePostfix = '; height: ' + defaultTextHeight + 'px; margin-left: 3px;'
+var textMarkderStylePrefix = 'background-image: linear-gradient(to top, '
+var textMarkderStylePostfix = ' 1px, transparent 1px);'
var addStyleRule = (function () {
- var added = {};
- var styleElement = document.createElement('style');
- document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);
- var styleSheet = styleElement.sheet;
-
- return function (css) {
- if (added[css]) {
- return;
- }
- added[css] = true;
- styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length);
- };
-}());
-function updateAuthorshipInner() {
+ var added = {}
+ var styleElement = document.createElement('style')
+ document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement)
+ var styleSheet = styleElement.sheet
+
+ return function (css) {
+ if (added[css]) {
+ return
+ }
+ added[css] = true
+ styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length)
+ }
+}())
+function updateAuthorshipInner () {
// ignore when ot not synced yet
- if (havePendingOperation()) return;
- authorMarks = {};
- for (var i = 0; i < authorship.length; i++) {
- var atom = authorship[i];
- var author = authors[atom[0]];
- if (author) {
- var prePos = editor.posFromIndex(atom[1]);
- var preLine = editor.getLine(prePos.line);
- var postPos = editor.posFromIndex(atom[2]);
- var postLine = editor.getLine(postPos.line);
- if (prePos.ch == 0 && postPos.ch == postLine.length) {
- for (var j = prePos.line; j <= postPos.line; j++) {
- if (editor.getLine(j)) {
- authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]);
- }
- }
- } else if (postPos.line - prePos.line >= 1) {
- var startLine = prePos.line;
- var endLine = postPos.line;
- if (prePos.ch == preLine.length) {
- startLine++;
- } else if (prePos.ch != 0) {
- var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]);
- var _postPos = {
- line: prePos.line,
- ch: preLine.length
- };
- if (JSON.stringify(prePos) != JSON.stringify(_postPos)) {
- mark.textmarkers.push({
- userid: author.userid,
- pos: [prePos, _postPos]
- });
- startLine++;
- }
- authorMarks[prePos.line] = mark;
- }
- if (postPos.ch == 0) {
- endLine--;
- } else if (postPos.ch != postLine.length) {
- var mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]);
- var _prePos = {
- line: postPos.line,
- ch: 0
- };
- if (JSON.stringify(_prePos) != JSON.stringify(postPos)) {
- mark.textmarkers.push({
- userid: author.userid,
- pos: [_prePos, postPos]
- });
- endLine--;
- }
- authorMarks[postPos.line] = mark;
- }
- for (var j = startLine; j <= endLine; j++) {
- if (editor.getLine(j)) {
- authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]);
- }
- }
- } else {
- var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]);
- if (JSON.stringify(prePos) != JSON.stringify(postPos)) {
- mark.textmarkers.push({
- userid: author.userid,
- pos: [prePos, postPos]
- });
- }
- authorMarks[prePos.line] = mark;
- }
- }
- }
- addTextMarkers = [];
- editor.eachLine(iterateLine);
- var allTextMarks = editor.getAllMarks();
- for (var i = 0; i < allTextMarks.length; i++) {
- var _textMarker = allTextMarks[i];
- var pos = _textMarker.find();
- var found = false;
- for (var j = 0; j < addTextMarkers.length; j++) {
- var textMarker = addTextMarkers[j];
- var author = authors[textMarker.userid];
- var className = 'authorship-inline-' + author.color.substr(1);
- var obj = {
- from: textMarker.pos[0],
- to: textMarker.pos[1]
- };
- if (JSON.stringify(pos) == JSON.stringify(obj) && _textMarker.className &&
+ if (havePendingOperation()) return
+ authorMarks = {}
+ for (let i = 0; i < authorship.length; i++) {
+ var atom = authorship[i]
+ let author = authors[atom[0]]
+ if (author) {
+ var prePos = editor.posFromIndex(atom[1])
+ var preLine = editor.getLine(prePos.line)
+ var postPos = editor.posFromIndex(atom[2])
+ var postLine = editor.getLine(postPos.line)
+ if (prePos.ch === 0 && postPos.ch === postLine.length) {
+ for (let j = prePos.line; j <= postPos.line; j++) {
+ if (editor.getLine(j)) {
+ authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3])
+ }
+ }
+ } else if (postPos.line - prePos.line >= 1) {
+ var startLine = prePos.line
+ var endLine = postPos.line
+ if (prePos.ch === preLine.length) {
+ startLine++
+ } else if (prePos.ch !== 0) {
+ let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3])
+ var _postPos = {
+ line: prePos.line,
+ ch: preLine.length
+ }
+ if (JSON.stringify(prePos) !== JSON.stringify(_postPos)) {
+ mark.textmarkers.push({ userid: author.userid, pos: [prePos, _postPos] })
+ startLine++
+ }
+ authorMarks[prePos.line] = mark
+ }
+ if (postPos.ch === 0) {
+ endLine--
+ } else if (postPos.ch !== postLine.length) {
+ let mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3])
+ var _prePos = {
+ line: postPos.line,
+ ch: 0
+ }
+ if (JSON.stringify(_prePos) !== JSON.stringify(postPos)) {
+ mark.textmarkers.push({ userid: author.userid, pos: [_prePos, postPos] })
+ endLine--
+ }
+ authorMarks[postPos.line] = mark
+ }
+ for (let j = startLine; j <= endLine; j++) {
+ if (editor.getLine(j)) {
+ authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3])
+ }
+ }
+ } else {
+ let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3])
+ if (JSON.stringify(prePos) !== JSON.stringify(postPos)) {
+ mark.textmarkers.push({
+ userid: author.userid,
+ pos: [prePos, postPos]
+ })
+ }
+ authorMarks[prePos.line] = mark
+ }
+ }
+ }
+ addTextMarkers = []
+ editor.eachLine(iterateLine)
+ var allTextMarks = editor.getAllMarks()
+ for (let i = 0; i < allTextMarks.length; i++) {
+ let _textMarker = allTextMarks[i]
+ var pos = _textMarker.find()
+ var found = false
+ for (let j = 0; j < addTextMarkers.length; j++) {
+ let textMarker = addTextMarkers[j]
+ let author = authors[textMarker.userid]
+ let className = 'authorship-inline-' + author.color.substr(1)
+ var obj = {
+ from: textMarker.pos[0],
+ to: textMarker.pos[1]
+ }
+ if (JSON.stringify(pos) === JSON.stringify(obj) && _textMarker.className &&
_textMarker.className.indexOf(className) > -1) {
- addTextMarkers.splice(j, 1);
- j--;
- found = true;
- break;
- }
- }
- if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) {
- _textMarker.clear();
- }
- }
- for (var i = 0; i < addTextMarkers.length; i++) {
- var textMarker = addTextMarkers[i];
- var author = authors[textMarker.userid];
- var rgbcolor = hex2rgb(author.color);
- var colorString = "rgba(" + rgbcolor.red + "," + rgbcolor.green + "," + rgbcolor.blue + ",0.7)";
- var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix;
- var className = 'authorship-inline-' + author.color.substr(1);
- var rule = "." + className + "{" + styleString + "}";
- addStyleRule(rule);
- var _textMarker = editor.markText(textMarker.pos[0], textMarker.pos[1], {
- className: 'authorship-inline ' + className,
- title: author.name
- });
- }
- authorshipMarks = authorMarks;
-}
-function iterateLine(line) {
- var lineNumber = line.lineNo();
- var currMark = authorMarks[lineNumber];
- var author = currMark ? authors[currMark.gutter.userid] : null;
- if (currMark && author) {
- var className = 'authorship-gutter-' + author.color.substr(1);
- var gutters = line.gutterMarkers;
- if (!gutters || !gutters['authorship-gutters'] ||
+ addTextMarkers.splice(j, 1)
+ j--
+ found = true
+ break
+ }
+ }
+ if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) {
+ _textMarker.clear()
+ }
+ }
+ for (let i = 0; i < addTextMarkers.length; i++) {
+ let textMarker = addTextMarkers[i]
+ let author = authors[textMarker.userid]
+ var rgbcolor = hex2rgb(author.color)
+ var colorString = 'rgba(' + rgbcolor.red + ',' + rgbcolor.green + ',' + rgbcolor.blue + ',0.7)'
+ var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix
+ let className = 'authorship-inline-' + author.color.substr(1)
+ var rule = '.' + className + '{' + styleString + '}'
+ addStyleRule(rule)
+ editor.markText(textMarker.pos[0], textMarker.pos[1], {
+ className: 'authorship-inline ' + className,
+ title: author.name
+ })
+ }
+}
+function iterateLine (line) {
+ var lineNumber = line.lineNo()
+ var currMark = authorMarks[lineNumber]
+ var author = currMark ? authors[currMark.gutter.userid] : null
+ if (currMark && author) {
+ let className = 'authorship-gutter-' + author.color.substr(1)
+ var gutters = line.gutterMarkers
+ if (!gutters || !gutters['authorship-gutters'] ||
!gutters['authorship-gutters'].className ||
!gutters['authorship-gutters'].className.indexOf(className) < 0) {
- var styleString = gutterStylePrefix + author.color + gutterStylePostfix;
- var rule = "." + className + "{" + styleString + "}";
- addStyleRule(rule);
- var gutter = $('<div>', {
- class: 'authorship-gutter ' + className,
- title: author.name
- });
- editor.setGutterMarker(line, "authorship-gutters", gutter[0]);
- }
- } else {
- editor.setGutterMarker(line, "authorship-gutters", null);
- }
- if (currMark && currMark.textmarkers.length > 0) {
- for (var i = 0; i < currMark.textmarkers.length; i++) {
- var textMarker = currMark.textmarkers[i];
- if (textMarker.userid != currMark.gutter.userid) {
- addTextMarkers.push(textMarker);
- }
- }
- }
+ var styleString = gutterStylePrefix + author.color + gutterStylePostfix
+ var rule = '.' + className + '{' + styleString + '}'
+ addStyleRule(rule)
+ var gutter = $('<div>', {
+ class: 'authorship-gutter ' + className,
+ title: author.name
+ })
+ editor.setGutterMarker(line, 'authorship-gutters', gutter[0])
+ }
+ } else {
+ editor.setGutterMarker(line, 'authorship-gutters', null)
+ }
+ if (currMark && currMark.textmarkers.length > 0) {
+ for (var i = 0; i < currMark.textmarkers.length; i++) {
+ let textMarker = currMark.textmarkers[i]
+ if (textMarker.userid !== currMark.gutter.userid) {
+ addTextMarkers.push(textMarker)
+ }
+ }
+ }
}
editor.on('update', function () {
- $('.authorship-gutter:not([data-original-title])').tooltip({
- container: '.CodeMirror-lines',
- placement: 'right',
- delay: { "show": 500, "hide": 100 }
- });
- $('.authorship-inline:not([data-original-title])').tooltip({
- container: '.CodeMirror-lines',
- placement: 'bottom',
- delay: { "show": 500, "hide": 100 }
- });
+ $('.authorship-gutter:not([data-original-title])').tooltip({
+ container: '.CodeMirror-lines',
+ placement: 'right',
+ delay: { 'show': 500, 'hide': 100 }
+ })
+ $('.authorship-inline:not([data-original-title])').tooltip({
+ container: '.CodeMirror-lines',
+ placement: 'bottom',
+ delay: { 'show': 500, 'hide': 100 }
+ })
// clear tooltip which described element has been removed
- $('[id^="tooltip"]').each(function (index, element) {
- var $ele = $(element);
- if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove();
- });
-});
+ $('[id^="tooltip"]').each(function (index, element) {
+ var $ele = $(element)
+ if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove()
+ })
+})
socket.on('check', function (data) {
- //console.log(data);
- updateInfo(data);
-});
+ // console.log(data);
+ updateInfo(data)
+})
socket.on('permission', function (data) {
- updatePermission(data.permission);
-});
-var docmaxlength = null;
-var permission = null;
+ updatePermission(data.permission)
+})
+var docmaxlength = null
+var permission = null
socket.on('refresh', function (data) {
- //console.log(data);
- docmaxlength = data.docmaxlength;
- editor.setOption("maxLength", docmaxlength);
- updateInfo(data);
- updatePermission(data.permission);
- if (!loaded) {
+ // console.log(data);
+ docmaxlength = data.docmaxlength
+ editor.setOption('maxLength', docmaxlength)
+ updateInfo(data)
+ updatePermission(data.permission)
+ if (!window.loaded) {
// auto change mode if no content detected
- var nocontent = editor.getValue().length <= 0;
- if (nocontent) {
- if (visibleXS)
- currentMode = modeType.edit;
- else
- currentMode = modeType.both;
- }
+ var nocontent = editor.getValue().length <= 0
+ if (nocontent) {
+ if (window.visibleXS) { window.currentMode = modeType.edit } else { window.currentMode = modeType.both }
+ }
// parse mode from url
- if (window.location.search.length > 0) {
- var urlMode = modeType[window.location.search.substr(1)];
- if (urlMode) currentMode = urlMode;
- }
- changeMode(currentMode);
- if (nocontent && !visibleXS) {
- editor.focus();
- editor.refresh();
- }
- updateViewInner(); // bring up view rendering earlier
- updateHistory(); //update history whether have content or not
- loaded = true;
- emitUserStatus(); //send first user status
- updateOnlineStatus(); //update first online status
- setTimeout(function () {
- //work around editor not refresh or doc not fully loaded
- windowResizeInner();
- //work around might not scroll to hash
- scrollToHash();
- }, 1);
- }
- if (editor.getOption('readOnly'))
- editor.setOption('readOnly', false);
-});
-
-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 setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force;
+ if (window.location.search.length > 0) {
+ var urlMode = modeType[window.location.search.substr(1)]
+ if (urlMode) window.currentMode = urlMode
+ }
+ changeMode(window.currentMode)
+ if (nocontent && !window.visibleXS) {
+ editor.focus()
+ editor.refresh()
+ }
+ updateViewInner() // bring up view rendering earlier
+ updateHistory() // update history whether have content or not
+ window.loaded = true
+ emitUserStatus() // send first user status
+ updateOnlineStatus() // update first online status
+ setTimeout(function () {
+ // work around editor not refresh or doc not fully loaded
+ windowResizeInner()
+ // work around might not scroll to hash
+ scrollToHash()
+ }, 1)
+ }
+ if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) }
+})
- saveInfo();
- if (setDoc && bodyMismatch) {
- if (cmClient) cmClient.editorAdapter.ignoreNextChange = true;
- if (body) editor.setValue(body);
- else editor.setValue("");
- }
+var EditorClient = ot.EditorClient
+var SocketIOAdapter = ot.SocketIOAdapter
+var CodeMirrorAdapter = ot.CodeMirrorAdapter
+var cmClient = null
+var synchronized_ = null
- if (!loaded) {
- editor.clearHistory();
- ui.spinner.hide();
- ui.content.fadeIn();
- } else {
- //if current doc is equal to the doc before disconnect
- if (setDoc && bodyMismatch) editor.clearHistory();
- else if (lastInfo.history) editor.setHistory(lastInfo.history);
- lastInfo.history = null;
- }
+function havePendingOperation () {
+ return !!((cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding')))
+}
- if (!cmClient) {
- cmClient = window.cmClient = new EditorClient(
+socket.on('doc', function (obj) {
+ var body = obj.str
+ var bodyMismatch = editor.getValue() !== body
+ var setDoc = !cmClient || (cmClient && (cmClient.revision === -1 || (cmClient.revision !== obj.revision && !havePendingOperation()))) || obj.force
+
+ saveInfo()
+ if (setDoc && bodyMismatch) {
+ if (cmClient) cmClient.editorAdapter.ignoreNextChange = true
+ if (body) editor.setValue(body)
+ else editor.setValue('')
+ }
+
+ if (!window.loaded) {
+ editor.clearHistory()
+ ui.spinner.hide()
+ ui.content.fadeIn()
+ } else {
+ // if current doc is equal to the doc before disconnect
+ if (setDoc && bodyMismatch) editor.clearHistory()
+ else if (window.lastInfo.history) editor.setHistory(window.lastInfo.history)
+ window.lastInfo.history = null
+ }
+
+ if (!cmClient) {
+ cmClient = window.cmClient = new EditorClient(
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(synchronized_);
- cmClient.initializeClientList();
- cmClient.initializeClients(obj.clients);
- } else if (havePendingOperation()) {
- cmClient.serverReconnect();
- }
-
- if (setDoc && bodyMismatch) {
- isDirty = true;
- updateView();
- }
-
- restoreInfo();
-});
+ )
+ synchronized_ = cmClient.state
+ } else if (setDoc) {
+ if (bodyMismatch) {
+ cmClient.undoManager.undoStack.length = 0
+ cmClient.undoManager.redoStack.length = 0
+ }
+ cmClient.revision = obj.revision
+ cmClient.setState(synchronized_)
+ cmClient.initializeClientList()
+ cmClient.initializeClients(obj.clients)
+ } else if (havePendingOperation()) {
+ cmClient.serverReconnect()
+ }
+
+ if (setDoc && bodyMismatch) {
+ window.isDirty = true
+ updateView()
+ }
+
+ restoreInfo()
+})
socket.on('ack', function () {
- isDirty = true;
- updateView();
-});
+ window.isDirty = true
+ updateView()
+})
socket.on('operation', function () {
- isDirty = true;
- updateView();
-});
+ window.isDirty = true
+ updateView()
+})
socket.on('online users', function (data) {
- if (debug)
- console.debug(data);
- onlineUsers = data.users;
- updateOnlineStatus();
- $('.CodeMirror-other-cursors').children().each(function (key, value) {
- var found = false;
- for (var i = 0; i < data.users.length; i++) {
- var user = data.users[i];
- if ($(this).attr('id') == user.id)
- found = true;
- }
- if (!found)
- $(this).stop(true).fadeOut("normal", function () {
- $(this).remove();
- });
- });
+ if (debug) { console.debug(data) }
+ window.onlineUsers = data.users
+ updateOnlineStatus()
+ $('.CodeMirror-other-cursors').children().each(function (key, value) {
+ var found = false
for (var i = 0; i < data.users.length; i++) {
- var user = data.users[i];
- if (user.id != socket.id)
- buildCursor(user);
- else
- personalInfo = user;
- }
-});
+ var user = data.users[i]
+ if ($(this).attr('id') === user.id) { found = true }
+ }
+ if (!found) {
+ $(this).stop(true).fadeOut('normal', function () {
+ $(this).remove()
+ })
+ }
+ })
+ for (var i = 0; i < data.users.length; i++) {
+ var user = data.users[i]
+ if (user.id !== socket.id) { buildCursor(user) } else { window.personalInfo = user }
+ }
+})
socket.on('user status', function (data) {
- if (debug)
- console.debug(data);
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == data.id) {
- onlineUsers[i] = data;
- }
- }
- updateOnlineStatus();
- if (data.id != socket.id)
- buildCursor(data);
-});
+ if (debug) { console.debug(data) }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === data.id) {
+ window.onlineUsers[i] = data
+ }
+ }
+ updateOnlineStatus()
+ if (data.id !== socket.id) { buildCursor(data) }
+})
socket.on('cursor focus', function (data) {
- if (debug)
- console.debug(data);
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == data.id) {
- onlineUsers[i].cursor = data.cursor;
- }
- }
- if (data.id != socket.id)
- buildCursor(data);
- //force show
- var cursor = $('div[data-clientid="' + data.id + '"]');
- if (cursor.length > 0) {
- cursor.stop(true).fadeIn();
- }
-});
+ if (debug) { console.debug(data) }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === data.id) {
+ window.onlineUsers[i].cursor = data.cursor
+ }
+ }
+ if (data.id !== socket.id) { buildCursor(data) }
+ // force show
+ var cursor = $('div[data-clientid="' + data.id + '"]')
+ if (cursor.length > 0) {
+ cursor.stop(true).fadeIn()
+ }
+})
socket.on('cursor activity', function (data) {
- if (debug)
- console.debug(data);
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == data.id) {
- onlineUsers[i].cursor = data.cursor;
- }
+ if (debug) { console.debug(data) }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === data.id) {
+ window.onlineUsers[i].cursor = data.cursor
}
- if (data.id != socket.id)
- buildCursor(data);
-});
+ }
+ if (data.id !== socket.id) { buildCursor(data) }
+})
socket.on('cursor blur', function (data) {
- if (debug)
- console.debug(data);
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == data.id) {
- onlineUsers[i].cursor = null;
- }
- }
- if (data.id != socket.id)
- buildCursor(data);
- //force hide
- var cursor = $('div[data-clientid="' + data.id + '"]');
- if (cursor.length > 0) {
- cursor.stop(true).fadeOut();
- }
-});
+ if (debug) { console.debug(data) }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === data.id) {
+ window.onlineUsers[i].cursor = null
+ }
+ }
+ if (data.id !== socket.id) { buildCursor(data) }
+ // force hide
+ var cursor = $('div[data-clientid="' + data.id + '"]')
+ if (cursor.length > 0) {
+ cursor.stop(true).fadeOut()
+ }
+})
var options = {
- valueNames: ['id', 'name'],
- item: '<li class="ui-user-item">\
- <span class="id" style="display:none;"></span>\
- <a href="#">\
- <span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>\
- </a>\
- </li>'
-};
-var onlineUserList = new List('online-user-list', options);
-var shortOnlineUserList = new List('short-online-user-list', options);
-
-function updateOnlineStatus() {
- if (!loaded || !socket.connected) return;
- var _onlineUsers = deduplicateOnlineUsers(onlineUsers);
- showStatus(statusType.online, _onlineUsers.length);
- var items = onlineUserList.items;
- //update or remove current list items
- for (var i = 0; i < items.length; i++) {
- var found = false;
- var foundindex = null;
- for (var j = 0; j < _onlineUsers.length; j++) {
- if (items[i].values().id == _onlineUsers[j].id) {
- foundindex = j;
- found = true;
- break;
- }
- }
- var id = items[i].values().id;
- if (found) {
- onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]);
- shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]);
- } else {
- onlineUserList.remove('id', id);
- shortOnlineUserList.remove('id', id);
- }
- }
- //add not in list items
- for (var i = 0; i < _onlineUsers.length; i++) {
- var found = false;
- for (var j = 0; j < items.length; j++) {
- if (items[j].values().id == _onlineUsers[i].id) {
- found = true;
- break;
- }
- }
- if (!found) {
- onlineUserList.add(_onlineUsers[i]);
- shortOnlineUserList.add(_onlineUsers[i]);
- }
+ valueNames: ['id', 'name'],
+ item: '<li class="ui-user-item">' +
+ '<span class="id" style="display:none;"></span>' +
+ '<a href="#">' +
+ '<span class="pull-left"><i class="ui-user-icon"></i></span><span class="ui-user-name name"></span><span class="pull-right"><i class="fa fa-circle ui-user-status"></i></span>' +
+ '</a>' +
+ '</li>'
+}
+var onlineUserList = new List('online-user-list', options)
+var shortOnlineUserList = new List('short-online-user-list', options)
+
+function updateOnlineStatus () {
+ if (!window.loaded || !socket.connected) return
+ var _onlineUsers = deduplicateOnlineUsers(window.onlineUsers)
+ showStatus(statusType.online, _onlineUsers.length)
+ var items = onlineUserList.items
+ // update or remove current list items
+ for (let i = 0; i < items.length; i++) {
+ let found = false
+ let foundindex = null
+ for (let j = 0; j < _onlineUsers.length; j++) {
+ if (items[i].values().id === _onlineUsers[j].id) {
+ foundindex = j
+ found = true
+ break
+ }
+ }
+ let id = items[i].values().id
+ if (found) {
+ onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex])
+ shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex])
+ } else {
+ onlineUserList.remove('id', id)
+ shortOnlineUserList.remove('id', id)
+ }
+ }
+ // add not in list items
+ for (let i = 0; i < _onlineUsers.length; i++) {
+ let found = false
+ for (let j = 0; j < items.length; j++) {
+ if (items[j].values().id === _onlineUsers[i].id) {
+ found = true
+ break
+ }
+ }
+ if (!found) {
+ onlineUserList.add(_onlineUsers[i])
+ shortOnlineUserList.add(_onlineUsers[i])
+ }
+ }
+ // sorting
+ sortOnlineUserList(onlineUserList)
+ sortOnlineUserList(shortOnlineUserList)
+ // render list items
+ renderUserStatusList(onlineUserList)
+ renderUserStatusList(shortOnlineUserList)
+}
+
+function sortOnlineUserList (list) {
+ // sort order by isSelf, login state, idle state, alphabet name, color brightness
+ list.sort('', {
+ sortFunction: function (a, b) {
+ var usera = a.values()
+ var userb = b.values()
+ var useraIsSelf = (usera.id === window.personalInfo.id || (usera.login && usera.userid === window.personalInfo.userid))
+ var userbIsSelf = (userb.id === window.personalInfo.id || (userb.login && userb.userid === window.personalInfo.userid))
+ if (useraIsSelf && !userbIsSelf) {
+ return -1
+ } else if (!useraIsSelf && userbIsSelf) {
+ return 1
+ } else {
+ if (usera.login && !userb.login) { return -1 } else if (!usera.login && userb.login) { return 1 } else {
+ if (!usera.idle && userb.idle) { return -1 } else if (usera.idle && !userb.idle) { return 1 } else {
+ if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) {
+ return -1
+ } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) { return 1 } else { if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase()) { return -1 } else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase()) { return 1 } else { return 0 } }
+ }
+ }
+ }
+ }
+ })
+}
+
+function renderUserStatusList (list) {
+ var items = list.items
+ for (var j = 0; j < items.length; j++) {
+ var item = items[j]
+ var userstatus = $(item.elm).find('.ui-user-status')
+ var usericon = $(item.elm).find('.ui-user-icon')
+ if (item.values().login && item.values().photo) {
+ usericon.css('background-image', 'url(' + item.values().photo + ')')
+ // add 1px more to right, make it feel aligned
+ usericon.css('margin-right', '6px')
+ $(item.elm).css('border-left', '4px solid ' + item.values().color)
+ usericon.css('margin-left', '-4px')
+ } else {
+ usericon.css('background-color', item.values().color)
}
- //sorting
- sortOnlineUserList(onlineUserList);
- sortOnlineUserList(shortOnlineUserList);
- //render list items
- renderUserStatusList(onlineUserList);
- renderUserStatusList(shortOnlineUserList);
-}
-
-function sortOnlineUserList(list) {
- //sort order by isSelf, login state, idle state, alphabet name, color brightness
- list.sort('', {
- sortFunction: function (a, b) {
- var usera = a.values();
- var userb = b.values();
- var useraIsSelf = (usera.id == personalInfo.id || (usera.login && usera.userid == personalInfo.userid));
- var userbIsSelf = (userb.id == personalInfo.id || (userb.login && userb.userid == personalInfo.userid));
- if (useraIsSelf && !userbIsSelf) {
- return -1;
- } else if (!useraIsSelf && userbIsSelf) {
- return 1;
- } else {
- if (usera.login && !userb.login)
- return -1;
- else if (!usera.login && userb.login)
- return 1;
- else {
- if (!usera.idle && userb.idle)
- return -1;
- else if (usera.idle && !userb.idle)
- return 1;
- else {
- if (usera.name && userb.name && usera.name.toLowerCase() < userb.name.toLowerCase()) {
- return -1;
- } else if (usera.name && userb.name && usera.name.toLowerCase() > userb.name.toLowerCase()) {
- return 1;
- } else {
- if (usera.color && userb.color && usera.color.toLowerCase() < userb.color.toLowerCase())
- return -1;
- else if (usera.color && userb.color && usera.color.toLowerCase() > userb.color.toLowerCase())
- return 1;
- else
- return 0;
- }
- }
- }
- }
- }
- });
-}
-
-function renderUserStatusList(list) {
- var items = list.items;
- for (var j = 0; j < items.length; j++) {
- var item = items[j];
- var userstatus = $(item.elm).find('.ui-user-status');
- var usericon = $(item.elm).find('.ui-user-icon');
- if (item.values().login && item.values().photo) {
- usericon.css('background-image', 'url(' + item.values().photo + ')');
- //add 1px more to right, make it feel aligned
- usericon.css('margin-right', '6px');
- $(item.elm).css('border-left', '4px solid ' + item.values().color);
- usericon.css('margin-left', '-4px');
- } else {
- usericon.css('background-color', item.values().color);
- }
- userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle');
- if (item.values().idle)
- userstatus.addClass('ui-user-status-idle');
- else
- userstatus.addClass('ui-user-status-online');
- }
-}
-
-function deduplicateOnlineUsers(list) {
- var _onlineUsers = [];
- for (var i = 0; i < list.length; i++) {
- var user = $.extend({}, list[i]);
- if (!user.userid)
- _onlineUsers.push(user);
- else {
- var found = false;
- for (var j = 0; j < _onlineUsers.length; j++) {
- if (_onlineUsers[j].userid == user.userid) {
- //keep self color when login
- if (user.id == personalInfo.id) {
- _onlineUsers[j].color = user.color;
- }
- //keep idle state if any of self client not idle
- if (!user.idle) {
- _onlineUsers[j].idle = user.idle;
- _onlineUsers[j].color = user.color;
- }
- found = true;
- break;
- }
- }
- if (!found)
- _onlineUsers.push(user);
+ userstatus.removeClass('ui-user-status-offline ui-user-status-online ui-user-status-idle')
+ if (item.values().idle) { userstatus.addClass('ui-user-status-idle') } else { userstatus.addClass('ui-user-status-online') }
+ }
+}
+
+function deduplicateOnlineUsers (list) {
+ var _onlineUsers = []
+ for (var i = 0; i < list.length; i++) {
+ var user = $.extend({}, list[i])
+ if (!user.userid) { _onlineUsers.push(user) } else {
+ var found = false
+ for (var j = 0; j < _onlineUsers.length; j++) {
+ if (_onlineUsers[j].userid === user.userid) {
+ // keep self color when login
+ if (user.id === window.personalInfo.id) {
+ _onlineUsers[j].color = user.color
+ }
+ // keep idle state if any of self client not idle
+ if (!user.idle) {
+ _onlineUsers[j].idle = user.idle
+ _onlineUsers[j].color = user.color
+ }
+ found = true
+ break
}
+ }
+ if (!found) { _onlineUsers.push(user) }
}
- return _onlineUsers;
+ }
+ return _onlineUsers
}
-var userStatusCache = null;
+var userStatusCache = null
-function emitUserStatus(force) {
- if (!loaded) return;
- var type = null;
- if (visibleXS)
- type = 'xs';
- else if (visibleSM)
- type = 'sm';
- else if (visibleMD)
- type = 'md';
- else if (visibleLG)
- type = 'lg';
+function emitUserStatus (force) {
+ if (!window.loaded) return
+ var type = null
+ if (window.visibleXS) { type = 'xs' } else if (window.visibleSM) { type = 'sm' } else if (window.visibleMD) { type = 'md' } else if (window.visibleLG) { type = 'lg' }
- personalInfo['idle'] = idle.isAway;
- personalInfo['type'] = type;
+ window.personalInfo['idle'] = idle.isAway
+ window.personalInfo['type'] = type
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == personalInfo.id) {
- onlineUsers[i] = personalInfo;
- }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === window.personalInfo.id) {
+ window.onlineUsers[i] = window.personalInfo
}
+ }
- var userStatus = {
- idle: idle.isAway,
- type: type
- };
+ var userStatus = {
+ idle: idle.isAway,
+ type: type
+ }
- if (force || JSON.stringify(userStatus) != JSON.stringify(userStatusCache)) {
- socket.emit('user status', userStatus);
- userStatusCache = userStatus;
- }
+ if (force || JSON.stringify(userStatus) !== JSON.stringify(userStatusCache)) {
+ socket.emit('user status', userStatus)
+ userStatusCache = userStatus
+ }
}
-function checkCursorTag(coord, ele) {
- if (!ele) return; // return if element not exists
+function checkCursorTag (coord, ele) {
+ if (!ele) return // return if element not exists
// set margin
- var tagRightMargin = 0;
- var tagBottomMargin = 2;
+ var tagRightMargin = 0
+ var tagBottomMargin = 2
// use sizer to get the real doc size (won't count status bar and gutters)
- var docWidth = ui.area.codemirrorSizer.width();
- var docHeight = ui.area.codemirrorSizer.height();
+ var docWidth = ui.area.codemirrorSizer.width()
// get editor size (status bar not count in)
- var editorWidth = ui.area.codemirror.width();
- var editorHeight = ui.area.codemirror.height();
+ var editorHeight = ui.area.codemirror.height()
// get element size
- var width = ele.outerWidth();
- var height = ele.outerHeight();
- var padding = (ele.outerWidth() - ele.width()) / 2;
+ var width = ele.outerWidth()
+ var height = ele.outerHeight()
+ var padding = (ele.outerWidth() - ele.width()) / 2
// get coord position
- var left = coord.left;
- var top = coord.top;
+ var left = coord.left
+ var top = coord.top
// get doc top offset (to workaround with viewport)
- var docTopOffset = ui.area.codemirrorSizerInner.position().top;
+ var docTopOffset = ui.area.codemirrorSizerInner.position().top
// set offset
- var offsetLeft = -3;
- var offsetTop = defaultTextHeight;
+ var offsetLeft = -3
+ var offsetTop = defaultTextHeight
// only do when have width and height
- if (width > 0 && height > 0) {
+ if (width > 0 && height > 0) {
// flip x when element right bound larger than doc width
- if (left + width + offsetLeft + tagRightMargin > docWidth) {
- offsetLeft = -(width + tagRightMargin) + padding + offsetLeft;
- }
+ if (left + width + offsetLeft + tagRightMargin > docWidth) {
+ offsetLeft = -(width + tagRightMargin) + padding + offsetLeft
+ }
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
- if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) {
- offsetTop = -(height);
- }
+ if (top + docTopOffset + height + offsetTop + tagBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + tagBottomMargin) {
+ offsetTop = -(height)
}
+ }
// set position
- ele[0].style.left = offsetLeft + 'px';
- ele[0].style.top = offsetTop + 'px';
-}
-
-function buildCursor(user) {
- if (currentMode == modeType.view) return;
- if (!user.cursor) return;
- var coord = editor.charCoords(user.cursor, 'windows');
- coord.left = coord.left < 4 ? 4 : coord.left;
- coord.top = coord.top < 0 ? 0 : coord.top;
- var iconClass = 'fa-user';
- switch (user.type) {
- case 'xs':
- iconClass = 'fa-mobile';
- break;
- case 'sm':
- iconClass = 'fa-tablet';
- break;
- case 'md':
- iconClass = 'fa-desktop';
- break;
- case 'lg':
- iconClass = 'fa-desktop';
- break;
- }
- if ($('.CodeMirror-other-cursors').length <= 0) {
- $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors');
- }
- if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
- var cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>');
- cursor.attr('data-line', user.cursor.line);
- cursor.attr('data-ch', user.cursor.ch);
- cursor.attr('data-offset-left', 0);
- cursor.attr('data-offset-top', 0);
-
- var cursorbar = $('<div class="cursorbar">&nbsp;</div>');
- cursorbar[0].style.height = defaultTextHeight + 'px';
- cursorbar[0].style.borderLeft = '2px solid ' + user.color;
-
- var icon = '<i class="fa ' + iconClass + '"></i>';
-
- var cursortag = $('<div class="cursortag">' + icon + '&nbsp;<span class="name">' + user.name + '</span></div>');
- //cursortag[0].style.background = color;
- cursortag[0].style.color = user.color;
-
- cursor.attr('data-mode', 'hover');
- cursortag.delay(2000).fadeOut("fast");
- cursor.hover(
+ ele[0].style.left = offsetLeft + 'px'
+ ele[0].style.top = offsetTop + 'px'
+}
+
+function buildCursor (user) {
+ if (window.currentMode === modeType.view) return
+ if (!user.cursor) return
+ var coord = editor.charCoords(user.cursor, 'windows')
+ coord.left = coord.left < 4 ? 4 : coord.left
+ coord.top = coord.top < 0 ? 0 : coord.top
+ var iconClass = 'fa-user'
+ switch (user.type) {
+ case 'xs':
+ iconClass = 'fa-mobile'
+ break
+ case 'sm':
+ iconClass = 'fa-tablet'
+ break
+ case 'md':
+ iconClass = 'fa-desktop'
+ break
+ case 'lg':
+ iconClass = 'fa-desktop'
+ break
+ }
+ if ($('.CodeMirror-other-cursors').length <= 0) {
+ $("<div class='CodeMirror-other-cursors'>").insertAfter('.CodeMirror-cursors')
+ }
+ if ($('div[data-clientid="' + user.id + '"]').length <= 0) {
+ let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>')
+ cursor.attr('data-line', user.cursor.line)
+ cursor.attr('data-ch', user.cursor.ch)
+ cursor.attr('data-offset-left', 0)
+ cursor.attr('data-offset-top', 0)
+
+ let cursorbar = $('<div class="cursorbar">&nbsp;</div>')
+ cursorbar[0].style.height = defaultTextHeight + 'px'
+ cursorbar[0].style.borderLeft = '2px solid ' + user.color
+
+ var icon = '<i class="fa ' + iconClass + '"></i>'
+
+ let cursortag = $('<div class="cursortag">' + icon + '&nbsp;<span class="name">' + user.name + '</span></div>')
+ // cursortag[0].style.background = color;
+ cursortag[0].style.color = user.color
+
+ cursor.attr('data-mode', 'hover')
+ cursortag.delay(2000).fadeOut('fast')
+ cursor.hover(
function () {
- if (cursor.attr('data-mode') == 'hover')
- cursortag.stop(true).fadeIn("fast");
+ if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeIn('fast') }
},
function () {
- if (cursor.attr('data-mode') == 'hover')
- cursortag.stop(true).fadeOut("fast");
- });
-
- function switchMode(ele) {
- if (ele.attr('data-mode') == 'state')
- ele.attr('data-mode', 'hover');
- else if (ele.attr('data-mode') == 'hover')
- ele.attr('data-mode', 'state');
- }
-
- function switchTag(ele) {
- if (ele.css('display') === 'none')
- ele.stop(true).fadeIn("fast");
- else
- ele.stop(true).fadeOut("fast");
- }
- var hideCursorTagDelay = 2000;
- var hideCursorTagTimer = null;
-
- function hideCursorTag() {
- if (cursor.attr('data-mode') == 'hover')
- cursortag.fadeOut("fast");
- }
- cursor.on('touchstart', function (e) {
- var display = cursortag.css('display');
- cursortag.stop(true).fadeIn("fast");
- clearTimeout(hideCursorTagTimer);
- hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay);
- if (display === 'none') {
- e.preventDefault();
- e.stopPropagation();
- }
- });
- cursortag.on('mousedown touchstart', function (e) {
- if (cursor.attr('data-mode') == 'state')
- switchTag(cursortag);
- switchMode(cursor);
- e.preventDefault();
- e.stopPropagation();
- });
-
- cursor.append(cursorbar);
- cursor.append(cursortag);
-
- cursor[0].style.left = coord.left + 'px';
- cursor[0].style.top = coord.top + 'px';
- $('.CodeMirror-other-cursors').append(cursor);
-
- if (!user.idle)
- cursor.stop(true).fadeIn();
-
- checkCursorTag(coord, cursortag);
- } else {
- var cursor = $('div[data-clientid="' + user.id + '"]');
- var lineDiff = Math.abs(cursor.attr('data-line') - user.cursor.line);
- cursor.attr('data-line', user.cursor.line);
- cursor.attr('data-ch', user.cursor.ch);
-
- var cursorbar = cursor.find('.cursorbar');
- cursorbar[0].style.height = defaultTextHeight + 'px';
- cursorbar[0].style.borderLeft = '2px solid ' + user.color;
-
- var cursortag = cursor.find('.cursortag');
- cursortag.find('i').removeClass().addClass('fa').addClass(iconClass);
- cursortag.find(".name").text(user.name);
-
- if (cursor.css('display') === 'none') {
- cursor[0].style.left = coord.left + 'px';
- cursor[0].style.top = coord.top + 'px';
- } else {
- cursor.animate({
- "left": coord.left,
- "top": coord.top
- }, {
- duration: cursorAnimatePeriod,
- queue: false
- });
- }
+ if (cursor.attr('data-mode') === 'hover') { cursortag.stop(true).fadeOut('fast') }
+ })
- if (user.idle && cursor.css('display') !== 'none')
- cursor.stop(true).fadeOut();
- else if (!user.idle && cursor.css('display') === 'none')
- cursor.stop(true).fadeIn();
+ var hideCursorTagDelay = 2000
+ var hideCursorTagTimer = null
- checkCursorTag(coord, cursortag);
+ var switchMode = function (ele) {
+ if (ele.attr('data-mode') === 'state') { ele.attr('data-mode', 'hover') } else if (ele.attr('data-mode') === 'hover') { ele.attr('data-mode', 'state') }
}
-}
-//editor actions
-function removeNullByte(cm, change) {
- var str = change.text.join("\n");
- if (/\u0000/g.test(str) && change.update) {
- change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n"));
+ var switchTag = function (ele) {
+ if (ele.css('display') === 'none') { ele.stop(true).fadeIn('fast') } else { ele.stop(true).fadeOut('fast') }
}
-}
-function enforceMaxLength(cm, change) {
- var maxLength = cm.getOption("maxLength");
- if (maxLength && change.update) {
- var str = change.text.join("\n");
- var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from));
- if (delta <= 0) {
- return false;
- }
- delta = cm.getValue().length + delta - maxLength;
- if (delta > 0) {
- str = str.substr(0, str.length - delta);
- change.update(change.from, change.to, str.split("\n"));
- return true;
- }
+
+ var hideCursorTag = function () {
+ if (cursor.attr('data-mode') === 'hover') { cursortag.fadeOut('fast') }
}
- return false;
-}
-var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
-editor.on('beforeChange', function (cm, change) {
- if (debug)
- console.debug(change);
- removeNullByte(cm, change);
- if (enforceMaxLength(cm, change)) {
- $('.limit-modal').modal('show');
- }
- var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1);
- if (!isIgnoreEmitEvent) {
- if (!havePermission()) {
- change.canceled = true;
- switch (permission) {
- case "editable":
- $('.signin-modal').modal('show');
- break;
- case "locked":
- case "private":
- $('.locked-modal').modal('show');
- break;
- }
- }
+ cursor.on('touchstart', function (e) {
+ var display = cursortag.css('display')
+ cursortag.stop(true).fadeIn('fast')
+ clearTimeout(hideCursorTagTimer)
+ hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay)
+ if (display === 'none') {
+ e.preventDefault()
+ e.stopPropagation()
+ }
+ })
+ cursortag.on('mousedown touchstart', function (e) {
+ if (cursor.attr('data-mode') === 'state') { switchTag(cursortag) }
+ switchMode(cursor)
+ e.preventDefault()
+ e.stopPropagation()
+ })
+
+ cursor.append(cursorbar)
+ cursor.append(cursortag)
+
+ cursor[0].style.left = coord.left + 'px'
+ cursor[0].style.top = coord.top + 'px'
+ $('.CodeMirror-other-cursors').append(cursor)
+
+ if (!user.idle) { cursor.stop(true).fadeIn() }
+
+ checkCursorTag(coord, cursortag)
+ } else {
+ let cursor = $('div[data-clientid="' + user.id + '"]')
+ cursor.attr('data-line', user.cursor.line)
+ cursor.attr('data-ch', user.cursor.ch)
+
+ let cursorbar = cursor.find('.cursorbar')
+ cursorbar[0].style.height = defaultTextHeight + 'px'
+ cursorbar[0].style.borderLeft = '2px solid ' + user.color
+
+ let cursortag = cursor.find('.cursortag')
+ cursortag.find('i').removeClass().addClass('fa').addClass(iconClass)
+ cursortag.find('.name').text(user.name)
+
+ if (cursor.css('display') === 'none') {
+ cursor[0].style.left = coord.left + 'px'
+ cursor[0].style.top = coord.top + 'px'
} else {
- if (change.origin == 'ignoreHistory') {
- setHaveUnreadChanges(true);
- updateTitleReminder();
- }
- }
- if (cmClient && !socket.connected)
- cmClient.editorAdapter.ignoreNextChange = true;
-});
+ cursor.animate({
+ 'left': coord.left,
+ 'top': coord.top
+ }, {
+ duration: cursorAnimatePeriod,
+ queue: false
+ })
+ }
+
+ if (user.idle && cursor.css('display') !== 'none') { cursor.stop(true).fadeOut() } else if (!user.idle && cursor.css('display') === 'none') { cursor.stop(true).fadeIn() }
+
+ checkCursorTag(coord, cursortag)
+ }
+}
+
+// editor actions
+function removeNullByte (cm, change) {
+ var str = change.text.join('\n')
+ if (/\u0000/g.test(str) && change.update) {
+ change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n'))
+ }
+}
+function enforceMaxLength (cm, change) {
+ var maxLength = cm.getOption('maxLength')
+ if (maxLength && change.update) {
+ var str = change.text.join('\n')
+ var delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from))
+ if (delta <= 0) {
+ return false
+ }
+ delta = cm.getValue().length + delta - maxLength
+ if (delta > 0) {
+ str = str.substr(0, str.length - delta)
+ change.update(change.from, change.to, str.split('\n'))
+ return true
+ }
+ }
+ return false
+}
+var ignoreEmitEvents = ['setValue', 'ignoreHistory']
+editor.on('beforeChange', function (cm, change) {
+ if (debug) { console.debug(change) }
+ removeNullByte(cm, change)
+ if (enforceMaxLength(cm, change)) {
+ $('.limit-modal').modal('show')
+ }
+ var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) !== -1)
+ if (!isIgnoreEmitEvent) {
+ if (!havePermission()) {
+ change.canceled = true
+ switch (permission) {
+ case 'editable':
+ $('.signin-modal').modal('show')
+ break
+ case 'locked':
+ case 'private':
+ $('.locked-modal').modal('show')
+ break
+ }
+ }
+ } else {
+ if (change.origin === 'ignoreHistory') {
+ setHaveUnreadChanges(true)
+ updateTitleReminder()
+ }
+ }
+ if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true }
+})
editor.on('cut', function () {
- //na
-});
+ // na
+})
editor.on('paste', function () {
- //na
-});
+ // na
+})
editor.on('changes', function (cm, changes) {
- updateHistory();
- var docLength = editor.getValue().length;
- //workaround for big documents
- var newViewportMargin = 20;
- if (docLength > 20000) {
- newViewportMargin = 1;
- } else if (docLength > 10000) {
- newViewportMargin = 10;
- } else if (docLength > 5000) {
- newViewportMargin = 15;
- }
- if (newViewportMargin != viewportMargin) {
- viewportMargin = newViewportMargin;
- windowResize();
- }
- checkEditorScrollbar();
- if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) {
- postUpdateEvent = function () {
- syncScrollToView();
- postUpdateEvent = null;
- };
- }
-});
+ updateHistory()
+ var docLength = editor.getValue().length
+ // workaround for big documents
+ var newViewportMargin = 20
+ if (docLength > 20000) {
+ newViewportMargin = 1
+ } else if (docLength > 10000) {
+ newViewportMargin = 10
+ } else if (docLength > 5000) {
+ newViewportMargin = 15
+ }
+ if (newViewportMargin !== viewportMargin) {
+ viewportMargin = newViewportMargin
+ windowResize()
+ }
+ checkEditorScrollbar()
+ if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) {
+ postUpdateEvent = function () {
+ syncScrollToView()
+ postUpdateEvent = null
+ }
+ }
+})
editor.on('focus', function (cm) {
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == personalInfo.id) {
- onlineUsers[i].cursor = editor.getCursor();
- }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === window.personalInfo.id) {
+ window.onlineUsers[i].cursor = editor.getCursor()
}
- personalInfo['cursor'] = editor.getCursor();
- socket.emit('cursor focus', editor.getCursor());
-});
+ }
+ window.personalInfo['cursor'] = editor.getCursor()
+ socket.emit('cursor focus', editor.getCursor())
+})
editor.on('cursorActivity', function (cm) {
- updateStatusBar();
- cursorActivity();
-});
+ updateStatusBar()
+ cursorActivity()
+})
editor.on('beforeSelectionChange', function (doc, selections) {
- if (selections)
- selection = selections.ranges[0];
- else
- selection = null;
- updateStatusBar();
-});
-
-var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce);
-
-function cursorActivityInner() {
- if (editorHasFocus() && !Visibility.hidden()) {
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == personalInfo.id) {
- onlineUsers[i].cursor = editor.getCursor();
- }
- }
- personalInfo['cursor'] = editor.getCursor();
- socket.emit('cursor activity', editor.getCursor());
+ if (selections) { selection = selections.ranges[0] } else { selection = null }
+ updateStatusBar()
+})
+
+var cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce)
+
+function cursorActivityInner () {
+ if (editorHasFocus() && !Visibility.hidden()) {
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === window.personalInfo.id) {
+ window.onlineUsers[i].cursor = editor.getCursor()
+ }
}
+ window.personalInfo['cursor'] = editor.getCursor()
+ socket.emit('cursor activity', editor.getCursor())
+ }
}
editor.on('blur', function (cm) {
- for (var i = 0; i < onlineUsers.length; i++) {
- if (onlineUsers[i].id == personalInfo.id) {
- onlineUsers[i].cursor = null;
- }
+ for (var i = 0; i < window.onlineUsers.length; i++) {
+ if (window.onlineUsers[i].id === window.personalInfo.id) {
+ window.onlineUsers[i].cursor = null
}
- personalInfo['cursor'] = null;
- socket.emit('cursor blur');
-});
-
-function saveInfo() {
- var scrollbarStyle = editor.getOption('scrollbarStyle');
- var left = $(window).scrollLeft();
- var top = $(window).scrollTop();
- switch (currentMode) {
- case modeType.edit:
- if (scrollbarStyle == 'native') {
- lastInfo.edit.scroll.left = left;
- lastInfo.edit.scroll.top = top;
- } else {
- lastInfo.edit.scroll = editor.getScrollInfo();
- }
- break;
- case modeType.view:
- lastInfo.view.scroll.left = left;
- lastInfo.view.scroll.top = top;
- break;
- case modeType.both:
- lastInfo.edit.scroll = editor.getScrollInfo();
- lastInfo.view.scroll.left = ui.area.view.scrollLeft();
- lastInfo.view.scroll.top = ui.area.view.scrollTop();
- break;
- }
- lastInfo.edit.cursor = editor.getCursor();
- lastInfo.edit.selections = editor.listSelections();
- lastInfo.needRestore = true;
-}
-
-function restoreInfo() {
- var scrollbarStyle = editor.getOption('scrollbarStyle');
- if (lastInfo.needRestore) {
- 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') {
- $(window).scrollLeft(lastInfo.edit.scroll.left);
- $(window).scrollTop(lastInfo.edit.scroll.top);
- } else {
- var left = lastInfo.edit.scroll.left;
- var top = lastInfo.edit.scroll.top;
- editor.scrollIntoView();
- editor.scrollTo(left, top);
- }
- break;
- case modeType.view:
- $(window).scrollLeft(lastInfo.view.scroll.left);
- $(window).scrollTop(lastInfo.view.scroll.top);
- break;
- case modeType.both:
- var left = lastInfo.edit.scroll.left;
- var top = lastInfo.edit.scroll.top;
- editor.scrollIntoView();
- editor.scrollTo(left, top);
- ui.area.view.scrollLeft(lastInfo.view.scroll.left);
- ui.area.view.scrollTop(lastInfo.view.scroll.top);
- break;
- }
+ }
+ window.personalInfo['cursor'] = null
+ socket.emit('cursor blur')
+})
- lastInfo.needRestore = false;
+function saveInfo () {
+ var scrollbarStyle = editor.getOption('scrollbarStyle')
+ var left = $(window).scrollLeft()
+ var top = $(window).scrollTop()
+ switch (window.currentMode) {
+ case modeType.edit:
+ if (scrollbarStyle === 'native') {
+ window.lastInfo.edit.scroll.left = left
+ window.lastInfo.edit.scroll.top = top
+ } else {
+ window.lastInfo.edit.scroll = editor.getScrollInfo()
+ }
+ break
+ case modeType.view:
+ window.lastInfo.view.scroll.left = left
+ window.lastInfo.view.scroll.top = top
+ break
+ case modeType.both:
+ window.lastInfo.edit.scroll = editor.getScrollInfo()
+ window.lastInfo.view.scroll.left = ui.area.view.scrollLeft()
+ window.lastInfo.view.scroll.top = ui.area.view.scrollTop()
+ break
+ }
+ window.lastInfo.edit.cursor = editor.getCursor()
+ window.lastInfo.edit.selections = editor.listSelections()
+ window.lastInfo.needRestore = true
+}
+
+function restoreInfo () {
+ var scrollbarStyle = editor.getOption('scrollbarStyle')
+ if (window.lastInfo.needRestore) {
+ var line = window.lastInfo.edit.cursor.line
+ var ch = window.lastInfo.edit.cursor.ch
+ editor.setCursor(line, ch)
+ editor.setSelections(window.lastInfo.edit.selections)
+ switch (window.currentMode) {
+ case modeType.edit:
+ if (scrollbarStyle === 'native') {
+ $(window).scrollLeft(window.lastInfo.edit.scroll.left)
+ $(window).scrollTop(window.lastInfo.edit.scroll.top)
+ } else {
+ let left = window.lastInfo.edit.scroll.left
+ let top = window.lastInfo.edit.scroll.top
+ editor.scrollIntoView()
+ editor.scrollTo(left, top)
+ }
+ break
+ case modeType.view:
+ $(window).scrollLeft(window.lastInfo.view.scroll.left)
+ $(window).scrollTop(window.lastInfo.view.scroll.top)
+ break
+ case modeType.both:
+ let left = window.lastInfo.edit.scroll.left
+ let top = window.lastInfo.edit.scroll.top
+ editor.scrollIntoView()
+ editor.scrollTo(left, top)
+ ui.area.view.scrollLeft(window.lastInfo.view.scroll.left)
+ ui.area.view.scrollTop(window.lastInfo.view.scroll.top)
+ break
}
+
+ window.lastInfo.needRestore = false
+ }
}
-//view actions
-function refreshView() {
- ui.area.markdown.html('');
- isDirty = true;
- updateViewInner();
+// view actions
+function refreshView () {
+ ui.area.markdown.html('')
+ window.isDirty = true
+ updateViewInner()
}
var updateView = _.debounce(function () {
- editor.operation(updateViewInner);
-}, updateViewDebounce);
-
-var lastResult = null;
-var postUpdateEvent = null;
-
-function updateViewInner() {
- if (currentMode == modeType.edit || !isDirty) return;
- 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 = {
- separator: '^(\r\n?|\n)---(\r\n?|\n)$',
- verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
- };
- var slides = RevealMarkdown.slidify(editor.getValue(), slideOptions);
- ui.area.markdown.html(slides);
- RevealMarkdown.initialize();
+ editor.operation(updateViewInner)
+}, updateViewDebounce)
+
+var lastResult = null
+var postUpdateEvent = null
+
+function updateViewInner () {
+ if (window.currentMode === modeType.edit || !window.isDirty) return
+ 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 = {
+ separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+ verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
+ }
+ var slides = window.RevealMarkdown.slidify(editor.getValue(), slideOptions)
+ ui.area.markdown.html(slides)
+ window.RevealMarkdown.initialize()
// prevent XSS
- ui.area.markdown.html(preventXSS(ui.area.markdown.html()));
- ui.area.markdown.addClass('slides');
- syncscroll = false;
- checkSyncToggle();
- } else {
- if (lastMeta.type && lastMeta.type === 'slide') {
- refreshView();
- ui.area.markdown.removeClass('slides');
- syncscroll = true;
- checkSyncToggle();
- }
+ ui.area.markdown.html(preventXSS(ui.area.markdown.html()))
+ ui.area.markdown.addClass('slides')
+ window.syncscroll = false
+ checkSyncToggle()
+ } else {
+ if (lastMeta.type && lastMeta.type === 'slide') {
+ refreshView()
+ ui.area.markdown.removeClass('slides')
+ window.syncscroll = true
+ checkSyncToggle()
+ }
// only render again when meta changed
- if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
- parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix'));
- rendered = md.render(value);
- }
+ if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
+ parseMeta(md, ui.area.codemirror, ui.area.markdown, $('#ui-toc'), $('#ui-toc-affix'))
+ rendered = md.render(value)
+ }
// prevent XSS
- rendered = preventXSS(rendered);
- var result = postProcess(rendered).children().toArray();
- partialUpdate(result, lastResult, ui.area.markdown.children().toArray());
- if (result && lastResult && result.length != lastResult.length)
- updateDataAttrs(result, ui.area.markdown.children().toArray());
- lastResult = $(result).clone();
- }
- finishView(ui.area.markdown);
- autoLinkify(ui.area.markdown);
- deduplicatedHeaderId(ui.area.markdown);
- renderTOC(ui.area.markdown);
- generateToc('ui-toc');
- generateToc('ui-toc-affix');
- generateScrollspy();
- updateScrollspy();
- smoothHashScroll();
- isDirty = false;
- clearMap();
- //buildMap();
- updateTitleReminder();
- if (postUpdateEvent && typeof postUpdateEvent === 'function')
- postUpdateEvent();
-}
-
-var updateHistoryDebounce = 600;
+ rendered = preventXSS(rendered)
+ var result = postProcess(rendered).children().toArray()
+ partialUpdate(result, lastResult, ui.area.markdown.children().toArray())
+ if (result && lastResult && result.length !== lastResult.length) { updateDataAttrs(result, ui.area.markdown.children().toArray()) }
+ lastResult = $(result).clone()
+ }
+ finishView(ui.area.markdown)
+ autoLinkify(ui.area.markdown)
+ deduplicatedHeaderId(ui.area.markdown)
+ renderTOC(ui.area.markdown)
+ generateToc('ui-toc')
+ generateToc('ui-toc-affix')
+ generateScrollspy()
+ updateScrollspy()
+ smoothHashScroll()
+ window.isDirty = false
+ clearMap()
+ // buildMap();
+ updateTitleReminder()
+ if (postUpdateEvent && typeof postUpdateEvent === 'function') { postUpdateEvent() }
+}
+
+var updateHistoryDebounce = 600
var updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce)
-function updateHistoryInner() {
- writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown));
-}
-
-function updateDataAttrs(src, des) {
- //sync data attr startline and endline
- for (var i = 0; i < src.length; i++) {
- copyAttribute(src[i], des[i], 'data-startline');
- copyAttribute(src[i], des[i], 'data-endline');
- }
-}
-
-function partialUpdate(src, tar, des) {
- if (!src || src.length == 0 || !tar || tar.length == 0 || !des || des.length == 0) {
- ui.area.markdown.html(src);
- return;
- }
- if (src.length == tar.length) { //same length
- for (var i = 0; i < src.length; i++) {
- copyAttribute(src[i], des[i], 'data-startline');
- copyAttribute(src[i], des[i], 'data-endline');
- var rawSrc = cloneAndRemoveDataAttr(src[i]);
- var rawTar = cloneAndRemoveDataAttr(tar[i]);
- if (rawSrc.outerHTML != rawTar.outerHTML) {
- //console.log(rawSrc);
- //console.log(rawTar);
- $(des[i]).replaceWith(src[i]);
- }
- }
- } else { //diff length
- var start = 0;
- var end = 0;
- //find diff start position
- for (var i = 0; i < tar.length; i++) {
- //copyAttribute(src[i], des[i], 'data-startline');
- //copyAttribute(src[i], des[i], 'data-endline');
- var rawSrc = cloneAndRemoveDataAttr(src[i]);
- var rawTar = cloneAndRemoveDataAttr(tar[i]);
- if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
- start = i;
- break;
- }
- }
- //find diff end position
- var srcEnd = 0;
- var tarEnd = 0;
- for (var i = 0; i < src.length; i++) {
- //copyAttribute(src[i], des[i], 'data-startline');
- //copyAttribute(src[i], des[i], 'data-endline');
- var rawSrc = cloneAndRemoveDataAttr(src[i]);
- var rawTar = cloneAndRemoveDataAttr(tar[i]);
- if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
- start = i;
- break;
- }
- }
- //tar end
- for (var i = 1; i <= tar.length + 1; i++) {
- var srcLength = src.length;
- var tarLength = tar.length;
- //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
- //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
- var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
- var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
- if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
- tarEnd = tar.length - i;
- break;
- }
- }
- //src end
- for (var i = 1; i <= src.length + 1; i++) {
- var srcLength = src.length;
- var tarLength = tar.length;
- //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
- //copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
- var rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]);
- var rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]);
- if (!rawSrc || !rawTar || rawSrc.outerHTML != rawTar.outerHTML) {
- srcEnd = src.length - i;
- break;
- }
- }
- //check if tar end overlap tar start
- var overlap = 0;
- for (var i = start; i >= 0; i--) {
- var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1]);
- var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i]);
- if (rawTarStart && rawTarEnd && rawTarStart.outerHTML == rawTarEnd.outerHTML)
- overlap++;
- else
- break;
- }
- if (debug)
- console.log('overlap:' + overlap);
- //show diff content
- if (debug) {
- console.log('start:' + start);
- console.log('tarEnd:' + tarEnd);
- console.log('srcEnd:' + srcEnd);
- }
- tarEnd += overlap;
- srcEnd += overlap;
- var repeatAdd = (start - srcEnd) < (start - tarEnd);
- var repeatDiff = Math.abs(srcEnd - tarEnd) - 1;
- //push new elements
- var newElements = [];
- if (srcEnd >= start) {
- for (var j = start; j <= srcEnd; j++) {
- if (!src[j]) continue;
- newElements.push(src[j].outerHTML);
- }
- } else if (repeatAdd) {
- for (var j = srcEnd - repeatDiff; j <= srcEnd; j++) {
- if (!des[j]) continue;
- newElements.push(des[j].outerHTML);
- }
- }
- //push remove elements
- var removeElements = [];
- if (tarEnd >= start) {
- for (var j = start; j <= tarEnd; j++) {
- if (!des[j]) continue;
- removeElements.push(des[j]);
- }
- } else if (!repeatAdd) {
- for (var j = start; j <= start + repeatDiff; j++) {
- if (!des[j]) continue;
- removeElements.push(des[j]);
- }
- }
- //add elements
- if (debug) {
- console.log('ADD ELEMENTS');
- console.log(newElements.join('\n'));
- }
- if (des[start])
- $(newElements.join('')).insertBefore(des[start]);
- else
- $(newElements.join('')).insertAfter(des[start - 1]);
- //remove elements
- if (debug)
- console.log('REMOVE ELEMENTS');
- for (var j = 0; j < removeElements.length; j++) {
- if (debug) {
- console.log(removeElements[j].outerHTML);
- }
- if (removeElements[j])
- $(removeElements[j]).remove();
- }
- }
-}
-
-function cloneAndRemoveDataAttr(el) {
- if (!el) return;
- var rawEl = $(el).clone();
- rawEl.removeAttr('data-startline data-endline');
- rawEl.find('[data-startline]').removeAttr('data-startline data-endline');
- return rawEl[0];
-}
-
-function copyAttribute(src, des, attr) {
- if (src && src.getAttribute(attr) && des)
- des.setAttribute(attr, src.getAttribute(attr));
+function updateHistoryInner () {
+ writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown))
+}
+
+function updateDataAttrs (src, des) {
+ // sync data attr startline and endline
+ for (var i = 0; i < src.length; i++) {
+ copyAttribute(src[i], des[i], 'data-startline')
+ copyAttribute(src[i], des[i], 'data-endline')
+ }
+}
+
+function partialUpdate (src, tar, des) {
+ if (!src || src.length === 0 || !tar || tar.length === 0 || !des || des.length === 0) {
+ ui.area.markdown.html(src)
+ return
+ }
+ if (src.length === tar.length) { // same length
+ for (let i = 0; i < src.length; i++) {
+ copyAttribute(src[i], des[i], 'data-startline')
+ copyAttribute(src[i], des[i], 'data-endline')
+ var rawSrc = cloneAndRemoveDataAttr(src[i])
+ var rawTar = cloneAndRemoveDataAttr(tar[i])
+ if (rawSrc.outerHTML !== rawTar.outerHTML) {
+ // console.log(rawSrc);
+ // console.log(rawTar);
+ $(des[i]).replaceWith(src[i])
+ }
+ }
+ } else { // diff length
+ var start = 0
+ // find diff start position
+ for (let i = 0; i < tar.length; i++) {
+ // copyAttribute(src[i], des[i], 'data-startline');
+ // copyAttribute(src[i], des[i], 'data-endline');
+ let rawSrc = cloneAndRemoveDataAttr(src[i])
+ let rawTar = cloneAndRemoveDataAttr(tar[i])
+ if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+ start = i
+ break
+ }
+ }
+ // find diff end position
+ var srcEnd = 0
+ var tarEnd = 0
+ for (let i = 0; i < src.length; i++) {
+ // copyAttribute(src[i], des[i], 'data-startline');
+ // copyAttribute(src[i], des[i], 'data-endline');
+ let rawSrc = cloneAndRemoveDataAttr(src[i])
+ let rawTar = cloneAndRemoveDataAttr(tar[i])
+ if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+ start = i
+ break
+ }
+ }
+ // tar end
+ for (let i = 1; i <= tar.length + 1; i++) {
+ let srcLength = src.length
+ let tarLength = tar.length
+ // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+ // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+ let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i])
+ let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i])
+ if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+ tarEnd = tar.length - i
+ break
+ }
+ }
+ // src end
+ for (let i = 1; i <= src.length + 1; i++) {
+ let srcLength = src.length
+ let tarLength = tar.length
+ // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-startline');
+ // copyAttribute(src[srcLength - i], des[srcLength - i], 'data-endline');
+ let rawSrc = cloneAndRemoveDataAttr(src[srcLength - i])
+ let rawTar = cloneAndRemoveDataAttr(tar[tarLength - i])
+ if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) {
+ srcEnd = src.length - i
+ break
+ }
+ }
+ // check if tar end overlap tar start
+ var overlap = 0
+ for (var i = start; i >= 0; i--) {
+ var rawTarStart = cloneAndRemoveDataAttr(tar[i - 1])
+ var rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i])
+ if (rawTarStart && rawTarEnd && rawTarStart.outerHTML === rawTarEnd.outerHTML) { overlap++ } else { break }
+ }
+ if (debug) { console.log('overlap:' + overlap) }
+ // show diff content
+ if (debug) {
+ console.log('start:' + start)
+ console.log('tarEnd:' + tarEnd)
+ console.log('srcEnd:' + srcEnd)
+ }
+ tarEnd += overlap
+ srcEnd += overlap
+ var repeatAdd = (start - srcEnd) < (start - tarEnd)
+ var repeatDiff = Math.abs(srcEnd - tarEnd) - 1
+ // push new elements
+ var newElements = []
+ if (srcEnd >= start) {
+ for (let j = start; j <= srcEnd; j++) {
+ if (!src[j]) continue
+ newElements.push(src[j].outerHTML)
+ }
+ } else if (repeatAdd) {
+ for (let j = srcEnd - repeatDiff; j <= srcEnd; j++) {
+ if (!des[j]) continue
+ newElements.push(des[j].outerHTML)
+ }
+ }
+ // push remove elements
+ var removeElements = []
+ if (tarEnd >= start) {
+ for (let j = start; j <= tarEnd; j++) {
+ if (!des[j]) continue
+ removeElements.push(des[j])
+ }
+ } else if (!repeatAdd) {
+ for (let j = start; j <= start + repeatDiff; j++) {
+ if (!des[j]) continue
+ removeElements.push(des[j])
+ }
+ }
+ // add elements
+ if (debug) {
+ console.log('ADD ELEMENTS')
+ console.log(newElements.join('\n'))
+ }
+ if (des[start]) { $(newElements.join('')).insertBefore(des[start]) } else { $(newElements.join('')).insertAfter(des[start - 1]) }
+ // remove elements
+ if (debug) { console.log('REMOVE ELEMENTS') }
+ for (let j = 0; j < removeElements.length; j++) {
+ if (debug) {
+ console.log(removeElements[j].outerHTML)
+ }
+ if (removeElements[j]) { $(removeElements[j]).remove() }
+ }
+ }
+}
+
+function cloneAndRemoveDataAttr (el) {
+ if (!el) return
+ var rawEl = $(el).clone()
+ rawEl.removeAttr('data-startline data-endline')
+ rawEl.find('[data-startline]').removeAttr('data-startline data-endline')
+ return rawEl[0]
+}
+
+function copyAttribute (src, des, attr) {
+ if (src && src.getAttribute(attr) && des) { des.setAttribute(attr, src.getAttribute(attr)) }
}
if ($('.cursor-menu').length <= 0) {
- $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors');
+ $("<div class='cursor-menu'>").insertAfter('.CodeMirror-cursors')
}
-function reverseSortCursorMenu(dropdown) {
- var items = dropdown.find('.textcomplete-item');
- items.sort(function (a, b) {
- return $(b).attr('data-index') - $(a).attr('data-index');
- });
- return items;
+function reverseSortCursorMenu (dropdown) {
+ var items = dropdown.find('.textcomplete-item')
+ items.sort(function (a, b) {
+ return $(b).attr('data-index') - $(a).attr('data-index')
+ })
+ return items
}
-var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle);
+var checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle)
-function checkCursorMenuInner() {
+function checkCursorMenuInner () {
// get element
- var dropdown = $('.cursor-menu > .dropdown-menu');
+ var dropdown = $('.cursor-menu > .dropdown-menu')
// return if not exists
- if (dropdown.length <= 0) return;
+ if (dropdown.length <= 0) return
// set margin
- var menuRightMargin = 10;
- var menuBottomMargin = 4;
+ var menuRightMargin = 10
+ var menuBottomMargin = 4
// use sizer to get the real doc size (won't count status bar and gutters)
- var docWidth = ui.area.codemirrorSizer.width();
- var docHeight = ui.area.codemirrorSizer.height();
+ var docWidth = ui.area.codemirrorSizer.width()
// get editor size (status bar not count in)
- var editorWidth = ui.area.codemirror.width();
- var editorHeight = ui.area.codemirror.height();
+ var editorHeight = ui.area.codemirror.height()
// get element size
- var width = dropdown.outerWidth();
- var height = dropdown.outerHeight();
+ var width = dropdown.outerWidth()
+ var height = dropdown.outerHeight()
// get cursor
- var cursor = editor.getCursor();
+ var cursor = editor.getCursor()
// set element cursor data
- if (!dropdown.hasClass('CodeMirror-other-cursor'))
- dropdown.addClass('CodeMirror-other-cursor');
- dropdown.attr('data-line', cursor.line);
- dropdown.attr('data-ch', cursor.ch);
+ if (!dropdown.hasClass('CodeMirror-other-cursor')) { dropdown.addClass('CodeMirror-other-cursor') }
+ dropdown.attr('data-line', cursor.line)
+ dropdown.attr('data-ch', cursor.ch)
// get coord position
- var coord = editor.charCoords({
- line: cursor.line,
- ch: cursor.ch
- }, 'windows');
- var left = coord.left;
- var top = coord.top;
+ var coord = editor.charCoords({
+ line: cursor.line,
+ ch: cursor.ch
+ }, 'windows')
+ var left = coord.left
+ var top = coord.top
// get doc top offset (to workaround with viewport)
- var docTopOffset = ui.area.codemirrorSizerInner.position().top;
+ var docTopOffset = ui.area.codemirrorSizerInner.position().top
// set offset
- var offsetLeft = 0;
- var offsetTop = defaultTextHeight;
+ var offsetLeft = 0
+ var offsetTop = defaultTextHeight
// set up side down
- window.upSideDown = false;
- var lastUpSideDown = upSideDown = false;
+ window.upSideDown = false
+ var lastUpSideDown = window.upSideDown = false
// only do when have width and height
- if (width > 0 && height > 0) {
+ if (width > 0 && height > 0) {
// make element right bound not larger than doc width
- if (left + width + offsetLeft + menuRightMargin > docWidth)
- offsetLeft = -(left + width - docWidth + menuRightMargin);
+ if (left + width + offsetLeft + menuRightMargin > docWidth) { offsetLeft = -(left + width - docWidth + menuRightMargin) }
// flip y when element bottom bound larger than doc height
// and element top position is larger than element height
- if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) {
- offsetTop = -(height + menuBottomMargin);
+ if (top + docTopOffset + height + offsetTop + menuBottomMargin > Math.max(editor.doc.height, editorHeight) && top + docTopOffset > height + menuBottomMargin) {
+ offsetTop = -(height + menuBottomMargin)
// reverse sort menu because upSideDown
- dropdown.html(reverseSortCursorMenu(dropdown));
- upSideDown = true;
- }
- var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown;
- lastUpSideDown = textCompleteDropdown.upSideDown;
- textCompleteDropdown.upSideDown = upSideDown;
+ dropdown.html(reverseSortCursorMenu(dropdown))
+ window.upSideDown = true
}
+ var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown
+ lastUpSideDown = textCompleteDropdown.upSideDown
+ textCompleteDropdown.upSideDown = window.upSideDown
+ }
// make menu scroll top only if upSideDown changed
- if (upSideDown !== lastUpSideDown)
- dropdown.scrollTop(dropdown[0].scrollHeight);
+ if (window.upSideDown !== lastUpSideDown) { dropdown.scrollTop(dropdown[0].scrollHeight) }
// set element offset data
- dropdown.attr('data-offset-left', offsetLeft);
- dropdown.attr('data-offset-top', offsetTop);
+ dropdown.attr('data-offset-left', offsetLeft)
+ dropdown.attr('data-offset-top', offsetTop)
// set position
- dropdown[0].style.left = left + offsetLeft + 'px';
- dropdown[0].style.top = top + offsetTop + 'px';
+ dropdown[0].style.left = left + offsetLeft + 'px'
+ dropdown[0].style.top = top + offsetTop + 'px'
}
-function checkInIndentCode() {
+function checkInIndentCode () {
// if line starts with tab or four spaces is a code block
- var line = editor.getLine(editor.getCursor().line);
- var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t'));
- return isIndentCode;
-}
-
-var isInCode = false;
-
-function checkInCode() {
- isInCode = checkAbove(matchInCode) || checkInIndentCode();
-}
-
-function checkAbove(method) {
- var cursor = editor.getCursor();
- var text = [];
- for (var i = 0; i < cursor.line; i++) //contain current line
- text.push(editor.getLine(i));
- text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch);
- //console.log(text);
- return method(text);
-}
-
-function checkBelow(method) {
- var cursor = editor.getCursor();
- var count = editor.lineCount();
- var text = [];
- for (var i = cursor.line + 1; i < count; i++) //contain current line
- text.push(editor.getLine(i));
- text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n');
- //console.log(text);
- return method(text);
-}
-
-function matchInCode(text) {
- var match;
- match = text.match(/`{3,}/g);
+ var line = editor.getLine(editor.getCursor().line)
+ var isIndentCode = ((line.substr(0, 4) === ' ') || (line.substr(0, 1) === '\t'))
+ return isIndentCode
+}
+
+var isInCode = false
+
+function checkInCode () {
+ isInCode = checkAbove(matchInCode) || checkInIndentCode()
+}
+
+function checkAbove (method) {
+ var cursor = editor.getCursor()
+ var text = []
+ for (var i = 0; i < cursor.line; i++) { // contain current line
+ text.push(editor.getLine(i))
+ }
+ text = text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch)
+ // console.log(text);
+ return method(text)
+}
+
+function checkBelow (method) {
+ var cursor = editor.getCursor()
+ var count = editor.lineCount()
+ var text = []
+ for (var i = cursor.line + 1; i < count; i++) { // contain current line
+ text.push(editor.getLine(i))
+ }
+ text = editor.getLine(cursor.line).slice(cursor.ch) + '\n' + text.join('\n')
+ // console.log(text);
+ return method(text)
+}
+
+function matchInCode (text) {
+ var match
+ match = text.match(/`{3,}/g)
+ if (match && match.length % 2) {
+ return true
+ } else {
+ match = text.match(/`/g)
if (match && match.length % 2) {
- return true;
+ return true
} else {
- match = text.match(/`/g);
- if (match && match.length % 2) {
- return true;
- } else {
- return false;
- }
+ return false
}
+ }
}
-var isInContainer = false;
-var isInContainerSyntax = false;
+var isInContainer = false
+var isInContainerSyntax = false
-function checkInContainer() {
- isInContainer = checkAbove(matchInContainer) && !checkInIndentCode();
+function checkInContainer () {
+ isInContainer = checkAbove(matchInContainer) && !checkInIndentCode()
}
-function checkInContainerSyntax() {
+function checkInContainerSyntax () {
// if line starts with :::, it's in container syntax
- var line = editor.getLine(editor.getCursor().line);
- isInContainerSyntax = (line.substr(0, 3) === ':::');
+ var line = editor.getLine(editor.getCursor().line)
+ isInContainerSyntax = (line.substr(0, 3) === ':::')
}
-function matchInContainer(text) {
- var match;
- match = text.match(/:{3,}/g);
- if (match && match.length % 2) {
- return true;
- } else {
- return false;
- }
+function matchInContainer (text) {
+ var match
+ match = text.match(/:{3,}/g)
+ if (match && match.length % 2) {
+ return true
+ } else {
+ return false
+ }
}
$(editor.getInputField())
.textcomplete([
- { // emoji strategy
- match: /(^|\n|\s)\B:([\-+\w]*)$/,
- search: function (term, callback) {
- var line = editor.getLine(editor.getCursor().line);
- term = line.match(this.match)[2];
- var list = [];
- $.map(emojify.emojiNames, function (emoji) {
- if (emoji.indexOf(term) === 0) //match at first character
- list.push(emoji);
- });
- $.map(emojify.emojiNames, function (emoji) {
- if (emoji.indexOf(term) !== -1) //match inside the word
- list.push(emoji);
- });
- callback(list);
- },
- template: function (value) {
- return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value;
- },
- replace: function (value) {
- return '$1:' + value + ': ';
- },
- index: 1,
- context: function (text) {
- checkInCode();
- checkInContainer();
- checkInContainerSyntax();
- return !isInCode && !isInContainerSyntax;
+ { // emoji strategy
+ match: /(^|\n|\s)\B:([-+\w]*)$/,
+ search: function (term, callback) {
+ var line = editor.getLine(editor.getCursor().line)
+ term = line.match(this.match)[2]
+ var list = []
+ $.map(window.emojify.emojiNames, function (emoji) {
+ if (emoji.indexOf(term) === 0) { // match at first character
+ list.push(emoji)
}
- },
- { // Code block language strategy
- langs: supportCodeModes,
- charts: supportCharts,
- match: /(^|\n)```(\w+)$/,
- search: function (term, callback) {
- var line = editor.getLine(editor.getCursor().line);
- term = line.match(this.match)[2];
- var list = [];
- $.map(this.langs, function (lang) {
- if (lang.indexOf(term) === 0 && lang !== term)
- list.push(lang);
- });
- $.map(this.charts, function (chart) {
- if (chart.indexOf(term) === 0 && chart !== term)
- list.push(chart);
- });
- callback(list);
- },
- replace: function (lang) {
- var ending = '';
- if (!checkBelow(matchInCode)) {
- ending = '\n\n```';
- }
- if (this.langs.indexOf(lang) !== -1)
- return '$1```' + lang + '=' + ending;
- else if (this.charts.indexOf(lang) !== -1)
- return '$1```' + lang + ending;
- },
- done: function () {
- var cursor = editor.getCursor();
- var text = [];
- text.push(editor.getLine(cursor.line - 1));
- text.push(editor.getLine(cursor.line));
- text = text.join('\n');
- //console.log(text);
- if (text == '\n```')
- editor.doc.cm.execCommand("goLineUp");
- },
- context: function (text) {
- return isInCode;
+ })
+ $.map(window.emojify.emojiNames, function (emoji) {
+ if (emoji.indexOf(term) !== -1) { // match inside the word
+ list.push(emoji)
}
+ })
+ callback(list)
},
- { // Container strategy
- containers: supportContainers,
- match: /(^|\n):::(\s*)(\w*)$/,
- search: function (term, callback) {
- var line = editor.getLine(editor.getCursor().line);
- term = line.match(this.match)[3].trim();
- var list = [];
- $.map(this.containers, function (container) {
- if (container.indexOf(term) === 0 && container !== term)
- list.push(container);
- });
- callback(list);
- },
- replace: function (lang) {
- var ending = '';
- if (!checkBelow(matchInContainer)) {
- ending = '\n\n:::';
- }
- if (this.containers.indexOf(lang) !== -1)
- return '$1:::$2' + lang + ending;
- },
- done: function () {
- var cursor = editor.getCursor();
- var text = [];
- text.push(editor.getLine(cursor.line - 1));
- text.push(editor.getLine(cursor.line));
- text = text.join('\n');
- //console.log(text);
- if (text == '\n:::')
- editor.doc.cm.execCommand("goLineUp");
- },
- context: function (text) {
- return !isInCode && isInContainer;
- }
+ template: function (value) {
+ return '<img class="emoji" src="' + serverurl + '/build/emojify.js/dist/images/basic/' + value + '.png"></img> ' + value
},
- { //header
- match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/,
- search: function (term, callback) {
- callback($.map(supportHeaders, function (header) {
- return header.search.indexOf(term) === 0 ? header.text : null;
- }));
- },
- replace: function (value) {
- return '$1' + value;
- },
- context: function (text) {
- return !isInCode;
- }
+ replace: function (value) {
+ return '$1:' + value + ': '
},
- { //extra tags for blockquote
- match: /(?:^|\n|\s)(\>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|\:|)\s*\w*)$/,
- search: function (term, callback) {
- var line = editor.getLine(editor.getCursor().line);
- var quote = line.match(this.match)[1].trim();
- var list = [];
- if (quote.indexOf('>') == 0) {
- $.map(supportExtraTags, function (extratag) {
- if (extratag.search.indexOf(term) === 0)
- list.push(extratag.command());
- });
- }
- $.map(supportReferrals, function (referral) {
- if (referral.search.indexOf(term) === 0)
- list.push(referral.text);
- })
- callback(list);
- },
- replace: function (value) {
- return '$1' + value;
- },
- context: function (text) {
- return !isInCode;
- }
+ index: 1,
+ context: function (text) {
+ checkInCode()
+ checkInContainer()
+ checkInContainerSyntax()
+ return !isInCode && !isInContainerSyntax
+ }
+ },
+ { // Code block language strategy
+ langs: supportCodeModes,
+ charts: supportCharts,
+ match: /(^|\n)```(\w+)$/,
+ search: function (term, callback) {
+ var line = editor.getLine(editor.getCursor().line)
+ term = line.match(this.match)[2]
+ var list = []
+ $.map(this.langs, function (lang) {
+ if (lang.indexOf(term) === 0 && lang !== term) { list.push(lang) }
+ })
+ $.map(this.charts, function (chart) {
+ if (chart.indexOf(term) === 0 && chart !== term) { list.push(chart) }
+ })
+ callback(list)
},
- { //extra tags for list
- match: /(^[>\s]*[\-\+\*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/,
- search: function (term, callback) {
- var list = [];
- $.map(supportExtraTags, function (extratag) {
- if (extratag.search.indexOf(term) === 0)
- list.push(extratag.command());
- });
- $.map(supportReferrals, function (referral) {
- if (referral.search.indexOf(term) === 0)
- list.push(referral.text);
- })
- callback(list);
- },
- replace: function (value) {
- return '$1' + value;
- },
- context: function (text) {
- return !isInCode;
- }
+ replace: function (lang) {
+ var ending = ''
+ if (!checkBelow(matchInCode)) {
+ ending = '\n\n```'
+ }
+ if (this.langs.indexOf(lang) !== -1) { return '$1```' + lang + '=' + ending } else if (this.charts.indexOf(lang) !== -1) { return '$1```' + lang + ending }
},
- { //referral
- match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|\!|\!\[\]|\!\[\]\[\]|\!\[\]\(\))\s*\w*)$/,
- search: function (term, callback) {
- callback($.map(supportReferrals, function (referral) {
- return referral.search.indexOf(term) === 0 ? referral.text : null;
- }));
- },
- replace: function (value) {
- return '$1' + value;
- },
- context: function (text) {
- return !isInCode;
- }
+ done: function () {
+ var cursor = editor.getCursor()
+ var text = []
+ text.push(editor.getLine(cursor.line - 1))
+ text.push(editor.getLine(cursor.line))
+ text = text.join('\n')
+ // console.log(text);
+ if (text === '\n```') { editor.doc.cm.execCommand('goLineUp') }
},
- { //externals
- match: /(^|\n|\s)\{\}(\w*)$/,
- search: function (term, callback) {
- callback($.map(supportExternals, function (external) {
- return external.search.indexOf(term) === 0 ? external.text : null;
- }));
- },
- replace: function (value) {
- return '$1' + value;
- },
- context: function (text) {
- return !isInCode;
- }
- }
- ], {
- appendTo: $('.cursor-menu')
- })
- .on({
- 'textComplete:beforeSearch': function (e) {
- //NA
+ context: function (text) {
+ return isInCode
+ }
+ },
+ { // Container strategy
+ containers: supportContainers,
+ match: /(^|\n):::(\s*)(\w*)$/,
+ search: function (term, callback) {
+ var line = editor.getLine(editor.getCursor().line)
+ term = line.match(this.match)[3].trim()
+ var list = []
+ $.map(this.containers, function (container) {
+ if (container.indexOf(term) === 0 && container !== term) { list.push(container) }
+ })
+ callback(list)
},
- 'textComplete:afterSearch': function (e) {
- checkCursorMenu();
+ replace: function (lang) {
+ var ending = ''
+ if (!checkBelow(matchInContainer)) {
+ ending = '\n\n:::'
+ }
+ if (this.containers.indexOf(lang) !== -1) { return '$1:::$2' + lang + ending }
},
- 'textComplete:select': function (e, value, strategy) {
- //NA
+ done: function () {
+ var cursor = editor.getCursor()
+ var text = []
+ text.push(editor.getLine(cursor.line - 1))
+ text.push(editor.getLine(cursor.line))
+ text = text.join('\n')
+ // console.log(text);
+ if (text === '\n:::') { editor.doc.cm.execCommand('goLineUp') }
},
- 'textComplete:show': function (e) {
- $(this).data('autocompleting', true);
- editor.setOption("extraKeys", {
- "Up": function () {
- return false;
- },
- "Right": function () {
- editor.doc.cm.execCommand("goCharRight");
- },
- "Down": function () {
- return false;
- },
- "Left": function () {
- editor.doc.cm.execCommand("goCharLeft");
- },
- "Enter": function () {
- return false;
- },
- "Backspace": function () {
- editor.doc.cm.execCommand("delCharBefore");
- }
- });
+ context: function (text) {
+ return !isInCode && isInContainer
+ }
+ },
+ { // header
+ match: /(?:^|\n)(\s{0,3})(#{1,6}\w*)$/,
+ search: function (term, callback) {
+ callback($.map(supportHeaders, function (header) {
+ return header.search.indexOf(term) === 0 ? header.text : null
+ }))
+ },
+ replace: function (value) {
+ return '$1' + value
+ },
+ context: function (text) {
+ return !isInCode
+ }
+ },
+ { // extra tags for blockquote
+ match: /(?:^|\n|\s)(>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|:|)\s*\w*)$/,
+ search: function (term, callback) {
+ var line = editor.getLine(editor.getCursor().line)
+ var quote = line.match(this.match)[1].trim()
+ var list = []
+ if (quote.indexOf('>') === 0) {
+ $.map(supportExtraTags, function (extratag) {
+ if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) }
+ })
+ }
+ $.map(supportReferrals, function (referral) {
+ if (referral.search.indexOf(term) === 0) { list.push(referral.text) }
+ })
+ callback(list)
+ },
+ replace: function (value) {
+ return '$1' + value
+ },
+ context: function (text) {
+ return !isInCode
+ }
+ },
+ { // extra tags for list
+ match: /(^[>\s]*[-+*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/,
+ search: function (term, callback) {
+ var list = []
+ $.map(supportExtraTags, function (extratag) {
+ if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) }
+ })
+ $.map(supportReferrals, function (referral) {
+ if (referral.search.indexOf(term) === 0) { list.push(referral.text) }
+ })
+ callback(list)
},
- 'textComplete:hide': function (e) {
- $(this).data('autocompleting', false);
- editor.setOption("extraKeys", defaultExtraKeys);
+ replace: function (value) {
+ return '$1' + value
+ },
+ context: function (text) {
+ return !isInCode
+ }
+ },
+ { // referral
+ match: /(^\s*|\n|\s{2})((\[\]|\[\]\[\]|\[\]\(\)|!|!\[\]|!\[\]\[\]|!\[\]\(\))\s*\w*)$/,
+ search: function (term, callback) {
+ callback($.map(supportReferrals, function (referral) {
+ return referral.search.indexOf(term) === 0 ? referral.text : null
+ }))
+ },
+ replace: function (value) {
+ return '$1' + value
+ },
+ context: function (text) {
+ return !isInCode
+ }
+ },
+ { // externals
+ match: /(^|\n|\s)\{\}(\w*)$/,
+ search: function (term, callback) {
+ callback($.map(supportExternals, function (external) {
+ return external.search.indexOf(term) === 0 ? external.text : null
+ }))
+ },
+ replace: function (value) {
+ return '$1' + value
+ },
+ context: function (text) {
+ return !isInCode
}
- });
+ }
+ ], {
+ appendTo: $('.cursor-menu')
+ })
+ .on({
+ 'textComplete:beforeSearch': function (e) {
+ // NA
+ },
+ 'textComplete:afterSearch': function (e) {
+ checkCursorMenu()
+ },
+ 'textComplete:select': function (e, value, strategy) {
+ // NA
+ },
+ 'textComplete:show': function (e) {
+ $(this).data('autocompleting', true)
+ editor.setOption('extraKeys', {
+ 'Up': function () {
+ return false
+ },
+ 'Right': function () {
+ editor.doc.cm.execCommand('goCharRight')
+ },
+ 'Down': function () {
+ return false
+ },
+ 'Left': function () {
+ editor.doc.cm.execCommand('goCharLeft')
+ },
+ 'Enter': function () {
+ return false
+ },
+ 'Backspace': function () {
+ editor.doc.cm.execCommand('delCharBefore')
+ }
+ })
+ },
+ 'textComplete:hide': function (e) {
+ $(this).data('autocompleting', false)
+ editor.setOption('extraKeys', defaultExtraKeys)
+ }
+ })
diff --git a/public/js/lib/common/login.js b/public/js/lib/common/login.js
index 58fa55c6..18cd377d 100644
--- a/public/js/lib/common/login.js
+++ b/public/js/lib/common/login.js
@@ -1,89 +1,92 @@
-import { serverurl } from '../config';
+/* eslint-env browser, jquery */
+/* global Cookies */
-let checkAuth = false;
-let profile = null;
-let lastLoginState = getLoginState();
-let lastUserId = getUserId();
-var loginStateChangeEvent = null;
+import { serverurl } from '../config'
-export function setloginStateChangeEvent(func) {
- loginStateChangeEvent = func;
+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 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 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 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 getLoginState () {
+ const state = Cookies.get('loginstate')
+ return state === 'true' || state === true
}
-export function getUserId() {
- return Cookies.get('userid');
+export function getUserId () {
+ return Cookies.get('userid')
}
-export function clearLoginState() {
- Cookies.remove('loginstate');
+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`)
+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);
- }
+ if (data && data.status === 'ok') {
+ profile = data
+ yesCallback(profile)
+ setLoginState(true, data.id)
+ } else {
+ noCallback()
+ setLoginState(false)
+ }
})
.fail(() => {
- noCallback();
+ noCallback()
})
.always(() => {
- checkAuth = true;
- });
- } else if (cookieLoginState) {
- yesCallback(profile);
- } else {
- noCallback();
- }
+ checkAuth = true
+ })
+ } else if (cookieLoginState) {
+ yesCallback(profile)
+ } else {
+ noCallback()
+ }
}
export default {
- checkAuth,
- profile,
- lastLoginState,
- lastUserId,
- loginStateChangeEvent
-};
+ checkAuth,
+ profile,
+ lastLoginState,
+ lastUserId,
+ loginStateChangeEvent
+}
diff --git a/public/js/lib/config/index.js b/public/js/lib/config/index.js
index 2b73679f..1ea7a7ab 100644
--- a/public/js/lib/config/index.js
+++ b/public/js/lib/config/index.js
@@ -1,19 +1,19 @@
-import configJson from '../../../../config.json'; // root path json config
+import configJson from '../../../../config.json' // root path json config
-const config = 'production' === process.env.NODE_ENV ? configJson.production : configJson.development;
+const config = process.env.NODE_ENV === 'production' ? 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 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 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 port = window.location.port
+export const serverurl = `${window.location.protocol}//${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';
+export const version = '0.5.0'
diff --git a/public/js/locale.js b/public/js/locale.js
index e6d11cd2..2a2c1814 100644
--- a/public/js/locale.js
+++ b/public/js/locale.js
@@ -1,26 +1,28 @@
-var lang = "en";
-var userLang = navigator.language || navigator.userLanguage;
-var userLangCode = userLang.split('-')[0];
-var userCountryCode = userLang.split('-')[1];
-var locale = $('.ui-locale');
-var supportLangs = [];
-$(".ui-locale option").each(function() {
- supportLangs.push($(this).val());
-});
+/* eslint-env browser, jquery */
+/* global Cookies */
+
+var lang = 'en'
+var userLang = navigator.language || navigator.userLanguage
+var userLangCode = userLang.split('-')[0]
+var locale = $('.ui-locale')
+var supportLangs = []
+$('.ui-locale option').each(function () {
+ supportLangs.push($(this).val())
+})
if (Cookies.get('locale')) {
- lang = Cookies.get('locale');
+ lang = Cookies.get('locale')
} else if (supportLangs.indexOf(userLang) !== -1) {
- lang = supportLangs[supportLangs.indexOf(userLang)];
+ lang = supportLangs[supportLangs.indexOf(userLang)]
} else if (supportLangs.indexOf(userLangCode) !== -1) {
- lang = supportLangs[supportLangs.indexOf(userLangCode)];
+ lang = supportLangs[supportLangs.indexOf(userLangCode)]
}
-locale.val(lang);
-$('select.ui-locale option[value="' + lang + '"]').attr('selected','selected');
+locale.val(lang)
+$('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected')
-locale.change(function() {
- Cookies.set('locale', $(this).val(), {
- expires: 365
- });
- window.location.reload();
-});
+locale.change(function () {
+ Cookies.set('locale', $(this).val(), {
+ expires: 365
+ })
+ window.location.reload()
+})
diff --git a/public/js/pretty.js b/public/js/pretty.js
index 18d0dc0d..718941a8 100644
--- a/public/js/pretty.js
+++ b/public/js/pretty.js
@@ -1,8 +1,11 @@
-require('../css/extra.css');
-require('../css/slide-preview.css');
-require('../css/site.css');
+/* eslint-env browser, jquery */
+/* global refreshView */
-require('highlight.js/styles/github-gist.css');
+require('../css/extra.css')
+require('../css/slide-preview.css')
+require('../css/site.css')
+
+require('highlight.js/styles/github-gist.css')
import {
autoLinkify,
@@ -16,126 +19,126 @@ import {
scrollToHash,
smoothHashScroll,
updateLastChange
-} from './extra';
+} from './extra'
-import { preventXSS } from './render';
+import { preventXSS } from './render'
-const markdown = $("#doc.markdown-body");
-const text = markdown.text();
-const lastMeta = md.meta;
-md.meta = {};
-delete md.metaError;
-let rendered = md.render(text);
+const markdown = $('#doc.markdown-body')
+const text = markdown.text()
+const lastMeta = md.meta
+md.meta = {}
+delete md.metaError
+let rendered = md.render(text)
if (md.meta.type && md.meta.type === 'slide') {
- const slideOptions = {
- separator: '^(\r\n?|\n)---(\r\n?|\n)$',
- verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
- };
- const slides = RevealMarkdown.slidify(text, slideOptions);
- markdown.html(slides);
- RevealMarkdown.initialize();
+ const slideOptions = {
+ separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+ verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
+ }
+ const slides = window.RevealMarkdown.slidify(text, slideOptions)
+ markdown.html(slides)
+ window.RevealMarkdown.initialize()
// prevent XSS
- markdown.html(preventXSS(markdown.html()));
- markdown.addClass('slides');
+ markdown.html(preventXSS(markdown.html()))
+ markdown.addClass('slides')
} else {
- if (lastMeta.type && lastMeta.type === 'slide') {
- refreshView();
- markdown.removeClass('slides');
- }
+ if (lastMeta.type && lastMeta.type === 'slide') {
+ refreshView()
+ markdown.removeClass('slides')
+ }
// only render again when meta changed
- if (JSON.stringify(md.meta) != JSON.stringify(lastMeta)) {
- parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'));
- rendered = md.render(text);
- }
+ if (JSON.stringify(md.meta) !== JSON.stringify(lastMeta)) {
+ parseMeta(md, null, markdown, $('#ui-toc'), $('#ui-toc-affix'))
+ rendered = md.render(text)
+ }
// prevent XSS
- rendered = preventXSS(rendered);
- const result = postProcess(rendered);
- markdown.html(result.html());
+ rendered = preventXSS(rendered)
+ const result = postProcess(rendered)
+ markdown.html(result.html())
}
-$(document.body).show();
+$(document.body).show()
-finishView(markdown);
-autoLinkify(markdown);
-deduplicatedHeaderId(markdown);
-renderTOC(markdown);
-generateToc('ui-toc');
-generateToc('ui-toc-affix');
-smoothHashScroll();
-createtime = lastchangeui.time.attr('data-createtime');
-lastchangetime = lastchangeui.time.attr('data-updatetime');
-updateLastChange();
+finishView(markdown)
+autoLinkify(markdown)
+deduplicatedHeaderId(markdown)
+renderTOC(markdown)
+generateToc('ui-toc')
+generateToc('ui-toc-affix')
+smoothHashScroll()
+window.createtime = window.lastchangeui.time.attr('data-createtime')
+window.lastchangetime = window.lastchangeui.time.attr('data-updatetime')
+updateLastChange()
-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
+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(e => {
- e.stopPropagation();
-});
+ e.stopPropagation()
+})
-let enoughForAffixToc = true;
+let enoughForAffixToc = true
-function generateScrollspy() {
- $(document.body).scrollspy({
- target: ''
- });
- $(document.body).scrollspy('refresh');
- if (enoughForAffixToc) {
- toc.hide();
- tocAffix.show();
- } else {
- tocAffix.hide();
- toc.show();
- }
- $(document.body).scroll();
+function generateScrollspy () {
+ $(document.body).scrollspy({
+ target: ''
+ })
+ $(document.body).scrollspy('refresh')
+ if (enoughForAffixToc) {
+ toc.hide()
+ tocAffix.show()
+ } else {
+ tocAffix.hide()
+ toc.show()
+ }
+ $(document.body).scroll()
}
-function windowResize() {
- //toc right
- 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
- let newbool;
- const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2;
- //for ipad or wider device
- if (rightMargin >= 133) {
- newbool = true;
- const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
- const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
- tocAffix.css('left', `${left}px`);
- } else {
- newbool = false;
- }
- if (newbool != enoughForAffixToc) {
- enoughForAffixToc = newbool;
- generateScrollspy();
- }
+function windowResize () {
+ // toc right
+ 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
+ let newbool
+ const rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2
+ // for ipad or wider device
+ if (rightMargin >= 133) {
+ newbool = true
+ const affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2
+ const left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin
+ tocAffix.css('left', `${left}px`)
+ } else {
+ newbool = false
+ }
+ if (newbool !== enoughForAffixToc) {
+ enoughForAffixToc = newbool
+ generateScrollspy()
+ }
}
$(window).resize(() => {
- windowResize();
-});
+ windowResize()
+})
$(document).ready(() => {
- windowResize();
- generateScrollspy();
- setTimeout(scrollToHash, 0);
- //tooltip
- $('[data-toggle="tooltip"]').tooltip();
-});
+ windowResize()
+ generateScrollspy()
+ setTimeout(scrollToHash, 0)
+ // tooltip
+ $('[data-toggle="tooltip"]').tooltip()
+})
-export function scrollToTop() {
- $('body, html').stop(true, true).animate({
- scrollTop: 0
- }, 100, "linear");
+export function scrollToTop () {
+ $('body, html').stop(true, true).animate({
+ scrollTop: 0
+ }, 100, 'linear')
}
-export function scrollToBottom() {
- $('body, html').stop(true, true).animate({
- scrollTop: $(document.body)[0].scrollHeight
- }, 100, "linear");
+export function scrollToBottom () {
+ $('body, html').stop(true, true).animate({
+ scrollTop: $(document.body)[0].scrollHeight
+ }, 100, 'linear')
}
-window.scrollToTop = scrollToTop;
-window.scrollToBottom = scrollToBottom;
+window.scrollToTop = scrollToTop
+window.scrollToBottom = scrollToBottom
diff --git a/public/js/render.js b/public/js/render.js
index 5d6d0aa2..61663a4b 100644
--- a/public/js/render.js
+++ b/public/js/render.js
@@ -1,62 +1,64 @@
+/* eslint-env browser, jquery */
+/* global filterXSS */
// allow some attributes
-var whiteListAttr = ['id', 'class', 'style'];
-window.whiteListAttr = whiteListAttr;
+var whiteListAttr = ['id', 'class', 'style']
+window.whiteListAttr = whiteListAttr
// allow link starts with '.', '/' and custom protocol with '://'
-var linkRegex = /^([\w|-]+:\/\/)|^([\.|\/])+/;
+var linkRegex = /^([\w|-]+:\/\/)|^([.|/])+/
// allow data uri, from https://gist.github.com/bgrins/6194623
-var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i;
+var dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)\s*$/i
// custom white list
-var whiteList = filterXSS.whiteList;
+var whiteList = filterXSS.whiteList
// allow ol specify start number
-whiteList['ol'] = ['start'];
+whiteList['ol'] = ['start']
// allow li specify value number
-whiteList['li'] = ['value'];
+whiteList['li'] = ['value']
// allow style tag
-whiteList['style'] = [];
+whiteList['style'] = []
// allow kbd tag
-whiteList['kbd'] = [];
+whiteList['kbd'] = []
// allow ifram tag with some safe attributes
-whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height'];
+whiteList['iframe'] = ['allowfullscreen', 'name', 'referrerpolicy', 'sandbox', 'src', 'srcdoc', 'width', 'height']
// allow summary tag
-whiteList['summary'] = [];
+whiteList['summary'] = []
var filterXSSOptions = {
- allowCommentTag: true,
- whiteList: whiteList,
- escapeHtml: function (html) {
+ allowCommentTag: true,
+ whiteList: whiteList,
+ escapeHtml: function (html) {
// allow html comment in multiple lines
- return html.replace(/<(.*?)>/g, '&lt;$1&gt;');
- },
- onIgnoreTag: function (tag, html, options) {
+ return html.replace(/<(.*?)>/g, '&lt;$1&gt;')
+ },
+ onIgnoreTag: function (tag, html, options) {
// allow comment tag
- if (tag == "!--") {
+ if (tag === '!--') {
// do not filter its attributes
- return html;
- }
- },
- onTagAttr: function (tag, name, value, isWhiteAttr) {
+ return html
+ }
+ },
+ onTagAttr: function (tag, name, value, isWhiteAttr) {
// allow href and src that match linkRegex
- if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
- return name + '="' + filterXSS.escapeAttrValue(value) + '"';
- }
+ if (isWhiteAttr && (name === 'href' || name === 'src') && linkRegex.test(value)) {
+ return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+ }
// allow data uri in img src
- if (isWhiteAttr && (tag == "img" && name === 'src') && dataUriRegex.test(value)) {
- return name + '="' + filterXSS.escapeAttrValue(value) + '"';
- }
- },
- onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
+ if (isWhiteAttr && (tag === 'img' && name === 'src') && dataUriRegex.test(value)) {
+ return name + '="' + filterXSS.escapeAttrValue(value) + '"'
+ }
+ },
+ onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
// allow attr start with 'data-' or in the whiteListAttr
- if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1) {
+ if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) {
// escape its value using built-in escapeAttrValue function
- return name + '="' + filterXSS.escapeAttrValue(value) + '"';
- }
+ return name + '="' + filterXSS.escapeAttrValue(value) + '"'
}
-};
+ }
+}
-function preventXSS(html) {
- return filterXSS(html, filterXSSOptions);
+function preventXSS (html) {
+ return filterXSS(html, filterXSSOptions)
}
-window.preventXSS = preventXSS;
+window.preventXSS = preventXSS
module.exports = {
preventXSS: preventXSS
diff --git a/public/js/reveal-markdown.js b/public/js/reveal-markdown.js
index 3c3e1f5b..eca148d8 100755
--- a/public/js/reveal-markdown.js
+++ b/public/js/reveal-markdown.js
@@ -1,396 +1,355 @@
+/* eslint-env browser, jquery */
+
+import { preventXSS } from './render'
+import { md } from './extra'
+
/**
* The reveal.js markdown plugin. Handles parsing of
* markdown inside of presentations as well as loading
* of external markdown documents.
*/
-(function( root, factory ) {
- if( typeof exports === 'object' ) {
- module.exports = factory();
- }
- else {
- // Browser globals (root is window)
- root.RevealMarkdown = factory();
- root.RevealMarkdown.initialize();
- }
-}( this, function() {
-
- var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$',
- DEFAULT_NOTES_SEPARATOR = 'note:',
- DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
- DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
-
- var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
-
-
- /**
- * Retrieves the markdown contents of a slide section
- * element. Normalizes leading tabs/whitespace.
- */
- function getMarkdownFromSlide( section ) {
-
- var template = section.querySelector( 'script' );
-
- // strip leading whitespace so it isn't evaluated as code
- var text = ( template || section ).textContent;
-
- // restore script end tags
- text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
-
- var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
- leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
-
- if( leadingTabs > 0 ) {
- text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
- }
- else if( leadingWs > 1 ) {
- text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
- }
-
- return text;
-
- }
-
- /**
- * Given a markdown slide section element, this will
- * return all arguments that aren't related to markdown
- * parsing. Used to forward any other user-defined arguments
- * to the output markdown slide.
- */
- function getForwardedAttributes( section ) {
-
- var attributes = section.attributes;
- var result = [];
-
- for( var i = 0, len = attributes.length; i < len; i++ ) {
- var name = attributes[i].name,
- value = attributes[i].value;
-
- // disregard attributes that are used for markdown loading/parsing
- if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
-
- if( value ) {
- result.push( name + '="' + value + '"' );
- }
- else {
- result.push( name );
- }
- }
-
- return result.join( ' ' );
-
- }
-
- /**
- * Inspects the given options and fills out default
- * values for what's not defined.
- */
- function getSlidifyOptions( options ) {
-
- options = options || {};
- options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
- options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
- options.attributes = options.attributes || '';
-
- return options;
-
- }
-
- /**
- * Helper function for constructing a markdown slide.
- */
- function createMarkdownSlide( content, options ) {
-
- options = getSlidifyOptions( options );
-
- var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
-
- if( notesMatch.length === 2 ) {
- content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>';
- }
-
- // prevent script end tags in the content from interfering
- // with parsing
- content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
-
- return '<script type="text/template">' + content + '</script>';
-
- }
-
- /**
- * Parses a data string into multiple slides based
- * on the passed in separator arguments.
- */
- function slidify( markdown, options ) {
-
- options = getSlidifyOptions( options );
-
- var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
- horizontalSeparatorRegex = new RegExp( options.separator );
-
- var matches,
- lastIndex = 0,
- isHorizontal,
- wasHorizontal = true,
- content,
- sectionStack = [];
-
- // iterate until all blocks between separators are stacked up
- while( matches = separatorRegex.exec( markdown ) ) {
- notes = null;
-
- // determine direction (horizontal by default)
- isHorizontal = horizontalSeparatorRegex.test( matches[0] );
-
- if( !isHorizontal && wasHorizontal ) {
- // create vertical stack
- sectionStack.push( [] );
- }
-
- // pluck slide content from markdown input
- content = markdown.substring( lastIndex, matches.index );
-
- if( isHorizontal && wasHorizontal ) {
- // add to horizontal stack
- sectionStack.push( content );
- }
- else {
- // add to vertical stack
- sectionStack[sectionStack.length-1].push( content );
- }
-
- lastIndex = separatorRegex.lastIndex;
- wasHorizontal = isHorizontal;
- }
-
- // add the remaining slide
- ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
-
- var markdownSections = '';
-
- // flatten the hierarchical stack, and insert <section data-markdown> tags
- for( var i = 0, len = sectionStack.length; i < len; i++ ) {
- // vertical
- if( sectionStack[i] instanceof Array ) {
- markdownSections += '<section '+ options.attributes +'>';
-
- sectionStack[i].forEach( function( child ) {
- markdownSections += '<section data-markdown>' + createMarkdownSlide( child, options ) + '</section>';
- } );
-
- markdownSections += '</section>';
- }
- else {
- markdownSections += '<section '+ options.attributes +' data-markdown>' + createMarkdownSlide( sectionStack[i], options ) + '</section>';
- }
- }
-
- return markdownSections;
-
- }
-
- /**
- * Parses any current data-markdown slides, splits
- * multi-slide markdown into separate sections and
- * handles loading of external markdown.
- */
- function processSlides() {
-
- var sections = document.querySelectorAll( '[data-markdown]'),
- section;
-
- for( var i = 0, len = sections.length; i < len; i++ ) {
-
- section = sections[i];
-
- if( section.getAttribute( 'data-markdown' ).length ) {
-
- var xhr = new XMLHttpRequest(),
- url = section.getAttribute( 'data-markdown' );
-
- datacharset = section.getAttribute( 'data-charset' );
-
- // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
- if( datacharset != null && datacharset != '' ) {
- xhr.overrideMimeType( 'text/html; charset=' + datacharset );
- }
-
- xhr.onreadystatechange = function() {
- if( xhr.readyState === 4 ) {
- // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
- if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
-
- section.outerHTML = slidify( xhr.responseText, {
- separator: section.getAttribute( 'data-separator' ),
- verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
- notesSeparator: section.getAttribute( 'data-separator-notes' ),
- attributes: getForwardedAttributes( section )
- });
-
- }
- else {
-
- section.outerHTML = '<section data-state="alert">' +
- 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
- 'Check your browser\'s JavaScript console for more details.' +
- '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
- '</section>';
-
- }
- }
- };
-
- xhr.open( 'GET', url, false );
-
- try {
- xhr.send();
- }
- catch ( e ) {
- alert( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
- }
-
- }
- else if( section.getAttribute( 'data-separator' ) || section.getAttribute( 'data-separator-vertical' ) || section.getAttribute( 'data-separator-notes' ) ) {
-
- section.outerHTML = slidify( getMarkdownFromSlide( section ), {
- separator: section.getAttribute( 'data-separator' ),
- verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
- notesSeparator: section.getAttribute( 'data-separator-notes' ),
- attributes: getForwardedAttributes( section )
- });
-
- }
- else {
- section.innerHTML = createMarkdownSlide( getMarkdownFromSlide( section ) );
- }
- }
-
- }
-
- /**
- * Check if a node value has the attributes pattern.
- * If yes, extract it and add that value as one or several attributes
- * the the terget element.
- *
- * You need Cache Killer on Chrome to see the effect on any FOM transformation
- * directly on refresh (F5)
- * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
- */
- function addAttributeInElement( node, elementTarget, separator ) {
-
- var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
- var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"=]+?)\"", 'mg' );
- var nodeValue = node.nodeValue;
- if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
-
- var classes = matches[1];
- nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
- node.nodeValue = nodeValue;
- while( matchesClass = mardownClassRegex.exec( classes ) ) {
- var name = matchesClass[1];
- var value = matchesClass[2];
- if (name.substr(0, 5) === 'data-' || whiteListAttr.indexOf(name) !== -1)
- elementTarget.setAttribute( name, filterXSS.escapeAttrValue(value) );
- }
- return true;
- }
- return false;
- }
-
- /**
- * Add attributes to the parent element of a text node,
- * or the element of an attribute node.
- */
- function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
-
- if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
- previousParentElement = element;
- for( var i = 0; i < element.childNodes.length; i++ ) {
- childElement = element.childNodes[i];
- if ( i > 0 ) {
- j = i - 1;
- while ( j >= 0 ) {
- aPreviousChildElement = element.childNodes[j];
- if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
- previousParentElement = aPreviousChildElement;
- break;
- }
- j = j - 1;
- }
- }
- parentSection = section;
- if( childElement.nodeName == "section" ) {
- parentSection = childElement ;
- previousParentElement = childElement ;
- }
- if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
- addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
- }
- }
- }
-
- if ( element.nodeType == Node.COMMENT_NODE ) {
- if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
- addAttributeInElement( element, section, separatorSectionAttributes );
- }
- }
- }
-
- /**
- * Converts any current data-markdown slides in the
- * DOM to HTML.
- */
- function convertSlides() {
-
- var sections = document.querySelectorAll( '[data-markdown]');
-
- for( var i = 0, len = sections.length; i < len; i++ ) {
-
- var section = sections[i];
-
- // Only parse the same slide once
- if( !section.getAttribute( 'data-markdown-parsed' ) ) {
-
- section.setAttribute( 'data-markdown-parsed', true )
-
- var notes = section.querySelector( 'aside.notes' );
- var markdown = getMarkdownFromSlide( section );
-
- var rendered = md.render(markdown);
- rendered = preventXSS(rendered);
- var result = postProcess(rendered);
- section.innerHTML = result[0].outerHTML;
- addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
- section.parentNode.getAttribute( 'data-element-attributes' ) ||
- DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
- section.getAttribute( 'data-attributes' ) ||
- section.parentNode.getAttribute( 'data-attributes' ) ||
- DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
-
- // If there were notes, we need to re-add them after
- // having overwritten the section's HTML
- if( notes ) {
- section.appendChild( notes );
- }
-
- }
-
- }
-
- }
-
- // API
- return {
-
- initialize: function() {
- processSlides();
- convertSlides();
- },
-
- // TODO: Do these belong in the API?
- processSlides: processSlides,
- convertSlides: convertSlides,
- slidify: slidify
-
- };
-
-}));
+(function (root, factory) {
+ if (typeof exports === 'object') {
+ module.exports = factory()
+ } else {
+ // Browser globals (root is window)
+ root.RevealMarkdown = factory()
+ root.RevealMarkdown.initialize()
+ }
+}(this, function () {
+ var DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$'
+ var DEFAULT_NOTES_SEPARATOR = 'note:'
+ var DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\.element\\s*?(.+?)$'
+ var DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\.slide:\\s*?(\\S.+?)$'
+
+ var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__'
+
+ /**
+ * Retrieves the markdown contents of a slide section
+ * element. Normalizes leading tabs/whitespace.
+ */
+ function getMarkdownFromSlide (section) {
+ var template = section.querySelector('script')
+
+ // strip leading whitespace so it isn't evaluated as code
+ var text = (template || section).textContent
+
+ // restore script end tags
+ text = text.replace(new RegExp(SCRIPT_END_PLACEHOLDER, 'g'), '</script>')
+
+ var leadingWs = text.match(/^\n?(\s*)/)[1].length
+ var leadingTabs = text.match(/^\n?(\t*)/)[1].length
+
+ if (leadingTabs > 0) {
+ text = text.replace(new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n')
+ } else if (leadingWs > 1) {
+ text = text.replace(new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n')
+ }
+
+ return text
+ }
+
+ /**
+ * Given a markdown slide section element, this will
+ * return all arguments that aren't related to markdown
+ * parsing. Used to forward any other user-defined arguments
+ * to the output markdown slide.
+ */
+ function getForwardedAttributes (section) {
+ var attributes = section.attributes
+ var result = []
+
+ for (var i = 0, len = attributes.length; i < len; i++) {
+ var name = attributes[i].name
+ var value = attributes[i].value
+
+ // disregard attributes that are used for markdown loading/parsing
+ if (/data-(markdown|separator|vertical|notes)/gi.test(name)) continue
+
+ if (value) {
+ result.push(name + '="' + value + '"')
+ } else {
+ result.push(name)
+ }
+ }
+
+ return result.join(' ')
+ }
+
+ /**
+ * Inspects the given options and fills out default
+ * values for what's not defined.
+ */
+ function getSlidifyOptions (options) {
+ options = options || {}
+ options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR
+ options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR
+ options.attributes = options.attributes || ''
+
+ return options
+ }
+
+ /**
+ * Helper function for constructing a markdown slide.
+ */
+ function createMarkdownSlide (content, options) {
+ options = getSlidifyOptions(options)
+
+ var notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi'))
+
+ if (notesMatch.length === 2) {
+ content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>'
+ }
+
+ // prevent script end tags in the content from interfering
+ // with parsing
+ content = content.replace(/<\/script>/g, SCRIPT_END_PLACEHOLDER)
+
+ return '<script type="text/template">' + content + '</script>'
+ }
+
+ /**
+ * Parses a data string into multiple slides based
+ * on the passed in separator arguments.
+ */
+ function slidify (markdown, options) {
+ options = getSlidifyOptions(options)
+
+ var separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? '|' + options.verticalSeparator : ''), 'mg')
+ var horizontalSeparatorRegex = new RegExp(options.separator)
+
+ var matches
+ var lastIndex = 0
+ var isHorizontal
+ var wasHorizontal = true
+ var content
+ var sectionStack = []
+
+ // iterate until all blocks between separators are stacked up
+ while ((matches = separatorRegex.exec(markdown)) !== null) {
+ // determine direction (horizontal by default)
+ isHorizontal = horizontalSeparatorRegex.test(matches[0])
+
+ if (!isHorizontal && wasHorizontal) {
+ // create vertical stack
+ sectionStack.push([])
+ }
+
+ // pluck slide content from markdown input
+ content = markdown.substring(lastIndex, matches.index)
+
+ if (isHorizontal && wasHorizontal) {
+ // add to horizontal stack
+ sectionStack.push(content)
+ } else {
+ // add to vertical stack
+ sectionStack[sectionStack.length - 1].push(content)
+ }
+
+ lastIndex = separatorRegex.lastIndex
+ wasHorizontal = isHorizontal
+ }
+
+ // add the remaining slide
+ (wasHorizontal ? sectionStack : sectionStack[sectionStack.length - 1]).push(markdown.substring(lastIndex))
+
+ var markdownSections = ''
+
+ // flatten the hierarchical stack, and insert <section data-markdown> tags
+ for (var i = 0, len = sectionStack.length; i < len; i++) {
+ // vertical
+ if (sectionStack[i] instanceof Array) {
+ markdownSections += '<section ' + options.attributes + '>'
+
+ sectionStack[i].forEach(function (child) {
+ markdownSections += '<section data-markdown>' + createMarkdownSlide(child, options) + '</section>'
+ })
+
+ markdownSections += '</section>'
+ } else {
+ markdownSections += '<section ' + options.attributes + ' data-markdown>' + createMarkdownSlide(sectionStack[i], options) + '</section>'
+ }
+ }
+
+ return markdownSections
+ }
+
+ /**
+ * Parses any current data-markdown slides, splits
+ * multi-slide markdown into separate sections and
+ * handles loading of external markdown.
+ */
+ function processSlides () {
+ var sections = document.querySelectorAll('[data-markdown]')
+ var section
+
+ for (var i = 0, len = sections.length; i < len; i++) {
+ section = sections[i]
+
+ if (section.getAttribute('data-markdown').length) {
+ var xhr = new XMLHttpRequest()
+ var url = section.getAttribute('data-markdown')
+
+ var datacharset = section.getAttribute('data-charset')
+
+ // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
+ if (datacharset !== null && datacharset !== '') {
+ xhr.overrideMimeType('text/html; charset=' + datacharset)
+ }
+
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
+ section.outerHTML = slidify(xhr.responseText, {
+ separator: section.getAttribute('data-separator'),
+ verticalSeparator: section.getAttribute('data-separator-vertical'),
+ notesSeparator: section.getAttribute('data-separator-notes'),
+ attributes: getForwardedAttributes(section)
+ })
+ } else {
+ section.outerHTML = '<section data-state="alert">' +
+ 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
+ 'Check your browser\'s JavaScript console for more details.' +
+ '<p>Remember that you need to serve the presentation HTML from a HTTP server.</p>' +
+ '</section>'
+ }
+ }
+ }
+
+ xhr.open('GET', url, false)
+
+ try {
+ xhr.send()
+ } catch (e) {
+ alert('Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e)
+ }
+ } else if (section.getAttribute('data-separator') || section.getAttribute('data-separator-vertical') || section.getAttribute('data-separator-notes')) {
+ section.outerHTML = slidify(getMarkdownFromSlide(section), {
+ separator: section.getAttribute('data-separator'),
+ verticalSeparator: section.getAttribute('data-separator-vertical'),
+ notesSeparator: section.getAttribute('data-separator-notes'),
+ attributes: getForwardedAttributes(section)
+ })
+ } else {
+ section.innerHTML = createMarkdownSlide(getMarkdownFromSlide(section))
+ }
+ }
+ }
+
+ /**
+ * Check if a node value has the attributes pattern.
+ * If yes, extract it and add that value as one or several attributes
+ * the the terget element.
+ *
+ * You need Cache Killer on Chrome to see the effect on any FOM transformation
+ * directly on refresh (F5)
+ * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
+ */
+ function addAttributeInElement (node, elementTarget, separator) {
+ var mardownClassesInElementsRegex = new RegExp(separator, 'mg')
+ var mardownClassRegex = new RegExp('([^"= ]+?)="([^"=]+?)"', 'mg')
+ var nodeValue = node.nodeValue
+ var matches
+ var matchesClass
+ if ((matches = mardownClassesInElementsRegex.exec(nodeValue))) {
+ var classes = matches[1]
+ nodeValue = nodeValue.substring(0, matches.index) + nodeValue.substring(mardownClassesInElementsRegex.lastIndex)
+ node.nodeValue = nodeValue
+ while ((matchesClass = mardownClassRegex.exec(classes))) {
+ var name = matchesClass[1]
+ var value = matchesClass[2]
+ if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, window.filterXSS.escapeAttrValue(value)) }
+ }
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Add attributes to the parent element of a text node,
+ * or the element of an attribute node.
+ */
+ function addAttributes (section, element, previousElement, separatorElementAttributes, separatorSectionAttributes) {
+ if (element != null && element.childNodes !== undefined && element.childNodes.length > 0) {
+ var previousParentElement = element
+ for (var i = 0; i < element.childNodes.length; i++) {
+ var childElement = element.childNodes[i]
+ if (i > 0) {
+ let j = i - 1
+ while (j >= 0) {
+ var aPreviousChildElement = element.childNodes[j]
+ if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== 'BR') {
+ previousParentElement = aPreviousChildElement
+ break
+ }
+ j = j - 1
+ }
+ }
+ var parentSection = section
+ if (childElement.nodeName === 'section') {
+ parentSection = childElement
+ previousParentElement = childElement
+ }
+ if (typeof childElement.setAttribute === 'function' || childElement.nodeType === Node.COMMENT_NODE) {
+ addAttributes(parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes)
+ }
+ }
+ }
+
+ if (element.nodeType === Node.COMMENT_NODE) {
+ if (addAttributeInElement(element, previousElement, separatorElementAttributes) === false) {
+ addAttributeInElement(element, section, separatorSectionAttributes)
+ }
+ }
+ }
+
+ /**
+ * Converts any current data-markdown slides in the
+ * DOM to HTML.
+ */
+ function convertSlides () {
+ var sections = document.querySelectorAll('[data-markdown]')
+
+ for (var i = 0, len = sections.length; i < len; i++) {
+ var section = sections[i]
+
+ // Only parse the same slide once
+ if (!section.getAttribute('data-markdown-parsed')) {
+ section.setAttribute('data-markdown-parsed', true)
+
+ var notes = section.querySelector('aside.notes')
+ var markdown = getMarkdownFromSlide(section)
+
+ var rendered = md.render(markdown)
+ rendered = preventXSS(rendered)
+ var result = window.postProcess(rendered)
+ section.innerHTML = result[0].outerHTML
+ addAttributes(section, section, null, section.getAttribute('data-element-attributes') ||
+ section.parentNode.getAttribute('data-element-attributes') ||
+ DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
+ section.getAttribute('data-attributes') ||
+ section.parentNode.getAttribute('data-attributes') ||
+ DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR)
+
+ // If there were notes, we need to re-add them after
+ // having overwritten the section's HTML
+ if (notes) {
+ section.appendChild(notes)
+ }
+ }
+ }
+ }
+
+ // API
+ return {
+ initialize: function () {
+ processSlides()
+ convertSlides()
+ },
+ // TODO: Do these belong in the API?
+ processSlides: processSlides,
+ convertSlides: convertSlides,
+ slidify: slidify
+ }
+}))
diff --git a/public/js/slide.js b/public/js/slide.js
index 63cf64c6..e743bb55 100644
--- a/public/js/slide.js
+++ b/public/js/slide.js
@@ -1,138 +1,139 @@
-require('../css/extra.css');
-require('../css/site.css');
+/* eslint-env browser, jquery */
+/* global serverurl, Reveal */
-import { md, updateLastChange, finishView } from './extra';
+require('../css/extra.css')
+require('../css/site.css')
-import { preventXSS } from './render';
+import { md, updateLastChange, finishView } from './extra'
-const body = $(".slides").text();
+const body = $('.slides').text()
-createtime = lastchangeui.time.attr('data-createtime');
-lastchangetime = lastchangeui.time.attr('data-updatetime');
-updateLastChange();
-const url = window.location.pathname;
-$('.ui-edit').attr('href', `${url}/edit`);
+window.createtime = window.lastchangeui.time.attr('data-createtime')
+window.lastchangetime = window.lastchangeui.time.attr('data-updatetime')
+updateLastChange()
+const url = window.location.pathname
+$('.ui-edit').attr('href', `${url}/edit`)
$(document).ready(() => {
- //tooltip
- $('[data-toggle="tooltip"]').tooltip();
-});
-
-function extend() {
- const target = {};
-
- for (const source of arguments) {
- for (const key in source) {
- if (source.hasOwnProperty(key)) {
- target[key] = source[key];
- }
- }
+ // tooltip
+ $('[data-toggle="tooltip"]').tooltip()
+})
+
+function extend () {
+ const target = {}
+
+ for (const source of arguments) {
+ for (const key in source) {
+ if (source.hasOwnProperty(key)) {
+ target[key] = source[key]
+ }
}
+ }
- return target;
+ return target
}
// Optional libraries used to extend on reveal.js
const deps = [{
- src: `${serverurl}/build/reveal.js/lib/js/classList.js`,
- condition() {
- return !document.body.classList;
- }
+ src: `${serverurl}/build/reveal.js/lib/js/classList.js`,
+ condition () {
+ return !document.body.classList
+ }
}, {
- src: `${serverurl}/js/reveal-markdown.js`,
- callback() {
- const slideOptions = {
- separator: '^(\r\n?|\n)---(\r\n?|\n)$',
- verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
- };
- const slides = RevealMarkdown.slidify(body, slideOptions);
- $(".slides").html(slides);
- RevealMarkdown.initialize();
- $(".slides").show();
+ src: `${serverurl}/js/reveal-markdown.js`,
+ callback () {
+ const slideOptions = {
+ separator: '^(\r\n?|\n)---(\r\n?|\n)$',
+ verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$'
}
+ const slides = window.RevealMarkdown.slidify(body, slideOptions)
+ $('.slides').html(slides)
+ window.RevealMarkdown.initialize()
+ $('.slides').show()
+ }
}, {
- src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`,
- async: true,
- condition() {
- return !!document.body.classList;
- }
-}];
+ src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`,
+ async: true,
+ condition () {
+ return !!document.body.classList
+ }
+}]
// default options to init reveal.js
const defaultOptions = {
- controls: true,
- progress: true,
- slideNumber: true,
- history: true,
- center: true,
- transition: 'none',
- dependencies: deps
-};
+ controls: true,
+ progress: true,
+ slideNumber: true,
+ history: true,
+ center: true,
+ transition: 'none',
+ dependencies: deps
+}
// options from yaml meta
-const meta = JSON.parse($("#meta").text());
-var options = meta.slideOptions || {};
+const meta = JSON.parse($('#meta').text())
+var options = meta.slideOptions || {}
-const view = $('.reveal');
+const view = $('.reveal')
-//text language
-if (meta.lang && typeof meta.lang == "string") {
- view.attr('lang', meta.lang);
+// text language
+if (meta.lang && typeof meta.lang === 'string') {
+ view.attr('lang', meta.lang)
} else {
- view.removeAttr('lang');
+ view.removeAttr('lang')
}
-//text direction
-if (meta.dir && typeof meta.dir == "string" && meta.dir == "rtl") {
- options.rtl = true;
+// text direction
+if (meta.dir && typeof meta.dir === 'string' && meta.dir === 'rtl') {
+ options.rtl = true
} else {
- options.rtl = false;
+ options.rtl = false
}
-//breaks
+// breaks
if (typeof meta.breaks === 'boolean' && !meta.breaks) {
- md.options.breaks = false;
+ md.options.breaks = false
} else {
- md.options.breaks = true;
+ md.options.breaks = true
}
// options from URL query string
-const queryOptions = Reveal.getQueryHash() || {};
+const queryOptions = Reveal.getQueryHash() || {}
-var options = extend(defaultOptions, options, queryOptions);
-Reveal.initialize(options);
+options = extend(defaultOptions, options, queryOptions)
+Reveal.initialize(options)
window.viewAjaxCallback = () => {
- Reveal.layout();
-};
-
-function renderSlide(event) {
- if (window.location.search.match( /print-pdf/gi )) {
- const slides = $('.slides');
- var title = document.title;
- finishView(slides);
- document.title = title;
- Reveal.layout();
- } else {
- const markdown = $(event.currentSlide);
- if (!markdown.attr('data-rendered')) {
- var title = document.title;
- finishView(markdown);
- markdown.attr('data-rendered', 'true');
- document.title = title;
- Reveal.layout();
- }
+ Reveal.layout()
+}
+
+function renderSlide (event) {
+ if (window.location.search.match(/print-pdf/gi)) {
+ const slides = $('.slides')
+ let title = document.title
+ finishView(slides)
+ document.title = title
+ Reveal.layout()
+ } else {
+ const markdown = $(event.currentSlide)
+ if (!markdown.attr('data-rendered')) {
+ let title = document.title
+ finishView(markdown)
+ markdown.attr('data-rendered', 'true')
+ document.title = title
+ Reveal.layout()
}
+ }
}
Reveal.addEventListener('ready', event => {
- renderSlide(event);
- const markdown = $(event.currentSlide);
+ renderSlide(event)
+ const markdown = $(event.currentSlide)
// force browser redraw
- setTimeout(() => {
- markdown.hide().show(0);
- }, 0);
-});
-Reveal.addEventListener('slidechanged', renderSlide);
+ setTimeout(() => {
+ markdown.hide().show(0)
+ }, 0)
+})
+Reveal.addEventListener('slidechanged', renderSlide)
-const isMacLike = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) ? true : false;
+const isMacLike = !!navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)
-if (!isMacLike) $('.container').addClass('hidescrollbar');
+if (!isMacLike) $('.container').addClass('hidescrollbar')
diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js
index c9693176..c227f83f 100644
--- a/public/js/syncscroll.js
+++ b/public/js/syncscroll.js
@@ -1,365 +1,367 @@
+/* eslint-env browser, jquery */
+/* global _ */
// Inject line numbers for sync scroll.
-import markdownitContainer from 'markdown-it-container';
+import markdownitContainer from 'markdown-it-container'
-import { md } from './extra';
+import { md } from './extra'
-function addPart(tokens, idx) {
- if (tokens[idx].map && tokens[idx].level === 0) {
- 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);
- }
+function addPart (tokens, idx) {
+ if (tokens[idx].map && tokens[idx].level === 0) {
+ 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)
+ }
}
md.renderer.rules.blockquote_open = function (tokens, idx, options, env, self) {
- tokens[idx].attrJoin('class', 'raw');
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
+}
md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
+}
md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) {
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ addPart(tokens, idx)
+ 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) {
- 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(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ if (tokens[idx].map) {
+ 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(...arguments)
+}
md.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) {
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
+}
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
+}
md.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) {
- addPart(tokens, idx);
- return self.renderToken(...arguments);
-};
+ addPart(tokens, idx)
+ 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(...arguments);
-};
+ tokens[idx].attrJoin('class', 'raw')
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
+}
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];
- if (/\!$/.test(info)) token.attrJoin('class', 'wrap');
- token.attrJoin('class', options.langPrefix + langName.replace(/\=$|\=\d+$|\=\+$|\!$|\=\!/, ''));
- token.attrJoin('class', 'hljs');
- token.attrJoin('class', 'raw');
- }
-
- if (options.highlight) {
- highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content);
- } else {
- highlighted = md.utils.escapeHtml(token.content);
- }
-
- if (highlighted.indexOf('<pre') === 0) {
- return `${highlighted}\n`;
- }
-
- if (tokens[idx].map && tokens[idx].level === 0) {
- 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`;
-};
+ 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]
+ if (/!$/.test(info)) token.attrJoin('class', 'wrap')
+ token.attrJoin('class', options.langPrefix + langName.replace(/=$|=\d+$|=\+$|!$|=!/, ''))
+ token.attrJoin('class', 'hljs')
+ token.attrJoin('class', 'raw')
+ }
+
+ if (options.highlight) {
+ highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
+ } else {
+ highlighted = md.utils.escapeHtml(token.content)
+ }
+
+ if (highlighted.indexOf('<pre') === 0) {
+ return `${highlighted}\n`
+ }
+
+ if (tokens[idx].map && tokens[idx].level === 0) {
+ 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`
+}
md.renderer.rules.code_block = (tokens, idx, options, env, self) => {
- if (tokens[idx].map && tokens[idx].level === 0) {
- 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`;
-};
-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()}`);
- addPart(tokens, idx);
- return self.renderToken(...arguments);
+ if (tokens[idx].map && tokens[idx].level === 0) {
+ 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`
+}
+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()}`)
+ addPart(tokens, idx)
+ return self.renderToken(...arguments)
}
-md.use(markdownitContainer, 'success', { render: renderContainer });
-md.use(markdownitContainer, 'info', { render: renderContainer });
-md.use(markdownitContainer, 'warning', { render: renderContainer });
-md.use(markdownitContainer, 'danger', { render: renderContainer });
+md.use(markdownitContainer, 'success', { render: renderContainer })
+md.use(markdownitContainer, 'info', { render: renderContainer })
+md.use(markdownitContainer, 'warning', { render: renderContainer })
+md.use(markdownitContainer, 'danger', { render: renderContainer })
// FIXME: expose syncscroll to window
-window.syncscroll = true;
+window.syncscroll = true
-window.preventSyncScrollToEdit = false;
-window.preventSyncScrollToView = false;
+window.preventSyncScrollToEdit = false
+window.preventSyncScrollToView = false
-const editScrollThrottle = 5;
-const viewScrollThrottle = 5;
-const buildMapThrottle = 100;
+const editScrollThrottle = 5
+const viewScrollThrottle = 5
+const buildMapThrottle = 100
-let viewScrolling = false;
-let editScrolling = false;
+let viewScrolling = false
+let editScrolling = false
-let editArea = null;
-let viewArea = null;
-let markdownArea = null;
+let editArea = null
+let viewArea = null
+let markdownArea = null
-export function setupSyncAreas(edit, view, markdown) {
- editArea = edit;
- viewArea = view;
- markdownArea = markdown;
- editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle));
- viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle));
+export function setupSyncAreas (edit, view, markdown) {
+ editArea = edit
+ viewArea = view
+ markdownArea = markdown
+ editArea.on('scroll', _.throttle(syncScrollToView, editScrollThrottle))
+ viewArea.on('scroll', _.throttle(syncScrollToEdit, viewScrollThrottle))
}
-let scrollMap, lineHeightMap, viewTop, viewBottom;
+let scrollMap, lineHeightMap, viewTop, viewBottom
-export function clearMap() {
- scrollMap = null;
- lineHeightMap = null;
- viewTop = null;
- viewBottom = null;
+export function clearMap () {
+ scrollMap = null
+ lineHeightMap = null
+ viewTop = null
+ viewBottom = null
}
-window.viewAjaxCallback = clearMap;
+window.viewAjaxCallback = clearMap
-const 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;
- let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap;
-
- offset = viewArea.scrollTop() - viewArea.offset().top;
- _scrollMap = [];
- nonEmptyList = [];
- _lineHeightMap = [];
- viewTop = 0;
- viewBottom = viewArea[0].scrollHeight - viewArea.height();
-
- acc = 0;
- const lines = editor.getValue().split('\n');
- const lineHeight = editor.defaultTextHeight();
- for (i = 0; i < lines.length; i++) {
- const str = lines[i];
-
- _lineHeightMap.push(acc);
-
- if (str.length === 0) {
- acc++;
- continue;
- }
-
- const h = editor.heightAtLine(i + 1) - editor.heightAtLine(i);
- acc += Math.round(h / lineHeight);
+function buildMapInner (callback) {
+ if (!viewArea || !markdownArea) return
+ let i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount, acc, _scrollMap
+
+ offset = viewArea.scrollTop() - viewArea.offset().top
+ _scrollMap = []
+ nonEmptyList = []
+ _lineHeightMap = []
+ viewTop = 0
+ viewBottom = viewArea[0].scrollHeight - viewArea.height()
+
+ acc = 0
+ const lines = window.editor.getValue().split('\n')
+ const lineHeight = window.editor.defaultTextHeight()
+ for (i = 0; i < lines.length; i++) {
+ const str = lines[i]
+
+ _lineHeightMap.push(acc)
+
+ if (str.length === 0) {
+ acc++
+ continue
}
- _lineHeightMap.push(acc);
- linesCount = acc;
- for (i = 0; i < linesCount; i++) {
- _scrollMap.push(-1);
- }
+ const h = window.editor.heightAtLine(i + 1) - window.editor.heightAtLine(i)
+ acc += Math.round(h / lineHeight)
+ }
+ _lineHeightMap.push(acc)
+ linesCount = acc
+
+ for (i = 0; i < linesCount; i++) {
+ _scrollMap.push(-1)
+ }
- nonEmptyList.push(0);
+ nonEmptyList.push(0)
// make the first line go top
- _scrollMap[0] = viewTop;
-
- const parts = markdownArea.find('.part').toArray();
- for (i = 0; i < parts.length; i++) {
- const $el = $(parts[i]);
- let t = $el.attr('data-startline') - 1;
- if (t === '') {
- return;
- }
- t = _lineHeightMap[t];
- if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) {
- nonEmptyList.push(t);
- }
- _scrollMap[t] = Math.round($el.offset().top + offset - 10);
+ _scrollMap[0] = viewTop
+
+ const parts = markdownArea.find('.part').toArray()
+ for (i = 0; i < parts.length; i++) {
+ const $el = $(parts[i])
+ let t = $el.attr('data-startline') - 1
+ if (t === '') {
+ return
}
+ t = _lineHeightMap[t]
+ if (t !== 0 && t !== nonEmptyList[nonEmptyList.length - 1]) {
+ nonEmptyList.push(t)
+ }
+ _scrollMap[t] = Math.round($el.offset().top + offset - 10)
+ }
- nonEmptyList.push(linesCount);
- _scrollMap[linesCount] = viewArea[0].scrollHeight;
-
- pos = 0;
- for (i = 1; i < linesCount; i++) {
- if (_scrollMap[i] !== -1) {
- pos++;
- continue;
- }
+ nonEmptyList.push(linesCount)
+ _scrollMap[linesCount] = viewArea[0].scrollHeight
- a = nonEmptyList[pos];
- b = nonEmptyList[pos + 1];
- _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a));
+ pos = 0
+ for (i = 1; i < linesCount; i++) {
+ if (_scrollMap[i] !== -1) {
+ pos++
+ continue
}
- _scrollMap[0] = 0;
+ a = nonEmptyList[pos]
+ b = nonEmptyList[pos + 1]
+ _scrollMap[i] = Math.round((_scrollMap[b] * (i - a) + _scrollMap[a] * (b - i)) / (b - a))
+ }
+
+ _scrollMap[0] = 0
- scrollMap = _scrollMap;
- lineHeightMap = _lineHeightMap;
+ scrollMap = _scrollMap
+ lineHeightMap = _lineHeightMap
- if (loaded && callback) callback();
+ if (window.loaded && callback) callback()
}
// sync view scroll progress to edit
-let viewScrollingTimer = null;
-
-export function syncScrollToEdit(event, preventAnimate) {
- if (currentMode != modeType.both || !syncscroll || !editArea) return;
- if (preventSyncScrollToEdit) {
- if (typeof preventSyncScrollToEdit === 'number') {
- preventSyncScrollToEdit--;
- } else {
- preventSyncScrollToEdit = false;
- }
- return;
- }
- if (!scrollMap || !lineHeightMap) {
- buildMap(() => {
- syncScrollToEdit(event, preventAnimate);
- });
- return;
- }
- if (editScrolling) return;
-
- const scrollTop = viewArea[0].scrollTop;
- let lineIndex = 0;
- for (var i = 0, l = scrollMap.length; i < l; i++) {
- if (scrollMap[i] > scrollTop) {
- break;
- } else {
- lineIndex = i;
- }
- }
- let lineNo = 0;
- let lineDiff = 0;
- for (var i = 0, l = lineHeightMap.length; i < l; i++) {
- if (lineHeightMap[i] > lineIndex) {
- break;
- } else {
- lineNo = lineHeightMap[i];
- lineDiff = lineHeightMap[i + 1] - lineNo;
- }
- }
+let viewScrollingTimer = null
- 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;
- topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos);
- posToNextDiff = textHeight * topDiffPercent;
- posTo += Math.ceil(posToNextDiff);
+export function syncScrollToEdit (event, preventAnimate) {
+ if (window.currentMode !== window.modeType.both || !window.syncscroll || !editArea) return
+ if (window.preventSyncScrollToEdit) {
+ if (typeof window.preventSyncScrollToEdit === 'number') {
+ window.preventSyncScrollToEdit--
} else {
- posTo = lineNo * textHeight;
- topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
- posToNextDiff = textHeight * lineDiff * topDiffPercent;
- posTo += Math.ceil(posToNextDiff);
+ window.preventSyncScrollToEdit = false
}
-
- if (preventAnimate) {
- editArea.scrollTop(posTo);
+ return
+ }
+ if (!scrollMap || !lineHeightMap) {
+ buildMap(() => {
+ syncScrollToEdit(event, preventAnimate)
+ })
+ return
+ }
+ if (editScrolling) return
+
+ const scrollTop = viewArea[0].scrollTop
+ let lineIndex = 0
+ for (let i = 0, l = scrollMap.length; i < l; i++) {
+ if (scrollMap[i] > scrollTop) {
+ break
} else {
- const posDiff = Math.abs(scrollInfo.top - posTo);
- var duration = posDiff / 50;
- duration = duration >= 100 ? duration : 100;
- editArea.stop(true, true).animate({
- scrollTop: posTo
- }, duration, "linear");
+ lineIndex = i
}
-
- viewScrolling = true;
- clearTimeout(viewScrollingTimer);
- viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5);
+ }
+ let lineNo = 0
+ let lineDiff = 0
+ for (let i = 0, l = lineHeightMap.length; i < l; i++) {
+ if (lineHeightMap[i] > lineIndex) {
+ break
+ } else {
+ lineNo = lineHeightMap[i]
+ lineDiff = lineHeightMap[i + 1] - lineNo
+ }
+ }
+
+ let posTo = 0
+ let topDiffPercent = 0
+ let posToNextDiff = 0
+ const scrollInfo = window.editor.getScrollInfo()
+ const textHeight = window.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
+ topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos)
+ posToNextDiff = textHeight * topDiffPercent
+ posTo += Math.ceil(posToNextDiff)
+ } else {
+ posTo = lineNo * textHeight
+ topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo])
+ posToNextDiff = textHeight * lineDiff * topDiffPercent
+ posTo += Math.ceil(posToNextDiff)
+ }
+
+ if (preventAnimate) {
+ editArea.scrollTop(posTo)
+ } else {
+ const posDiff = Math.abs(scrollInfo.top - posTo)
+ var duration = posDiff / 50
+ duration = duration >= 100 ? duration : 100
+ editArea.stop(true, true).animate({
+ scrollTop: posTo
+ }, duration, 'linear')
+ }
+
+ viewScrolling = true
+ clearTimeout(viewScrollingTimer)
+ viewScrollingTimer = setTimeout(viewScrollingTimeoutInner, duration * 1.5)
}
-function viewScrollingTimeoutInner() {
- viewScrolling = false;
+function viewScrollingTimeoutInner () {
+ viewScrolling = false
}
// sync edit scroll progress to view
-let editScrollingTimer = null;
-
-export function syncScrollToView(event, preventAnimate) {
- if (currentMode != modeType.both || !syncscroll || !viewArea) return;
- if (preventSyncScrollToView) {
- if (typeof preventSyncScrollToView === 'number') {
- preventSyncScrollToView--;
- } else {
- preventSyncScrollToView = false;
- }
- return;
- }
- if (!scrollMap || !lineHeightMap) {
- buildMap(() => {
- syncScrollToView(event, preventAnimate);
- });
- return;
- }
- if (viewScrolling) return;
-
- 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
- const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight);
- if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
- topDiffPercent = diffToBottom / textHeight;
- posTo = scrollMap[lineNo + 1];
- posToNextDiff = (viewBottom - posTo) * topDiffPercent;
- posTo += Math.floor(posToNextDiff);
- } else {
- topDiffPercent = (scrollInfo.top % textHeight) / textHeight;
- posTo = scrollMap[lineNo];
- posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent;
- posTo += Math.floor(posToNextDiff);
- }
+let editScrollingTimer = null
- if (preventAnimate) {
- viewArea.scrollTop(posTo);
+export function syncScrollToView (event, preventAnimate) {
+ if (window.currentMode !== window.modeType.both || !window.syncscroll || !viewArea) return
+ if (window.preventSyncScrollToView) {
+ if (typeof preventSyncScrollToView === 'number') {
+ window.preventSyncScrollToView--
} else {
- const posDiff = Math.abs(viewArea.scrollTop() - posTo);
- var duration = posDiff / 50;
- duration = duration >= 100 ? duration : 100;
- viewArea.stop(true, true).animate({
- scrollTop: posTo
- }, duration, "linear");
+ window.preventSyncScrollToView = false
}
-
- editScrolling = true;
- clearTimeout(editScrollingTimer);
- editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5);
+ return
+ }
+ if (!scrollMap || !lineHeightMap) {
+ buildMap(() => {
+ syncScrollToView(event, preventAnimate)
+ })
+ return
+ }
+ if (viewScrolling) return
+
+ let lineNo, posTo
+ let topDiffPercent, posToNextDiff
+ const scrollInfo = window.editor.getScrollInfo()
+ const textHeight = window.editor.defaultTextHeight()
+ lineNo = Math.floor(scrollInfo.top / textHeight)
+ // if reach the last line, will start lerp to the bottom
+ const diffToBottom = (scrollInfo.top + scrollInfo.clientHeight) - (scrollInfo.height - textHeight)
+ if (scrollInfo.height > scrollInfo.clientHeight && diffToBottom > 0) {
+ topDiffPercent = diffToBottom / textHeight
+ posTo = scrollMap[lineNo + 1]
+ posToNextDiff = (viewBottom - posTo) * topDiffPercent
+ posTo += Math.floor(posToNextDiff)
+ } else {
+ topDiffPercent = (scrollInfo.top % textHeight) / textHeight
+ posTo = scrollMap[lineNo]
+ posToNextDiff = (scrollMap[lineNo + 1] - posTo) * topDiffPercent
+ posTo += Math.floor(posToNextDiff)
+ }
+
+ if (preventAnimate) {
+ viewArea.scrollTop(posTo)
+ } else {
+ const posDiff = Math.abs(viewArea.scrollTop() - posTo)
+ var duration = posDiff / 50
+ duration = duration >= 100 ? duration : 100
+ viewArea.stop(true, true).animate({
+ scrollTop: posTo
+ }, duration, 'linear')
+ }
+
+ editScrolling = true
+ clearTimeout(editScrollingTimer)
+ editScrollingTimer = setTimeout(editScrollingTimeoutInner, duration * 1.5)
}
-function editScrollingTimeoutInner() {
- editScrolling = false;
+function editScrollingTimeoutInner () {
+ editScrolling = false
}
diff --git a/public/vendor/md-toc.js b/public/vendor/md-toc.js
index 200275a5..f93f7921 100755
--- a/public/vendor/md-toc.js
+++ b/public/vendor/md-toc.js
@@ -1,129 +1,123 @@
+/* eslint-env browser, jquery */
/**
* md-toc.js v1.0.2
* https://github.com/yijian166/md-toc.js
*/
(function (window) {
- function Toc(id, options) {
- this.el = document.getElementById(id);
- if (!this.el) return;
- this.options = options || {};
- this.tocLevel = parseInt(options.level) || 0;
- this.tocClass = options['class'] || 'toc';
- this.ulClass = options['ulClass'];
- this.tocTop = parseInt(options.top) || 0;
- this.elChilds = this.el.children;
- this.process = options['process'];
- if (!this.elChilds.length) return;
- this._init();
- }
+ function Toc (id, options) {
+ this.el = document.getElementById(id)
+ if (!this.el) return
+ this.options = options || {}
+ this.tocLevel = parseInt(options.level) || 0
+ this.tocClass = options['class'] || 'toc'
+ this.ulClass = options['ulClass']
+ this.tocTop = parseInt(options.top) || 0
+ this.elChilds = this.el.children
+ this.process = options['process']
+ if (!this.elChilds.length) return
+ this._init()
+ }
- Toc.prototype._init = function () {
- this._collectTitleElements();
- this._createTocContent();
- this._showToc();
- };
+ Toc.prototype._init = function () {
+ this._collectTitleElements()
+ this._createTocContent()
+ this._showToc()
+ }
- Toc.prototype._collectTitleElements = function () {
- this._elTitlesNames = [],
- this.elTitleElements = [];
- for (var i = 1; i < 7; i++) {
- if (this.el.getElementsByTagName('h' + i).length) {
- this._elTitlesNames.push('h' + i);
- }
- }
+ Toc.prototype._collectTitleElements = function () {
+ this._elTitlesNames = []
+ this.elTitleElements = []
+ for (var i = 1; i < 7; i++) {
+ if (this.el.getElementsByTagName('h' + i).length) {
+ this._elTitlesNames.push('h' + i)
+ }
+ }
- this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length;
+ this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length
- for (var j = 0; j < this.elChilds.length; j++) {
- this._elChildName = this.elChilds[j].tagName.toLowerCase();
- if (this._elTitlesNames.toString().match(this._elChildName)) {
- this.elTitleElements.push(this.elChilds[j]);
- }
- }
- };
+ for (var j = 0; j < this.elChilds.length; j++) {
+ this._elChildName = this.elChilds[j].tagName.toLowerCase()
+ if (this._elTitlesNames.toString().match(this._elChildName)) {
+ this.elTitleElements.push(this.elChilds[j])
+ }
+ }
+ }
- Toc.prototype._createTocContent = function () {
- this._elTitleElementsLen = this.elTitleElements.length;
- if (!this._elTitleElementsLen) return;
- this.tocContent = '';
- this._tempLists = [];
+ Toc.prototype._createTocContent = function () {
+ this._elTitleElementsLen = this.elTitleElements.length
+ if (!this._elTitleElementsLen) return
+ this.tocContent = ''
+ this._tempLists = []
- var url = location.origin + location.pathname;
- for (var i = 0; i < this._elTitleElementsLen; i++) {
- var j = i + 1;
- this._elTitleElement = this.elTitleElements[i];
- this._elTitleElementName = this._elTitleElement.tagName;
- this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '');
- var id = this._elTitleElement.getAttribute('id');
- if (!id) {
- this._elTitleElement.setAttribute('id', 'tip' + i);
- id = '#tip' + i;
- } else {
- id = '#' + id;
- }
+ for (var i = 0; i < this._elTitleElementsLen; i++) {
+ var j = i + 1
+ this._elTitleElement = this.elTitleElements[i]
+ this._elTitleElementName = this._elTitleElement.tagName
+ this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '')
+ var id = this._elTitleElement.getAttribute('id')
+ if (!id) {
+ this._elTitleElement.setAttribute('id', 'tip' + i)
+ id = '#tip' + i
+ } else {
+ id = '#' + id
+ }
- this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>';
+ this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>'
- if (j != this._elTitleElementsLen) {
- this._elNextTitleElementName = this.elTitleElements[j].tagName;
- if (this._elTitleElementName != this._elNextTitleElementName) {
- var checkColse = false,
- y = 1;
- for (var t = this._tempLists.length - 1; t >= 0; t--) {
- if (this._tempLists[t].tagName == this._elNextTitleElementName) {
- checkColse = true;
- break;
- }
- y++;
- }
- if (checkColse) {
- this.tocContent += new Array(y + 1).join('</li></ul>');
- this._tempLists.length = this._tempLists.length - y;
- } else {
- this._tempLists.push(this._elTitleElement);
- if (this.ulClass)
- this.tocContent += '<ul class="' + this.ulClass + '">';
- else
- this.tocContent += '<ul>';
- }
- } else {
- this.tocContent += '</li>';
- }
- } else {
- if (this._tempLists.length) {
- this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>');
- } else {
- this.tocContent += '</li>';
- }
+ if (j !== this._elTitleElementsLen) {
+ this._elNextTitleElementName = this.elTitleElements[j].tagName
+ if (this._elTitleElementName !== this._elNextTitleElementName) {
+ var checkColse = false
+ var y = 1
+ for (var t = this._tempLists.length - 1; t >= 0; t--) {
+ if (this._tempLists[t].tagName === this._elNextTitleElementName) {
+ checkColse = true
+ break
}
+ y++
+ }
+ if (checkColse) {
+ this.tocContent += new Array(y + 1).join('</li></ul>')
+ this._tempLists.length = this._tempLists.length - y
+ } else {
+ this._tempLists.push(this._elTitleElement)
+ if (this.ulClass) { this.tocContent += '<ul class="' + this.ulClass + '">' } else { this.tocContent += '<ul>' }
+ }
+ } else {
+ this.tocContent += '</li>'
}
- if (this.ulClass)
- this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>';
- else
- this.tocContent = '<ul>' + this.tocContent + '</ul>';
- };
-
- Toc.prototype._showToc = function () {
- this.toc = document.createElement('div');
- this.toc.innerHTML = this.tocContent;
- this.toc.setAttribute('class', this.tocClass);
- if (!this.options.targetId) {
- this.el.appendChild(this.toc);
+ } else {
+ if (this._tempLists.length) {
+ this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>')
} else {
- document.getElementById(this.options.targetId).appendChild(this.toc);
+ this.tocContent += '</li>'
}
- var self = this;
- if (this.tocTop > -1) {
- window.onscroll = function () {
- var t = document.documentElement.scrollTop || document.body.scrollTop;
- if (t < self.tocTop) {
- self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;');
- } else {
- self.toc.setAttribute('style', 'position:fixed;top:10px;');
- }
- }
+ }
+ }
+ if (this.ulClass) { this.tocContent = '<ul class="' + this.ulClass + '">' + this.tocContent + '</ul>' } else { this.tocContent = '<ul>' + this.tocContent + '</ul>' }
+ }
+
+ Toc.prototype._showToc = function () {
+ this.toc = document.createElement('div')
+ this.toc.innerHTML = this.tocContent
+ this.toc.setAttribute('class', this.tocClass)
+ if (!this.options.targetId) {
+ this.el.appendChild(this.toc)
+ } else {
+ document.getElementById(this.options.targetId).appendChild(this.toc)
+ }
+ var self = this
+ if (this.tocTop > -1) {
+ window.onscroll = function () {
+ var t = document.documentElement.scrollTop || document.body.scrollTop
+ if (t < self.tocTop) {
+ self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;')
+ } else {
+ self.toc.setAttribute('style', 'position:fixed;top:10px;')
}
- };
- window.Toc = Toc;
-})(window); \ No newline at end of file
+ }
+ }
+ }
+ window.Toc = Toc
+})(window)
diff --git a/webpack.config.js b/webpack.config.js
index 236490b9..f9f0a1c9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,33 +1,33 @@
-var baseConfig = require('./webpackBaseConfig');
-var ExtractTextPlugin = require("extract-text-webpack-plugin");
-var path = require('path');
+var baseConfig = require('./webpackBaseConfig')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var path = require('path')
module.exports = [Object.assign({}, baseConfig, {
- plugins: baseConfig.plugins.concat([
- new ExtractTextPlugin("[name].css")
- ])
+ plugins: baseConfig.plugins.concat([
+ new ExtractTextPlugin('[name].css')
+ ])
}), {
- entry: {
- htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
- },
- module: {
- loaders: [{
- test: /\.css$/,
- loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
- }, {
- test: /\.scss$/,
- loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
- }, {
- test: /\.less$/,
- loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
- }]
- },
- output: {
- path: path.join(__dirname, 'public/build'),
- publicPath: '/build/',
- filename: '[name].js'
- },
- plugins: [
- new ExtractTextPlugin("html.min.css")
- ]
-}];
+ entry: {
+ htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
+ },
+ module: {
+ loaders: [{
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+ }, {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+ }, {
+ test: /\.less$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+ }]
+ },
+ output: {
+ path: path.join(__dirname, 'public/build'),
+ publicPath: '/build/',
+ filename: '[name].js'
+ },
+ plugins: [
+ new ExtractTextPlugin('html.min.css')
+ ]
+}]
diff --git a/webpack.production.js b/webpack.production.js
index 7c690d28..7b42843a 100644
--- a/webpack.production.js
+++ b/webpack.production.js
@@ -1,63 +1,63 @@
-var baseConfig = require('./webpackBaseConfig');
-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');
+var baseConfig = require('./webpackBaseConfig')
+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([
- new webpack.DefinePlugin({
- 'process.env': {
- 'NODE_ENV': JSON.stringify('production')
- }
- }),
- new ParallelUglifyPlugin({
- uglifyJS: {
- compress: {
- warnings: false
- },
- mangle: false,
- sourceMap: false
- }
- }),
- new ExtractTextPlugin("[name].[hash].css")
- ]),
+ plugins: baseConfig.plugins.concat([
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }),
+ new ParallelUglifyPlugin({
+ uglifyJS: {
+ compress: {
+ warnings: false
+ },
+ mangle: false,
+ sourceMap: false
+ }
+ }),
+ new ExtractTextPlugin('[name].[hash].css')
+ ]),
- output: {
- path: path.join(__dirname, 'public/build'),
- publicPath: '/build/',
- filename: '[id].[name].[hash].js',
- baseUrl: '<%- url %>'
- }
+ output: {
+ path: path.join(__dirname, 'public/build'),
+ publicPath: '/build/',
+ filename: '[id].[name].[hash].js',
+ baseUrl: '<%- url %>'
+ }
}), {
- entry: {
- htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
- },
- module: {
- loaders: [{
- test: /\.css$/,
- loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
- }, {
- test: /\.scss$/,
- loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
- }, {
- test: /\.less$/,
- loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
- }]
- },
- output: {
- path: path.join(__dirname, 'public/build'),
- publicPath: '/build/',
- filename: '[name].js'
- },
- plugins: [
- new webpack.DefinePlugin({
- 'process.env': {
- 'NODE_ENV': JSON.stringify('production')
- }
- }),
- new ExtractTextPlugin("html.min.css"),
- new OptimizeCssAssetsPlugin()
- ]
-}];
+ entry: {
+ htmlExport: path.join(__dirname, 'public/js/htmlExport.js')
+ },
+ module: {
+ loaders: [{
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+ }, {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+ }, {
+ test: /\.less$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+ }]
+ },
+ output: {
+ path: path.join(__dirname, 'public/build'),
+ publicPath: '/build/',
+ filename: '[name].js'
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production')
+ }
+ }),
+ new ExtractTextPlugin('html.min.css'),
+ new OptimizeCssAssetsPlugin()
+ ]
+}]
diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js
index 419149c7..9ab4c06a 100644
--- a/webpackBaseConfig.js
+++ b/webpackBaseConfig.js
@@ -1,423 +1,439 @@
-var webpack = require('webpack');
-var path = require('path');
-var ExtractTextPlugin = require("extract-text-webpack-plugin");
-var HtmlWebpackPlugin = require('html-webpack-plugin');
-var CopyWebpackPlugin = require('copy-webpack-plugin');
+var webpack = require('webpack')
+var path = require('path')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
- plugins: [
- new webpack.ProvidePlugin({
- Visibility: "visibilityjs",
- Cookies: "js-cookie",
- key: "keymaster",
- $: "jquery",
- jQuery: "jquery",
- "window.jQuery": "jquery",
- "moment": "moment",
- "Handlebars": "handlebars"
- }),
- new webpack.optimize.OccurrenceOrderPlugin(true),
- new webpack.optimize.CommonsChunkPlugin({
- names: ["cover", "index", "pretty", "slide", "vendor"],
- children: true,
- async: true,
- filename: '[name].js',
- minChunks: Infinity
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font', 'index-styles', 'index'],
- filename: path.join(__dirname, 'public/views/build/index-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font-pack', 'index-styles-pack', 'index-styles', 'index'],
- filename: path.join(__dirname, 'public/views/build/index-pack-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['index'],
- filename: path.join(__dirname, 'public/views/build/index-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['common', 'index-pack'],
- filename: path.join(__dirname, 'public/views/build/index-pack-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font', 'cover'],
- filename: path.join(__dirname, 'public/views/build/cover-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font-pack', 'cover-styles-pack', 'cover'],
- filename: path.join(__dirname, 'public/views/build/cover-pack-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['cover'],
- filename: path.join(__dirname, 'public/views/build/cover-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['common', 'cover-pack'],
- filename: path.join(__dirname, 'public/views/build/cover-pack-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font', 'pretty-styles', 'pretty'],
- filename: path.join(__dirname, 'public/views/build/pretty-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font-pack', 'pretty-styles-pack', 'pretty-styles', 'pretty'],
- filename: path.join(__dirname, 'public/views/build/pretty-pack-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['pretty'],
- filename: path.join(__dirname, 'public/views/build/pretty-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['common', 'pretty-pack'],
- filename: path.join(__dirname, 'public/views/build/pretty-pack-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font', 'slide-styles', 'slide'],
- filename: path.join(__dirname, 'public/views/build/slide-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/header.ejs',
- chunks: ['font-pack', 'slide-styles-pack', 'slide-styles', 'slide'],
- filename: path.join(__dirname, 'public/views/build/slide-pack-header.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['slide'],
- filename: path.join(__dirname, 'public/views/build/slide-scripts.ejs'),
- inject: false
- }),
- new HtmlWebpackPlugin({
- template: 'public/views/includes/scripts.ejs',
- chunks: ['slide-pack'],
- filename: path.join(__dirname, 'public/views/build/slide-pack-scripts.ejs'),
- inject: false
- }),
- new CopyWebpackPlugin([
- {
- context: path.join(__dirname, 'node_modules/mathjax'),
- from: {
- glob: '**/*',
- dot: false
- },
- to: 'MathJax/'
- },
- {
- context: path.join(__dirname, 'node_modules/emojify.js'),
- from: {
- glob: '**/*',
- dot: false
- },
- to: 'emojify.js/'
- },
- {
- context: path.join(__dirname, 'node_modules/reveal.js'),
- from: {
- glob: '**/*',
- dot: false
- },
- to: 'reveal.js/'
- }
- ])
+ plugins: [
+ new webpack.ProvidePlugin({
+ Visibility: 'visibilityjs',
+ Cookies: 'js-cookie',
+ key: 'keymaster',
+ $: 'jquery',
+ jQuery: 'jquery',
+ 'window.jQuery': 'jquery',
+ 'moment': 'moment',
+ 'Handlebars': 'handlebars'
+ }),
+ new webpack.optimize.OccurrenceOrderPlugin(true),
+ new webpack.optimize.CommonsChunkPlugin({
+ names: ['cover', 'index', 'pretty', 'slide', 'vendor'],
+ children: true,
+ async: true,
+ filename: '[name].js',
+ minChunks: Infinity
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font', 'index-styles', 'index'],
+ filename: path.join(__dirname, 'public/views/build/index-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font-pack', 'index-styles-pack', 'index-styles', 'index'],
+ filename: path.join(__dirname, 'public/views/build/index-pack-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['index'],
+ filename: path.join(__dirname, 'public/views/build/index-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['common', 'index-pack'],
+ filename: path.join(__dirname, 'public/views/build/index-pack-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font', 'cover'],
+ filename: path.join(__dirname, 'public/views/build/cover-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font-pack', 'cover-styles-pack', 'cover'],
+ filename: path.join(__dirname, 'public/views/build/cover-pack-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['cover'],
+ filename: path.join(__dirname, 'public/views/build/cover-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['common', 'cover-pack'],
+ filename: path.join(__dirname, 'public/views/build/cover-pack-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font', 'pretty-styles', 'pretty'],
+ filename: path.join(__dirname, 'public/views/build/pretty-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font-pack', 'pretty-styles-pack', 'pretty-styles', 'pretty'],
+ filename: path.join(__dirname, 'public/views/build/pretty-pack-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['pretty'],
+ filename: path.join(__dirname, 'public/views/build/pretty-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['common', 'pretty-pack'],
+ filename: path.join(__dirname, 'public/views/build/pretty-pack-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font', 'slide-styles', 'slide'],
+ filename: path.join(__dirname, 'public/views/build/slide-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/header.ejs',
+ chunks: ['font-pack', 'slide-styles-pack', 'slide-styles', 'slide'],
+ filename: path.join(__dirname, 'public/views/build/slide-pack-header.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['slide'],
+ filename: path.join(__dirname, 'public/views/build/slide-scripts.ejs'),
+ inject: false
+ }),
+ new HtmlWebpackPlugin({
+ template: 'public/views/includes/scripts.ejs',
+ chunks: ['slide-pack'],
+ filename: path.join(__dirname, 'public/views/build/slide-pack-scripts.ejs'),
+ inject: false
+ }),
+ new CopyWebpackPlugin([
+ {
+ context: path.join(__dirname, 'node_modules/mathjax'),
+ from: {
+ glob: '**/*',
+ dot: false
+ },
+ to: 'MathJax/'
+ },
+ {
+ context: path.join(__dirname, 'node_modules/emojify.js'),
+ from: {
+ glob: 'dist/**/*',
+ dot: false
+ },
+ to: 'emojify.js/'
+ },
+ {
+ context: path.join(__dirname, 'node_modules/reveal.js'),
+ from: 'js',
+ to: 'reveal.js/js'
+ },
+ {
+ context: path.join(__dirname, 'node_modules/reveal.js'),
+ from: 'css',
+ to: 'reveal.js/css'
+ },
+ {
+ context: path.join(__dirname, 'node_modules/reveal.js'),
+ from: 'lib',
+ to: 'reveal.js/lib'
+ },
+ {
+ context: path.join(__dirname, 'node_modules/reveal.js'),
+ from: 'plugin',
+ to: 'reveal.js/plugin'
+ }
+ ])
+ ],
+ entry: {
+ font: path.join(__dirname, 'public/css/google-font.css'),
+ 'font-pack': path.join(__dirname, 'public/css/font.css'),
+ common: [
+ 'expose?jQuery!expose?$!jquery',
+ 'velocity-animate',
+ 'imports?$=jquery!jquery-mousewheel',
+ 'bootstrap'
],
+ cover: [
+ 'babel-polyfill',
+ path.join(__dirname, 'public/js/cover.js')
+ ],
+ 'cover-styles-pack': [
+ path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+ path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+ path.join(__dirname, 'public/css/bootstrap-social.css'),
+ path.join(__dirname, 'node_modules/select2/select2.css'),
+ path.join(__dirname, 'node_modules/select2/select2-bootstrap.css')
+ ],
+ 'cover-pack': [
+ 'babel-polyfill',
+ 'bootstrap-validator',
+ 'script!listPagnation',
+ 'expose?select2!select2',
+ 'expose?moment!moment',
+ 'script!js-url',
+ path.join(__dirname, 'public/js/cover.js')
+ ],
+ index: [
+ 'babel-polyfill',
+ 'script!jquery-ui-resizable',
+ 'script!js-url',
+ 'expose?filterXSS!xss',
+ 'script!Idle.Js',
+ 'expose?LZString!lz-string',
+ 'script!codemirror',
+ 'script!inlineAttachment',
+ 'script!jqueryTextcomplete',
+ 'script!codemirrorSpellChecker',
+ 'script!codemirrorInlineAttachment',
+ 'script!ot',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/google-drive-upload.js'),
+ path.join(__dirname, 'public/js/google-drive-picker.js'),
+ path.join(__dirname, 'public/js/index.js')
+ ],
+ 'index-styles': [
+ path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'),
+ path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'),
+ path.join(__dirname, 'node_modules/codemirror/lib/codemirror.css'),
+ path.join(__dirname, 'node_modules/codemirror/addon/fold/foldgutter.css'),
+ path.join(__dirname, 'node_modules/codemirror/addon/display/fullscreen.css'),
+ path.join(__dirname, 'node_modules/codemirror/addon/dialog/dialog.css'),
+ path.join(__dirname, 'node_modules/codemirror/addon/scroll/simplescrollbars.css'),
+ path.join(__dirname, 'node_modules/codemirror/addon/search/matchesonscrollbar.css'),
+ path.join(__dirname, 'node_modules/codemirror/theme/monokai.css'),
+ path.join(__dirname, 'node_modules/codemirror/theme/one-dark.css'),
+ path.join(__dirname, 'node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css'),
+ path.join(__dirname, 'node_modules/codemirror/mode/mediawiki/mediawiki.css'),
+ path.join(__dirname, 'public/css/github-extract.css'),
+ path.join(__dirname, 'public/vendor/showup/showup.css'),
+ path.join(__dirname, 'public/css/mermaid.css'),
+ path.join(__dirname, 'public/css/markdown.css'),
+ path.join(__dirname, 'public/css/slide-preview.css')
+ ],
+ 'index-styles-pack': [
+ path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+ path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+ path.join(__dirname, 'public/css/bootstrap-social.css'),
+ path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+ path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+ ],
+ 'index-pack': [
+ 'babel-polyfill',
+ 'expose?Spinner!spin.js',
+ 'script!jquery-ui-resizable',
+ 'bootstrap-validator',
+ 'expose?jsyaml!js-yaml',
+ 'script!mermaid',
+ 'expose?moment!moment',
+ 'script!js-url',
+ 'script!handlebars',
+ 'expose?hljs!highlight.js',
+ 'expose?emojify!emojify.js',
+ 'expose?filterXSS!xss',
+ 'script!Idle.Js',
+ 'script!gist-embed',
+ 'expose?LZString!lz-string',
+ 'script!codemirror',
+ 'script!inlineAttachment',
+ 'script!jqueryTextcomplete',
+ 'script!codemirrorSpellChecker',
+ 'script!codemirrorInlineAttachment',
+ 'script!ot',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?Viz!viz.js',
+ 'expose?io!socket.io-client',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/google-drive-upload.js'),
+ path.join(__dirname, 'public/js/google-drive-picker.js'),
+ path.join(__dirname, 'public/js/index.js')
+ ],
+ pretty: [
+ 'babel-polyfill',
+ 'expose?filterXSS!xss',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/pretty.js')
+ ],
+ 'pretty-styles': [
+ path.join(__dirname, 'public/css/github-extract.css'),
+ path.join(__dirname, 'public/css/mermaid.css'),
+ path.join(__dirname, 'public/css/markdown.css'),
+ path.join(__dirname, 'public/css/slide-preview.css')
+ ],
+ 'pretty-styles-pack': [
+ path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
+ path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+ path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+ path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+ ],
+ 'pretty-pack': [
+ 'babel-polyfill',
+ 'expose?jsyaml!js-yaml',
+ 'script!mermaid',
+ 'expose?moment!moment',
+ 'script!handlebars',
+ 'expose?hljs!highlight.js',
+ 'expose?emojify!emojify.js',
+ 'expose?filterXSS!xss',
+ 'script!gist-embed',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?Viz!viz.js',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/pretty.js')
+ ],
+ slide: [
+ 'babel-polyfill',
+ 'bootstrap-tooltip',
+ 'expose?filterXSS!xss',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/slide.js')
+ ],
+ 'slide-styles': [
+ path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.css'),
+ path.join(__dirname, 'public/css/github-extract.css'),
+ path.join(__dirname, 'public/css/mermaid.css'),
+ path.join(__dirname, 'public/css/markdown.css')
+ ],
+ 'slide-styles-pack': [
+ path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
+ path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
+ path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
+ ],
+ 'slide-pack': [
+ 'babel-polyfill',
+ 'expose?jQuery!expose?$!jquery',
+ 'velocity-animate',
+ 'imports?$=jquery!jquery-mousewheel',
+ 'bootstrap-tooltip',
+ 'expose?jsyaml!js-yaml',
+ 'script!mermaid',
+ 'expose?moment!moment',
+ 'script!handlebars',
+ 'expose?hljs!highlight.js',
+ 'expose?emojify!emojify.js',
+ 'expose?filterXSS!xss',
+ 'script!gist-embed',
+ 'flowchart.js',
+ 'js-sequence-diagrams',
+ 'expose?Viz!viz.js',
+ 'headjs',
+ 'expose?Reveal!reveal.js',
+ 'expose?RevealMarkdown!reveal-markdown',
+ path.join(__dirname, 'public/js/slide.js')
+ ]
+ },
- entry: {
- font: path.join(__dirname, 'public/css/google-font.css'),
- "font-pack": path.join(__dirname, 'public/css/font.css'),
- common: [
- "expose?jQuery!expose?$!jquery",
- "velocity-animate",
- "imports?$=jquery!jquery-mousewheel",
- "bootstrap"
- ],
- cover: [
- "babel-polyfill",
- path.join(__dirname, 'public/js/cover.js')
- ],
- "cover-styles-pack": [
- path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
- path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
- path.join(__dirname, 'public/css/bootstrap-social.css'),
- path.join(__dirname, 'node_modules/select2/select2.css'),
- path.join(__dirname, 'node_modules/select2/select2-bootstrap.css'),
- ],
- "cover-pack": [
- "babel-polyfill",
- "bootstrap-validator",
- "script!listPagnation",
- "expose?select2!select2",
- "expose?moment!moment",
- "script!js-url",
- path.join(__dirname, 'public/js/cover.js')
- ],
- index: [
- "babel-polyfill",
- "script!jquery-ui-resizable",
- "script!js-url",
- "expose?filterXSS!xss",
- "script!Idle.Js",
- "expose?LZString!lz-string",
- "script!codemirror",
- "script!inlineAttachment",
- "script!jqueryTextcomplete",
- "script!codemirrorSpellChecker",
- "script!codemirrorInlineAttachment",
- "script!ot",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/google-drive-upload.js'),
- path.join(__dirname, 'public/js/google-drive-picker.js'),
- path.join(__dirname, 'public/js/index.js')
- ],
- "index-styles": [
- path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'),
- path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'),
- path.join(__dirname, 'node_modules/codemirror/lib/codemirror.css'),
- path.join(__dirname, 'node_modules/codemirror/addon/fold/foldgutter.css'),
- path.join(__dirname, 'node_modules/codemirror/addon/display/fullscreen.css'),
- path.join(__dirname, 'node_modules/codemirror/addon/dialog/dialog.css'),
- path.join(__dirname, 'node_modules/codemirror/addon/scroll/simplescrollbars.css'),
- path.join(__dirname, 'node_modules/codemirror/addon/search/matchesonscrollbar.css'),
- path.join(__dirname, 'node_modules/codemirror/theme/monokai.css'),
- path.join(__dirname, 'node_modules/codemirror/theme/one-dark.css'),
- path.join(__dirname, 'node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css'),
- path.join(__dirname, 'node_modules/codemirror/mode/mediawiki/mediawiki.css'),
- path.join(__dirname, 'public/css/github-extract.css'),
- path.join(__dirname, 'public/vendor/showup/showup.css'),
- path.join(__dirname, 'public/css/mermaid.css'),
- path.join(__dirname, 'public/css/markdown.css'),
- path.join(__dirname, 'public/css/slide-preview.css')
- ],
- "index-styles-pack": [
- path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
- path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
- path.join(__dirname, 'public/css/bootstrap-social.css'),
- path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
- path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
- ],
- "index-pack": [
- "babel-polyfill",
- "expose?Spinner!spin.js",
- "script!jquery-ui-resizable",
- "bootstrap-validator",
- "expose?jsyaml!js-yaml",
- "script!mermaid",
- "expose?moment!moment",
- "script!js-url",
- "script!handlebars",
- "expose?hljs!highlight.js",
- "expose?emojify!emojify.js",
- "expose?filterXSS!xss",
- "script!Idle.Js",
- "script!gist-embed",
- "expose?LZString!lz-string",
- "script!codemirror",
- "script!inlineAttachment",
- "script!jqueryTextcomplete",
- "script!codemirrorSpellChecker",
- "script!codemirrorInlineAttachment",
- "script!ot",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?Viz!viz.js",
- "expose?io!socket.io-client",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/google-drive-upload.js'),
- path.join(__dirname, 'public/js/google-drive-picker.js'),
- path.join(__dirname, 'public/js/index.js')
- ],
- pretty: [
- "babel-polyfill",
- "expose?filterXSS!xss",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/pretty.js')
- ],
- "pretty-styles": [
- path.join(__dirname, 'public/css/github-extract.css'),
- path.join(__dirname, 'public/css/mermaid.css'),
- path.join(__dirname, 'public/css/markdown.css'),
- path.join(__dirname, 'public/css/slide-preview.css')
- ],
- "pretty-styles-pack": [
- path.join(__dirname, 'node_modules/bootstrap/dist/css/bootstrap.min.css'),
- path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
- path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
- path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
- ],
- "pretty-pack": [
- "babel-polyfill",
- "expose?jsyaml!js-yaml",
- "script!mermaid",
- "expose?moment!moment",
- "script!handlebars",
- "expose?hljs!highlight.js",
- "expose?emojify!emojify.js",
- "expose?filterXSS!xss",
- "script!gist-embed",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?Viz!viz.js",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/pretty.js')
- ],
- slide: [
- "babel-polyfill",
- "bootstrap-tooltip",
- "expose?filterXSS!xss",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/slide.js')
- ],
- "slide-styles": [
- path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.css'),
- path.join(__dirname, 'public/css/github-extract.css'),
- path.join(__dirname, 'public/css/mermaid.css'),
- path.join(__dirname, 'public/css/markdown.css')
- ],
- "slide-styles-pack": [
- path.join(__dirname, 'node_modules/font-awesome/css/font-awesome.min.css'),
- path.join(__dirname, 'node_modules/ionicons/css/ionicons.min.css'),
- path.join(__dirname, 'node_modules/octicons/octicons/octicons.css')
- ],
- "slide-pack": [
- "babel-polyfill",
- "expose?jQuery!expose?$!jquery",
- "velocity-animate",
- "imports?$=jquery!jquery-mousewheel",
- "bootstrap-tooltip",
- "expose?jsyaml!js-yaml",
- "script!mermaid",
- "expose?moment!moment",
- "script!handlebars",
- "expose?hljs!highlight.js",
- "expose?emojify!emojify.js",
- "expose?filterXSS!xss",
- "script!gist-embed",
- "flowchart.js",
- "js-sequence-diagrams",
- "expose?Viz!viz.js",
- "headjs",
- "expose?Reveal!reveal.js",
- "expose?RevealMarkdown!reveal-markdown",
- path.join(__dirname, 'public/js/slide.js')
- ]
- },
-
- output: {
- path: path.join(__dirname, 'public/build'),
- publicPath: '/build/',
- filename: '[name].js',
- baseUrl: '<%- url %>'
- },
+ output: {
+ path: path.join(__dirname, 'public/build'),
+ publicPath: '/build/',
+ filename: '[name].js',
+ baseUrl: '<%- url %>'
+ },
- resolve: {
- modulesDirectories: [
- path.resolve(__dirname, 'src'),
- path.resolve(__dirname, 'node_modules')
- ],
- extensions: ["", ".js"],
- alias: {
- codemirror: path.join(__dirname, 'node_modules/codemirror/codemirror.min.js'),
- inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'),
- jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'),
- codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'),
- codemirrorInlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/codemirror.inline-attachment.js'),
- ot: path.join(__dirname, 'public/vendor/ot/ot.min.js'),
- listPagnation: path.join(__dirname, 'node_modules/list.pagination.js/dist/list.pagination.min.js'),
- mermaid: path.join(__dirname, 'node_modules/mermaid/dist/mermaid.min.js'),
- handlebars: path.join(__dirname, 'node_modules/handlebars/dist/handlebars.min.js'),
- "jquery-ui-resizable": path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.js'),
- "gist-embed": path.join(__dirname, 'node_modules/gist-embed/gist-embed.min.js'),
- "bootstrap-tooltip": path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.js'),
- "headjs": path.join(__dirname, 'node_modules/reveal.js/lib/js/head.min.js'),
- "reveal-markdown": path.join(__dirname, 'public/js/reveal-markdown.js')
- }
- },
+ resolve: {
+ modulesDirectories: [
+ path.resolve(__dirname, 'src'),
+ path.resolve(__dirname, 'node_modules')
+ ],
+ extensions: ['', '.js'],
+ alias: {
+ codemirror: path.join(__dirname, 'node_modules/codemirror/codemirror.min.js'),
+ inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'),
+ jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'),
+ codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'),
+ codemirrorInlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/codemirror.inline-attachment.js'),
+ ot: path.join(__dirname, 'public/vendor/ot/ot.min.js'),
+ listPagnation: path.join(__dirname, 'node_modules/list.pagination.js/dist/list.pagination.min.js'),
+ mermaid: path.join(__dirname, 'node_modules/mermaid/dist/mermaid.min.js'),
+ handlebars: path.join(__dirname, 'node_modules/handlebars/dist/handlebars.min.js'),
+ 'jquery-ui-resizable': path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.js'),
+ 'gist-embed': path.join(__dirname, 'node_modules/gist-embed/gist-embed.min.js'),
+ 'bootstrap-tooltip': path.join(__dirname, 'public/vendor/bootstrap/tooltip.min.js'),
+ 'headjs': path.join(__dirname, 'node_modules/reveal.js/lib/js/head.min.js'),
+ 'reveal-markdown': path.join(__dirname, 'public/js/reveal-markdown.js')
+ }
+ },
- externals: {
- "viz.js": "Viz",
- "socket.io-client": "io",
- "lodash": "_",
- "jquery": "$",
- "moment": "moment",
- "handlebars": "Handlebars",
- "highlight.js": "hljs",
- "select2": "select2"
- },
+ externals: {
+ 'viz.js': 'Viz',
+ 'socket.io-client': 'io',
+ 'lodash': '_',
+ 'jquery': '$',
+ 'moment': 'moment',
+ 'handlebars': 'Handlebars',
+ 'highlight.js': 'hljs',
+ 'select2': 'select2'
+ },
- module: {
- loaders: [{
- test: /\.json$/,
- loader: 'json-loader'
- }, {
- test: /\.js$/,
- loader: 'babel',
- exclude: [/node_modules/, /public\/vendor/]
- }, {
- test: /\.css$/,
- loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
- }, {
- test: /\.scss$/,
- loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
- }, {
- test: /\.less$/,
- loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
- }, {
- test: require.resolve("js-sequence-diagrams"),
- loader: "imports?Raphael=raphael"
- }, {
- test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
- loader: "file"
- }, {
- test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
- loader: "url?prefix=font/&limit=5000"
- }, {
- test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
- loader: "url?limit=10000&mimetype=application/octet-stream"
- }, {
- test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
- loader: "url?limit=10000&mimetype=image/svg+xml"
- }, {
- test: /\.png(\?v=\d+\.\d+\.\d+)?$/,
- loader: "url?limit=10000&mimetype=image/png"
- }, {
- test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,
- loader: "url?limit=10000&mimetype=image/gif"
- }]
- },
+ module: {
+ loaders: [{
+ test: /\.json$/,
+ loader: 'json-loader'
+ }, {
+ test: /\.js$/,
+ loader: 'babel',
+ exclude: [/node_modules/, /public\/vendor/]
+ }, {
+ test: /\.css$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
+ }, {
+ test: /\.scss$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'sass-loader')
+ }, {
+ test: /\.less$/,
+ loader: ExtractTextPlugin.extract('style-loader', 'less-loader')
+ }, {
+ test: require.resolve('js-sequence-diagrams'),
+ loader: 'imports?Raphael=raphael'
+ }, {
+ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'file'
+ }, {
+ test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url?prefix=font/&limit=5000'
+ }, {
+ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url?limit=10000&mimetype=application/octet-stream'
+ }, {
+ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url?limit=10000&mimetype=image/svg+xml'
+ }, {
+ test: /\.png(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url?limit=10000&mimetype=image/png'
+ }, {
+ test: /\.gif(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'url?limit=10000&mimetype=image/gif'
+ }]
+ },
+ node: {
+ fs: 'empty'
+ },
- node: {
- fs: "empty"
- }
-};
+ quiet: false,
+ noInfo: false,
+ stats: {
+ assets: false
+ }
+}