diff options
51 files changed, 2241 insertions, 1535 deletions
@@ -1,44 +1,44 @@ 'use strict' // app // external modules -var express = require('express') - -var ejs = require('ejs') -var passport = require('passport') -var methodOverride = require('method-override') -var cookieParser = require('cookie-parser') -var compression = require('compression') -var session = require('express-session') -var SequelizeStore = require('connect-session-sequelize')(session.Store) -var fs = require('fs') -var path = require('path') - -var morgan = require('morgan') -var passportSocketIo = require('passport.socketio') -var helmet = require('helmet') -var i18n = require('i18n') -var flash = require('connect-flash') +const express = require('express') + +const ejs = require('ejs') +const passport = require('passport') +const methodOverride = require('method-override') +const cookieParser = require('cookie-parser') +const compression = require('compression') +const session = require('express-session') +const SequelizeStore = require('connect-session-sequelize')(session.Store) +const fs = require('fs') +const path = require('path') + +const morgan = require('morgan') +const passportSocketIo = require('passport.socketio') +const helmet = require('helmet') +const i18n = require('i18n') +const flash = require('connect-flash') // core -var config = require('./lib/config') -var logger = require('./lib/logger') -var errors = require('./lib/errors') -var models = require('./lib/models') -var csp = require('./lib/csp') +const config = require('./lib/config') +const logger = require('./lib/logger') +const errors = require('./lib/errors') +const models = require('./lib/models') +const csp = require('./lib/csp') // server setup -var app = express() -var server = null +const app = express() +let server = null if (config.useSSL) { - var ca = (function () { - var i, len, results - results = [] + const ca = (function () { + let i, len + const results = [] for (i = 0, len = config.sslCAPath.length; i < len; i++) { results.push(fs.readFileSync(config.sslCAPath[i], 'utf8')) } return results })() - var options = { + const options = { key: fs.readFileSync(config.sslKeyPath, 'utf8'), cert: fs.readFileSync(config.sslCertPath, 'utf8'), ca: ca, @@ -60,18 +60,18 @@ if (!config.useSSL && config.protocolUseSSL) { // logger app.use(morgan('combined', { - 'stream': logger.stream + stream: logger.stream })) // socket io -var io = require('socket.io')(server, { cookie: false }) +const io = require('socket.io')(server, { cookie: false }) io.engine.ws = new (require('ws').Server)({ noServer: true, perMessageDeflate: false }) // others -var realtime = require('./lib/realtime.js') +const realtime = require('./lib/realtime.js') // assign socket io to realtime realtime.io = io @@ -80,7 +80,7 @@ realtime.io = io app.use(methodOverride('_method')) // session store -var sessionStore = new SequelizeStore({ +const sessionStore = new SequelizeStore({ db: models.sequelize }) @@ -154,7 +154,7 @@ app.use(session({ })) // session resumption -var tlsSessionStore = {} +const tlsSessionStore = {} server.on('newSession', function (id, data, cb) { tlsSessionStore[id.toString('hex')] = data cb() @@ -246,9 +246,9 @@ io.sockets.on('connection', realtime.connection) // listen function startListen () { - var address - var listenCallback = function () { - var schema = config.useSSL ? 'HTTPS' : 'HTTP' + let address + const listenCallback = function () { + const schema = config.useSSL ? 'HTTPS' : 'HTTP' logger.info('%s Server listening at %s', schema, address) realtime.maintenance = false } @@ -290,7 +290,7 @@ function handleTermSignals () { realtime.maintenance = true // disconnect all socket.io clients Object.keys(io.sockets.sockets).forEach(function (key) { - var socket = io.sockets.sockets[key] + const socket = io.sockets.sockets[key] // notify client server going into maintenance status socket.emit('maintenance') setTimeout(function () { @@ -300,7 +300,7 @@ function handleTermSignals () { if (config.path) { fs.unlink(config.path) } - var checkCleanTimer = setInterval(function () { + const checkCleanTimer = setInterval(function () { if (realtime.isReady()) { models.Revision.checkAllNotesRevision(function (err, notes) { if (err) return logger.error(err) diff --git a/lib/config/index.js b/lib/config/index.js index 1657ba7a..17c13f5f 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -58,14 +58,14 @@ if (!['strict', 'lax', 'none'].includes(config.cookiePolicy)) { // load LDAP CA if (config.ldap.tlsca) { - let ca = config.ldap.tlsca.split(',') - let caContent = [] - for (let i of ca) { + const ca = config.ldap.tlsca.split(',') + const caContent = [] + for (const i of ca) { if (fs.existsSync(i)) { caContent.push(fs.readFileSync(i, 'utf8')) } } - let tlsOptions = { + const tlsOptions = { ca: caContent } config.ldap.tlsOptions = config.ldap.tlsOptions ? Object.assign(config.ldap.tlsOptions, tlsOptions) : tlsOptions @@ -90,9 +90,9 @@ config.isStandardHTTPPort = (function isStandardHTTPPort () { // cache serverURL config.serverURL = (function getserverurl () { - var url = '' + let url = '' if (config.domain) { - var protocol = config.protocolUseSSL ? 'https://' : 'http://' + const protocol = config.protocolUseSSL ? 'https://' : 'http://' url = protocol + config.domain if (config.urlAddPort) { if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) { @@ -138,10 +138,10 @@ config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === config.updateI18nFiles = (env === Environment.development) // merge legacy values -let keys = Object.keys(config) +const keys = Object.keys(config) const uppercase = /[A-Z]/ for (let i = keys.length; i--;) { - let lowercaseKey = keys[i].toLowerCase() + const lowercaseKey = keys[i].toLowerCase() // if the config contains uppercase letters // and a lowercase version of this setting exists // and the config with uppercase is not set @@ -1,9 +1,9 @@ -var config = require('./config') -var uuid = require('uuid') +const config = require('./config') +const uuid = require('uuid') -var CspStrategy = {} +const CspStrategy = {} -var defaultDirectives = { +const defaultDirectives = { defaultSrc: ['\'self\''], scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', '\'unsafe-eval\''], // ^ TODO: Remove unsafe-eval - webpack script-loader issues https://github.com/hackmdio/codimd/issues/594 @@ -16,28 +16,28 @@ var defaultDirectives = { connectSrc: ['*'] } -var cdnDirectives = { +const cdnDirectives = { scriptSrc: ['https://cdnjs.cloudflare.com', 'https://cdn.mathjax.org'], styleSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.googleapis.com'], fontSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com'] } -var disqusDirectives = { +const disqusDirectives = { scriptSrc: ['https://disqus.com', 'https://*.disqus.com', 'https://*.disquscdn.com'], styleSrc: ['https://*.disquscdn.com'], fontSrc: ['https://*.disquscdn.com'] } -var googleAnalyticsDirectives = { +const googleAnalyticsDirectives = { scriptSrc: ['https://www.google-analytics.com'] } -var dropboxDirectives = { +const dropboxDirectives = { scriptSrc: ['https://www.dropbox.com', '\'unsafe-inline\''] } CspStrategy.computeDirectives = function () { - var directives = {} + const directives = {} mergeDirectives(directives, config.csp.directives) mergeDirectivesIf(config.csp.addDefaults, directives, defaultDirectives) mergeDirectivesIf(config.useCDN, directives, cdnDirectives) @@ -53,10 +53,10 @@ CspStrategy.computeDirectives = function () { } function mergeDirectives (existingDirectives, newDirectives) { - for (var propertyName in newDirectives) { - var newDirective = newDirectives[propertyName] + for (const propertyName in newDirectives) { + const newDirective = newDirectives[propertyName] if (newDirective) { - var existingDirective = existingDirectives[propertyName] || [] + const existingDirective = existingDirectives[propertyName] || [] existingDirectives[propertyName] = existingDirective.concat(newDirective) } } diff --git a/lib/history.js b/lib/history.js index 3ebf77fd..e4ce9221 100644 --- a/lib/history.js +++ b/lib/history.js @@ -1,15 +1,15 @@ 'use strict' // history // external modules -var LZString = require('lz-string') +const LZString = require('lz-string') // core -var logger = require('./logger') -var models = require('./models') +const logger = require('./logger') +const models = require('./models') const errors = require('./errors') // public -var History = { +const History = { historyGet: historyGet, historyPost: historyPost, historyDelete: historyDelete, @@ -25,7 +25,7 @@ function getHistory (userid, callback) { if (!user) { return callback(null, null) } - var history = {} + let history = {} if (user.history) { history = JSON.parse(user.history) // migrate LZString encoded note id to base64url encoded note id @@ -40,7 +40,7 @@ function getHistory (userid, callback) { continue } try { - let id = LZString.decompressFromBase64(history[i].id) + const id = LZString.decompressFromBase64(history[i].id) if (id && models.Note.checkNoteIdValid(id)) { history[i].id = models.Note.encodeNoteId(id) } @@ -85,8 +85,8 @@ function updateHistory (userid, noteId, document, time) { if (!history[noteId]) { history[noteId] = {} } - var noteHistory = history[noteId] - var noteInfo = models.Note.parseNoteInfo(document) + const noteHistory = history[noteId] + const noteInfo = models.Note.parseNoteInfo(document) noteHistory.id = noteId noteHistory.text = noteInfo.title noteHistory.time = time || Date.now() @@ -101,18 +101,18 @@ function updateHistory (userid, noteId, document, time) { } function parseHistoryToArray (history) { - var _history = [] + const _history = [] Object.keys(history).forEach(function (key) { - var item = history[key] + const item = history[key] _history.push(item) }) return _history } function parseHistoryToObject (history) { - var _history = {} - for (var i = 0, l = history.length; i < l; i++) { - var item = history[i] + const _history = {} + for (let i = 0, l = history.length; i < l; i++) { + const item = history[i] _history[item.id] = item } return _history @@ -134,25 +134,25 @@ function historyGet (req, res) { function historyPost (req, res) { if (req.isAuthenticated()) { - var noteId = req.params.noteId + const noteId = req.params.noteId if (!noteId) { - if (typeof req.body['history'] === 'undefined') return errors.errorBadRequest(res) + if (typeof req.body.history === 'undefined') return errors.errorBadRequest(res) logger.debug(`SERVER received history from [${req.user.id}]: ${req.body.history}`) try { - var history = JSON.parse(req.body.history) + const history = JSON.parse(req.body.history) + if (Array.isArray(history)) { + setHistory(req.user.id, history, function (err, count) { + if (err) return errors.errorInternalError(res) + res.end() + }) + } else { + return errors.errorBadRequest(res) + } } catch (err) { return errors.errorBadRequest(res) } - if (Array.isArray(history)) { - setHistory(req.user.id, history, function (err, count) { - if (err) return errors.errorInternalError(res) - res.end() - }) - } else { - return errors.errorBadRequest(res) - } } else { - if (typeof req.body['pinned'] === 'undefined') return errors.errorBadRequest(res) + if (typeof req.body.pinned === 'undefined') return errors.errorBadRequest(res) getHistory(req.user.id, function (err, history) { if (err) return errors.errorInternalError(res) if (!history) return errors.errorNotFound(res) @@ -175,7 +175,7 @@ function historyPost (req, res) { function historyDelete (req, res) { if (req.isAuthenticated()) { - var noteId = req.params.noteId + const noteId = req.params.noteId if (!noteId) { setHistory(req.user.id, [], function (err, count) { if (err) return errors.errorInternalError(res) diff --git a/lib/letter-avatars.js b/lib/letter-avatars.js index 6fb1888a..bb7a9fe8 100644 --- a/lib/letter-avatars.js +++ b/lib/letter-avatars.js @@ -32,9 +32,9 @@ exports.generateAvatarURL = function (name, email = '', big = true) { } name = encodeURIComponent(name) - let hash = crypto.createHash('md5') + const hash = crypto.createHash('md5') hash.update(email.toLowerCase()) - let hexDigest = hash.digest('hex') + const hexDigest = hash.digest('hex') if (email !== '' && config.allowGravatar) { photo = 'https://cdn.libravatar.org/avatar/' + hexDigest diff --git a/lib/models/author.js b/lib/models/author.js index e65791cb..4c995745 100644 --- a/lib/models/author.js +++ b/lib/models/author.js @@ -1,9 +1,9 @@ 'use strict' // external modules -var Sequelize = require('sequelize') +const Sequelize = require('sequelize') module.exports = function (sequelize, DataTypes) { - var Author = sequelize.define('Author', { + const Author = sequelize.define('Author', { id: { type: Sequelize.INTEGER, primaryKey: true, diff --git a/lib/models/index.js b/lib/models/index.js index 88c1b168..e752a993 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -1,20 +1,22 @@ 'use strict' // external modules -var fs = require('fs') -var path = require('path') -var Sequelize = require('sequelize') +const fs = require('fs') +const path = require('path') +const Sequelize = require('sequelize') const { cloneDeep } = require('lodash') // core -var config = require('../config') -var logger = require('../logger') +const config = require('../config') +const logger = require('../logger') -var dbconfig = cloneDeep(config.db) -dbconfig.logging = config.debug ? (data) => { - logger.info(data) -} : false +const dbconfig = cloneDeep(config.db) +dbconfig.logging = config.debug + ? (data) => { + logger.info(data) + } + : false -var sequelize = null +let sequelize = null // Heroku specific if (config.dbURL) { @@ -38,14 +40,14 @@ function processData (data, _default, process) { } sequelize.processData = processData -var db = {} +const db = {} fs.readdirSync(__dirname) .filter(function (file) { return (file.indexOf('.') !== 0) && (file !== 'index.js') }) .forEach(function (file) { - var model = sequelize.import(path.join(__dirname, file)) + const model = sequelize.import(path.join(__dirname, file)) db[model.name] = model }) diff --git a/lib/models/note.js b/lib/models/note.js index 7418c47e..7b8b6783 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -1,32 +1,32 @@ 'use strict' // external modules -var fs = require('fs') -var path = require('path') -var LZString = require('lz-string') -var base64url = require('base64url') -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') +const fs = require('fs') +const path = require('path') +const LZString = require('lz-string') +const base64url = require('base64url') +const md = require('markdown-it')() +const metaMarked = require('meta-marked') +const cheerio = require('cheerio') +const shortId = require('shortid') +const Sequelize = require('sequelize') +const async = require('async') +const moment = require('moment') +const DiffMatchPatch = require('diff-match-patch') +const dmp = new DiffMatchPatch() +const S = require('string') // core -var config = require('../config') -var logger = require('../logger') +const config = require('../config') +const logger = require('../logger') // ot -var ot = require('../ot') +const ot = require('../ot') // permission types -var permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', 'private'] +const permissionTypes = ['freely', 'editable', 'limited', 'locked', 'protected', 'private'] module.exports = function (sequelize, DataTypes) { - var Note = sequelize.define('Note', { + const Note = sequelize.define('Note', { id: { type: DataTypes.UUID, primaryKey: true, @@ -91,7 +91,7 @@ module.exports = function (sequelize, DataTypes) { return new Promise(function (resolve, reject) { // if no content specified then use default note if (!note.content) { - var body = null + let body = null let filePath = null if (note.alias) { filePath = path.join(config.docsPath, note.alias + '.md') @@ -100,7 +100,7 @@ module.exports = function (sequelize, DataTypes) { filePath = config.defaultNotePath } if (Note.checkFileExist(filePath)) { - var fsCreatedTime = moment(fs.statSync(filePath).ctime) + const fsCreatedTime = moment(fs.statSync(filePath).ctime) body = fs.readFileSync(filePath, 'utf8') note.title = Note.parseNoteTitle(body) note.content = body @@ -165,15 +165,15 @@ module.exports = function (sequelize, DataTypes) { } Note.encodeNoteId = function (id) { // remove dashes in UUID and encode in url-safe base64 - let str = id.replace(/-/g, '') - let hexStr = Buffer.from(str, 'hex') + const str = id.replace(/-/g, '') + const hexStr = Buffer.from(str, 'hex') return base64url.encode(hexStr) } Note.decodeNoteId = function (encodedId) { // decode from url-safe base64 - let id = base64url.toBuffer(encodedId).toString('hex') + const id = base64url.toBuffer(encodedId).toString('hex') // add dashes between the UUID string parts - let idParts = [] + const idParts = [] idParts.push(id.substr(0, 8)) idParts.push(id.substr(8, 4)) idParts.push(id.substr(12, 4)) @@ -182,8 +182,8 @@ module.exports = function (sequelize, DataTypes) { return idParts.join('-') } Note.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) + const 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 + const result = id.match(uuidRegex) if (result && result.length === 1) { return true } else { return false } } Note.parseNoteId = function (noteId, callback) { @@ -196,15 +196,15 @@ module.exports = function (sequelize, DataTypes) { } }).then(function (note) { if (note) { - let filePath = path.join(config.docsPath, noteId + '.md') + const 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) + const fsModifiedTime = moment(fs.statSync(filePath).mtime) + const dbModifiedTime = moment(note.lastchangeAt || note.createdAt) + const body = fs.readFileSync(filePath, 'utf8') + const contentLength = body.length + const title = Note.parseNoteTitle(body) if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) { note.update({ title: title, @@ -214,9 +214,9 @@ module.exports = function (sequelize, DataTypes) { 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 + const patch = dmp.patch_fromText(revision.patch) + const operations = Note.transformPatchToOperations(patch, contentLength) + let authorship = note.authorship for (let i = 0; i < operations.length; i++) { authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship) } @@ -238,7 +238,7 @@ module.exports = function (sequelize, DataTypes) { return callback(null, note.id) } } else { - var filePath = path.join(config.docsPath, noteId + '.md') + const filePath = path.join(config.docsPath, noteId + '.md') if (Note.checkFileExist(filePath)) { Note.create({ alias: noteId, @@ -270,7 +270,7 @@ module.exports = function (sequelize, DataTypes) { } // try to parse note id by LZString Base64 try { - var id = LZString.decompressFromBase64(noteId) + const id = LZString.decompressFromBase64(noteId) if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) } } catch (err) { if (err.message === 'Cannot read property \'charAt\' of undefined') { @@ -284,7 +284,7 @@ module.exports = function (sequelize, DataTypes) { parseNoteIdByBase64Url: function (_callback) { // try to parse note id by base64url try { - var id = Note.decodeNoteId(noteId) + const id = Note.decodeNoteId(noteId) if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) } } catch (err) { logger.error(err) @@ -321,24 +321,24 @@ module.exports = function (sequelize, DataTypes) { }) } Note.parseNoteInfo = function (body) { - var parsed = Note.extractMeta(body) - var $ = cheerio.load(md.render(parsed.markdown)) + const parsed = Note.extractMeta(body) + const $ = cheerio.load(md.render(parsed.markdown)) return { title: Note.extractNoteTitle(parsed.meta, $), tags: Note.extractNoteTags(parsed.meta, $) } } Note.parseNoteTitle = function (body) { - var parsed = Note.extractMeta(body) - var $ = cheerio.load(md.render(parsed.markdown)) + const parsed = Note.extractMeta(body) + const $ = cheerio.load(md.render(parsed.markdown)) return Note.extractNoteTitle(parsed.meta, $) } Note.extractNoteTitle = function (meta, $) { - var title = '' + let title = '' if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { title = meta.title } else { - var h1s = $('h1') + const h1s = $('h1') if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s } } if (!title) title = 'Untitled' @@ -355,28 +355,28 @@ module.exports = function (sequelize, DataTypes) { return title } Note.extractNoteTags = function (meta, $) { - var tags = [] - var rawtags = [] + const tags = [] + const rawtags = [] if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) { - var metaTags = ('' + meta.tags).split(',') + const metaTags = ('' + meta.tags).split(',') for (let i = 0; i < metaTags.length; i++) { - var text = metaTags[i].trim() + const text = metaTags[i].trim() if (text) rawtags.push(text) } } else { - var h6s = $('h6') + const h6s = $('h6') h6s.each(function (key, value) { if (/^tags/gmi.test($(value).text())) { - var codes = $(value).find('code') + const codes = $(value).find('code') for (let i = 0; i < codes.length; i++) { - var text = S($(codes[i]).text().trim()).stripTags().s + const text = S($(codes[i]).text().trim()).stripTags().s if (text) rawtags.push(text) } } }) } for (let i = 0; i < rawtags.length; i++) { - var found = false + let found = false for (let j = 0; j < tags.length; j++) { if (tags[j] === rawtags[i]) { found = true @@ -388,7 +388,7 @@ module.exports = function (sequelize, DataTypes) { return tags } Note.extractMeta = function (content) { - var obj = null + let obj = null try { obj = metaMarked(content) if (!obj.markdown) obj.markdown = '' @@ -402,7 +402,7 @@ module.exports = function (sequelize, DataTypes) { return obj } Note.parseMeta = function (meta) { - var _meta = {} + const _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 } @@ -416,7 +416,7 @@ module.exports = function (sequelize, DataTypes) { return _meta } Note.parseOpengraph = function (meta, title) { - var _ogdata = {} + let _ogdata = {} if (meta.opengraph) { _ogdata = meta.opengraph } if (!(_ogdata.title && (typeof _ogdata.title === 'string' || typeof _ogdata.title === 'number'))) { _ogdata.title = title } if (!(_ogdata.description && (typeof _ogdata.description === 'string' || typeof _ogdata.description === 'number'))) { _ogdata.description = meta.description || '' } @@ -424,27 +424,27 @@ module.exports = function (sequelize, DataTypes) { return _ogdata } Note.updateAuthorshipByOperation = function (operation, userId, authorships) { - var index = 0 - var timestamp = Date.now() + let index = 0 + const timestamp = Date.now() for (let i = 0; i < operation.length; i++) { - var op = operation[i] + const 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 + const opStart = index + const opEnd = index + op.length + let 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] + const authorship = authorships[j] if (!inserted) { - let nextAuthorship = authorships[j + 1] || -1 + const 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 + const postLength = authorship[2] - opStart authorship[2] = opStart authorship[4] = timestamp authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]) @@ -470,13 +470,13 @@ module.exports = function (sequelize, DataTypes) { } index += op.length } else if (ot.TextOperation.isDelete(op)) { - let opStart = index - let opEnd = index - op + const opStart = index + const 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] + const authorship = authorships[j] if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) { authorships.splice(j, 1) j -= 1 @@ -501,12 +501,12 @@ module.exports = function (sequelize, DataTypes) { } // merge for (let j = 0; j < authorships.length; j++) { - let authorship = authorships[j] + const authorship = authorships[j] for (let k = j + 1; k < authorships.length; k++) { - let nextAuthorship = authorships[k] + const 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]) + const minTimestamp = Math.min(authorship[3], nextAuthorship[3]) + const 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 @@ -516,7 +516,7 @@ module.exports = function (sequelize, DataTypes) { } // clear for (let j = 0; j < authorships.length; j++) { - let authorship = authorships[j] + const authorship = authorships[j] if (!authorship[0]) { authorships.splice(j, 1) j -= 1 @@ -525,13 +525,13 @@ module.exports = function (sequelize, DataTypes) { return authorships } Note.transformPatchToOperations = function (patch, contentLength) { - var operations = [] + const operations = [] if (patch.length > 0) { // calculate original content length for (let j = patch.length - 1; j >= 0; j--) { - var p = patch[j] + const p = patch[j] for (let i = 0; i < p.diffs.length; i++) { - var diff = p.diffs[i] + const diff = p.diffs[i] switch (diff[0]) { case 1: // insert contentLength -= diff[1].length @@ -543,15 +543,15 @@ module.exports = function (sequelize, DataTypes) { } } // generate operations - var bias = 0 - var lengthBias = 0 + let bias = 0 + let lengthBias = 0 for (let j = 0; j < patch.length; j++) { - var operation = [] - let p = patch[j] - var currIndex = p.start1 - var currLength = contentLength - bias + const operation = [] + const p = patch[j] + let currIndex = p.start1 + const currLength = contentLength - bias for (let i = 0; i < p.diffs.length; i++) { - let diff = p.diffs[i] + const diff = p.diffs[i] switch (diff[0]) { case 0: // retain if (i === 0) { diff --git a/lib/models/revision.js b/lib/models/revision.js index dbd76e4e..80136c68 100644 --- a/lib/models/revision.js +++ b/lib/models/revision.js @@ -1,22 +1,22 @@ '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 path = require('path') +const Sequelize = require('sequelize') +const async = require('async') +const moment = require('moment') +const childProcess = require('child_process') +const shortId = require('shortid') +const path = require('path') -var Op = Sequelize.Op +const Op = Sequelize.Op // core -var logger = require('../logger') +const logger = require('../logger') -var dmpWorker = createDmpWorker() -var dmpCallbackCache = {} +let dmpWorker = createDmpWorker() +const dmpCallbackCache = {} function createDmpWorker () { - var worker = childProcess.fork(path.resolve(__dirname, '../workers/dmpWorker.js'), { + const worker = childProcess.fork(path.resolve(__dirname, '../workers/dmpWorker.js'), { stdio: 'ignore' }) logger.debug('dmp worker process started') @@ -24,7 +24,7 @@ function createDmpWorker () { if (!data || !data.msg || !data.cacheKey) { return logger.error('dmp worker error: not enough data on message') } - var cacheKey = data.cacheKey + const cacheKey = data.cacheKey switch (data.msg) { case 'error': dmpCallbackCache[cacheKey](data.error, null) @@ -44,7 +44,7 @@ function createDmpWorker () { function sendDmpWorker (data, callback) { if (!dmpWorker) dmpWorker = createDmpWorker() - var cacheKey = Date.now() + '_' + shortId.generate() + const cacheKey = Date.now() + '_' + shortId.generate() dmpCallbackCache[cacheKey] = callback data = Object.assign(data, { cacheKey: cacheKey @@ -53,7 +53,7 @@ function sendDmpWorker (data, callback) { } module.exports = function (sequelize, DataTypes) { - var Revision = sequelize.define('Revision', { + const Revision = sequelize.define('Revision', { id: { type: DataTypes.UUID, primaryKey: true, @@ -116,9 +116,9 @@ module.exports = function (sequelize, DataTypes) { }, order: [['createdAt', 'DESC']] }).then(function (revisions) { - var data = [] - for (var i = 0, l = revisions.length; i < l; i++) { - var revision = revisions[i] + const data = [] + for (let i = 0, l = revisions.length; i < l; i++) { + const revision = revisions[i] data.push({ time: moment(revision.createdAt).valueOf(), length: revision.length @@ -199,12 +199,12 @@ module.exports = function (sequelize, DataTypes) { } }).then(function (notes) { if (notes.length <= 0) return callback(null, notes) - var savedNotes = [] + const 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) + const lastchangeAt = moment(note.lastchangeAt) + const savedAt = moment(note.savedAt) if (moment().isAfter(lastchangeAt.add(5, 'minutes'))) { savedNotes.push(note) Revision.saveNoteRevision(note, _callback) @@ -223,7 +223,7 @@ module.exports = function (sequelize, DataTypes) { 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 + const result = ((savedNotes.length === 0) && (notes.length > savedNotes.length)) ? null : savedNotes return callback(null, result) }) }).catch(function (err) { @@ -250,9 +250,9 @@ module.exports = function (sequelize, DataTypes) { return callback(err, null) }) } else { - var latestRevision = revisions[0] - var lastContent = latestRevision.content || latestRevision.lastContent - var content = note.content + const latestRevision = revisions[0] + const lastContent = latestRevision.content || latestRevision.lastContent + const content = note.content sendDmpWorker({ msg: 'create patch', lastDoc: lastContent, diff --git a/lib/models/temp.js b/lib/models/temp.js index 2ad23fb5..dee6c573 100644 --- a/lib/models/temp.js +++ b/lib/models/temp.js @@ -1,9 +1,9 @@ 'use strict' // external modules -var shortId = require('shortid') +const shortId = require('shortid') module.exports = function (sequelize, DataTypes) { - var Temp = sequelize.define('Temp', { + const Temp = sequelize.define('Temp', { id: { type: DataTypes.STRING, primaryKey: true, diff --git a/lib/models/user.js b/lib/models/user.js index 50c78108..26a557a7 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -17,7 +17,7 @@ const logger = require('../logger') const { generateAvatarURL } = require('../letter-avatars') module.exports = function (sequelize, DataTypes) { - var User = sequelize.define('User', { + const User = sequelize.define('User', { id: { type: DataTypes.UUID, primaryKey: true, @@ -91,7 +91,7 @@ module.exports = function (sequelize, DataTypes) { return profile } User.parsePhotoByProfile = function (profile, bigger) { - var photo = null + let photo = null switch (profile.provider) { case 'facebook': photo = 'https://graph.facebook.com/' + profile.id + '/picture' diff --git a/lib/realtime.js b/lib/realtime.js index ec99707e..abea7165 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -1,25 +1,25 @@ 'use strict' // realtime // external modules -var cookie = require('cookie') -var cookieParser = require('cookie-parser') -var async = require('async') -var randomcolor = require('randomcolor') -var Chance = require('chance') -var chance = new Chance() -var moment = require('moment') +const cookie = require('cookie') +const cookieParser = require('cookie-parser') +const async = require('async') +const randomcolor = require('randomcolor') +const Chance = require('chance') +const chance = new Chance() +const moment = require('moment') // core -var config = require('./config') -var logger = require('./logger') -var history = require('./history') -var models = require('./models') +const config = require('./config') +const logger = require('./logger') +const history = require('./history') +const models = require('./models') // ot -var ot = require('./ot') +const ot = require('./ot') // public -var realtime = { +const realtime = { io: null, onAuthorizeSuccess: onAuthorizeSuccess, onAuthorizeFail: onAuthorizeFail, @@ -41,7 +41,7 @@ function onAuthorizeFail (data, message, error, accept) { // secure the origin by the cookie function secure (socket, next) { try { - var handshakeData = socket.request + const handshakeData = socket.request if (handshakeData.headers.cookie) { handshakeData.cookie = cookie.parse(handshakeData.headers.cookie) handshakeData.sessionID = cookieParser.signedCookie(handshakeData.cookie[config.sessionName], config.sessionSecret) @@ -62,7 +62,7 @@ function secure (socket, next) { } function emitCheck (note) { - var out = { + const out = { title: note.title, updatetime: note.updatetime, lastchangeuser: note.lastchangeuser, @@ -74,12 +74,12 @@ function emitCheck (note) { } // actions -var users = {} -var notes = {} +const users = {} +const notes = {} // update when the note is dirty setInterval(function () { async.each(Object.keys(notes), function (key, callback) { - var note = notes[key] + const note = notes[key] if (note.server.isDirty) { logger.debug(`updater found dirty note: ${key}`) note.server.isDirty = false @@ -93,8 +93,8 @@ setInterval(function () { 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] + for (let i = 0, l = note.socks.length; i < l; i++) { + const sock = note.socks[i] if (typeof sock !== 'undefined' && sock) { setTimeout(function () { sock.disconnect(true) @@ -123,7 +123,7 @@ function updateNote (note, callback) { }).then(function (_note) { if (!_note) return callback(null, null) // update user note history - var tempUsers = Object.assign({}, note.tempUsers) + const tempUsers = Object.assign({}, note.tempUsers) note.tempUsers = {} Object.keys(tempUsers).forEach(function (key) { updateHistory(key, note, tempUsers[key]) @@ -157,9 +157,9 @@ function updateNote (note, callback) { 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 = { + const body = note.server.document + const title = note.title = models.Note.parseNoteTitle(body) + const values = { title: title, content: body, authorship: note.authorship, @@ -178,7 +178,7 @@ function finishUpdateNote (note, _note, callback) { // 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] + let socket = realtime.io.sockets.connected[key] if ((!socket && users[key]) || (socket && (!socket.rooms || socket.rooms.length <= 0))) { logger.debug(`cleaner found redundant user: ${key}`) @@ -196,7 +196,7 @@ setInterval(function () { }) }, 60000) -var saverSleep = false +let saverSleep = false // save note revision in interval setInterval(function () { if (saverSleep) return @@ -210,11 +210,11 @@ setInterval(function () { function getStatus (callback) { models.Note.count().then(function (notecount) { - var distinctaddresses = [] - var regaddresses = [] - var distinctregaddresses = [] + const distinctaddresses = [] + const regaddresses = [] + const distinctregaddresses = [] Object.keys(users).forEach(function (key) { - var user = users[key] + const user = users[key] if (!user) return let found = false for (let i = 0; i < distinctaddresses.length; i++) { @@ -241,20 +241,22 @@ function getStatus (callback) { } }) models.User.count().then(function (regcount) { - // eslint-disable-next-line standard/no-callback-literal - return callback ? callback({ - onlineNotes: Object.keys(notes).length, - onlineUsers: Object.keys(users).length, - distinctOnlineUsers: distinctaddresses.length, - notesCount: notecount, - registeredUsers: regcount, - onlineRegisteredUsers: regaddresses.length, - distinctOnlineRegisteredUsers: distinctregaddresses.length, - isConnectionBusy: isConnectionBusy, - connectionSocketQueueLength: connectionSocketQueue.length, - isDisconnectBusy: isDisconnectBusy, - disconnectSocketQueueLength: disconnectSocketQueue.length - }) : null + return callback + // eslint-disable-next-line node/no-callback-literal + ? 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) }) @@ -282,7 +284,7 @@ function extractNoteIdFromSocket (socket) { } function parseNoteIdFromSocket (socket, callback) { - var noteId = extractNoteIdFromSocket(socket) + const noteId = extractNoteIdFromSocket(socket) if (!noteId) { return callback(null, null) } @@ -293,32 +295,32 @@ function parseNoteIdFromSocket (socket, callback) { } function emitOnlineUsers (socket) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var users = [] + const users = [] Object.keys(notes[noteId].users).forEach(function (key) { - var user = notes[noteId].users[key] + const user = notes[noteId].users[key] if (user) { users.push(buildUserOutData(user)) } }) - var out = { + const out = { users: users } realtime.io.to(noteId).emit('online users', out) } function emitUserStatus (socket) { - var noteId = socket.noteId - var user = users[socket.id] + const noteId = socket.noteId + const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return - var out = buildUserOutData(user) + const out = buildUserOutData(user) socket.broadcast.to(noteId).emit('user status', out) } function emitRefresh (socket) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var note = notes[noteId] - var out = { + const note = notes[noteId] + const out = { title: note.title, docmaxlength: config.documentMaxLength, owner: note.owner, @@ -335,7 +337,7 @@ function emitRefresh (socket) { } function isDuplicatedInSocketQueue (queue, socket) { - for (var i = 0; i < queue.length; i++) { + for (let i = 0; i < queue.length; i++) { if (queue[i] && queue[i].id === socket.id) { return true } @@ -344,7 +346,7 @@ function isDuplicatedInSocketQueue (queue, socket) { } function clearSocketQueue (queue, socket) { - for (var i = 0; i < queue.length; i++) { + for (let i = 0; i < queue.length; i++) { if (!queue[i] || queue[i].id === socket.id) { queue.splice(i, 1) i-- @@ -378,10 +380,10 @@ function checkViewPermission (req, note) { } } -var isConnectionBusy = false -var connectionSocketQueue = [] -var isDisconnectBusy = false -var disconnectSocketQueue = [] +let isConnectionBusy = false +const connectionSocketQueue = [] +let isDisconnectBusy = false +const disconnectSocketQueue = [] function finishConnection (socket, noteId, socketId) { // if no valid info provided will drop the client @@ -393,8 +395,8 @@ function finishConnection (socket, noteId, socketId) { interruptConnection(socket, noteId, socketId) return failConnection(403, 'connection forbidden', socket) } - let note = notes[noteId] - let user = users[socketId] + const note = notes[noteId] + const 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 @@ -417,7 +419,7 @@ function finishConnection (socket, noteId, socketId) { connectNextSocket() if (config.debug) { - let noteId = socket.noteId + const noteId = socket.noteId logger.debug(`SERVER connected a client to [${noteId}]:`) logger.debug(JSON.stringify(user)) logger.debug(notes) @@ -431,13 +433,13 @@ function startConnection (socket) { if (isConnectionBusy) return isConnectionBusy = true - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId) { return failConnection(404, 'note id not found', socket) } if (!notes[noteId]) { - var include = [{ + const include = [{ model: models.User, as: 'owner' }, { @@ -461,21 +463,21 @@ function startConnection (socket) { if (!note) { return failConnection(404, 'note not found', socket) } - var owner = note.ownerId - var ownerprofile = note.owner ? models.User.getProfile(note.owner) : null + const owner = note.ownerId + const ownerprofile = note.owner ? models.User.getProfile(note.owner) : null - var lastchangeuser = note.lastchangeuserId - var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null + const lastchangeuser = note.lastchangeuserId + const 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) + const body = note.content + const createtime = note.createdAt + const updatetime = note.lastchangeAt + const 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) + const authors = {} + for (let i = 0; i < note.authors.length; i++) { + const author = note.authors[i] + const profile = models.User.getProfile(author.user) if (profile) { authors[author.userId] = { userid: author.userId, @@ -536,16 +538,17 @@ function disconnect (socket) { if (users[socket.id]) { delete users[socket.id] } - var noteId = socket.noteId - var note = notes[noteId] + const noteId = socket.noteId + const 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 + let index do { - var index = note.socks.indexOf(socket) + index = note.socks.indexOf(socket) if (index !== -1) { note.socks.splice(index, 1) } @@ -590,7 +593,7 @@ function disconnect (socket) { } function buildUserOutData (user) { - var out = { + const out = { id: user.id, login: user.login, userid: user.userid, @@ -607,7 +610,7 @@ function buildUserOutData (user) { 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) + const profile = models.User.getProfile(socket.request.user) user.photo = profile.photo user.name = profile.name user.userid = socket.request.user.id @@ -620,10 +623,10 @@ function updateUserData (socket, user) { } function ifMayEdit (socket, callback) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var note = notes[noteId] - var mayEdit = true + const note = notes[noteId] + let mayEdit = true switch (note.permission) { case 'freely': // not blocking anyone @@ -650,13 +653,13 @@ function ifMayEdit (socket, callback) { } function operationCallback (socket, operation) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var note = notes[noteId] - var userId = null + const note = notes[noteId] + let userId = null // save authors if (socket.request.user && socket.request.user.logged_in) { - var user = users[socket.id] + const user = users[socket.id] if (!user) return userId = socket.request.user.id if (!note.authors[userId]) { @@ -692,7 +695,7 @@ function operationCallback (socket, operation) { } function updateHistory (userId, note, time) { - var noteId = note.alias ? note.alias : models.Note.encodeNoteId(note.id) + const noteId = note.alias ? note.alias : models.Note.encodeNoteId(note.id) if (note.server) history.updateHistory(userId, noteId, note.server.document, time) } @@ -713,12 +716,12 @@ function connection (socket) { // initialize user data // random color - var color = randomcolor() + let color = randomcolor() // make sure color not duplicated or reach max random count if (notes[noteId]) { - var randomcount = 0 - var maxrandomcount = 10 - var found = false + let randomcount = 0 + const maxrandomcount = 10 + let found = false do { Object.keys(notes[noteId].users).forEach(function (userId) { if (notes[noteId].users[userId].color === color) { @@ -758,8 +761,8 @@ function connection (socket) { // received user status socket.on('user status', function (data) { - var noteId = socket.noteId - var user = users[socket.id] + const noteId = socket.noteId + const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return logger.debug(`SERVER received [${noteId}] user status from [${socket.id}]: ${JSON.stringify(data)}`) if (data) { @@ -773,9 +776,9 @@ function connection (socket) { socket.on('permission', function (permission) { // need login to do more actions if (socket.request.user && socket.request.user.logged_in) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var note = notes[noteId] + const note = notes[noteId] // Only owner can change permission if (note.owner && note.owner === socket.request.user.id) { if (permission === 'freely' && !config.allowAnonymous && !config.allowAnonymousEdits) return @@ -790,12 +793,12 @@ function connection (socket) { if (!count) { return } - var out = { + const 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] + for (let i = 0, l = note.socks.length; i < l; i++) { + const sock = note.socks[i] if (typeof sock !== 'undefined' && sock) { // check view permission if (!checkViewPermission(sock.request, note)) { @@ -819,9 +822,9 @@ function connection (socket) { socket.on('delete', function () { // need login to do more actions if (socket.request.user && socket.request.user.logged_in) { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var note = notes[noteId] + const note = notes[noteId] // Only owner can delete note if (note.owner && note.owner === socket.request.user.id) { models.Note.destroy({ @@ -830,8 +833,8 @@ function connection (socket) { } }).then(function (count) { if (!count) return - for (var i = 0, l = note.socks.length; i < l; i++) { - var sock = note.socks[i] + for (let i = 0, l = note.socks.length; i < l; i++) { + const sock = note.socks[i] if (typeof sock !== 'undefined' && sock) { sock.emit('delete') setTimeout(function () { @@ -849,9 +852,9 @@ function connection (socket) { // reveiced when user logout or changed socket.on('user changed', function () { logger.info('user changed') - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var user = notes[noteId].users[socket.id] + const user = notes[noteId].users[socket.id] if (!user) return updateUserData(socket, user) emitOnlineUsers(socket) @@ -859,14 +862,14 @@ function connection (socket) { // received sync of online users request socket.on('online users', function () { - var noteId = socket.noteId + const noteId = socket.noteId if (!noteId || !notes[noteId]) return - var users = [] + const users = [] Object.keys(notes[noteId].users).forEach(function (key) { - var user = notes[noteId].users[key] + const user = notes[noteId].users[key] if (user) { users.push(buildUserOutData(user)) } }) - var out = { + const out = { users: users } socket.emit('online users', out) @@ -882,31 +885,31 @@ function connection (socket) { // received cursor focus socket.on('cursor focus', function (data) { - var noteId = socket.noteId - var user = users[socket.id] + const noteId = socket.noteId + const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return user.cursor = data - var out = buildUserOutData(user) + const 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] + const noteId = socket.noteId + const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return user.cursor = data - var out = buildUserOutData(user) + const 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] + const noteId = socket.noteId + const user = users[socket.id] if (!noteId || !notes[noteId] || !user) return user.cursor = null - var out = { + const out = { id: socket.id } socket.broadcast.to(noteId).emit('cursor blur', out) diff --git a/lib/response.js b/lib/response.js index a56273a2..10ecd035 100644 --- a/lib/response.js +++ b/lib/response.js @@ -1,28 +1,28 @@ 'use strict' // response // external modules -var fs = require('fs') -var path = require('path') -var request = require('request') +const fs = require('fs') +const path = require('path') +const request = require('request') // core -var config = require('./config') -var logger = require('./logger') -var models = require('./models') +const config = require('./config') +const logger = require('./logger') +const models = require('./models') const noteUtil = require('./web/note/util') const errors = require('./errors') // public -var response = { +const response = { showIndex: showIndex, githubActions: githubActions, gitlabActions: gitlabActions } function showIndex (req, res, next) { - var authStatus = req.isAuthenticated() - var deleteToken = '' + const authStatus = req.isAuthenticated() + const deleteToken = '' - var data = { + const data = { signin: authStatus, infoMessage: req.flash('info'), errorMessage: req.flash('error'), @@ -49,9 +49,9 @@ function showIndex (req, res, next) { } function githubActions (req, res, next) { - var noteId = req.params.noteId + const noteId = req.params.noteId noteUtil.findNote(req, res, function (note) { - var action = req.params.action + const action = req.params.action switch (action) { case 'gist': githubActionGist(req, res, note) @@ -64,41 +64,41 @@ function githubActions (req, res, next) { } function githubActionGist (req, res, note) { - var code = req.query.code - var state = req.query.state + const code = req.query.code + const state = req.query.state if (!code || !state) { return errors.errorForbidden(res) } else { - var data = { + const data = { client_id: config.github.clientID, client_secret: config.github.clientSecret, code: code, state: state } - var authUrl = 'https://github.com/login/oauth/access_token' + const 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 + const 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': {} + const content = note.content + const title = models.Note.decodeTitle(note.title) + const filename = title.replace('/', ' ') + '.md' + const gist = { + files: {} } gist.files[filename] = { - 'content': content + content: content } - var gistUrl = 'https://api.github.com/gists' + const gistUrl = 'https://api.github.com/gists' request({ url: gistUrl, headers: { 'User-Agent': 'HedgeDoc', - 'Authorization': 'token ' + accessToken + Authorization: 'token ' + accessToken }, method: 'POST', json: gist @@ -121,9 +121,9 @@ function githubActionGist (req, res, note) { } function gitlabActions (req, res, next) { - var noteId = req.params.noteId + const noteId = req.params.noteId noteUtil.findNote(req, res, function (note) { - var action = req.params.action + const action = req.params.action switch (action) { case 'projects': gitlabActionProjects(req, res, note) @@ -143,7 +143,7 @@ function gitlabActionProjects (req, res, note) { } }).then(function (user) { if (!user) { return errors.errorNotFound(res) } - var ret = { baseURL: config.gitlab.baseURL, version: config.gitlab.version } + const ret = { baseURL: config.gitlab.baseURL, version: config.gitlab.version } ret.accesstoken = user.accessToken ret.profileid = user.profileid request( diff --git a/lib/utils.js b/lib/utils.js index 23873ffc..44ff8892 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,7 +5,7 @@ exports.isSQLite = function isSQLite (sequelize) { } exports.getImageMimeType = function getImageMimeType (imagePath) { - var fileExtension = /[^.]+$/.exec(imagePath) + const fileExtension = /[^.]+$/.exec(imagePath) switch (fileExtension[0].toLowerCase()) { case 'bmp': diff --git a/lib/web/auth/dropbox/index.js b/lib/web/auth/dropbox/index.js index aef011cb..c35f04e3 100644 --- a/lib/web/auth/dropbox/index.js +++ b/lib/web/auth/dropbox/index.js @@ -6,7 +6,7 @@ const DropboxStrategy = require('passport-dropbox-oauth2').Strategy const config = require('../../../config') const { passportGeneralCallback } = require('../utils') -let dropboxAuth = module.exports = Router() +const dropboxAuth = module.exports = Router() passport.use(new DropboxStrategy({ apiVersion: '2', diff --git a/lib/web/auth/email/index.js b/lib/web/auth/email/index.js index 78ca933b..74922966 100644 --- a/lib/web/auth/email/index.js +++ b/lib/web/auth/email/index.js @@ -10,7 +10,7 @@ const logger = require('../../../logger') const { urlencodedParser } = require('../../utils') const errors = require('../../../errors') -let emailAuth = module.exports = Router() +const emailAuth = module.exports = Router() passport.use(new LocalStrategy({ usernameField: 'email' diff --git a/lib/web/auth/facebook/index.js b/lib/web/auth/facebook/index.js index 0ba948bb..acf566eb 100644 --- a/lib/web/auth/facebook/index.js +++ b/lib/web/auth/facebook/index.js @@ -7,7 +7,7 @@ const FacebookStrategy = require('passport-facebook').Strategy const config = require('../../../config') const { passportGeneralCallback } = require('../utils') -let facebookAuth = module.exports = Router() +const facebookAuth = module.exports = Router() passport.use(new FacebookStrategy({ clientID: config.facebook.clientID, diff --git a/lib/web/auth/github/index.js b/lib/web/auth/github/index.js index 3a3a84c6..c7f7e5d1 100644 --- a/lib/web/auth/github/index.js +++ b/lib/web/auth/github/index.js @@ -7,7 +7,7 @@ const config = require('../../../config') const response = require('../../../response') const { passportGeneralCallback } = require('../utils') -let githubAuth = module.exports = Router() +const githubAuth = module.exports = Router() passport.use(new GithubStrategy({ clientID: config.github.clientID, diff --git a/lib/web/auth/gitlab/index.js b/lib/web/auth/gitlab/index.js index 1b628e81..11579bd1 100644 --- a/lib/web/auth/gitlab/index.js +++ b/lib/web/auth/gitlab/index.js @@ -7,7 +7,7 @@ const config = require('../../../config') const response = require('../../../response') const { passportGeneralCallback } = require('../utils') -let gitlabAuth = module.exports = Router() +const gitlabAuth = module.exports = Router() passport.use(new GitlabStrategy({ baseURL: config.gitlab.baseURL, diff --git a/lib/web/auth/google/index.js b/lib/web/auth/google/index.js index 6edf07a9..0262dedf 100644 --- a/lib/web/auth/google/index.js +++ b/lib/web/auth/google/index.js @@ -2,11 +2,11 @@ const Router = require('express').Router const passport = require('passport') -var GoogleStrategy = require('passport-google-oauth20').Strategy +const GoogleStrategy = require('passport-google-oauth20').Strategy const config = require('../../../config') const { passportGeneralCallback } = require('../utils') -let googleAuth = module.exports = Router() +const googleAuth = module.exports = Router() passport.use(new GoogleStrategy({ clientID: config.google.clientID, diff --git a/lib/web/auth/ldap/index.js b/lib/web/auth/ldap/index.js index b501106d..4142194f 100644 --- a/lib/web/auth/ldap/index.js +++ b/lib/web/auth/ldap/index.js @@ -9,7 +9,7 @@ const logger = require('../../../logger') const { urlencodedParser } = require('../../utils') const errors = require('../../../errors') -let ldapAuth = module.exports = Router() +const ldapAuth = module.exports = Router() passport.use(new LDAPStrategy({ server: { @@ -22,7 +22,7 @@ passport.use(new LDAPStrategy({ tlsOptions: config.ldap.tlsOptions || null } }, function (user, done) { - var uuid = user.uidNumber || user.uid || user.sAMAccountName || undefined + let uuid = user.uidNumber || user.uid || user.sAMAccountName || undefined if (config.ldap.useridField && user[config.ldap.useridField]) { uuid = user[config.ldap.useridField] } @@ -34,12 +34,12 @@ passport.use(new LDAPStrategy({ '"useridField" option in ldap settings.') } - var username = uuid + let username = uuid if (config.ldap.usernameField && user[config.ldap.usernameField]) { username = user[config.ldap.usernameField] } - var profile = { + const profile = { id: 'LDAP-' + uuid, username: username, displayName: user.displayName, @@ -48,7 +48,7 @@ passport.use(new LDAPStrategy({ profileUrl: null, provider: 'ldap' } - var stringifiedProfile = JSON.stringify(profile) + const stringifiedProfile = JSON.stringify(profile) models.User.findOrCreate({ where: { profileid: profile.id.toString() @@ -58,7 +58,7 @@ passport.use(new LDAPStrategy({ } }).spread(function (user, created) { if (user) { - var needSave = false + let needSave = false if (user.profile !== stringifiedProfile) { user.profile = stringifiedProfile needSave = true diff --git a/lib/web/auth/mattermost/index.js b/lib/web/auth/mattermost/index.js index 78eca2af..2f15c812 100644 --- a/lib/web/auth/mattermost/index.js +++ b/lib/web/auth/mattermost/index.js @@ -9,9 +9,9 @@ const { passportGeneralCallback } = require('../utils') const mattermost = new Mattermost.Client() -let mattermostAuth = module.exports = Router() +const mattermostAuth = module.exports = Router() -let mattermostStrategy = new OAuthStrategy({ +const mattermostStrategy = new OAuthStrategy({ authorizationURL: config.mattermost.baseURL + '/oauth/authorize', tokenURL: config.mattermost.baseURL + '/oauth/access_token', clientID: config.mattermost.clientID, diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js index 9cb17f26..e9032e0b 100644 --- a/lib/web/auth/oauth2/index.js +++ b/lib/web/auth/oauth2/index.js @@ -7,7 +7,7 @@ const config = require('../../../config') const logger = require('../../../logger') const { passportGeneralCallback } = require('../utils') -let oauth2Auth = module.exports = Router() +const oauth2Auth = module.exports = Router() class OAuth2CustomStrategy extends Strategy { constructor (options, verify) { @@ -20,7 +20,7 @@ class OAuth2CustomStrategy extends Strategy { userProfile (accessToken, done) { this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { - var json + let json if (err) { return done(new InternalOAuthError('Failed to fetch user profile', err)) @@ -33,7 +33,7 @@ class OAuth2CustomStrategy extends Strategy { } checkAuthorization(json, done) - let profile = parseProfile(json) + const profile = parseProfile(json) profile.provider = 'oauth2' done(null, profile) @@ -91,7 +91,7 @@ function checkAuthorization (data, done) { OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { - var json + let json if (err) { return done(new InternalOAuthError('Failed to fetch user profile', err)) @@ -104,7 +104,7 @@ OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { } checkAuthorization(json, done) - let profile = parseProfile(json) + const profile = parseProfile(json) profile.provider = 'oauth2' done(null, profile) diff --git a/lib/web/auth/openid/index.js b/lib/web/auth/openid/index.js index 28e164f5..84d0970c 100644 --- a/lib/web/auth/openid/index.js +++ b/lib/web/auth/openid/index.js @@ -8,14 +8,14 @@ const models = require('../../../models') const logger = require('../../../logger') const { urlencodedParser } = require('../../utils') -let openIDAuth = module.exports = Router() +const openIDAuth = module.exports = Router() passport.use(new OpenIDStrategy({ returnURL: config.serverURL + '/auth/openid/callback', realm: config.serverURL, profile: true }, function (openid, profile, done) { - var stringifiedProfile = JSON.stringify(profile) + const stringifiedProfile = JSON.stringify(profile) models.User.findOrCreate({ where: { profileid: openid @@ -25,7 +25,7 @@ passport.use(new OpenIDStrategy({ } }).spread(function (user, created) { if (user) { - var needSave = false + let needSave = false if (user.profile !== stringifiedProfile) { user.profile = stringifiedProfile needSave = true diff --git a/lib/web/auth/saml/index.js b/lib/web/auth/saml/index.js index c48b93e2..deb04007 100644 --- a/lib/web/auth/saml/index.js +++ b/lib/web/auth/saml/index.js @@ -10,19 +10,21 @@ const { urlencodedParser } = require('../../utils') const fs = require('fs') const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) } -let samlAuth = module.exports = Router() +const samlAuth = module.exports = Router() passport.use(new SamlStrategy({ callbackUrl: config.serverURL + '/auth/saml/callback', entryPoint: config.saml.idpSsoUrl, issuer: config.saml.issuer || config.serverURL, - privateCert: config.saml.clientCert === undefined ? undefined : (function () { - try { - return fs.readFileSync(config.saml.clientCert, 'utf-8') - } catch (e) { - logger.error(`SAML client certificate: ${e.message}`) - } - }()), + privateCert: config.saml.clientCert === undefined + ? undefined + : (function () { + try { + return fs.readFileSync(config.saml.clientCert, 'utf-8') + } catch (e) { + logger.error(`SAML client certificate: ${e.message}`) + } + }()), cert: (function () { try { return fs.readFileSync(config.saml.idpCert, 'utf-8') @@ -36,7 +38,7 @@ passport.use(new SamlStrategy({ }, function (user, done) { // check authorization if needed if (config.saml.externalGroups && config.saml.groupAttribute) { - var externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute]) + const externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute]) if (externalGroups.length > 0) { logger.error('saml permission denied: ' + externalGroups.join(', ')) return done('Permission denied', null) @@ -49,8 +51,8 @@ passport.use(new SamlStrategy({ } } // user creation - var uuid = user[config.saml.attribute.id] || user.nameID - var profile = { + const uuid = user[config.saml.attribute.id] || user.nameID + const profile = { provider: 'saml', id: 'SAML-' + uuid, username: user[config.saml.attribute.username] || user.nameID, @@ -59,7 +61,7 @@ passport.use(new SamlStrategy({ if (profile.emails.length === 0 && config.saml.identifierFormat === 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress') { profile.emails.push(user.nameID) } - var stringifiedProfile = JSON.stringify(profile) + const stringifiedProfile = JSON.stringify(profile) models.User.findOrCreate({ where: { profileid: profile.id.toString() @@ -69,7 +71,7 @@ passport.use(new SamlStrategy({ } }).spread(function (user, created) { if (user) { - var needSave = false + let needSave = false if (user.profile !== stringifiedProfile) { user.profile = stringifiedProfile needSave = true diff --git a/lib/web/auth/twitter/index.js b/lib/web/auth/twitter/index.js index 56389f84..76744e09 100644 --- a/lib/web/auth/twitter/index.js +++ b/lib/web/auth/twitter/index.js @@ -7,7 +7,7 @@ const TwitterStrategy = require('passport-twitter').Strategy const config = require('../../../config') const { passportGeneralCallback } = require('../utils') -let twitterAuth = module.exports = Router() +const twitterAuth = module.exports = Router() passport.use(new TwitterStrategy({ consumerKey: config.twitter.consumerKey, diff --git a/lib/web/auth/utils.js b/lib/web/auth/utils.js index fb69f08c..bb69f15f 100644 --- a/lib/web/auth/utils.js +++ b/lib/web/auth/utils.js @@ -4,7 +4,7 @@ const models = require('../../models') const logger = require('../../logger') exports.passportGeneralCallback = function callback (accessToken, refreshToken, profile, done) { - var stringifiedProfile = JSON.stringify(profile) + const stringifiedProfile = JSON.stringify(profile) models.User.findOrCreate({ where: { profileid: profile.id.toString() @@ -16,7 +16,7 @@ exports.passportGeneralCallback = function callback (accessToken, refreshToken, } }).spread(function (user, created) { if (user) { - var needSave = false + let needSave = false if (user.profile !== stringifiedProfile) { user.profile = stringifiedProfile needSave = true diff --git a/lib/web/imageRouter/azure.js b/lib/web/imageRouter/azure.js index 22ee5585..c56ac860 100644 --- a/lib/web/imageRouter/azure.js +++ b/lib/web/imageRouter/azure.js @@ -17,7 +17,7 @@ exports.uploadImage = function (imagePath, callback) { return } - var azureBlobService = azure.createBlobService(config.azure.connectionString) + const azureBlobService = azure.createBlobService(config.azure.connectionString) azureBlobService.createContainerIfNotExists(config.azure.container, { publicAccessLevel: 'blob' }, function (err, result, response) { if (err) { diff --git a/lib/web/imageRouter/index.js b/lib/web/imageRouter/index.js index afa9bbf6..0a72c65c 100644 --- a/lib/web/imageRouter/index.js +++ b/lib/web/imageRouter/index.js @@ -12,20 +12,28 @@ const config = require('../../config') const logger = require('../../logger') const errors = require('../../errors') -const imageRouter = module.exports = Router() +const imageRouter = (module.exports = Router()) async function checkUploadType (filePath) { const typeFromMagic = await FileType.fromFile(filePath) if (typeFromMagic === undefined) { - logger.error(`Image upload error: Could not determine MIME-type`) + logger.error('Image upload error: Could not determine MIME-type') return false } if (path.extname(filePath) !== '.' + typeFromMagic.ext) { - logger.error(`Image upload error: Provided file extension does not match MIME-type`) + logger.error( + 'Image upload error: Provided file extension does not match MIME-type' + ) return false } if (!config.allowedUploadMimeTypes.includes(typeFromMagic.mime)) { - logger.error(`Image upload error: MIME-type "${typeFromMagic.mime}" of uploaded file not allowed, only "${config.allowedUploadMimeTypes.join(', ')}" are allowed`) + logger.error( + `Image upload error: MIME-type "${ + typeFromMagic.mime + }" of uploaded file not allowed, only "${config.allowedUploadMimeTypes.join( + ', ' + )}" are allowed` + ) return false } return true @@ -33,12 +41,18 @@ async function checkUploadType (filePath) { // upload image imageRouter.post('/uploadimage', function (req, res) { - if (!req.isAuthenticated() && !config.allowAnonymous && !config.allowAnonymousEdits) { - logger.error(`Image upload error: Anonymous edits and therefore uploads are not allowed)`) + if ( + !req.isAuthenticated() && + !config.allowAnonymous && + !config.allowAnonymousEdits + ) { + logger.error( + 'Image upload error: Anonymous edits and therefore uploads are not allowed' + ) return errors.errorForbidden(res) } - var form = new formidable.IncomingForm() + const form = new formidable.IncomingForm() form.keepExtensions = true const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hedgedoc-')) form.uploadDir = tmpDir @@ -49,17 +63,21 @@ imageRouter.post('/uploadimage', function (req, res) { rimraf(tmpDir) return errors.errorForbidden(res) } else if (!files.image || !files.image.path) { - logger.error(`Image upload error: Upload didn't contain file)`) + logger.error("Image upload error: Upload didn't contain file)") rimraf.sync(tmpDir) return errors.errorBadRequest(res) - } else if (!await checkUploadType(files.image.path)) { + } else if (!(await checkUploadType(files.image.path))) { rimraf.sync(tmpDir) return errors.errorBadRequest(res) } else { - logger.debug(`SERVER received uploadimage: ${JSON.stringify(files.image)}`) + logger.debug( + `SERVER received uploadimage: ${JSON.stringify(files.image)}` + ) const uploadProvider = require('./' + config.imageUploadType) - logger.debug(`imageRouter: Uploading ${files.image.path} using ${config.imageUploadType}`) + logger.debug( + `imageRouter: Uploading ${files.image.path} using ${config.imageUploadType}` + ) uploadProvider.uploadImage(files.image.path, function (err, url) { rimraf.sync(tmpDir) if (err !== null) { diff --git a/lib/web/imageRouter/minio.js b/lib/web/imageRouter/minio.js index 91de5ff1..3ced94e2 100644 --- a/lib/web/imageRouter/minio.js +++ b/lib/web/imageRouter/minio.js @@ -32,16 +32,16 @@ exports.uploadImage = function (imagePath, callback) { return } - let key = path.join('uploads', path.basename(imagePath)) - let protocol = config.minio.secure ? 'https' : 'http' + const key = path.join('uploads', path.basename(imagePath)) + const protocol = config.minio.secure ? 'https' : 'http' minioClient.putObject(config.s3bucket, key, buffer, buffer.size, getImageMimeType(imagePath), function (err, data) { if (err) { callback(new Error(err), null) return } - let hidePort = [80, 443].includes(config.minio.port) - let urlPort = hidePort ? '' : `:${config.minio.port}` + const hidePort = [80, 443].includes(config.minio.port) + const urlPort = hidePort ? '' : `:${config.minio.port}` callback(null, `${protocol}://${config.minio.endPoint}${urlPort}/${config.s3bucket}/${key}`) }) }) diff --git a/lib/web/imageRouter/s3.js b/lib/web/imageRouter/s3.js index 2bf08cc7..5bb8e160 100644 --- a/lib/web/imageRouter/s3.js +++ b/lib/web/imageRouter/s3.js @@ -26,7 +26,7 @@ exports.uploadImage = function (imagePath, callback) { callback(new Error(err), null) return } - let params = { + const params = { Bucket: config.s3bucket, Key: path.join('uploads', path.basename(imagePath)), Body: buffer diff --git a/lib/web/statusRouter.js b/lib/web/statusRouter.js index febe2df3..d939a3fe 100644 --- a/lib/web/statusRouter.js +++ b/lib/web/statusRouter.js @@ -25,11 +25,11 @@ statusRouter.get('/status', function (req, res, next) { }) // get status statusRouter.get('/temp', function (req, res) { - var host = req.get('host') + const host = req.get('host') if (config.allowOrigin.indexOf(host) === -1) { errors.errorForbidden(res) } else { - var tempid = req.query.tempid + const tempid = req.query.tempid if (!tempid) { errors.errorForbidden(res) } else { @@ -60,11 +60,11 @@ statusRouter.get('/temp', function (req, res) { }) // post status statusRouter.post('/temp', urlencodedParser, function (req, res) { - var host = req.get('host') + const host = req.get('host') if (config.allowOrigin.indexOf(host) === -1) { errors.errorForbidden(res) } else { - var data = req.body.data + const data = req.body.data if (!data) { errors.errorForbidden(res) } else { @@ -90,7 +90,7 @@ statusRouter.post('/temp', urlencodedParser, function (req, res) { }) statusRouter.get('/config', function (req, res) { - var data = { + const data = { domain: config.domain, urlpath: config.urlPath, debug: config.debug, diff --git a/lib/web/userRouter.js b/lib/web/userRouter.js index f1f999f1..117668fa 100644 --- a/lib/web/userRouter.js +++ b/lib/web/userRouter.js @@ -21,7 +21,7 @@ UserRouter.get('/me', function (req, res) { } }).then(function (user) { if (!user) { return errors.errorNotFound(res) } - var profile = models.User.getProfile(user) + const profile = models.User.getProfile(user) res.send({ status: 'ok', id: req.user.id, @@ -70,7 +70,7 @@ UserRouter.get('/me/delete/:token?', function (req, res) { UserRouter.get('/me/export', function (req, res) { if (req.isAuthenticated()) { // let output = fs.createWriteStream(__dirname + '/example.zip'); - let archive = archiver('zip', { + const archive = archiver('zip', { zlib: { level: 3 } // Sets the compression level. }) res.setHeader('Content-Type', 'application/zip') @@ -90,13 +90,13 @@ UserRouter.get('/me/export', function (req, res) { ownerId: user.id } }).then(function (notes) { - let filenames = {} + const filenames = {} async.each(notes, function (note, callback) { - let basename = note.title.replace(/\//g, '-') // Prevent subdirectories + const basename = note.title.replace(/\//g, '-') // Prevent subdirectories let filename let suffix = '' do { - let seperator = typeof suffix === 'number' ? '-' : '' + const seperator = typeof suffix === 'number' ? '-' : '' filename = basename + seperator + suffix + '.md' suffix++ } while (filenames[filename]) diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js index ca68b4ab..7b5439c7 100644 --- a/lib/workers/dmpWorker.js +++ b/lib/workers/dmpWorker.js @@ -1,10 +1,10 @@ 'use strict' // external modules -var DiffMatchPatch = require('diff-match-patch') -var dmp = new DiffMatchPatch() +const DiffMatchPatch = require('diff-match-patch') +const dmp = new DiffMatchPatch() // core -var logger = require('../logger') +const logger = require('../logger') process.on('message', function (data) { if (!data || !data.msg || !data.cacheKey) { @@ -12,11 +12,16 @@ process.on('message', function (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') + if ( + !Object.prototype.hasOwnProperty.call(data, 'lastDoc') || + !Object.prototype.hasOwnProperty.call(data, 'currDoc') + ) { + return logger.error( + 'dmp worker error: not enough data on create patch' + ) } try { - var patch = createPatch(data.lastDoc, data.currDoc) + const patch = createPatch(data.lastDoc, data.currDoc) process.send({ msg: 'check', result: patch, @@ -32,11 +37,16 @@ process.on('message', function (data) { } break case 'get revision': - if (!data.hasOwnProperty('revisions') || !data.hasOwnProperty('count')) { - return logger.error('dmp worker error: not enough data on get revision') + if ( + !Object.prototype.hasOwnProperty.call(data, 'revisions') || + !Object.prototype.hasOwnProperty.call(data, 'count') + ) { + return logger.error( + 'dmp worker error: not enough data on get revision' + ) } try { - var result = getRevision(data.revisions, data.count) + const result = getRevision(data.revisions, data.count) process.send({ msg: 'check', result: result, @@ -55,31 +65,31 @@ process.on('message', function (data) { }) function createPatch (lastDoc, currDoc) { - var msStart = (new Date()).getTime() - var diff = dmp.diff_main(lastDoc, currDoc) - var patch = dmp.patch_make(lastDoc, diff) + const msStart = new Date().getTime() + const diff = dmp.diff_main(lastDoc, currDoc) + let patch = dmp.patch_make(lastDoc, diff) patch = dmp.patch_toText(patch) - var msEnd = (new Date()).getTime() + const msEnd = new Date().getTime() logger.debug(patch) - logger.debug((msEnd - msStart) + 'ms') + logger.debug(msEnd - msStart + 'ms') return patch } function getRevision (revisions, count) { - var msStart = (new Date()).getTime() - var startContent = null - var lastPatch = [] - var applyPatches = [] - var authorship = [] + const msStart = new Date().getTime() + let startContent = null + let lastPatch = [] + let applyPatches = [] + let authorship = [] if (count <= Math.round(revisions.length / 2)) { // start from top to target for (let i = 0; i < count; i++) { - let revision = revisions[i] + const revision = revisions[i] if (i === 0) { startContent = revision.content || revision.lastContent } if (i !== count - 1) { - let patch = dmp.patch_fromText(revision.patch) + const patch = dmp.patch_fromText(revision.patch) applyPatches = applyPatches.concat(patch) } lastPatch = revision.patch @@ -88,21 +98,25 @@ function getRevision (revisions, count) { // 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 } + const 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--) { - let revision = revisions[i] + const l = revisions.length - 1 + for (let i = l; i >= count - 1; i--) { + const revision = revisions[i] if (i === l) { startContent = revision.lastContent authorship = revision.authorship } if (revision.patch) { - let patch = dmp.patch_fromText(revision.patch) + const patch = dmp.patch_fromText(revision.patch) applyPatches = applyPatches.concat(patch) } lastPatch = revision.patch @@ -110,18 +124,18 @@ function getRevision (revisions, count) { } } try { - var finalContent = dmp.patch_apply(applyPatches, startContent)[0] + const finalContent = dmp.patch_apply(applyPatches, startContent)[0] + const data = { + content: finalContent, + patch: dmp.patch_fromText(lastPatch), + authorship: authorship + } + const msEnd = new Date().getTime() + logger.debug(msEnd - msStart + 'ms') + return data } catch (err) { throw new Error(err) } - var data = { - content: finalContent, - patch: dmp.patch_fromText(lastPatch), - authorship: authorship - } - var msEnd = (new Date()).getTime() - logger.debug((msEnd - msStart) + 'ms') - return data } // log uncaught exception diff --git a/public/.eslintrc.js b/public/.eslintrc.js index dc37b3cb..77cd2c0d 100644 --- a/public/.eslintrc.js +++ b/public/.eslintrc.js @@ -1,28 +1,28 @@ // this config file is used in concert with the root .eslintrc.js in the root dir. module.exports = { - "env": { - "browser": true + env: { + browser: true }, - "globals": { - "$": false, - "CodeMirror": false, - "Cookies": false, - "moment": false, - "editor": false, - "ui": false, - "Spinner": false, - "modeType": false, - "Idle": false, - "serverurl": false, - "key": false, - "gapi": false, - "Dropbox": false, - "FilePicker": false, - "ot": false, - "MediaUploader": false, - "hex2rgb": false, - "num_loaded": false, - "Visibility": false, - "inlineAttachment": false + globals: { + $: false, + CodeMirror: false, + Cookies: false, + moment: false, + editor: false, + ui: false, + Spinner: false, + modeType: false, + Idle: false, + serverurl: false, + key: false, + gapi: false, + Dropbox: false, + FilePicker: false, + ot: false, + MediaUploader: false, + hex2rgb: false, + num_loaded: false, + Visibility: false, + inlineAttachment: false } -}; +} diff --git a/public/js/cover.js b/public/js/cover.js index ed10afbf..bad92574 100644 --- a/public/js/cover.js +++ b/public/js/cover.js @@ -262,8 +262,8 @@ function updateItemFromNow () { } } -var clearHistory = false -var deleteId = null +let clearHistory = false +let deleteId = null function deleteHistory () { checkIfAuth(() => { @@ -431,9 +431,9 @@ $('.search').keyup(() => { // focus user field after opening login modal $('.signin-modal').on('shown.bs.modal', function () { - let fieldLDAP = $('input[name=username]') - let fieldEmail = $('input[name=email]') - let fieldOpenID = $('input[name=openid_identifier]') + const fieldLDAP = $('input[name=username]') + const fieldEmail = $('input[name=email]') + const fieldOpenID = $('input[name=openid_identifier]') if (fieldLDAP.length === 1) { fieldLDAP.focus() } else if (fieldEmail.length === 1) { diff --git a/public/js/extra.js b/public/js/extra.js index 44db742a..7f06ebda 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -29,7 +29,7 @@ require('prismjs/components/prism-gherkin') require('./lib/common/login') require('./locale') require('../vendor/md-toc') -var Viz = require('viz.js') +const Viz = require('viz.js') const ui = getUIElements() // auto update last change @@ -314,8 +314,9 @@ export function finishView (view) { // sequence diagram const sequences = view.find('div.sequence-diagram.raw').removeClass('raw') sequences.each((key, value) => { + let $value try { - var $value = $(value) + $value = $(value) const $ele = $(value).parent().parent() const sequence = $value @@ -337,15 +338,16 @@ export function finishView (view) { // flowchart const flow = view.find('div.flow-chart.raw').removeClass('raw') flow.each((key, value) => { + let $value try { - var $value = $(value) + $value = $(value) const $ele = $(value).parent().parent() const chart = window.flowchart.parse($value.text()) $value.html('') chart.drawSVG(value, { 'line-width': 2, - 'fill': 'none', + fill: 'none', 'font-size': '16px', 'font-family': "'Andale Mono', monospace" }) @@ -359,13 +361,14 @@ export function finishView (view) { } }) // graphviz - var graphvizs = view.find('div.graphviz.raw').removeClass('raw') + const graphvizs = view.find('div.graphviz.raw').removeClass('raw') graphvizs.each(function (key, value) { + let $value try { - var $value = $(value) - var $ele = $(value).parent().parent() + $value = $(value) + const $ele = $(value).parent().parent() - var graphviz = Viz($value.text()) + const graphviz = Viz($value.text()) if (!graphviz) throw Error('viz.js output empty graph') $value.html(graphviz) @@ -380,8 +383,9 @@ export function finishView (view) { // mermaid const mermaids = view.find('div.mermaid.raw').removeClass('raw') mermaids.each((key, value) => { + let $value try { - var $value = $(value) + $value = $(value) const $ele = $(value).closest('pre') window.mermaid.mermaidAPI.parse($value.text()) @@ -389,7 +393,7 @@ export function finishView (view) { $ele.text($value.text()) window.mermaid.init(undefined, $ele) } catch (err) { - var errormessage = err + let errormessage = err if (err.str) { errormessage = err.str } @@ -402,9 +406,10 @@ export function finishView (view) { // abc.js const abcs = view.find('div.abc.raw').removeClass('raw') abcs.each((key, value) => { + let $value try { - var $value = $(value) - var $ele = $(value).parent().parent() + $value = $(value) + const $ele = $(value).parent().parent() window.ABCJS.renderAbc(value, $value.text()) @@ -493,7 +498,7 @@ export function finishView (view) { let code = '' if (codeDiv.length > 0) code = codeDiv.html() else code = langDiv.html() - var result + let result if (!reallang) { result = { value: code @@ -571,7 +576,7 @@ export function postProcess (code) { } // show yaml meta paring error if (md.metaError) { - var warning = result.find('div#meta-error') + let warning = result.find('div#meta-error') if (warning && warning.length > 0) { warning.text(md.metaError) } else { @@ -583,14 +588,14 @@ export function postProcess (code) { } window.postProcess = postProcess -var domevents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) { +const domevents = Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(window))).filter(function (i) { return !i.indexOf('on') && (document[i] === null || typeof document[i] === 'function') }).filter(function (elem, pos, self) { return self.indexOf(elem) === pos }) export function removeDOMEvents (view) { - for (var i = 0, l = domevents.length; i < l; i++) { + for (let i = 0, l = domevents.length; i < l; i++) { view.find('[' + domevents[i] + ']').removeAttr(domevents[i]) } } @@ -739,13 +744,13 @@ 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 + const toc = new window.Toc('doc', { + level: 3, + top: -1, + class: 'toc', + ulClass: 'nav', + targetId: id, + process: getHeaderContent }) /* eslint-enable no-unused-vars */ if (target.text() === 'undefined') { target.html('') } @@ -858,7 +863,7 @@ const linkifyAnchors = (level, containingElement) => { const headers = containingElement.getElementsByTagName(`h${level}`) for (let i = 0, l = headers.length; i < l; i++) { - let header = headers[i] + const header = headers[i] if (header.getElementsByClassName('anchor').length === 0) { if (typeof header.id === 'undefined' || header.id === '') { header.id = createHeaderId(getHeaderContent(header)) @@ -903,7 +908,7 @@ export function deduplicatedHeaderId (view) { if (window.linkifyHeaderStyle === 'gfm') { // consistent with GitHub, GitLab, Pandoc & co. // all headers contained in the document, in order of appearance - const allHeaders = view.find(`:header`).toArray() + const allHeaders = view.find(':header').toArray() // list of finaly assigned header IDs const headerIds = new Set() for (let j = 0; j < allHeaders.length; j++) { @@ -938,12 +943,12 @@ export function renderTOC (view) { const target = $(`#${id}`) target.html('') /* eslint-disable no-unused-vars */ - let TOC = new window.Toc('doc', { - 'level': 3, - 'top': -1, - 'class': 'toc', - 'targetId': id, - 'process': getHeaderContent + const 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('') } @@ -991,7 +996,7 @@ function highlightRender (code, lang) { return result.value } -export let md = markdownit('default', { +export const md = markdownit('default', { html: true, breaks: true, langPrefix: '', @@ -1044,7 +1049,7 @@ md.use(markdownitContainer, 'info', { render: renderContainer }) md.use(markdownitContainer, 'warning', { render: renderContainer }) md.use(markdownitContainer, 'danger', { render: renderContainer }) -let defaultImageRender = md.renderer.rules.image +const defaultImageRender = md.renderer.rules.image md.renderer.rules.image = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw') return defaultImageRender(...arguments) @@ -1203,7 +1208,8 @@ function meta (state, start, end, silent) { if (!get(state, start).match(/^---$/)) return false const data = [] - for (var line = start + 1; line < end; line++) { + let line + for (line = start + 1; line < end; line++) { const str = get(state, line) if (str.match(/^(\.{3}|-{3})$/)) break if (state.tShift[line] < 0) break diff --git a/public/js/history.js b/public/js/history.js index e0154185..b8935eb3 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -147,7 +147,7 @@ export function writeHistory (title, tags) { } function writeHistoryToStorage (title, tags) { - let data = store.get('notehistory') + const data = store.get('notehistory') let notehistory if (data && typeof data === 'string') { notehistory = JSON.parse(data) @@ -220,7 +220,7 @@ export function getStorageHistory (callback) { if (typeof data === 'string') { data = JSON.parse(data) } callback(data) } - // eslint-disable-next-line standard/no-callback-literal + // eslint-disable-next-line node/no-callback-literal callback([]) } @@ -263,7 +263,7 @@ function parseToHistory (list, notehistory, callback) { for (let i = 0; i < notehistory.length; i++) { // migrate LZString encoded id to base64url encoded id try { - let id = LZString.decompressFromBase64(notehistory[i].id) + const id = LZString.decompressFromBase64(notehistory[i].id) if (id && checkNoteIdValid(id)) { notehistory[i].id = encodeNoteId(id) } diff --git a/public/js/index.js b/public/js/index.js index adc95461..03cae1b4 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -85,19 +85,49 @@ require('../css/site.css') require('highlight.js/styles/github-gist.css') -var defaultTextHeight = 20 -var viewportMargin = 20 -var defaultEditorMode = 'gfm' - -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', 'gherkin'].concat(hljs.listLanguages()) -var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid', 'abc'] -var supportHeaders = [ +let defaultTextHeight = 20 +let viewportMargin = 20 +const defaultEditorMode = 'gfm' + +const idleTime = 300000 // 5 mins +const updateViewDebounce = 100 +const cursorMenuThrottle = 50 +const cursorActivityDebounce = 50 +const cursorAnimatePeriod = 100 +const supportContainers = ['success', 'info', 'warning', 'danger'] +const 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', + 'gherkin' +].concat(hljs.listLanguages()) +const supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid', 'abc'] +const supportHeaders = [ { text: '# h1', search: '#' @@ -225,7 +255,7 @@ const supportExtraTags = [ text: '[random color tag]', search: '[]', command: function () { - var color = randomColor() + const color = randomColor() return '[color=' + color + ']' } } @@ -259,7 +289,7 @@ let visibleMD = false let visibleLG = false const isTouchDevice = 'ontouchstart' in document.documentElement let currentStatus = statusType.offline -let lastInfo = { +const lastInfo = { needRestore: false, cursor: null, scroll: null, @@ -285,14 +315,14 @@ let lastInfo = { let personalInfo = {} let onlineUsers = [] const fileTypes = { - 'pl': 'perl', - 'cgi': 'perl', - 'js': 'javascript', - 'php': 'php', - 'sh': 'bash', - 'rb': 'ruby', - 'html': 'html', - 'py': 'python' + pl: 'perl', + cgi: 'perl', + js: 'javascript', + php: 'php', + sh: 'bash', + rb: 'ruby', + html: 'html', + py: 'python' } // editor settings @@ -302,7 +332,7 @@ if (!textit) { } const editorInstance = new Editor() -var editor = editorInstance.init(textit) +const editor = editorInstance.init(textit) // FIXME: global referncing in jquery-textcomplete patch window.editor = editor @@ -313,7 +343,7 @@ defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) const ui = getUIElements() // page actions -var opts = { +const opts = { lines: 11, // The number of lines to draw length: 20, // The length of each line width: 2, // The line thickness @@ -333,11 +363,11 @@ var opts = { } /* eslint-disable no-unused-vars */ -var spinner = new Spinner(opts).spin(ui.spinner[0]) +const spinner = new Spinner(opts).spin(ui.spinner[0]) /* eslint-enable no-unused-vars */ // idle -var idle = new Idle({ +const idle = new Idle({ onAway: function () { idle.isAway = true emitUserStatus() @@ -356,7 +386,7 @@ ui.area.codemirror.on('touchstart', function () { idle.onActive() }) -var haveUnreadChanges = false +let haveUnreadChanges = false function setHaveUnreadChanges (bool) { if (!window.loaded) return @@ -379,7 +409,9 @@ function updateTitleReminder () { function setRefreshModal (status) { $('#refreshModal').modal('show') $('#refreshModal').find('.modal-body > div').hide() - $('#refreshModal').find('.' + status).show() + $('#refreshModal') + .find('.' + status) + .show() } function setNeedRefresh () { @@ -395,9 +427,9 @@ setloginStateChangeEvent(function () { }) // visibility -var wasFocus = false +let wasFocus = false Visibility.change(function (e, state) { - var hidden = Visibility.hidden() + const hidden = Visibility.hidden() if (hidden) { if (editorHasFocus()) { wasFocus = true @@ -421,7 +453,7 @@ $(document).ready(function () { idle.checkAway() checkResponsive() // if in smaller screen, we don't need advanced scrollbar - var scrollbarStyle + let scrollbarStyle if (visibleXS) { scrollbarStyle = 'native' } else { @@ -434,7 +466,7 @@ $(document).ready(function () { checkEditorStyle() /* cache dom references */ - var $body = $('body') + const $body = $('body') /* we need this only on touch devices */ if (isTouchDevice) { @@ -463,7 +495,9 @@ $(document).ready(function () { $('[data-toggle="tooltip"]').tooltip() // shortcuts // allow on all tags - key.filter = function (e) { return true } + key.filter = function (e) { + return true + } key('ctrl+alt+e', function (e) { changeMode(modeType.edit) }) @@ -488,13 +522,18 @@ $(window).resize(function () { }) // when page unload $(window).on('unload', function () { -// updateHistoryInner(); + // updateHistoryInner(); }) $(window).on('error', function () { // setNeedRefresh(); }) -setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown, editor) +setupSyncAreas( + ui.area.codemirrorScroll, + ui.area.view, + ui.area.markdown, + editor +) function autoSyncscroll () { if (editorHasFocus()) { @@ -504,8 +543,8 @@ function autoSyncscroll () { } } -var windowResizeDebounce = 200 -var windowResize = _.debounce(windowResizeInner, windowResizeDebounce) +const windowResizeDebounce = 200 +const windowResize = _.debounce(windowResizeInner, windowResizeDebounce) function windowResizeInner (callback) { checkLayout() @@ -520,7 +559,9 @@ function windowResizeInner (callback) { clearMap() autoSyncscroll() updateScrollspy() - if (callback && typeof callback === 'function') { callback() } + if (callback && typeof callback === 'function') { + callback() + } }, 1) } else { // force it load all docs at once to prevent scroll knob blink @@ -530,18 +571,22 @@ function windowResizeInner (callback) { 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]) } + for (let i = 0; i < onlineUsers.length; i++) { + if (onlineUsers[i].id !== personalInfo.id) { + buildCursor(onlineUsers[i]) + } } updateScrollspy() - if (callback && typeof callback === 'function') { callback() } + if (callback && typeof callback === 'function') { + callback() + } }, 1) } } } function checkLayout () { - var navbarHieght = $('.navbar').outerHeight() + const navbarHieght = $('.navbar').outerHeight() $('body').css('padding-top', navbarHieght + 'px') } @@ -557,22 +602,28 @@ function checkResponsive () { visibleLG = $('.visible-lg').is(':visible') if (visibleXS && appState.currentMode === modeType.both) { - if (editorHasFocus()) { changeMode(modeType.edit) } else { changeMode(modeType.view) } + if (editorHasFocus()) { + changeMode(modeType.edit) + } else { + changeMode(modeType.view) + } } emitUserStatus() } -var lastEditorWidth = 0 -var previousFocusOnEditor = null +let lastEditorWidth = 0 +let previousFocusOnEditor = null function checkEditorStyle () { - var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height() + let desireHeight = editorInstance.statusBar + ? ui.area.edit.height() - editorInstance.statusBar.outerHeight() + : ui.area.edit.height() if (editorInstance.toolBar) { desireHeight = desireHeight - editorInstance.toolBar.outerHeight() } // set editor height and min height based on scrollbar style and mode - var scrollbarStyle = editor.getOption('scrollbarStyle') + const scrollbarStyle = editor.getOption('scrollbarStyle') if (scrollbarStyle === 'overlay' || appState.currentMode === modeType.both) { ui.area.codemirrorScroll.css('height', desireHeight + 'px') ui.area.codemirrorScroll.css('min-height', '') @@ -590,9 +641,11 @@ function checkEditorStyle () { maxWidth: $(window).width() * 0.7, minWidth: $(window).width() * 0.2, create: function (e, ui) { - $(this).parent().on('resize', function (e) { - e.stopPropagation() - }) + $(this) + .parent() + .on('resize', function (e) { + e.stopPropagation() + }) }, start: function (e) { editor.setOption('viewportMargin', Infinity) @@ -622,23 +675,31 @@ function checkEditorStyle () { 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 = $( + '<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 () { appState.syncscroll = !appState.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) - }) + 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) + } + ) } } @@ -651,37 +712,53 @@ function checkSyncToggle () { window.preventSyncScrollToEdit = false syncScrollToEdit() } - ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link') + 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') + ui.area.resize.syncToggle + .find('i') + .removeClass('fa-link') + .addClass('fa-unlink') } } -var checkEditorScrollbar = _.debounce(function () { +const checkEditorScrollbar = _.debounce(function () { editor.operation(checkEditorScrollbarInner) }, 50) function checkEditorScrollbarInner () { // workaround simple scroll bar knob // will get wrong position when editor height changed - var scrollInfo = editor.getScrollInfo() + const 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)) + const paddingRight = parseFloat(ui.area.markdown.css('padding-right')) + const 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 + let newbool + const 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 + const affixLeftMargin = + (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2 + const 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 { @@ -708,12 +785,12 @@ function checkTocStyle () { 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 = '' + const shortStatus = ui.toolbar.shortStatus + const status = ui.toolbar.status + const label = $('<span class="label"></span>') + const fa = $('<i class="fa"></i>') + let msg = '' + let shortMsg = '' shortStatus.html('') status.html('') @@ -738,7 +815,7 @@ function showStatus (type, num) { } label.append(fa) - var shortLabel = label.clone() + const shortLabel = label.clone() shortLabel.append(' ' + shortMsg) shortStatus.append(shortLabel) @@ -761,7 +838,7 @@ function toggleMode () { } } -var lastMode = null +let lastMode = null function changeMode (type) { // lock navbar to prevent it hide after changeMode @@ -771,8 +848,8 @@ function changeMode (type) { lastMode = appState.currentMode appState.currentMode = type } - var responsiveClass = 'col-lg-6 col-md-6 col-sm-6' - var scrollClass = 'ui-scrollable' + const responsiveClass = 'col-lg-6 col-md-6 col-sm-6' + const scrollClass = 'ui-scrollable' ui.area.codemirror.removeClass(scrollClass) ui.area.edit.removeClass(responsiveClass) ui.area.view.removeClass(scrollClass) @@ -798,11 +875,20 @@ function changeMode (type) { break } // save mode to url - if (history.replaceState && window.loaded) history.replaceState(null, '', serverurl + '/' + noteid + '?' + appState.currentMode.name) + if (history.replaceState && window.loaded) { + history.replaceState( + null, + '', + serverurl + '/' + noteid + '?' + appState.currentMode.name + ) + } if (appState.currentMode === modeType.view) { editor.getInputField().blur() } - if (appState.currentMode === modeType.edit || appState.currentMode === modeType.both) { + if ( + appState.currentMode === modeType.edit || + appState.currentMode === modeType.both + ) { // add and update status bar if (!editorInstance.statusBar) { editorInstance.addStatusBar() @@ -820,7 +906,10 @@ function changeMode (type) { $(document.body).css('background-color', 'white') updateView() } else { - $(document.body).css('background-color', ui.area.codemirror.css('background-color')) + $(document.body).css( + 'background-color', + ui.area.codemirror.css('background-color') + ) } // check resizable editor style if (appState.currentMode === modeType.both) { @@ -864,15 +953,18 @@ function changeMode (type) { ui.toolbar.both.removeClass('active') ui.toolbar.edit.removeClass('active') ui.toolbar.view.removeClass('active') - var modeIcon = ui.toolbar.mode.find('i') + const 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 + 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 + } 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 + } else if (ui.area.view.is(':visible')) { + // view ui.toolbar.view.addClass('active') modeIcon.addClass('fa-pencil') } @@ -883,17 +975,27 @@ function lockNavbar () { $('.navbar').addClass('locked') } -var unlockNavbar = _.debounce(function () { +const unlockNavbar = _.debounce(function () { $('.navbar').removeClass('locked') }, 200) function showMessageModal (title, header, href, text, success) { - var modal = $('.message-modal') + const 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') } + 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') } @@ -923,9 +1025,9 @@ ui.toolbar.extra.slide.attr('href', noteurl + '/slide') 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], { + const filename = renderFilename(ui.area.markdown) + '.md' + const markdown = editor.getValue() + const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8' }) saveAs(blob, filename, true) @@ -945,12 +1047,12 @@ ui.toolbar.download.rawhtml.click(function (e) { // export to dropbox ui.toolbar.export.dropbox.click(function (event) { event.preventDefault() - var filename = renderFilename(ui.area.markdown) + '.md' - var options = { + const filename = renderFilename(ui.area.markdown) + '.md' + const options = { files: [ { - 'url': noteurl + '/download', - 'filename': filename + url: noteurl + '/download', + filename: filename } ], error: function (errorMessage) { @@ -971,25 +1073,48 @@ ui.toolbar.export.snippet.click(function () { $('#snippetExportModalVersion').val(data.version) $('#snippetExportModalLoading').hide() $('#snippetExportModal').modal('toggle') - $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') + $('#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) + 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)) { + 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') + $('<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() @@ -998,10 +1123,10 @@ ui.toolbar.export.snippet.click(function () { // import from dropbox ui.toolbar.import.dropbox.click(function (event) { event.preventDefault() - var options = { + const options = { success: function (files) { ui.spinner.show() - var url = files[0].link + const url = files[0].link importFromUrl(url) }, linkType: 'direct', @@ -1026,25 +1151,48 @@ ui.toolbar.import.snippet.click(function () { $('#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>') + $('#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) + 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)) { + 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') + $('<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() @@ -1060,15 +1208,15 @@ $('a[href="#"]').click(function (e) { }) // 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 +let revisions = [] +let revisionViewer = null +let revisionInsert = [] +let revisionDelete = [] +let revisionInsertAnnotation = null +let revisionDeleteAnnotation = null +const revisionList = ui.modal.revision.find('.ui-revision-list') +let revision = null +let revisionTime = null ui.modal.revision.on('show.bs.modal', function (e) { $.get(noteurl + '/revision') .done(function (data) { @@ -1087,7 +1235,7 @@ ui.modal.revision.on('show.bs.modal', function (e) { }) function checkRevisionViewer () { if (revisionViewer) { - var container = $(revisionViewer.display.wrapper).parent() + const container = $(revisionViewer.display.wrapper).parent() $(revisionViewer.display.scroller).css('height', container.height() + 'px') revisionViewer.refresh() } @@ -1097,23 +1245,27 @@ $(window).resize(checkRevisionViewer) function parseRevisions (_revisions) { if (_revisions.length !== revisions) { revisions = _revisions - var lastRevision = null + let 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 class="list-group-item"></a>') + for (let i = 0; i < revisions.length; i++) { + const revision = revisions[i] + const item = $('<a 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) + const itemHeading = $('<h5 class="list-group-item-heading"></h5>') + itemHeading.html( + '<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll') + ) + const 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') + const time = $(this).attr('data-revision-time') selectRevision(time) }) revisionList.append(item) @@ -1129,52 +1281,59 @@ function selectRevision (time) { .done(function (data) { revision = data revisionTime = time - var lastScrollInfo = revisionViewer.getScrollInfo() + const lastScrollInfo = revisionViewer.getScrollInfo() revisionList.children().removeClass('active') - revisionList.find('[data-revision-time="' + time + '"]').addClass('active') - var content = revision.content + revisionList + .find('[data-revision-time="' + time + '"]') + .addClass('active') + const 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] + let bias = 0 + for (let j = 0; j < revision.patch.length; j++) { + const patch = revision.patch[j] + let currIndex = patch.start1 + bias + for (let i = 0; i < patch.diffs.length; i++) { + const diff = patch.diffs[i] // ignore if diff only contains line breaks - if ((diff[1].match(/\n/g) || []).length === diff[1].length) continue - var prePos - var postPos + if ((diff[1].match(/\n/g) || []).length === diff[1].length) { continue } + let prePos, 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) + 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;' + 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) + 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;' + css: + 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;' }) bias += diff[1].length currIndex += diff[1].length @@ -1198,7 +1357,7 @@ function selectRevision (time) { } function initRevisionViewer () { if (revisionViewer) return - var revisionViewerTextArea = document.getElementById('revisionViewer') + const revisionViewerTextArea = document.getElementById('revisionViewer') revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { mode: defaultEditorMode, viewportMargin: viewportMargin, @@ -1213,14 +1372,19 @@ function initRevisionViewer () { autoRefresh: true, scrollbarStyle: 'overlay' }) - revisionInsertAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-insert-match' }) - revisionDeleteAnnotation = revisionViewer.annotateScrollbar({ className: 'CodeMirror-delete-match' }) + 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], { + const filename = + renderFilename(ui.area.markdown) + '_' + revisionTime + '.md' + const blob = new Blob([revision.content], { type: 'text/markdown;charset=utf-8' }) saveAs(blob, filename, true) @@ -1232,17 +1396,34 @@ $('#revisionModalRevert').click(function () { }) // snippet projects ui.modal.snippetImportProjects.change(function () { - var accesstoken = $('#snippetImportModalAccessToken').val() - var baseURL = $('#snippetImportModalBaseURL').val() - var project = $('#snippetImportModalProjects').val() - var version = $('#snippetImportModalVersion').val() + const accesstoken = $('#snippetImportModalAccessToken').val() + const baseURL = $('#snippetImportModalBaseURL').val() + const project = $('#snippetImportModalProjects').val() + const version = $('#snippetImportModalVersion').val() $('#snippetImportModalLoading').show() $('#snippetImportModalContent').val('/projects/' + project) - $.get(baseURL + '/api/' + version + '/projects/' + project + '/snippets?access_token=' + accesstoken) + $.get( + baseURL + + '/api/' + + version + + '/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>') + $('#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')) + $('<option>') + .val(snippet.id) + .text(snippet.title) + .appendTo($('#snippetImportModalSnippets')) }) $('#snippetImportModalLoading').hide() $('#snippetImportModalSnippets').prop('disabled', false) @@ -1259,44 +1440,68 @@ ui.modal.snippetImportProjects.change(function () { }) // snippet snippets ui.modal.snippetImportSnippets.change(function () { - var snippet = $('#snippetImportModalSnippets').val() - $('#snippetImportModalContent').val($('#snippetImportModalContent').val() + '/snippets/' + snippet) + const snippet = $('#snippetImportModalSnippets').val() + $('#snippetImportModalContent').val( + $('#snippetImportModalContent').val() + '/snippets/' + snippet + ) }) function scrollToTop () { if (appState.currentMode === modeType.both) { - if (editor.getScrollInfo().top !== 0) { editor.scrollTo(0, 0) } else { - ui.area.view.animate({ - scrollTop: 0 - }, 100, 'linear') + 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') + $('body, html').stop(true, true).animate( + { + scrollTop: 0 + }, + 100, + 'linear' + ) } } function scrollToBottom () { if (appState.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') + const scrollInfo = editor.getScrollInfo() + const 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') + $('body, html') + .stop(true, true) + .animate( + { + scrollTop: $(document.body)[0].scrollHeight + }, + 100, + 'linear' + ) } } window.scrollToTop = scrollToTop window.scrollToBottom = scrollToBottom -var enoughForAffixToc = true +let enoughForAffixToc = true // scrollspy function generateScrollspy () { @@ -1320,29 +1525,53 @@ function generateScrollspy () { } 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'))) + const headers = ui.area.markdown.find('h1, h2, h3').toArray() + const headerMap = [] + for (let 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) + applyScrollspyActive( + $(window).scrollTop(), + headerMap, + headers, + $('.scrollspy-body'), + 0 + ) + const 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)) { + let index = 0 + for (let 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') + const header = $(headers[index]) + const 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 @@ -1354,7 +1583,7 @@ $('#clipboardModalClear').click(function () { $('#clipboardModalContent').html('') }) $('#clipboardModalConfirm').click(function () { - var data = $('#clipboardModalContent').html() + const data = $('#clipboardModalContent').html() if (data) { parseToEditor(data) $('#clipboardModal').modal('hide') @@ -1372,22 +1601,34 @@ $('#gistImportModalClear').click(function () { $('#gistImportModalContent').val('') }) $('#gistImportModalConfirm').click(function () { - var gisturl = $('#gistImportModalContent').val() + const 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) + showMessageModal( + '<i class="fa fa-github"></i> Import from Gist', + 'Not a valid URL :(', + '', + '', + false + ) } else { - var hostname = url('hostname', gisturl) + const 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) + 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)) .done(function (data) { if (data.files) { - var contents = '' + let contents = '' Object.keys(data.files).forEach(function (key) { contents += key contents += '\n---\n' @@ -1396,11 +1637,23 @@ $('#gistImportModalConfirm').click(function () { }) replaceAll(contents) } else { - showMessageModal('<i class="fa fa-github"></i> Import from Gist', 'Unable to fetch gist files :(', '', '', false) + 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() @@ -1417,21 +1670,32 @@ $('#snippetImportModalClear').click(function () { $('#snippetImportModalSnippets').prop('disabled', true) }) $('#snippetImportModalConfirm').click(function () { - var snippeturl = $('#snippetImportModalContent').val() + const 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) + 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/' + $('#snippetImportModalVersion').val() + snippeturl + const accessToken = + '?access_token=' + $('#snippetImportModalAccessToken').val() + const fullURL = + $('#snippetImportModalBaseURL').val() + + '/api/' + + $('#snippetImportModalVersion').val() + + 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' + let content = '# ' + (data.title || 'Snippet Import') + const fileInfo = data.file_name.split('.') + fileInfo[1] = fileInfo[1] ? fileInfo[1] : 'md' $.get(fullURL + '/raw' + accessToken) .done(function (raw) { if (raw) { @@ -1447,82 +1711,147 @@ $('#snippetImportModalConfirm').click(function () { } }) .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() }) }) .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 + ) }) } }) // snippet export modal $('#snippetExportModalConfirm').click(function () { - var accesstoken = $('#snippetExportModalAccessToken').val() - var baseURL = $('#snippetExportModalBaseURL').val() - var version = $('#snippetExportModalVersion').val() + const accesstoken = $('#snippetExportModalAccessToken').val() + const baseURL = $('#snippetExportModalBaseURL').val() + const version = $('#snippetExportModalVersion').val() - var data = { + const data = { title: $('#snippetExportModalTitle').val(), file_name: $('#snippetExportModalFileName').val(), code: editor.getValue(), visibility_level: $('#snippetExportModalVisibility').val(), - visibility: $('#snippetExportModalVisibility').val() === '0' ? 'private' : ($('#snippetExportModalVisibility').val() === '10' ? 'internal' : 'private') - } - - if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return + visibility: + $('#snippetExportModalVisibility').val() === '0' + ? 'private' + : $('#snippetExportModalVisibility').val() === '10' + ? 'internal' + : 'private' + } + + if ( + !data.title || + !data.file_name || + !data.code || + !data.visibility_level || + !$('#snippetExportModalProjects').val() + ) { return } $('#snippetExportModalLoading').show() - var fullURL = baseURL + '/api/' + version + '/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) - } - ) + const fullURL = + baseURL + + '/api/' + + version + + '/projects/' + + $('#snippetExportModalProjects').val() + + '/snippets?access_token=' + + accesstoken + $.post(fullURL, data, function (ret) { + $('#snippetExportModalLoading').hide() + $('#snippetExportModal').modal('hide') + const 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 parseToEditor (data) { - var turndownService = new TurndownService({ + const turndownService = new TurndownService({ defaultReplacement: function (innerHTML, node) { return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML } }) - var parsed = turndownService.turndown(data) - if (parsed) { replaceAll(parsed) } + const parsed = turndownService.turndown(data) + if (parsed) { + replaceAll(parsed) + } } function replaceAll (data) { - editor.replaceRange(data, { - line: 0, - ch: 0 - }, { - line: editor.lastLine(), - ch: editor.lastLine().length - }, '+input') + editor.replaceRange( + data, + { + line: 0, + ch: 0 + }, + { + line: editor.lastLine(), + ch: editor.lastLine().length + }, + '+input' + ) } function importFromUrl (url) { // console.debug(url); if (!url) return if (!isValidURL(url)) { - showMessageModal('<i class="fa fa-cloud-download"></i> Import from URL', 'Not a valid URL :(', '', '', false) + 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) } + const 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) + showMessageModal( + '<i class="fa fa-cloud-download"></i> Import from URL', + 'Import failed :(', + '', + JSON.stringify(data), + false + ) }, complete: function () { ui.spinner.hide() @@ -1584,8 +1913,8 @@ $('.ui-delete-modal-confirm').click(function () { }) function toggleNightMode () { - var $body = $('body') - var isActive = ui.toolbar.night.hasClass('active') + const $body = $('body') + const isActive = ui.toolbar.night.hasClass('active') if (isActive) { $body.removeClass('night') appState.nightMode = false @@ -1613,8 +1942,8 @@ function updatePermission (newPermission) { permission = newPermission if (window.loaded) refreshView() } - var label = null - var title = null + let label = null + let title = null switch (permission) { case 'freely': label = '<i class="fa fa-leaf"></i> Freely' @@ -1641,7 +1970,11 @@ function updatePermission (newPermission) { title = 'Only owner can view & edit' break } - if (personalInfo.userid && window.owner && personalInfo.userid === window.owner) { + if ( + personalInfo.userid && + window.owner && + personalInfo.userid === window.owner + ) { label += ' <i class="fa fa-caret-down"></i>' ui.infobar.permission.label.removeClass('disabled') } else { @@ -1651,7 +1984,7 @@ function updatePermission (newPermission) { } function havePermission () { - var bool = false + let bool = false switch (permission) { case 'freely': bool = true @@ -1680,8 +2013,8 @@ function havePermission () { window.havePermission = havePermission // socket.io actions -var io = require('socket.io-client') -var socket = io.connect({ +const io = require('socket.io-client') +const socket = io.connect({ path: urlpath ? '/' + urlpath + '/socket.io/' : '', query: { noteId: noteid @@ -1690,13 +2023,17 @@ var socket = io.connect({ reconnectionAttempts: 20 // retry 20 times on connect failed }) // overwrite original event for checking login state -var on = socket.on +const on = socket.on socket.on = function () { - if (!checkLoginStateChanged() && !needRefresh) { return on.apply(socket, arguments) } + if (!checkLoginStateChanged() && !needRefresh) { + return on.apply(socket, arguments) + } } -var emit = socket.emit +const emit = socket.emit socket.emit = function () { - if (!checkLoginStateChanged() && !needRefresh) { emit.apply(socket, arguments) } + if (!checkLoginStateChanged() && !needRefresh) { + emit.apply(socket, arguments) + } } socket.on('info', function (data) { console.error(data) @@ -1714,7 +2051,9 @@ socket.on('info', function (data) { }) socket.on('error', function (data) { console.error(data) - if (data.message && data.message.indexOf('AUTH failed') === 0) { location.href = serverurl + '/403' } + if (data.message && data.message.indexOf('AUTH failed') === 0) { + location.href = serverurl + '/403' + } }) socket.on('delete', function () { if (personalInfo.login) { @@ -1723,13 +2062,13 @@ socket.on('delete', function () { }) } else { getHistory(function (notehistory) { - var newnotehistory = removeHistory(noteid, notehistory) + const newnotehistory = removeHistory(noteid, notehistory) saveHistory(newnotehistory) location.href = serverurl }) } }) -var retryTimer = null +let retryTimer = null socket.on('maintenance', function () { cmClient.revision = -1 }) @@ -1739,7 +2078,9 @@ socket.on('disconnect', function (data) { saveInfo() lastInfo.history = editor.getHistory() } - if (!editor.getOption('readOnly')) { editor.setOption('readOnly', true) } + if (!editor.getOption('readOnly')) { + editor.setOption('readOnly', true) + } if (!retryTimer) { retryTimer = setInterval(function () { if (!needRefresh) socket.connect() @@ -1755,7 +2096,7 @@ socket.on('reconnect', function (data) { socket.on('connect', function (data) { clearInterval(retryTimer) retryTimer = null - personalInfo['id'] = socket.id + personalInfo.id = socket.id showStatus(statusType.connected) socket.emit('version') }) @@ -1769,40 +2110,58 @@ socket.on('version', function (data) { } } }) -var authors = [] -var authorship = [] -var authorMarks = {} // temp variable -var addTextMarkers = [] // temp variable +let authors = [] +let authorship = [] +let authorMarks = {} // temp variable +let addTextMarkers = [] // temp variable function updateInfo (data) { // console.debug(data); - if (data.hasOwnProperty('createtime') && window.createtime !== data.createtime) { + if ( + Object.prototype.hasOwnProperty.call(data, 'createtime') && + window.createtime !== data.createtime + ) { window.createtime = data.createtime updateLastChange() } - if (data.hasOwnProperty('updatetime') && window.lastchangetime !== data.updatetime) { + if ( + Object.prototype.hasOwnProperty.call(data, 'updatetime') && + window.lastchangetime !== data.updatetime + ) { window.lastchangetime = data.updatetime updateLastChange() } - if (data.hasOwnProperty('owner') && window.owner !== data.owner) { + if ( + Object.prototype.hasOwnProperty.call(data, 'owner') && + window.owner !== data.owner + ) { window.owner = data.owner window.ownerprofile = data.ownerprofile updateOwner() } - if (data.hasOwnProperty('lastchangeuser') && window.lastchangeuser !== data.lastchangeuser) { + if ( + Object.prototype.hasOwnProperty.call(data, 'lastchangeuser') && + window.lastchangeuser !== data.lastchangeuser + ) { window.lastchangeuser = data.lastchangeuser window.lastchangeuserprofile = data.lastchangeuserprofile updateLastChangeUser() updateOwner() } - if (data.hasOwnProperty('authors') && authors !== data.authors) { + if ( + Object.prototype.hasOwnProperty.call(data, 'authors') && + authors !== data.authors + ) { authors = data.authors } - if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { + if ( + Object.prototype.hasOwnProperty.call(data, 'authorship') && + authorship !== data.authorship + ) { authorship = data.authorship updateAuthorship() } } -var updateAuthorship = _.debounce(function () { +const updateAuthorship = _.debounce(function () { editor.operation(updateAuthorshipInner) }, 50) function initMark () { @@ -1822,46 +2181,59 @@ function initMarkAndCheckGutter (mark, author, timestamp) { } return mark } -var addStyleRule = (function () { - var added = {} - var styleElement = document.createElement('style') - document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement) - var styleSheet = styleElement.sheet +const addStyleRule = (function () { + const added = {} + const styleElement = document.createElement('style') + document.documentElement + .getElementsByTagName('head')[0] + .appendChild(styleElement) + const styleSheet = styleElement.sheet return function (css) { if (added[css]) { return } added[css] = true - styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length) + styleSheet.insertRule( + css, + (styleSheet.cssRules || styleSheet.rules).length + ) } -}()) +})() function updateAuthorshipInner () { // ignore when ot not synced yet if (havePendingOperation()) return authorMarks = {} for (let i = 0; i < authorship.length; i++) { - var atom = authorship[i] - let author = authors[atom[0]] + const atom = authorship[i] + const 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) + const prePos = editor.posFromIndex(atom[1]) + const preLine = editor.getLine(prePos.line) + const postPos = editor.posFromIndex(atom[2]) + const 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]) + authorMarks[j] = initMarkAndCheckGutter( + authorMarks[j], + author, + atom[3] + ) } } } else if (postPos.line - prePos.line >= 1) { - var startLine = prePos.line - var endLine = postPos.line + let startLine = prePos.line + let 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 = { + const mark = initMarkAndCheckGutter( + authorMarks[prePos.line], + author, + atom[3] + ) + const _postPos = { line: prePos.line, ch: preLine.length } @@ -1877,8 +2249,12 @@ function updateAuthorshipInner () { if (postPos.ch === 0) { endLine-- } else if (postPos.ch !== postLine.length) { - let mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]) - var _prePos = { + const mark = initMarkAndCheckGutter( + authorMarks[postPos.line], + author, + atom[3] + ) + const _prePos = { line: postPos.line, ch: 0 } @@ -1893,11 +2269,19 @@ function updateAuthorshipInner () { } for (let j = startLine; j <= endLine; j++) { if (editor.getLine(j)) { - authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]) + authorMarks[j] = initMarkAndCheckGutter( + authorMarks[j], + author, + atom[3] + ) } } } else { - let mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]) + const mark = initMarkAndCheckGutter( + authorMarks[prePos.line], + author, + atom[3] + ) if (JSON.stringify(prePos) !== JSON.stringify(postPos)) { mark.textmarkers.push({ userid: author.userid, @@ -1910,38 +2294,45 @@ function updateAuthorshipInner () { } addTextMarkers = [] editor.eachLine(iterateLine) - var allTextMarks = editor.getAllMarks() + const allTextMarks = editor.getAllMarks() for (let i = 0; i < allTextMarks.length; i++) { - let _textMarker = allTextMarks[i] - var pos = _textMarker.find() - var found = false + const _textMarker = allTextMarks[i] + const pos = _textMarker.find() + let 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 = { + const textMarker = addTextMarkers[j] + const author = authors[textMarker.userid] + const className = 'authorship-inline-' + author.color.substr(1) + const obj = { from: textMarker.pos[0], to: textMarker.pos[1] } - if (JSON.stringify(pos) === JSON.stringify(obj) && _textMarker.className && - _textMarker.className.indexOf(className) > -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) { + 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] + const textMarker = addTextMarkers[i] + const author = authors[textMarker.userid] const rgbcolor = hex2rgb(author.color) const colorString = `rgba(${rgbcolor.red},${rgbcolor.green},${rgbcolor.blue},0.7)` const styleString = `background-image: linear-gradient(to top, ${colorString} 1px, transparent 1px);` - let className = `authorship-inline-${author.color.substr(1)}` + const className = `authorship-inline-${author.color.substr(1)}` const rule = `.${className} { ${styleString} }` addStyleRule(rule) editor.markText(textMarker.pos[0], textMarker.pos[1], { @@ -1951,19 +2342,22 @@ function updateAuthorshipInner () { } } function iterateLine (line) { - var lineNumber = line.lineNo() - var currMark = authorMarks[lineNumber] - var author = currMark ? authors[currMark.gutter.userid] : null + const lineNumber = line.lineNo() + const currMark = authorMarks[lineNumber] + const author = currMark ? authors[currMark.gutter.userid] : null if (currMark && author) { - let className = 'authorship-gutter-' + author.color.substr(1) + const className = 'authorship-gutter-' + author.color.substr(1) const gutters = line.gutterMarkers - if (!gutters || !gutters['authorship-gutters'] || - !gutters['authorship-gutters'].className || - !gutters['authorship-gutters'].className.indexOf(className) < 0) { + if ( + !gutters || + !gutters['authorship-gutters'] || + !gutters['authorship-gutters'].className || + !gutters['authorship-gutters'].className.indexOf(className) < 0 + ) { const styleString = `border-left: 3px solid ${author.color}; height: ${defaultTextHeight}px; margin-left: 3px;` const rule = `.${className} { ${styleString} }` addStyleRule(rule) - var gutter = $('<div>', { + const gutter = $('<div>', { class: 'authorship-gutter ' + className, title: author.name }) @@ -1973,8 +2367,8 @@ function iterateLine (line) { 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] + for (let i = 0; i < currMark.textmarkers.length; i++) { + const textMarker = currMark.textmarkers[i] if (textMarker.userid !== currMark.gutter.userid) { addTextMarkers.push(textMarker) } @@ -1985,17 +2379,17 @@ editorInstance.on('update', function () { $('.authorship-gutter:not([data-original-title])').tooltip({ container: '.CodeMirror-lines', placement: 'right', - delay: { 'show': 500, 'hide': 100 } + delay: { show: 500, hide: 100 } }) $('.authorship-inline:not([data-original-title])').tooltip({ container: '.CodeMirror-lines', placement: 'bottom', - delay: { 'show': 500, 'hide': 100 } + 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() + const $ele = $(element) + if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) { $ele.remove() } }) }) socket.on('check', function (data) { @@ -2006,7 +2400,7 @@ socket.on('permission', function (data) { updatePermission(data.permission) }) -var permission = null +let permission = null socket.on('refresh', function (data) { // console.debug(data); editorInstance.config.docmaxlength = data.docmaxlength @@ -2015,13 +2409,17 @@ socket.on('refresh', function (data) { updatePermission(data.permission) if (!window.loaded) { // auto change mode if no content detected - var nocontent = editor.getValue().length <= 0 + const nocontent = editor.getValue().length <= 0 if (nocontent) { - if (visibleXS) { appState.currentMode = modeType.edit } else { appState.currentMode = modeType.both } + if (visibleXS) { + appState.currentMode = modeType.edit + } else { + appState.currentMode = modeType.both + } } // parse mode from url if (window.location.search.length > 0) { - var urlMode = modeType[window.location.search.substr(1)] + const urlMode = modeType[window.location.search.substr(1)] if (urlMode) appState.currentMode = urlMode } changeMode(appState.currentMode) @@ -2041,23 +2439,34 @@ socket.on('refresh', function (data) { scrollToHash() }, 1) } - if (editor.getOption('readOnly')) { editor.setOption('readOnly', false) } + 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 +const EditorClient = ot.EditorClient +const SocketIOAdapter = ot.SocketIOAdapter +const CodeMirrorAdapter = ot.CodeMirrorAdapter +let cmClient = null +let synchronized_ = null function havePendingOperation () { - return !!((cmClient && cmClient.state && cmClient.state.hasOwnProperty('outstanding'))) + return !!( + cmClient && + cmClient.state && + Object.prototype.hasOwnProperty.call(cmClient, 'outstanding') + ) } 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 + const body = obj.str + const bodyMismatch = editor.getValue() !== body + const setDoc = + !cmClient || + (cmClient && + (cmClient.revision === -1 || + (cmClient.revision !== obj.revision && !havePendingOperation()))) || + obj.force saveInfo() if (setDoc && bodyMismatch) { @@ -2079,8 +2488,10 @@ socket.on('doc', function (obj) { if (!cmClient) { cmClient = window.cmClient = new EditorClient( - obj.revision, obj.clients, - new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) + obj.revision, + obj.clients, + new SocketIOAdapter(socket), + new CodeMirrorAdapter(editor) ) synchronized_ = cmClient.state } else if (setDoc) { @@ -2115,91 +2526,120 @@ socket.on('operation', function () { }) socket.on('online users', function (data) { - if (debug) { console.debug(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() - }) + $('.CodeMirror-other-cursors') + .children() + .each(function (key, value) { + let found = false + for (let i = 0; i < data.users.length; i++) { + const user = data.users[i] + if ($(this).attr('id') === user.id) { + found = true + } + } + if (!found) { + $(this) + .stop(true) + .fadeOut('normal', function () { + $(this).remove() + }) + } + }) + for (let i = 0; i < data.users.length; i++) { + const user = data.users[i] + if (user.id !== socket.id) { + buildCursor(user) + } else { + personalInfo = user } - }) - for (var i = 0; i < data.users.length; i++) { - var user = data.users[i] - if (user.id !== socket.id) { buildCursor(user) } else { personalInfo = user } } }) socket.on('user status', function (data) { - if (debug) { console.debug(data) } - for (var i = 0; i < onlineUsers.length; i++) { + if (debug) { + console.debug(data) + } + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === data.id) { onlineUsers[i] = data } } updateOnlineStatus() - if (data.id !== socket.id) { buildCursor(data) } + 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 (debug) { + console.debug(data) + } + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === data.id) { onlineUsers[i].cursor = data.cursor } } - if (data.id !== socket.id) { buildCursor(data) } + if (data.id !== socket.id) { + buildCursor(data) + } // force show - var cursor = $('div[data-clientid="' + data.id + '"]') + const 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 (debug) { + console.debug(data) + } + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === data.id) { 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 (debug) { + console.debug(data) + } + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === data.id) { onlineUsers[i].cursor = null } } - if (data.id !== socket.id) { buildCursor(data) } + if (data.id !== socket.id) { + buildCursor(data) + } // force hide - var cursor = $('div[data-clientid="' + data.id + '"]') + const cursor = $('div[data-clientid="' + data.id + '"]') if (cursor.length > 0) { cursor.stop(true).fadeOut() } }) -var options = { +const 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>' + 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) +const onlineUserList = new List('online-user-list', options) +const shortOnlineUserList = new List('short-online-user-list', options) function updateOnlineStatus () { if (!window.loaded || !socket.connected) return - var _onlineUsers = deduplicateOnlineUsers(onlineUsers) + const _onlineUsers = deduplicateOnlineUsers(onlineUsers) showStatus(statusType.online, _onlineUsers.length) - var items = onlineUserList.items + const items = onlineUserList.items // update or remove current list items for (let i = 0; i < items.length; i++) { let found = false @@ -2211,7 +2651,7 @@ function updateOnlineStatus () { break } } - let id = items[i].values().id + const id = items[i].values().id if (found) { onlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) shortOnlineUserList.get('id', id)[0].values(_onlineUsers[foundindex]) @@ -2246,23 +2686,57 @@ 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)) + const usera = a.values() + const userb = b.values() + const useraIsSelf = + usera.id === personalInfo.id || + (usera.login && usera.userid === personalInfo.userid) + const 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()) { + 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()) { + } 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 } + 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 + } } } } @@ -2272,11 +2746,11 @@ function sortOnlineUserList (list) { } 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') + const items = list.items + for (let j = 0; j < items.length; j++) { + const item = items[j] + const userstatus = $(item.elm).find('.ui-user-status') + const 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 @@ -2286,18 +2760,26 @@ function renderUserStatusList (list) { } 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') } + 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++) { + const _onlineUsers = [] + for (let i = 0; i < list.length; i++) { + const user = $.extend({}, list[i]) + if (!user.userid) { + _onlineUsers.push(user) + } else { + let found = false + for (let j = 0; j < _onlineUsers.length; j++) { if (_onlineUsers[j].userid === user.userid) { // keep self color when login if (user.id === personalInfo.id) { @@ -2312,29 +2794,39 @@ function deduplicateOnlineUsers (list) { break } } - if (!found) { _onlineUsers.push(user) } + if (!found) { + _onlineUsers.push(user) + } } } return _onlineUsers } -var userStatusCache = null +let userStatusCache = null function emitUserStatus (force) { if (!window.loaded) return - var type = null - if (visibleXS) { type = 'xs' } else if (visibleSM) { type = 'sm' } else if (visibleMD) { type = 'md' } else if (visibleLG) { type = 'lg' } + let type = null + if (visibleXS) { + type = 'xs' + } else if (visibleSM) { + type = 'sm' + } else if (visibleMD) { + type = 'md' + } else if (visibleLG) { + type = 'lg' + } - personalInfo['idle'] = idle.isAway - personalInfo['type'] = type + personalInfo.idle = idle.isAway + personalInfo.type = type - for (var i = 0; i < onlineUsers.length; i++) { + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === personalInfo.id) { onlineUsers[i] = personalInfo } } - var userStatus = { + const userStatus = { idle: idle.isAway, type: type } @@ -2348,24 +2840,24 @@ function emitUserStatus (force) { function checkCursorTag (coord, ele) { if (!ele) return // return if element not exists // set margin - var tagRightMargin = 0 - var tagBottomMargin = 2 + const tagRightMargin = 0 + const tagBottomMargin = 2 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width() + const docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorHeight = ui.area.codemirror.height() + const editorHeight = ui.area.codemirror.height() // get element size - var width = ele.outerWidth() - var height = ele.outerHeight() - var padding = (ele.outerWidth() - ele.width()) / 2 + const width = ele.outerWidth() + const height = ele.outerHeight() + const padding = (ele.outerWidth() - ele.width()) / 2 // get coord position - var left = coord.left - var top = coord.top + const left = coord.left + const top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top + const docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = -3 - var offsetTop = defaultTextHeight + let offsetLeft = -3 + let offsetTop = defaultTextHeight // only do when have width and height if (width > 0 && height > 0) { // flip x when element right bound larger than doc width @@ -2374,8 +2866,12 @@ function checkCursorTag (coord, ele) { } // 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 @@ -2386,10 +2882,10 @@ function checkCursorTag (coord, ele) { function buildCursor (user) { if (appState.currentMode === modeType.view) return if (!user.cursor) return - var coord = editor.charCoords(user.cursor, 'windows') + const 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' + let iconClass = 'fa-user' switch (user.type) { case 'xs': iconClass = 'fa-mobile' @@ -2405,19 +2901,29 @@ function buildCursor (user) { break } if ($('div[data-clientid="' + user.id + '"]').length <= 0) { - let cursor = $('<div data-clientid="' + user.id + '" class="CodeMirror-other-cursor" style="display:none;"></div>') + const 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"> </div>') + const cursorbar = $('<div class="cursorbar"> </div>') cursorbar[0].style.height = defaultTextHeight + 'px' cursorbar[0].style.borderLeft = '2px solid ' + user.color - var icon = '<i class="fa ' + iconClass + '"></i>' + const icon = '<i class="fa ' + iconClass + '"></i>' - let cursortag = $('<div class="cursortag">' + icon + ' <span class="name">' + user.name + '</span></div>') + const cursortag = $( + '<div class="cursortag">' + + icon + + ' <span class="name">' + + user.name + + '</span></div>' + ) // cursortag[0].style.background = color; cursortag[0].style.color = user.color @@ -2425,28 +2931,43 @@ function buildCursor (user) { 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') } - }) + if (cursor.attr('data-mode') === 'hover') { + cursortag.stop(true).fadeOut('fast') + } + } + ) - var hideCursorTagDelay = 2000 - var hideCursorTagTimer = null + const hideCursorTagDelay = 2000 + let hideCursorTagTimer = null - 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') } + const 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') + } } - var switchTag = function (ele) { - if (ele.css('display') === 'none') { ele.stop(true).fadeIn('fast') } else { ele.stop(true).fadeOut('fast') } + const switchTag = function (ele) { + if (ele.css('display') === 'none') { + ele.stop(true).fadeIn('fast') + } else { + ele.stop(true).fadeOut('fast') + } } - var hideCursorTag = function () { - if (cursor.attr('data-mode') === 'hover') { cursortag.fadeOut('fast') } + const hideCursorTag = function () { + if (cursor.attr('data-mode') === 'hover') { + cursortag.fadeOut('fast') + } } cursor.on('touchstart', function (e) { - var display = cursortag.css('display') + const display = cursortag.css('display') cursortag.stop(true).fadeIn('fast') clearTimeout(hideCursorTagTimer) hideCursorTagTimer = setTimeout(hideCursorTag, hideCursorTagDelay) @@ -2456,7 +2977,9 @@ function buildCursor (user) { } }) cursortag.on('mousedown touchstart', function (e) { - if (cursor.attr('data-mode') === 'state') { switchTag(cursortag) } + if (cursor.attr('data-mode') === 'state') { + switchTag(cursortag) + } switchMode(cursor) e.preventDefault() e.stopPropagation() @@ -2469,19 +2992,21 @@ function buildCursor (user) { cursor[0].style.top = coord.top + 'px' $('.CodeMirror-other-cursors').append(cursor) - if (!user.idle) { cursor.stop(true).fadeIn() } + if (!user.idle) { + cursor.stop(true).fadeIn() + } checkCursorTag(coord, cursortag) } else { - let cursor = $('div[data-clientid="' + user.id + '"]') + const 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') + const cursorbar = cursor.find('.cursorbar') cursorbar[0].style.height = defaultTextHeight + 'px' cursorbar[0].style.borderLeft = '2px solid ' + user.color - let cursortag = cursor.find('.cursortag') + const cursortag = cursor.find('.cursortag') cursortag.find('i').removeClass().addClass('fa').addClass(iconClass) cursortag.find('.name').text(user.name) @@ -2489,16 +3014,23 @@ function buildCursor (user) { 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 - }) + 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() } + 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) } @@ -2506,18 +3038,23 @@ function buildCursor (user) { // editor actions function removeNullByte (cm, change) { - var str = change.text.join('\n') + const str = change.text.join('\n') // eslint-disable-next-line no-control-regex if (/\u0000/g.test(str) && change.update) { - // eslint-disable-next-line no-control-regex - change.update(change.from, change.to, str.replace(/\u0000/g, '').split('\n')) + change.update( + change.from, + change.to, + // eslint-disable-next-line no-control-regex + str.replace(/\u0000/g, '').split('\n') + ) } } function enforceMaxLength (cm, change) { - var maxLength = cm.getOption('maxLength') + const 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)) + let str = change.text.join('\n') + let delta = + str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from)) if (delta <= 0) { return false } @@ -2530,14 +3067,16 @@ function enforceMaxLength (cm, change) { } return false } -var ignoreEmitEvents = ['setValue', 'ignoreHistory'] +const ignoreEmitEvents = ['setValue', 'ignoreHistory'] editorInstance.on('beforeChange', function (cm, change) { - if (debug) { console.debug(change) } + if (debug) { + console.debug(change) + } removeNullByte(cm, change) if (enforceMaxLength(cm, change)) { $('.limit-modal').modal('show') } - var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) !== -1) + const isIgnoreEmitEvent = ignoreEmitEvents.indexOf(change.origin) !== -1 if (!isIgnoreEmitEvent) { if (!havePermission()) { change.canceled = true @@ -2557,7 +3096,9 @@ editorInstance.on('beforeChange', function (cm, change) { updateTitleReminder() } } - if (cmClient && !socket.connected) { cmClient.editorAdapter.ignoreNextChange = true } + if (cmClient && !socket.connected) { + cmClient.editorAdapter.ignoreNextChange = true + } }) editorInstance.on('cut', function () { // na @@ -2567,9 +3108,9 @@ editorInstance.on('paste', function () { }) editorInstance.on('changes', function (editor, changes) { updateHistory() - var docLength = editor.getValue().length + const docLength = editor.getValue().length // workaround for big documents - var newViewportMargin = 20 + let newViewportMargin = 20 if (docLength > 20000) { newViewportMargin = 1 } else if (docLength > 10000) { @@ -2582,7 +3123,10 @@ editorInstance.on('changes', function (editor, changes) { windowResize() } checkEditorScrollbar() - if (ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && editorHasFocus()) { + if ( + ui.area.codemirrorScroll[0].scrollHeight > ui.area.view[0].scrollHeight && + editorHasFocus() + ) { postUpdateEvent = function () { syncScrollToView() postUpdateEvent = null @@ -2590,12 +3134,12 @@ editorInstance.on('changes', function (editor, changes) { } }) editorInstance.on('focus', function (editor) { - for (var i = 0; i < onlineUsers.length; i++) { + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === personalInfo.id) { onlineUsers[i].cursor = editor.getCursor() } } - personalInfo['cursor'] = editor.getCursor() + personalInfo.cursor = editor.getCursor() socket.emit('cursor focus', editor.getCursor()) }) @@ -2603,12 +3147,12 @@ const cursorActivity = _.debounce(cursorActivityInner, cursorActivityDebounce) function cursorActivityInner (editor) { if (editorHasFocus() && !Visibility.hidden()) { - for (var i = 0; i < onlineUsers.length; i++) { + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === personalInfo.id) { onlineUsers[i].cursor = editor.getCursor() } } - personalInfo['cursor'] = editor.getCursor() + personalInfo.cursor = editor.getCursor() socket.emit('cursor activity', editor.getCursor()) } } @@ -2632,7 +3176,7 @@ editorInstance.on('beforeSelectionChange', function (doc, selections) { // borrow from brackets EditorStatusBar.js if (start.line !== end.line) { - var lines = end.line - start.line + 1 + let lines = end.line - start.line + 1 if (end.ch === 0) { lines-- } @@ -2650,19 +3194,19 @@ editorInstance.on('beforeSelectionChange', function (doc, selections) { }) editorInstance.on('blur', function (cm) { - for (var i = 0; i < onlineUsers.length; i++) { + for (let i = 0; i < onlineUsers.length; i++) { if (onlineUsers[i].id === personalInfo.id) { onlineUsers[i].cursor = null } } - personalInfo['cursor'] = null + personalInfo.cursor = null socket.emit('cursor blur') }) function saveInfo () { - var scrollbarStyle = editor.getOption('scrollbarStyle') - var left = $(window).scrollLeft() - var top = $(window).scrollTop() + const scrollbarStyle = editor.getOption('scrollbarStyle') + const left = $(window).scrollLeft() + const top = $(window).scrollTop() switch (appState.currentMode) { case modeType.edit: if (scrollbarStyle === 'native') { @@ -2688,10 +3232,10 @@ function saveInfo () { } function restoreInfo () { - var scrollbarStyle = editor.getOption('scrollbarStyle') + const scrollbarStyle = editor.getOption('scrollbarStyle') if (lastInfo.needRestore) { - var line = lastInfo.edit.cursor.line - var ch = lastInfo.edit.cursor.ch + const line = lastInfo.edit.cursor.line + const ch = lastInfo.edit.cursor.ch editor.setCursor(line, ch) editor.setSelections(lastInfo.edit.selections) switch (appState.currentMode) { @@ -2700,8 +3244,8 @@ function restoreInfo () { $(window).scrollLeft(lastInfo.edit.scroll.left) $(window).scrollTop(lastInfo.edit.scroll.top) } else { - let left = lastInfo.edit.scroll.left - let top = lastInfo.edit.scroll.top + const left = lastInfo.edit.scroll.left + const top = lastInfo.edit.scroll.top editor.scrollIntoView() editor.scrollTo(left, top) } @@ -2711,10 +3255,8 @@ function restoreInfo () { $(window).scrollTop(lastInfo.view.scroll.top) break case modeType.both: - let left = lastInfo.edit.scroll.left - let top = lastInfo.edit.scroll.top editor.scrollIntoView() - editor.scrollTo(left, top) + editor.scrollTo(lastInfo.edit.scroll.left, lastInfo.edit.scroll.top) ui.area.view.scrollLeft(lastInfo.view.scroll.left) ui.area.view.scrollTop(lastInfo.view.scroll.top) break @@ -2731,27 +3273,30 @@ function refreshView () { updateViewInner() } -var updateView = _.debounce(function () { +const updateView = _.debounce(function () { editor.operation(updateViewInner) }, updateViewDebounce) -var lastResult = null -var postUpdateEvent = null +let lastResult = null +let postUpdateEvent = null function updateViewInner () { if (appState.currentMode === modeType.edit || !isDirty) return - var value = editor.getValue() - var lastMeta = md.meta + const value = editor.getValue() + const lastMeta = md.meta md.meta = {} delete md.metaError - var rendered = md.render(value) + let rendered = md.render(value) if (md.meta.type && md.meta.type === 'slide') { ui.area.view.addClass('black') - var slideOptions = { + const slideOptions = { separator: '^(\r\n?|\n)---(\r\n?|\n)$', verticalSeparator: '^(\r\n?|\n)----(\r\n?|\n)$' } - var slides = window.RevealMarkdown.slidify(editor.getValue(), slideOptions) + const slides = window.RevealMarkdown.slidify( + editor.getValue(), + slideOptions + ) ui.area.markdown.html(slides) window.RevealMarkdown.initialize() // prevent XSS @@ -2769,14 +3314,22 @@ function updateViewInner () { } // 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')) + 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() + const 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()) } + if (result && lastResult && result.length !== lastResult.length) { + updateDataAttrs(result, ui.area.markdown.children().toArray()) + } lastResult = $(result).clone() } removeDOMEvents(ui.area.markdown) @@ -2794,12 +3347,14 @@ function updateViewInner () { clearMap() // buildMap(); updateTitleReminder() - if (postUpdateEvent && typeof postUpdateEvent === 'function') { postUpdateEvent() } + if (postUpdateEvent && typeof postUpdateEvent === 'function') { + postUpdateEvent() + } } -var updateHistoryDebounce = 600 +const updateHistoryDebounce = 600 -var updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce) +const updateHistory = _.debounce(updateHistoryInner, updateHistoryDebounce) function updateHistoryInner () { writeHistory(renderFilename(ui.area.markdown), renderTags(ui.area.markdown)) @@ -2807,50 +3362,59 @@ function updateHistoryInner () { function updateDataAttrs (src, des) { // sync data attr startline and endline - for (var i = 0; i < src.length; i++) { + for (let 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) { + 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 + 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]) + const rawSrc = cloneAndRemoveDataAttr(src[i]) + const rawTar = cloneAndRemoveDataAttr(tar[i]) if (rawSrc.outerHTML !== rawTar.outerHTML) { // console.debug(rawSrc); // console.debug(rawTar); $(des[i]).replaceWith(src[i]) } } - } else { // diff length - var start = 0 + } else { + // diff length + let 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]) + const rawSrc = cloneAndRemoveDataAttr(src[i]) + const rawTar = cloneAndRemoveDataAttr(tar[i]) if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { start = i break } } // find diff end position - var srcEnd = 0 - var tarEnd = 0 + let srcEnd = 0 + let 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]) + const rawSrc = cloneAndRemoveDataAttr(src[i]) + const rawTar = cloneAndRemoveDataAttr(tar[i]) if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { start = i break @@ -2858,12 +3422,12 @@ function partialUpdate (src, tar, des) { } // tar end for (let i = 1; i <= tar.length + 1; i++) { - let srcLength = src.length - let tarLength = tar.length + const srcLength = src.length + const 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]) + const rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + const rawTar = cloneAndRemoveDataAttr(tar[tarLength - i]) if (!rawSrc || !rawTar || rawSrc.outerHTML !== rawTar.outerHTML) { tarEnd = tar.length - i break @@ -2871,25 +3435,35 @@ function partialUpdate (src, tar, des) { } // src end for (let i = 1; i <= src.length + 1; i++) { - let srcLength = src.length - let tarLength = tar.length + const srcLength = src.length + const 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]) + const rawSrc = cloneAndRemoveDataAttr(src[srcLength - i]) + const 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 } + let overlap = 0 + for (let i = start; i >= 0; i--) { + const rawTarStart = cloneAndRemoveDataAttr(tar[i - 1]) + const rawTarEnd = cloneAndRemoveDataAttr(tar[tarEnd + 1 + start - i]) + if ( + rawTarStart && + rawTarEnd && + rawTarStart.outerHTML === rawTarEnd.outerHTML + ) { + overlap++ + } else { + break + } + } + if (debug) { + console.debug('overlap:' + overlap) } - if (debug) { console.debug('overlap:' + overlap) } // show diff content if (debug) { console.debug('start:' + start) @@ -2898,10 +3472,10 @@ function partialUpdate (src, tar, des) { } tarEnd += overlap srcEnd += overlap - var repeatAdd = (start - srcEnd) < (start - tarEnd) - var repeatDiff = Math.abs(srcEnd - tarEnd) - 1 + const repeatAdd = start - srcEnd < start - tarEnd + const repeatDiff = Math.abs(srcEnd - tarEnd) - 1 // push new elements - var newElements = [] + const newElements = [] if (srcEnd >= start) { for (let j = start; j <= srcEnd; j++) { if (!src[j]) continue @@ -2914,7 +3488,7 @@ function partialUpdate (src, tar, des) { } } // push remove elements - var removeElements = [] + const removeElements = [] if (tarEnd >= start) { for (let j = start; j <= tarEnd; j++) { if (!des[j]) continue @@ -2931,28 +3505,38 @@ function partialUpdate (src, tar, des) { console.debug('ADD ELEMENTS') console.debug(newElements.join('\n')) } - if (des[start]) { $(newElements.join('')).insertBefore(des[start]) } else { $(newElements.join('')).insertAfter(des[start - 1]) } + if (des[start]) { + $(newElements.join('')).insertBefore(des[start]) + } else { + $(newElements.join('')).insertAfter(des[start - 1]) + } // remove elements - if (debug) { console.debug('REMOVE ELEMENTS') } + if (debug) { + console.debug('REMOVE ELEMENTS') + } for (let j = 0; j < removeElements.length; j++) { if (debug) { console.debug(removeElements[j].outerHTML) } - if (removeElements[j]) { $(removeElements[j]).remove() } + if (removeElements[j]) { + $(removeElements[j]).remove() + } } } } function cloneAndRemoveDataAttr (el) { if (!el) return - var rawEl = $(el).clone() + const 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 (src && src.getAttribute(attr) && des) { + des.setAttribute(attr, src.getAttribute(attr)) + } } if ($('.cursor-menu').length <= 0) { @@ -2960,69 +3544,83 @@ if ($('.cursor-menu').length <= 0) { } function reverseSortCursorMenu (dropdown) { - var items = dropdown.find('.textcomplete-item') + const 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) +const checkCursorMenu = _.throttle(checkCursorMenuInner, cursorMenuThrottle) function checkCursorMenuInner () { // get element - var dropdown = $('.cursor-menu > .dropdown-menu') + const dropdown = $('.cursor-menu > .dropdown-menu') // return if not exists if (dropdown.length <= 0) return // set margin - var menuRightMargin = 10 - var menuBottomMargin = 4 + const menuRightMargin = 10 + const menuBottomMargin = 4 // use sizer to get the real doc size (won't count status bar and gutters) - var docWidth = ui.area.codemirrorSizer.width() + const docWidth = ui.area.codemirrorSizer.width() // get editor size (status bar not count in) - var editorHeight = ui.area.codemirror.height() + const editorHeight = ui.area.codemirror.height() // get element size - var width = dropdown.outerWidth() - var height = dropdown.outerHeight() + const width = dropdown.outerWidth() + const height = dropdown.outerHeight() // get cursor - var cursor = editor.getCursor() + const cursor = editor.getCursor() // set element cursor data - if (!dropdown.hasClass('CodeMirror-other-cursor')) { dropdown.addClass('CodeMirror-other-cursor') } + 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 + const coord = editor.charCoords( + { + line: cursor.line, + ch: cursor.ch + }, + 'windows' + ) + const left = coord.left + const top = coord.top // get doc top offset (to workaround with viewport) - var docTopOffset = ui.area.codemirrorSizerInner.position().top + const docTopOffset = ui.area.codemirrorSizerInner.position().top // set offset - var offsetLeft = 0 - var offsetTop = defaultTextHeight + let offsetLeft = 0 + let offsetTop = defaultTextHeight // set up side down window.upSideDown = false - var lastUpSideDown = window.upSideDown = false + let lastUpSideDown = (window.upSideDown = false) // only do when have width and height 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) { + 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)) window.upSideDown = true } - var textCompleteDropdown = $(editor.getInputField()).data('textComplete').dropdown + const textCompleteDropdown = $(editor.getInputField()).data('textComplete') + .dropdown lastUpSideDown = textCompleteDropdown.upSideDown textCompleteDropdown.upSideDown = window.upSideDown } // make menu scroll top only if upSideDown changed - if (window.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) @@ -3033,33 +3631,37 @@ function checkCursorMenuInner () { 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')) + const line = editor.getLine(editor.getCursor().line) + const isIndentCode = + line.substr(0, 4) === ' ' || line.substr(0, 1) === '\t' return isIndentCode } -var isInCode = false +let 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 + const cursor = editor.getCursor() + let text = [] + for (let 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) + text = + text.join('\n') + '\n' + editor.getLine(cursor.line).slice(0, cursor.ch) // console.debug(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 + const cursor = editor.getCursor() + const count = editor.lineCount() + let text = [] + for (let 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') @@ -3068,7 +3670,7 @@ function checkBelow (method) { } function matchInCode (text) { - var match + let match match = text.match(/`{3,}/g) if (match && match.length % 2) { return true @@ -3082,8 +3684,8 @@ function matchInCode (text) { } } -var isInContainer = false -var isInContainerSyntax = false +let isInContainer = false +let isInContainerSyntax = false function checkInContainer () { isInContainer = checkAbove(matchInContainer) && !checkInIndentCode() @@ -3091,13 +3693,12 @@ function checkInContainer () { function checkInContainerSyntax () { // if line starts with :::, it's in container syntax - var line = editor.getLine(editor.getCursor().line) - isInContainerSyntax = (line.substr(0, 3) === ':::') + const line = editor.getLine(editor.getCursor().line) + isInContainerSyntax = line.substr(0, 3) === ':::' } function matchInContainer (text) { - var match - match = text.match(/:{3,}/g) + const match = text.match(/:{3,}/g) if (match && match.length % 2) { return true } else { @@ -3106,194 +3707,244 @@ function matchInContainer (text) { } $(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(window.emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) === 0) { // match at first character - list.push(emoji) + .textcomplete( + [ + { + // emoji strategy + match: /(^|\n|\s)\B:([-+\w]*)$/, + search: function (term, callback) { + const line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + const list = [] + $.map(window.emojify.emojiNames, function (emoji) { + if (emoji.indexOf(term) === 0) { + // match at first character + list.push(emoji) + } + }) + $.map(window.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 + } + }, + { + // Code block language strategy + langs: supportCodeModes, + charts: supportCharts, + match: /(^|\n)```(\w+)$/, + search: function (term, callback) { + const line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[2] + const 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) { + let ending = '' + if (!checkBelow(matchInCode)) { + ending = '\n\n```' } - }) - $.map(window.emojify.emojiNames, function (emoji) { - if (emoji.indexOf(term) !== -1) { // match inside the word - list.push(emoji) + if (this.langs.indexOf(lang) !== -1) { + return '$1```' + lang + '=' + ending + } else if (this.charts.indexOf(lang) !== -1) { + return '$1```' + lang + ending } - }) - 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 - } - }, - { // 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```' + }, + done: function () { + const cursor = editor.getCursor() + let text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.debug(text); + if (text === '\n```') { + editor.doc.cm.execCommand('goLineUp') + } + }, + context: function (text) { + return isInCode } - 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.debug(text); - if (text === '\n```') { editor.doc.cm.execCommand('goLineUp') } - }, - 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) - }, - replace: function (lang) { - var ending = '' - if (!checkBelow(matchInContainer)) { - ending = '\n\n:::' + { + // Container strategy + containers: supportContainers, + match: /(^|\n):::(\s*)(\w*)$/, + search: function (term, callback) { + const line = editor.getLine(editor.getCursor().line) + term = line.match(this.match)[3].trim() + const list = [] + $.map(this.containers, function (container) { + if (container.indexOf(term) === 0 && container !== term) { + list.push(container) + } + }) + callback(list) + }, + replace: function (lang) { + let ending = '' + if (!checkBelow(matchInContainer)) { + ending = '\n\n:::' + } + if (this.containers.indexOf(lang) !== -1) { + return '$1:::$2' + lang + ending + } + }, + done: function () { + const cursor = editor.getCursor() + let text = [] + text.push(editor.getLine(cursor.line - 1)) + text.push(editor.getLine(cursor.line)) + text = text.join('\n') + // console.debug(text); + if (text === '\n:::') { + editor.doc.cm.execCommand('goLineUp') + } + }, + context: function (text) { + return !isInCode && isInContainer } - 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.debug(text); - if (text === '\n:::') { editor.doc.cm.execCommand('goLineUp') } - }, - 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 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 + { + // 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 + } }, - 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) { + { + // extra tags for list + match: /(^[>\s]*[-+*]\s(?:\[[x ]\]|.*))(\[\])(\w*)$/, + search: function (term, callback) { + const list = [] $.map(supportExtraTags, function (extratag) { - if (extratag.search.indexOf(term) === 0) { list.push(extratag.command()) } + 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 } - $.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 - } - }, - { // 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 - })) + { + // extra tags for blockquote + match: /(?:^|\n|\s)(>.*|\s|)((\^|)\[(\^|)\](\[\]|\(\)|:|)\s*\w*)$/, + search: function (term, callback) { + const line = editor.getLine(editor.getCursor().line) + const quote = line.match(this.match)[1].trim() + const 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 + } }, - replace: function (value) { - return '$1' + value + { + // 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 + } }, - 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') } - ], { - appendTo: $('.cursor-menu') - }) + ) .on({ 'textComplete:beforeSearch': function (e) { // NA @@ -3307,22 +3958,22 @@ $(editor.getInputField()) 'textComplete:show': function (e) { $(this).data('autocompleting', true) editor.setOption('extraKeys', { - 'Up': function () { + Up: function () { return false }, - 'Right': function () { + Right: function () { editor.doc.cm.execCommand('goCharRight') }, - 'Down': function () { + Down: function () { return false }, - 'Left': function () { + Left: function () { editor.doc.cm.execCommand('goCharLeft') }, - 'Enter': function () { + Enter: function () { return false }, - 'Backspace': function () { + Backspace: function () { editor.doc.cm.execCommand('delCharBefore') } }) diff --git a/public/js/lib/appState.js b/public/js/lib/appState.js index 87aaf737..f409908c 100644 --- a/public/js/lib/appState.js +++ b/public/js/lib/appState.js @@ -1,6 +1,6 @@ import modeType from './modeType' -let state = { +const state = { syncscroll: true, currentMode: modeType.view, nightMode: false diff --git a/public/js/lib/common/login.js b/public/js/lib/common/login.js index 3f7a3e4d..88e8f8cf 100644 --- a/public/js/lib/common/login.js +++ b/public/js/lib/common/login.js @@ -7,7 +7,7 @@ let checkAuth = false let profile = null let lastLoginState = getLoginState() let lastUserId = getUserId() -var loginStateChangeEvent = null +let loginStateChangeEvent = null export function setloginStateChangeEvent (func) { loginStateChangeEvent = func diff --git a/public/js/lib/editor/config.js b/public/js/lib/editor/config.js index 9508b847..338ef6dc 100644 --- a/public/js/lib/editor/config.js +++ b/public/js/lib/editor/config.js @@ -1,4 +1,4 @@ -let config = { +const config = { docmaxlength: null } diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js index d86ebf3c..c84a3725 100644 --- a/public/js/lib/editor/index.js +++ b/public/js/lib/editor/index.js @@ -35,30 +35,30 @@ export default class Editor { }, Enter: 'newlineAndIndentContinueMarkdownList', Tab: function (cm) { - var tab = '\t' + const tab = '\t' // contruct x length spaces - var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ') + const 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) + const cursor = cm.getCursor() + const line = cm.getLine(cursor.line) // this regex match the following patterns // 1. blockquote starts with "> " or ">>" // 2. unorder list starts with *+- // 3. order list starts with "1." or "1)" - var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/ + const regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/ - var match - var multiple = cm.getSelection().split('\n').length > 1 || + let match + const 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 = { + const ch = match[1].length + const pos = { line: cursor.line, ch: ch } @@ -77,8 +77,8 @@ export default class Editor { }, 'Cmd-Left': 'goLineLeftSmart', 'Cmd-Right': 'goLineRight', - 'Home': 'goLineLeftSmart', - 'End': 'goLineRight', + Home: 'goLineLeftSmart', + End: 'goLineRight', 'Ctrl-C': function (cm) { if (!isMac && cm.getOption('keyMap').substr(0, 3) === 'vim') { document.execCommand('copy') @@ -140,27 +140,27 @@ export default class Editor { } addToolBar () { - var inlineAttach = inlineAttachment.editors.codemirror4.attach(this.editor) + const inlineAttach = inlineAttachment.editors.codemirror4.attach(this.editor) this.toolBar = $(toolBarTemplate) this.toolbarPanel = this.editor.addPanel(this.toolBar[0], { position: 'top' }) - var makeBold = $('#makeBold') - var makeItalic = $('#makeItalic') - var makeStrike = $('#makeStrike') - var makeHeader = $('#makeHeader') - var makeCode = $('#makeCode') - var makeQuote = $('#makeQuote') - var makeGenericList = $('#makeGenericList') - var makeOrderedList = $('#makeOrderedList') - var makeCheckList = $('#makeCheckList') - var makeLink = $('#makeLink') - var makeImage = $('#makeImage') - var makeTable = $('#makeTable') - var makeLine = $('#makeLine') - var makeComment = $('#makeComment') - var uploadImage = $('#uploadImage') + const makeBold = $('#makeBold') + const makeItalic = $('#makeItalic') + const makeStrike = $('#makeStrike') + const makeHeader = $('#makeHeader') + const makeCode = $('#makeCode') + const makeQuote = $('#makeQuote') + const makeGenericList = $('#makeGenericList') + const makeOrderedList = $('#makeOrderedList') + const makeCheckList = $('#makeCheckList') + const makeLink = $('#makeLink') + const makeImage = $('#makeImage') + const makeTable = $('#makeTable') + const makeLine = $('#makeLine') + const makeComment = $('#makeComment') + const uploadImage = $('#uploadImage') makeBold.click(() => { utils.wrapTextWith(this.editor, this.editor, '**') @@ -223,7 +223,7 @@ export default class Editor { }) uploadImage.bind('change', function (e) { - var files = e.target.files || e.dataTransfer.files + const files = e.target.files || e.dataTransfer.files e.dataTransfer = {} e.dataTransfer.files = files inlineAttach.onDrop(e) @@ -256,12 +256,12 @@ export default class Editor { updateStatusBar () { if (!this.statusBar) return - var cursor = this.editor.getCursor() - var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1) + const cursor = this.editor.getCursor() + const cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1) this.statusCursor.text(cursorText) - var fileText = ' — ' + editor.lineCount() + ' Lines' + const fileText = ' — ' + editor.lineCount() + ' Lines' this.statusFile.text(fileText) - var docLength = editor.getValue().length + const docLength = editor.getValue().length this.statusLength.text('Length ' + docLength) if (docLength > (config.docmaxlength * 0.95)) { this.statusLength.css('color', 'red') @@ -276,9 +276,9 @@ export default class Editor { } setIndent () { - var cookieIndentType = Cookies.get('indent_type') - var cookieTabSize = parseInt(Cookies.get('tab_size')) - var cookieSpaceUnits = parseInt(Cookies.get('space_units')) + const cookieIndentType = Cookies.get('indent_type') + let cookieTabSize = parseInt(Cookies.get('tab_size')) + let cookieSpaceUnits = parseInt(Cookies.get('space_units')) if (cookieIndentType) { if (cookieIndentType === 'tab') { this.editor.setOption('indentWithTabs', true) @@ -296,9 +296,9 @@ export default class Editor { this.editor.setOption('tabSize', cookieTabSize) } - var type = this.statusIndicators.find('.indent-type') - var widthLabel = this.statusIndicators.find('.indent-width-label') - var widthInput = this.statusIndicators.find('.indent-width-input') + const type = this.statusIndicators.find('.indent-type') + const widthLabel = this.statusIndicators.find('.indent-width-label') + const widthInput = this.statusIndicators.find('.indent-width-input') const setType = () => { if (this.editor.getOption('indentWithTabs')) { @@ -318,7 +318,7 @@ export default class Editor { setType() const setUnit = () => { - var unit = this.editor.getOption('indentUnit') + const unit = this.editor.getOption('indentUnit') if (this.editor.getOption('indentWithTabs')) { Cookies.set('tab_size', unit, { expires: 365, @@ -364,7 +364,7 @@ export default class Editor { } }) widthInput.on('change', () => { - var val = parseInt(widthInput.val()) + let val = parseInt(widthInput.val()) if (!val) val = this.editor.getOption('indentUnit') if (val < 1) val = 1 else if (val > 10) val = 10 @@ -382,18 +382,18 @@ export default class Editor { } setKeymap () { - var cookieKeymap = Cookies.get('keymap') + const cookieKeymap = Cookies.get('keymap') if (cookieKeymap) { this.editor.setOption('keyMap', cookieKeymap) } - var label = this.statusIndicators.find('.ui-keymap-label') - var sublime = this.statusIndicators.find('.ui-keymap-sublime') - var emacs = this.statusIndicators.find('.ui-keymap-emacs') - var vim = this.statusIndicators.find('.ui-keymap-vim') + const label = this.statusIndicators.find('.ui-keymap-label') + const sublime = this.statusIndicators.find('.ui-keymap-sublime') + const emacs = this.statusIndicators.find('.ui-keymap-emacs') + const vim = this.statusIndicators.find('.ui-keymap-vim') const setKeymapLabel = () => { - var keymap = this.editor.getOption('keyMap') + const keymap = this.editor.getOption('keyMap') Cookies.set('keymap', keymap, { expires: 365, sameSite: window.cookiePolicy @@ -419,15 +419,15 @@ export default class Editor { } setTheme () { - var cookieTheme = Cookies.get('theme') + const cookieTheme = Cookies.get('theme') if (cookieTheme) { this.editor.setOption('theme', cookieTheme) } - var themeToggle = this.statusTheme.find('.ui-theme-toggle') + const themeToggle = this.statusTheme.find('.ui-theme-toggle') const checkTheme = () => { - var theme = this.editor.getOption('theme') + const theme = this.editor.getOption('theme') if (theme === 'one-dark') { themeToggle.removeClass('active') } else { @@ -436,7 +436,7 @@ export default class Editor { } themeToggle.click(() => { - var theme = this.editor.getOption('theme') + let theme = this.editor.getOption('theme') if (theme === 'one-dark') { theme = 'default' } else { @@ -455,9 +455,9 @@ export default class Editor { } setSpellcheck () { - var cookieSpellcheck = Cookies.get('spellcheck') + const cookieSpellcheck = Cookies.get('spellcheck') if (cookieSpellcheck) { - var mode = null + let mode = null if (cookieSpellcheck === 'true' || cookieSpellcheck === true) { mode = 'spell-checker' } else { @@ -468,10 +468,10 @@ export default class Editor { } } - var spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle') + const spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle') const checkSpellcheck = () => { - var mode = this.editor.getOption('mode') + const mode = this.editor.getOption('mode') if (mode === defaultEditorMode) { spellcheckToggle.removeClass('active') } else { @@ -480,7 +480,7 @@ export default class Editor { } spellcheckToggle.click(() => { - var mode = this.editor.getOption('mode') + let mode = this.editor.getOption('mode') if (mode === defaultEditorMode) { mode = 'spell-checker' } else { @@ -501,7 +501,7 @@ export default class Editor { // workaround spellcheck might not activate beacuse the ajax loading if (window.num_loaded < 2) { - var spellcheckTimer = setInterval( + const spellcheckTimer = setInterval( () => { if (window.num_loaded >= 2) { if (this.editor.getOption('mode') === 'spell-checker') { @@ -516,7 +516,7 @@ export default class Editor { } resetEditorKeymapToBrowserKeymap () { - var keymap = this.editor.getOption('keyMap') + const keymap = this.editor.getOption('keyMap') if (!this.jumpToAddressBarKeymapValue) { this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] @@ -524,14 +524,15 @@ export default class Editor { } restoreOverrideEditorKeymap () { - var keymap = this.editor.getOption('keyMap') + const keymap = this.editor.getOption('keyMap') if (this.jumpToAddressBarKeymapValue) { CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue this.jumpToAddressBarKeymapValue = null } } + setOverrideBrowserKeymap () { - var overrideBrowserKeymap = $( + const overrideBrowserKeymap = $( '.ui-preferences-override-browser-keymap label > input[type="checkbox"]' ) if (overrideBrowserKeymap.is(':checked')) { @@ -547,10 +548,10 @@ export default class Editor { } setPreferences () { - var overrideBrowserKeymap = $( + const overrideBrowserKeymap = $( '.ui-preferences-override-browser-keymap label > input[type="checkbox"]' ) - var cookieOverrideBrowserKeymap = Cookies.get( + const cookieOverrideBrowserKeymap = Cookies.get( 'preferences-override-browser-keymap' ) if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') { diff --git a/public/js/lib/editor/utils.js b/public/js/lib/editor/utils.js index d87c7e41..70428b28 100644 --- a/public/js/lib/editor/utils.js +++ b/public/js/lib/editor/utils.js @@ -3,17 +3,17 @@ export function wrapTextWith (editor, cm, symbol) { if (!cm.getSelection()) { return CodeMirror.Pass } else { - let ranges = cm.listSelections() + const ranges = cm.listSelections() for (let i = 0; i < ranges.length; i++) { - let range = ranges[i] + const range = ranges[i] if (!range.empty()) { const from = range.from() const to = range.to() if (symbol !== 'Backspace') { - let selection = cm.getRange(from, to) - let anchorIndex = editor.indexFromPos(ranges[i].anchor) - let headIndex = editor.indexFromPos(ranges[i].head) + const selection = cm.getRange(from, to) + const anchorIndex = editor.indexFromPos(ranges[i].anchor) + const headIndex = editor.indexFromPos(ranges[i].head) cm.replaceRange(symbol + selection + symbol, from, to, '+input') if (anchorIndex > headIndex) { ranges[i].anchor.ch += symbol.length @@ -24,18 +24,18 @@ export function wrapTextWith (editor, cm, symbol) { } cm.setSelections(ranges) } else { - let preEndPos = { + const preEndPos = { line: to.line, ch: to.ch + symbol.length } - let preText = cm.getRange(to, preEndPos) - let preIndex = wrapSymbols.indexOf(preText) - let postEndPos = { + const preText = cm.getRange(to, preEndPos) + const preIndex = wrapSymbols.indexOf(preText) + const postEndPos = { line: from.line, ch: from.ch - symbol.length } - let postText = cm.getRange(postEndPos, from) - let postIndex = wrapSymbols.indexOf(postText) + const postText = cm.getRange(postEndPos, from) + const 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') @@ -48,25 +48,25 @@ export function wrapTextWith (editor, cm, symbol) { } export function insertText (cm, text, cursorEnd = 0) { - let cursor = cm.getCursor() + const cursor = cm.getCursor() cm.replaceSelection(text, cursor, cursor) cm.focus() cm.setCursor({ line: cursor.line, ch: cursor.ch + cursorEnd }) } export function insertLink (cm, isImage) { - let cursor = cm.getCursor() - let ranges = cm.listSelections() + const cursor = cm.getCursor() + const ranges = cm.listSelections() const linkEnd = '](https://)' const symbol = (isImage) ? '![' : '[' for (let i = 0; i < ranges.length; i++) { - let range = ranges[i] + const range = ranges[i] if (!range.empty()) { const from = range.from() const to = range.to() - let anchorIndex = editor.indexFromPos(ranges[i].anchor) - let headIndex = editor.indexFromPos(ranges[i].head) + const anchorIndex = editor.indexFromPos(ranges[i].anchor) + const headIndex = editor.indexFromPos(ranges[i].head) let selection = cm.getRange(from, to) selection = symbol + selection + linkEnd cm.replaceRange(selection, from, to) @@ -87,9 +87,9 @@ export function insertLink (cm, isImage) { } export function insertHeader (cm) { - let cursor = cm.getCursor() - let startOfLine = { line: cursor.line, ch: 0 } - let startOfLineText = cm.getRange(startOfLine, { line: cursor.line, ch: 1 }) + const cursor = cm.getCursor() + const startOfLine = { line: cursor.line, ch: 0 } + const startOfLineText = cm.getRange(startOfLine, { line: cursor.line, ch: 1 }) // See if it is already a header if (startOfLineText === '#') { cm.replaceRange('#', startOfLine, startOfLine) @@ -100,11 +100,11 @@ export function insertHeader (cm) { } export function insertOnStartOfLines (cm, symbol) { - let cursor = cm.getCursor() - let ranges = cm.listSelections() + const cursor = cm.getCursor() + const ranges = cm.listSelections() for (let i = 0; i < ranges.length; i++) { - let range = ranges[i] + const range = ranges[i] if (!range.empty()) { const from = range.from() const to = range.to() diff --git a/public/js/lib/syncscroll.js b/public/js/lib/syncscroll.js index d492fbc9..f033d00d 100644 --- a/public/js/lib/syncscroll.js +++ b/public/js/lib/syncscroll.js @@ -155,12 +155,12 @@ const buildMap = _.throttle(buildMapInner, buildMapThrottle) // 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 + let i, pos, a, b, acc - offset = viewArea.scrollTop() - viewArea.offset().top - _scrollMap = [] - nonEmptyList = [] - _lineHeightMap = [] + const offset = viewArea.scrollTop() - viewArea.offset().top + const _scrollMap = [] + const nonEmptyList = [] + const _lineHeightMap = [] viewTop = 0 viewBottom = viewArea[0].scrollHeight - viewArea.height() @@ -181,7 +181,7 @@ function buildMapInner (callback) { acc += Math.round(h / lineHeight) } _lineHeightMap.push(acc) - linesCount = acc + const linesCount = acc for (i = 0; i < linesCount; i++) { _scrollMap.push(-1) @@ -290,11 +290,12 @@ export function syncScrollToEdit (event, preventAnimate) { posTo += Math.ceil(posToNextDiff) } + let duration = 0 if (preventAnimate) { editArea.scrollTop(posTo) } else { const posDiff = Math.abs(scrollInfo.top - posTo) - var duration = posDiff / 50 + duration = posDiff / 50 duration = duration >= 100 ? duration : 100 editArea.stop(true, true).animate({ scrollTop: posTo @@ -331,11 +332,11 @@ export function syncScrollToView (event, preventAnimate) { } if (viewScrolling) return - let lineNo, posTo + let posTo let topDiffPercent, posToNextDiff const scrollInfo = editor.getScrollInfo() const textHeight = editor.defaultTextHeight() - lineNo = Math.floor(scrollInfo.top / textHeight) + const 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) { @@ -350,11 +351,12 @@ export function syncScrollToView (event, preventAnimate) { posTo += Math.floor(posToNextDiff) } + let duration = 0 if (preventAnimate) { viewArea.scrollTop(posTo) } else { const posDiff = Math.abs(viewArea.scrollTop() - posTo) - var duration = posDiff / 50 + duration = posDiff / 50 duration = duration >= 100 ? duration : 100 viewArea.stop(true, true).animate({ scrollTop: posTo diff --git a/public/js/render.js b/public/js/render.js index ebda2984..af6fb3d4 100644 --- a/public/js/render.js +++ b/public/js/render.js @@ -1,40 +1,40 @@ /* eslint-env browser, jquery */ // allow some attributes -var filterXSS = require('xss') +const filterXSS = require('xss') -var whiteListAttr = ['id', 'class', 'style'] +const whiteListAttr = ['id', 'class', 'style'] window.whiteListAttr = whiteListAttr // allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript:// -var linkRegex = /^(?!javascript:\/\/)([\w|-]+:\/\/)|^([.|/])+/i +const linkRegex = /^(?!javascript:\/\/)([\w|-]+:\/\/)|^([.|/])+/i // 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 +const 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 +const 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', 'src', 'width', 'height'] +whiteList.iframe = ['allowfullscreen', 'name', 'referrerpolicy', 'src', 'width', 'height'] // allow summary tag -whiteList['summary'] = [] +whiteList.summary = [] // allow ruby tag -whiteList['ruby'] = [] +whiteList.ruby = [] // allow rp tag for ruby -whiteList['rp'] = [] +whiteList.rp = [] // allow rt tag for ruby -whiteList['rt'] = [] +whiteList.rt = [] // allow figure tag -whiteList['figure'] = [] +whiteList.figure = [] // allow figcaption tag -whiteList['figcaption'] = [] +whiteList.figcaption = [] -var filterXSSOptions = { +const filterXSSOptions = { allowCommentTag: true, whiteList: whiteList, escapeHtml: function (html) { diff --git a/public/js/reveal-markdown.js b/public/js/reveal-markdown.js index c49bb9a2..89cd0887 100644 --- a/public/js/reveal-markdown.js +++ b/public/js/reveal-markdown.js @@ -17,28 +17,28 @@ import { md } from './extra' 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.+?)$' + const DEFAULT_SLIDE_SEPARATOR = '^\r?\n---\r?\n$' + const DEFAULT_NOTES_SEPARATOR = '^note:' + const DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\.element\\s*?(.+?)$' + const DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\.slide:\\s*?(\\S.+?)$' - var SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__' + const 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') + const template = section.querySelector('script') // strip leading whitespace so it isn't evaluated as code - var text = (template || section).textContent + let 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 + const leadingWs = text.match(/^\n?(\s*)/)[1].length + const leadingTabs = text.match(/^\n?(\t*)/)[1].length if (leadingTabs > 0) { text = text.replace(new RegExp('\\n?\\t{' + leadingTabs + '}', 'g'), '\n') @@ -56,12 +56,12 @@ import { md } from './extra' * to the output markdown slide. */ function getForwardedAttributes (section) { - var attributes = section.attributes - var result = [] + const attributes = section.attributes + const result = [] - for (var i = 0, len = attributes.length; i < len; i++) { - var name = attributes[i].name - var value = attributes[i].value + for (let i = 0, len = attributes.length; i < len; i++) { + const name = attributes[i].name + const value = attributes[i].value // disregard attributes that are used for markdown loading/parsing if (/data-(markdown|separator|vertical|notes)/gi.test(name)) continue @@ -95,7 +95,7 @@ import { md } from './extra' function createMarkdownSlide (content, options) { options = getSlidifyOptions(options) - var notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi')) + const notesMatch = content.split(new RegExp(options.notesSeparator, 'mgi')) if (notesMatch.length === 2) { content = notesMatch[0] + '<aside class="notes" data-markdown>' + notesMatch[1].trim() + '</aside>' @@ -115,15 +115,15 @@ import { md } from './extra' function slidify (markdown, options) { options = getSlidifyOptions(options) - var separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? '|' + options.verticalSeparator : ''), 'mg') - var horizontalSeparatorRegex = new RegExp(options.separator) + const separatorRegex = new RegExp(options.separator + (options.verticalSeparator ? '|' + options.verticalSeparator : ''), 'mg') + const horizontalSeparatorRegex = new RegExp(options.separator) - var matches - var lastIndex = 0 - var isHorizontal - var wasHorizontal = true - var content - var sectionStack = [] + let matches + let lastIndex = 0 + let isHorizontal + let wasHorizontal = true + let content + const sectionStack = [] // iterate until all blocks between separators are stacked up while ((matches = separatorRegex.exec(markdown)) !== null) { @@ -153,10 +153,10 @@ import { md } from './extra' // add the remaining slide (wasHorizontal ? sectionStack : sectionStack[sectionStack.length - 1]).push(markdown.substring(lastIndex)) - var markdownSections = '' + let markdownSections = '' // flatten the hierarchical stack, and insert <section data-markdown> tags - for (var i = 0, len = sectionStack.length; i < len; i++) { + for (let i = 0, len = sectionStack.length; i < len; i++) { // vertical if (sectionStack[i] instanceof Array) { markdownSections += '<section ' + options.attributes + '>' @@ -180,17 +180,17 @@ import { md } from './extra' * handles loading of external markdown. */ function processSlides () { - var sections = document.querySelectorAll('[data-markdown]') - var section + const sections = document.querySelectorAll('[data-markdown]') + let section - for (var i = 0, len = sections.length; i < len; i++) { + for (let 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') + const xhr = new XMLHttpRequest() + const url = section.getAttribute('data-markdown') - var datacharset = section.getAttribute('data-charset') + const datacharset = section.getAttribute('data-charset') // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes if (datacharset !== null && datacharset !== '') { @@ -247,18 +247,18 @@ import { md } from './extra' * 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 + const mardownClassesInElementsRegex = new RegExp(separator, 'mg') + const mardownClassRegex = /([^"= ]+?)="([^"=]+?)"/mg + let nodeValue = node.nodeValue + let matches + let matchesClass if ((matches = mardownClassesInElementsRegex.exec(nodeValue))) { - var classes = matches[1] + const 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] + const name = matchesClass[1] + const value = matchesClass[2] if (name.substr(0, 5) === 'data-' || window.whiteListAttr.indexOf(name) !== -1) { elementTarget.setAttribute(name, escapeAttrValue(value)) } } return true @@ -272,13 +272,13 @@ import { md } from './extra' */ 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] + let previousParentElement = element + for (let i = 0; i < element.childNodes.length; i++) { + const childElement = element.childNodes[i] if (i > 0) { let j = i - 1 while (j >= 0) { - var aPreviousChildElement = element.childNodes[j] + const aPreviousChildElement = element.childNodes[j] if (typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== 'BR') { previousParentElement = aPreviousChildElement break @@ -286,7 +286,7 @@ import { md } from './extra' j = j - 1 } } - var parentSection = section + let parentSection = section if (childElement.nodeName === 'section') { parentSection = childElement previousParentElement = childElement @@ -309,21 +309,21 @@ import { md } from './extra' * DOM to HTML. */ function convertSlides () { - var sections = document.querySelectorAll('[data-markdown]') + const sections = document.querySelectorAll('[data-markdown]') - for (var i = 0, len = sections.length; i < len; i++) { - var section = sections[i] + for (let i = 0, len = sections.length; i < len; i++) { + const 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) + const notes = section.querySelector('aside.notes') + let markdown = getMarkdownFromSlide(section) markdown = markdown.replace(/</g, '<').replace(/>/g, '>') - var rendered = md.render(markdown) + let rendered = md.render(markdown) rendered = preventXSS(rendered) - var result = window.postProcess(rendered) + const result = window.postProcess(rendered) section.innerHTML = result[0].outerHTML addAttributes(section, section, null, section.getAttribute('data-element-attributes') || section.parentNode.getAttribute('data-element-attributes') || diff --git a/public/js/slide.js b/public/js/slide.js index b8374cbb..5a28993f 100644 --- a/public/js/slide.js +++ b/public/js/slide.js @@ -26,7 +26,7 @@ function extend () { for (const source of arguments) { for (const key in source) { - if (source.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } @@ -36,18 +36,21 @@ function extend () { } // 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/plugin/notes/notes.js`, - async: true, - condition () { - return !!document.body.classList +const deps = [ + { + src: `${serverurl}/build/reveal.js/lib/js/classList.js`, + condition () { + return !document.body.classList + } + }, + { + src: `${serverurl}/build/reveal.js/plugin/notes/notes.js`, + async: true, + condition () { + return !!document.body.classList + } } -}] +] const slideOptions = { separator: '^(\r\n?|\n)---(\r\n?|\n)$', @@ -73,60 +76,64 @@ const defaultOptions = { // options from yaml meta const meta = JSON.parse($('#meta').text()) const metaSlideOptions = !!meta && !!meta.slideOptions ? meta.slideOptions : {} -var options = { - autoPlayMedia: metaSlideOptions.autoPlayMedia, - autoSlide: metaSlideOptions.autoSlide, - autoSlideStoppable: metaSlideOptions.autoSlideStoppable, - backgroundTransition: metaSlideOptions.backgroundTransition, - center: metaSlideOptions.center, - controls: metaSlideOptions.controls, - controlsBackArrows: metaSlideOptions.controlsBackArrows, - controlsLayout: metaSlideOptions.controlsLayout, - controlsTutorial: metaSlideOptions.controlsTutorial, - defaultTiming: metaSlideOptions.defaultTiming, - display: metaSlideOptions.display, - embedded: metaSlideOptions.embedded, - fragmentInURL: metaSlideOptions.fragmentInURL, - fragments: metaSlideOptions.fragments, - hash: metaSlideOptions.hash, - height: metaSlideOptions.height, - help: metaSlideOptions.help, - hideAddressBar: metaSlideOptions.hideAddressBar, - hideCursorTime: metaSlideOptions.hideCursorTime, - hideInactiveCursor: metaSlideOptions.hideInactiveCursor, - history: metaSlideOptions.history, - keyboard: metaSlideOptions.keyboard, - loop: metaSlideOptions.loop, - margin: metaSlideOptions.margin, - maxScale: metaSlideOptions.maxScale, - minScale: metaSlideOptions.minScale, - minimumTimePerSlide: metaSlideOptions.minimumTimePerSlide, - mobileViewDistance: metaSlideOptions.mobileViewDistance, - mouseWheel: metaSlideOptions.mouseWheel, - navigationMode: metaSlideOptions.navigationMode, - overview: metaSlideOptions.overview, - parallaxBackgroundHorizontal: metaSlideOptions.parallaxBackgroundHorizontal, - parallaxBackgroundImage: metaSlideOptions.parallaxBackgroundImage, - parallaxBackgroundSize: metaSlideOptions.parallaxBackgroundSize, - parallaxBackgroundVertical: metaSlideOptions.parallaxBackgroundVertical, - preloadIframes: metaSlideOptions.preloadIframes, - previewLinks: metaSlideOptions.previewLinks, - progress: metaSlideOptions.progress, - rtl: metaSlideOptions.rtl, - showNotes: metaSlideOptions.showNotes, - shuffle: metaSlideOptions.shuffle, - slideNumber: metaSlideOptions.slideNumber, - theme: metaSlideOptions.theme, - totalTime: metaSlideOptions.totalTime, - touch: metaSlideOptions.touch, - transition: metaSlideOptions.transition, - transitionSpeed: metaSlideOptions.transitionSpeed, - viewDistance: metaSlideOptions.viewDistance, - width: metaSlideOptions.width -} || {} +let options = + { + autoPlayMedia: metaSlideOptions.autoPlayMedia, + autoSlide: metaSlideOptions.autoSlide, + autoSlideStoppable: metaSlideOptions.autoSlideStoppable, + backgroundTransition: metaSlideOptions.backgroundTransition, + center: metaSlideOptions.center, + controls: metaSlideOptions.controls, + controlsBackArrows: metaSlideOptions.controlsBackArrows, + controlsLayout: metaSlideOptions.controlsLayout, + controlsTutorial: metaSlideOptions.controlsTutorial, + defaultTiming: metaSlideOptions.defaultTiming, + display: metaSlideOptions.display, + embedded: metaSlideOptions.embedded, + fragmentInURL: metaSlideOptions.fragmentInURL, + fragments: metaSlideOptions.fragments, + hash: metaSlideOptions.hash, + height: metaSlideOptions.height, + help: metaSlideOptions.help, + hideAddressBar: metaSlideOptions.hideAddressBar, + hideCursorTime: metaSlideOptions.hideCursorTime, + hideInactiveCursor: metaSlideOptions.hideInactiveCursor, + history: metaSlideOptions.history, + keyboard: metaSlideOptions.keyboard, + loop: metaSlideOptions.loop, + margin: metaSlideOptions.margin, + maxScale: metaSlideOptions.maxScale, + minScale: metaSlideOptions.minScale, + minimumTimePerSlide: metaSlideOptions.minimumTimePerSlide, + mobileViewDistance: metaSlideOptions.mobileViewDistance, + mouseWheel: metaSlideOptions.mouseWheel, + navigationMode: metaSlideOptions.navigationMode, + overview: metaSlideOptions.overview, + parallaxBackgroundHorizontal: metaSlideOptions.parallaxBackgroundHorizontal, + parallaxBackgroundImage: metaSlideOptions.parallaxBackgroundImage, + parallaxBackgroundSize: metaSlideOptions.parallaxBackgroundSize, + parallaxBackgroundVertical: metaSlideOptions.parallaxBackgroundVertical, + preloadIframes: metaSlideOptions.preloadIframes, + previewLinks: metaSlideOptions.previewLinks, + progress: metaSlideOptions.progress, + rtl: metaSlideOptions.rtl, + showNotes: metaSlideOptions.showNotes, + shuffle: metaSlideOptions.shuffle, + slideNumber: metaSlideOptions.slideNumber, + theme: metaSlideOptions.theme, + totalTime: metaSlideOptions.totalTime, + touch: metaSlideOptions.touch, + transition: metaSlideOptions.transition, + transitionSpeed: metaSlideOptions.transitionSpeed, + viewDistance: metaSlideOptions.viewDistance, + width: metaSlideOptions.width + } || {} for (const key in options) { - if (options.hasOwnProperty(key) && options[key] === undefined) { + if ( + Object.prototype.hasOwnProperty.call(options, key) && + options[key] === undefined + ) { delete options[key] } } @@ -165,14 +172,14 @@ window.viewAjaxCallback = () => { function renderSlide (event) { if (window.location.search.match(/print-pdf/gi)) { const slides = $('.slides') - let title = document.title + const title = document.title finishView(slides) document.title = title Reveal.layout() } else { const markdown = $(event.currentSlide) if (!markdown.attr('data-rendered')) { - let title = document.title + const title = document.title finishView(markdown) markdown.attr('data-rendered', 'true') document.title = title @@ -181,7 +188,7 @@ function renderSlide (event) { } } -Reveal.addEventListener('ready', event => { +Reveal.addEventListener('ready', (event) => { renderSlide(event) const markdown = $(event.currentSlide) // force browser redraw diff --git a/public/js/utils.js b/public/js/utils.js index 91e7f133..d42a18e7 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -1,9 +1,9 @@ import base64url from 'base64url' -let 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 +const 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 export function checkNoteIdValid (id) { - let result = id.match(uuidRegex) + const result = id.match(uuidRegex) if (result && result.length === 1) { return true } else { @@ -13,16 +13,16 @@ export function checkNoteIdValid (id) { export function encodeNoteId (id) { // remove dashes in UUID and encode in url-safe base64 - let str = id.replace(/-/g, '') - let hexStr = Buffer.from(str, 'hex') + const str = id.replace(/-/g, '') + const hexStr = Buffer.from(str, 'hex') return base64url.encode(hexStr) } export function decodeNoteId (encodedId) { // decode from url-safe base64 - let id = base64url.toBuffer(encodedId).toString('hex') + const id = base64url.toBuffer(encodedId).toString('hex') // add dashes between the UUID string parts - let idParts = [] + const idParts = [] idParts.push(id.substr(0, 8)) idParts.push(id.substr(8, 4)) idParts.push(id.substr(12, 4)) diff --git a/test/csp.js b/test/csp.js index d081cef0..70598156 100644 --- a/test/csp.js +++ b/test/csp.js @@ -46,7 +46,7 @@ describe('Content security policies', function () { // beginnging Tests it('Disable CDN', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig testconfig.useCDN = false mock('../lib/config', testconfig) csp = mock.reRequire('../lib/csp') @@ -60,7 +60,7 @@ describe('Content security policies', function () { }) it('Disable Google Analytics', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig testconfig.csp.addGoogleAnalytics = false mock('../lib/config', testconfig) csp = mock.reRequire('../lib/csp') @@ -69,7 +69,7 @@ describe('Content security policies', function () { }) it('Disable Disqus', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig testconfig.csp.addDisqus = false mock('../lib/config', testconfig) csp = mock.reRequire('../lib/csp') @@ -82,7 +82,7 @@ describe('Content security policies', function () { }) it('Include dropbox if configured', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig testconfig.dropbox.appKey = 'hedgedoc' mock('../lib/config', testconfig) csp = mock.reRequire('../lib/csp') @@ -92,7 +92,7 @@ describe('Content security policies', function () { }) it('Set ReportURI', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig testconfig.csp.reportURI = 'https://example.com/reportURI' mock('../lib/config', testconfig) csp = mock.reRequire('../lib/csp') @@ -101,7 +101,7 @@ describe('Content security policies', function () { }) it('Set own directives', function () { - let testconfig = defaultConfig + const testconfig = defaultConfig mock('../lib/config', defaultConfig) csp = mock.reRequire('../lib/csp') const unextendedCSP = csp.computeDirectives() diff --git a/test/letter-avatars.js b/test/letter-avatars.js index 8cc32d8b..0645ef87 100644 --- a/test/letter-avatars.js +++ b/test/letter-avatars.js @@ -9,7 +9,7 @@ describe('generateAvatarURL() gravatar enabled', function () { let avatars beforeEach(function () { // Reset config to make sure we don't influence other tests - let testconfig = { + const testconfig = { allowGravatar: true, serverURL: 'http://localhost:3000', port: 3000 @@ -32,7 +32,7 @@ describe('generateAvatarURL() gravatar disabled', function () { let avatars beforeEach(function () { // Reset config to make sure we don't influence other tests - let testconfig = { + const testconfig = { allowGravatar: false, serverURL: 'http://localhost:3000', port: 3000 |