Merge pull request #1190 from hackmdio/refactor

replace package with npm published version
This commit is contained in:
Raccoon 2019-04-12 19:02:43 +08:00 committed by GitHub
commit 1434cdb6b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2317 additions and 3582 deletions

View File

@ -8,12 +8,6 @@ env:
jobs: jobs:
include: include:
- env: task=npm-test
node_js:
- 6
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
- export PATH="$HOME/.yarn/bin:$PATH"
- env: task=npm-test - env: task=npm-test
node_js: node_js:
- 8 - 8

View File

@ -1,119 +1,117 @@
#!/usr/bin/env node #!/usr/bin/env node
// First configure the logger so it does not spam the console // First configure the logger so it does not spam the console
const logger = require("../lib/logger"); const logger = require('../lib/logger')
logger.transports.forEach((transport) => transport.level = "warning") logger.transports.forEach((transport) => {
transport.level = 'warning'
})
const models = require("../lib/models/"); const models = require('../lib/models/')
const readline = require("readline-sync"); const readline = require('readline-sync')
const minimist = require("minimist"); const minimist = require('minimist')
function showUsage(tips) { function showUsage (tips) {
console.log(`${tips} console.log(`${tips}
Command-line utility to create users for email-signin. Command-line utility to create users for email-signin.
Usage: bin/manage_users [--pass password] (--add | --del) user-email Usage: bin/manage_users [--pass password] (--add | --del) user-email
Options: Options:
--add Add user with the specified user-email --add\tAdd user with the specified user-email
--del Delete user with specified user-email --del\tDelete user with specified user-email
--reset Reset user password with specified user-email --reset\tReset user password with specified user-email
--pass Use password from cmdline rather than prompting --pass\tUse password from cmdline rather than prompting
`); `)
process.exit(1); process.exit(1)
} }
function getPass(argv, action) { function getPass (argv, action) {
// Find whether we use cmdline or prompt password // Find whether we use cmdline or prompt password
if(typeof argv["pass"] !== 'string') { if (typeof argv['pass'] !== 'string') {
return readline.question(`Password for ${argv[action]}:`, {hideEchoBack: true}); return readline.question(`Password for ${argv[action]}:`, { hideEchoBack: true })
} }
console.log("Using password from commandline..."); console.log('Using password from commandline...')
return argv["pass"]; return argv['pass']
} }
// Using an async function to be able to use await inside // Using an async function to be able to use await inside
async function createUser(argv) { async function createUser (argv) {
const existing_user = await models.User.findOne({where: {email: argv["add"]}}); const existingUser = await models.User.findOne({ where: { email: argv['add'] } })
// Cannot create already-existing users // Cannot create already-existing users
if(existing_user != undefined) { if (existingUser !== undefined) {
console.log(`User with e-mail ${existing_user.email} already exists! Aborting ...`); console.log(`User with e-mail ${existingUser.email} already exists! Aborting ...`)
process.exit(1); process.exit(1)
} }
const pass = getPass(argv, "add"); const pass = getPass(argv, 'add')
// Lets try to create, and check success // Lets try to create, and check success
const ref = await models.User.create({email: argv["add"], password: pass}); const ref = await models.User.create({ email: argv['add'], password: pass })
if(ref == undefined) { if (ref === undefined) {
console.log(`Could not create user with email ${argv["add"]}`); console.log(`Could not create user with email ${argv['add']}`)
process.exit(1); process.exit(1)
} else } else { console.log(`Created user with email ${argv['add']}`) }
console.log(`Created user with email ${argv["add"]}`);
} }
// Using an async function to be able to use await inside // Using an async function to be able to use await inside
async function deleteUser(argv) { async function deleteUser (argv) {
// Cannot delete non-existing users // Cannot delete non-existing users
const existing_user = await models.User.findOne({where: {email: argv["del"]}}); const existingUser = await models.User.findOne({ where: { email: argv['del'] } })
if(existing_user === undefined) { if (existingUser === undefined) {
console.log(`User with e-mail ${argv["del"]} does not exist, cannot delete`); console.log(`User with e-mail ${argv['del']} does not exist, cannot delete`)
process.exit(1); process.exit(1)
} }
// Sadly .destroy() does not return any success value with all // Sadly .destroy() does not return any success value with all
// backends. See sequelize #4124 // backends. See sequelize #4124
await existing_user.destroy(); await existingUser.destroy()
console.log(`Deleted user ${argv["del"]} ...`); console.log(`Deleted user ${argv['del']} ...`)
} }
// Using an async function to be able to use await inside // Using an async function to be able to use await inside
async function resetUser(argv) { async function resetUser (argv) {
const existing_user = await models.User.findOne({where: {email: argv["reset"]}}); const existingUser = await models.User.findOne({ where: { email: argv['reset'] } })
// Cannot reset non-existing users // Cannot reset non-existing users
if(existing_user == undefined) { if (existingUser === undefined) {
console.log(`User with e-mail ${argv["reset"]} does not exist, cannot reset`); console.log(`User with e-mail ${argv['reset']} does not exist, cannot reset`)
process.exit(1); process.exit(1)
} }
const pass = getPass(argv, "reset"); const pass = getPass(argv, 'reset')
// set password and save // set password and save
existing_user.password = pass; existingUser.password = pass
await existing_user.save(); await existingUser.save()
console.log(`User with email ${argv["reset"]} password has been reset`); console.log(`User with email ${argv['reset']} password has been reset`)
} }
const options = { const options = {
add: createUser, add: createUser,
del: deleteUser, del: deleteUser,
reset: resetUser, reset: resetUser
}; }
// Perform commandline-parsing // Perform commandline-parsing
const argv = minimist(process.argv.slice(2)); const argv = minimist(process.argv.slice(2))
const keys = Object.keys(options); const keys = Object.keys(options)
const opts = keys.filter((key) => argv[key] !== undefined); const opts = keys.filter((key) => argv[key] !== undefined)
const action = opts[0]; const action = opts[0]
// Check for options missing // Check for options missing
if (opts.length === 0) { if (opts.length === 0) {
showUsage(`You did not specify either ${keys.map((key) => `--${key}`).join(' or ')}!`); showUsage(`You did not specify either ${keys.map((key) => `--${key}`).join(' or ')}!`)
} }
// Check if both are specified // Check if both are specified
if (opts.length > 1) { if (opts.length > 1) {
showUsage(`You cannot ${action.join(' and ')} at the same time!`); showUsage(`You cannot ${action.join(' and ')} at the same time!`)
} }
// Check if not string // Check if not string
if (typeof argv[action] !== 'string') { if (typeof argv[action] !== 'string') {
showUsage(`You must follow an email after --${action}`); showUsage(`You must follow an email after --${action}`)
} }
// Call respective processing functions // Call respective processing functions
options[action](argv).then(function() { options[action](argv).then(function () {
process.exit(0); process.exit(0)
}); })

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils') const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils')
module.exports = { module.exports = {
sourceURL: process.env.CMD_SOURCE_URL, sourceURL: process.env.CMD_SOURCE_URL,

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils') const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils')
module.exports = { module.exports = {
domain: process.env.HMD_DOMAIN, domain: process.env.HMD_DOMAIN,

View File

@ -4,11 +4,11 @@
const crypto = require('crypto') const crypto = require('crypto')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const {merge} = require('lodash') const { merge } = require('lodash')
const deepFreeze = require('deep-freeze') const deepFreeze = require('deep-freeze')
const {Environment, Permission} = require('./enum') const { Environment, Permission } = require('./enum')
const logger = require('../logger') const logger = require('../logger')
const {getGitCommit, getGitHubURL} = require('./utils') const { getGitCommit, getGitHubURL } = require('./utils')
const appRootPath = path.resolve(__dirname, '../../') const appRootPath = path.resolve(__dirname, '../../')
const env = process.env.NODE_ENV || Environment.development const env = process.env.NODE_ENV || Environment.development
@ -17,7 +17,7 @@ const debugConfig = {
} }
// Get version string from package.json // Get version string from package.json
const {version, repository} = require(path.join(appRootPath, 'package.json')) const { version, repository } = require(path.join(appRootPath, 'package.json'))
const commitID = getGitCommit(appRootPath) const commitID = getGitCommit(appRootPath)
const sourceURL = getGitHubURL(repository.url, commitID || version) const sourceURL = getGitHubURL(repository.url, commitID || version)

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
const {toBooleanConfig} = require('./utils') const { toBooleanConfig } = require('./utils')
module.exports = { module.exports = {
debug: toBooleanConfig(process.env.DEBUG), debug: toBooleanConfig(process.env.DEBUG),

View File

@ -1,7 +1,7 @@
'use strict' 'use strict'
// history // history
// external modules // external modules
var LZString = require('lz-string') var LZString = require('@hackmd/lz-string')
// core // core
var config = require('./config') var config = require('./config')

View File

@ -30,14 +30,14 @@ exports.generateAvatarURL = function (name, email = '', big = true) {
if (typeof email !== 'string') { if (typeof email !== 'string') {
email = '' + name + '@example.com' email = '' + name + '@example.com'
} }
name=encodeURIComponent(name) name = encodeURIComponent(name)
let hash = crypto.createHash('md5') let hash = crypto.createHash('md5')
hash.update(email.toLowerCase()) hash.update(email.toLowerCase())
let hexDigest = hash.digest('hex') let hexDigest = hash.digest('hex')
if (email !== '' && config.allowGravatar) { if (email !== '' && config.allowGravatar) {
photo = 'https://www.gravatar.com/avatar/' + hexDigest; photo = 'https://www.gravatar.com/avatar/' + hexDigest
if (big) { if (big) {
photo += '?s=400' photo += '?s=400'
} else { } else {

View File

@ -1,5 +1,5 @@
'use strict' 'use strict'
const {createLogger, format, transports} = require('winston') const { createLogger, format, transports } = require('winston')
const logger = createLogger({ const logger = createLogger({
level: 'debug', level: 'debug',

View File

@ -1,16 +1,16 @@
'use strict' 'use strict'
module.exports = { module.exports = {
up: function (queryInterface, Sequelize) { up: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'content', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT('long') })
queryInterface.changeColumn('Revisions', 'patch', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT('long') })
queryInterface.changeColumn('Revisions', 'content', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT('long') })
queryInterface.changeColumn('Revisions', 'lastContent', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT('long') })
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'content', {type: Sequelize.TEXT}) queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT })
queryInterface.changeColumn('Revisions', 'patch', {type: Sequelize.TEXT}) queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT })
queryInterface.changeColumn('Revisions', 'content', {type: Sequelize.TEXT}) queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT })
queryInterface.changeColumn('Revisions', 'lastContent', {type: Sequelize.TEXT}) queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT })
} }
} }

View File

@ -2,12 +2,12 @@
module.exports = { module.exports = {
up: function (queryInterface, Sequelize) { up: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'authorship', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT('long') })
queryInterface.changeColumn('Revisions', 'authorship', {type: Sequelize.TEXT('long')}) queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT('long') })
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'authorship', {type: Sequelize.TEXT}) queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT })
queryInterface.changeColumn('Revisions', 'authorship', {type: Sequelize.TEXT}) queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT })
} }
} }

View File

@ -2,10 +2,10 @@
module.exports = { module.exports = {
up: function (queryInterface, Sequelize) { up: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'permission', {type: Sequelize.ENUM('freely', 'editable', 'limited', 'locked', 'protected', 'private')}) queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'limited', 'locked', 'protected', 'private') })
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
queryInterface.changeColumn('Notes', 'permission', {type: Sequelize.ENUM('freely', 'editable', 'locked', 'private')}) queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'locked', 'private') })
} }
} }

View File

@ -18,9 +18,10 @@ module.exports = function (sequelize, DataTypes) {
unique: true, unique: true,
fields: ['noteId', 'userId'] fields: ['noteId', 'userId']
} }
], ]
classMethods: { })
associate: function (models) {
Author.associate = function (models) {
Author.belongsTo(models.Note, { Author.belongsTo(models.Note, {
foreignKey: 'noteId', foreignKey: 'noteId',
as: 'note', as: 'note',
@ -36,7 +37,6 @@ module.exports = function (sequelize, DataTypes) {
hooks: true hooks: true
}) })
} }
}
})
return Author return Author
} }

View File

@ -3,14 +3,16 @@
var fs = require('fs') var fs = require('fs')
var path = require('path') var path = require('path')
var Sequelize = require('sequelize') var Sequelize = require('sequelize')
const {cloneDeep} = require('lodash') const { cloneDeep } = require('lodash')
// core // core
var config = require('../config') var config = require('../config')
var logger = require('../logger') var logger = require('../logger')
var dbconfig = cloneDeep(config.db) var dbconfig = cloneDeep(config.db)
dbconfig.logging = config.debug ? logger.info : false dbconfig.logging = config.debug ? (data) => {
logger.info(data)
} : false
var sequelize = null var sequelize = null

View File

@ -2,7 +2,7 @@
// external modules // external modules
var fs = require('fs') var fs = require('fs')
var path = require('path') var path = require('path')
var LZString = require('lz-string') var LZString = require('@hackmd/lz-string')
var base64url = require('base64url') var base64url = require('base64url')
var md = require('markdown-it')() var md = require('markdown-it')()
var metaMarked = require('meta-marked') var metaMarked = require('meta-marked')
@ -11,9 +11,10 @@ var shortId = require('shortid')
var Sequelize = require('sequelize') var Sequelize = require('sequelize')
var async = require('async') var async = require('async')
var moment = require('moment') var moment = require('moment')
var DiffMatchPatch = require('diff-match-patch') var DiffMatchPatch = require('@hackmd/diff-match-patch')
var dmp = new DiffMatchPatch() var dmp = new DiffMatchPatch()
var S = require('string')
const { stripTags } = require('../../utils/string')
// core // core
var config = require('../config') var config = require('../config')
@ -86,8 +87,53 @@ module.exports = function (sequelize, DataTypes) {
} }
}, { }, {
paranoid: false, paranoid: false,
classMethods: { hooks: {
associate: function (models) { beforeCreate: function (note, options) {
return new Promise(function (resolve, reject) {
// if no content specified then use default note
if (!note.content) {
var body = null
let filePath = null
if (!note.alias) {
filePath = config.defaultNotePath
} else {
filePath = path.join(config.docsPath, note.alias + '.md')
}
if (Note.checkFileExist(filePath)) {
var fsCreatedTime = moment(fs.statSync(filePath).ctime)
body = fs.readFileSync(filePath, 'utf8')
note.title = Note.parseNoteTitle(body)
note.content = body
if (filePath !== config.defaultNotePath) {
note.createdAt = fsCreatedTime
}
}
}
// if no permission specified and have owner then give default permission in config, else default permission is freely
if (!note.permission) {
if (note.ownerId) {
note.permission = config.defaultPermission
} else {
note.permission = 'freely'
}
}
return resolve(note)
})
},
afterCreate: function (note, options, callback) {
return new Promise(function (resolve, reject) {
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
if (err) {
return reject(err)
}
return resolve(note)
})
})
}
}
})
Note.associate = function (models) {
Note.belongsTo(models.User, { Note.belongsTo(models.User, {
foreignKey: 'ownerId', foreignKey: 'ownerId',
as: 'owner', as: 'owner',
@ -109,21 +155,21 @@ module.exports = function (sequelize, DataTypes) {
as: 'authors', as: 'authors',
constraints: false constraints: false
}) })
}, }
checkFileExist: function (filePath) { Note.checkFileExist = function (filePath) {
try { try {
return fs.statSync(filePath).isFile() return fs.statSync(filePath).isFile()
} catch (err) { } catch (err) {
return false return false
} }
}, }
encodeNoteId: function (id) { Note.encodeNoteId = function (id) {
// remove dashes in UUID and encode in url-safe base64 // remove dashes in UUID and encode in url-safe base64
let str = id.replace(/-/g, '') let str = id.replace(/-/g, '')
let hexStr = Buffer.from(str, 'hex') let hexStr = Buffer.from(str, 'hex')
return base64url.encode(hexStr) return base64url.encode(hexStr)
}, }
decodeNoteId: function (encodedId) { Note.decodeNoteId = function (encodedId) {
// decode from url-safe base64 // decode from url-safe base64
let id = base64url.toBuffer(encodedId).toString('hex') let id = base64url.toBuffer(encodedId).toString('hex')
// add dashes between the UUID string parts // add dashes between the UUID string parts
@ -134,13 +180,13 @@ module.exports = function (sequelize, DataTypes) {
idParts.push(id.substr(16, 4)) idParts.push(id.substr(16, 4))
idParts.push(id.substr(20, 12)) idParts.push(id.substr(20, 12))
return idParts.join('-') return idParts.join('-')
}, }
checkNoteIdValid: function (id) { 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 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) var result = id.match(uuidRegex)
if (result && result.length === 1) { return true } else { return false } if (result && result.length === 1) { return true } else { return false }
}, }
parseNoteId: function (noteId, callback) { Note.parseNoteId = function (noteId, callback) {
async.series({ async.series({
parseNoteIdByAlias: function (_callback) { parseNoteIdByAlias: function (_callback) {
// try to parse note id by alias (e.g. doc) // try to parse note id by alias (e.g. doc)
@ -273,42 +319,42 @@ module.exports = function (sequelize, DataTypes) {
} }
return callback(null, null) return callback(null, null)
}) })
}, }
parseNoteInfo: function (body) { Note.parseNoteInfo = function (body) {
var parsed = Note.extractMeta(body) var parsed = Note.extractMeta(body)
var $ = cheerio.load(md.render(parsed.markdown)) var $ = cheerio.load(md.render(parsed.markdown))
return { return {
title: Note.extractNoteTitle(parsed.meta, $), title: Note.extractNoteTitle(parsed.meta, $),
tags: Note.extractNoteTags(parsed.meta, $) tags: Note.extractNoteTags(parsed.meta, $)
} }
}, }
parseNoteTitle: function (body) { Note.parseNoteTitle = function (body) {
var parsed = Note.extractMeta(body) var parsed = Note.extractMeta(body)
var $ = cheerio.load(md.render(parsed.markdown)) var $ = cheerio.load(md.render(parsed.markdown))
return Note.extractNoteTitle(parsed.meta, $) return Note.extractNoteTitle(parsed.meta, $)
}, }
extractNoteTitle: function (meta, $) { Note.extractNoteTitle = function (meta, $) {
var title = '' var title = ''
if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) {
title = meta.title title = meta.title
} else { } else {
var h1s = $('h1') var h1s = $('h1')
if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s } if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = stripTags(h1s.first().text()) }
} }
if (!title) title = 'Untitled' if (!title) title = 'Untitled'
return title return title
}, }
generateDescription: function (markdown) { Note.generateDescription = function (markdown) {
return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ') return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ')
}, }
decodeTitle: function (title) { Note.decodeTitle = function (title) {
return title || 'Untitled' return title || 'Untitled'
}, }
generateWebTitle: function (title) { Note.generateWebTitle = function (title) {
title = !title || title === 'Untitled' ? 'CodiMD - Collaborative markdown notes' : title + ' - CodiMD' title = !title || title === 'Untitled' ? 'CodiMD - Collaborative markdown notes' : title + ' - CodiMD'
return title return title
}, }
extractNoteTags: function (meta, $) { Note.extractNoteTags = function (meta, $) {
var tags = [] var tags = []
var rawtags = [] var rawtags = []
if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) { if (meta.tags && (typeof meta.tags === 'string' || typeof meta.tags === 'number')) {
@ -323,7 +369,7 @@ module.exports = function (sequelize, DataTypes) {
if (/^tags/gmi.test($(value).text())) { if (/^tags/gmi.test($(value).text())) {
var codes = $(value).find('code') var codes = $(value).find('code')
for (let i = 0; i < codes.length; i++) { for (let i = 0; i < codes.length; i++) {
var text = S($(codes[i]).text().trim()).stripTags().s var text = stripTags($(codes[i]).text().trim())
if (text) rawtags.push(text) if (text) rawtags.push(text)
} }
} }
@ -340,8 +386,8 @@ module.exports = function (sequelize, DataTypes) {
if (!found) { tags.push(rawtags[i]) } if (!found) { tags.push(rawtags[i]) }
} }
return tags return tags
}, }
extractMeta: function (content) { Note.extractMeta = function (content) {
var obj = null var obj = null
try { try {
obj = metaMarked(content) obj = metaMarked(content)
@ -354,8 +400,8 @@ module.exports = function (sequelize, DataTypes) {
} }
} }
return obj return obj
}, }
parseMeta: function (meta) { Note.parseMeta = function (meta) {
var _meta = {} var _meta = {}
if (meta) { if (meta) {
if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { _meta.title = meta.title } if (meta.title && (typeof meta.title === 'string' || typeof meta.title === 'number')) { _meta.title = meta.title }
@ -366,8 +412,8 @@ module.exports = function (sequelize, DataTypes) {
if (meta.slideOptions && (typeof meta.slideOptions === 'object')) { _meta.slideOptions = meta.slideOptions } if (meta.slideOptions && (typeof meta.slideOptions === 'object')) { _meta.slideOptions = meta.slideOptions }
} }
return _meta return _meta
}, }
updateAuthorshipByOperation: function (operation, userId, authorships) { Note.updateAuthorshipByOperation = function (operation, userId, authorships) {
var index = 0 var index = 0
var timestamp = Date.now() var timestamp = Date.now()
for (let i = 0; i < operation.length; i++) { for (let i = 0; i < operation.length; i++) {
@ -467,8 +513,8 @@ module.exports = function (sequelize, DataTypes) {
} }
} }
return authorships return authorships
}, }
transformPatchToOperations: function (patch, contentLength) { Note.transformPatchToOperations = function (patch, contentLength) {
var operations = [] var operations = []
if (patch.length > 0) { if (patch.length > 0) {
// calculate original content length // calculate original content length
@ -527,45 +573,6 @@ module.exports = function (sequelize, DataTypes) {
} }
return operations return operations
} }
},
hooks: {
beforeCreate: function (note, options, callback) {
// if no content specified then use default note
if (!note.content) {
var body = null
let filePath = null
if (!note.alias) {
filePath = config.defaultNotePath
} else {
filePath = path.join(config.docsPath, note.alias + '.md')
}
if (Note.checkFileExist(filePath)) {
var fsCreatedTime = moment(fs.statSync(filePath).ctime)
body = fs.readFileSync(filePath, 'utf8')
note.title = Note.parseNoteTitle(body)
note.content = body
if (filePath !== config.defaultNotePath) {
note.createdAt = fsCreatedTime
}
}
}
// if no permission specified and have owner then give default permission in config, else default permission is freely
if (!note.permission) {
if (note.ownerId) {
note.permission = config.defaultPermission
} else {
note.permission = 'freely'
}
}
return callback(null, note)
},
afterCreate: function (note, options, callback) {
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
callback(err, note)
})
}
}
})
return Note return Note
} }

View File

@ -7,6 +7,8 @@ var childProcess = require('child_process')
var shortId = require('shortid') var shortId = require('shortid')
var path = require('path') var path = require('path')
var Op = Sequelize.Op
// core // core
var config = require('../config') var config = require('../config')
var logger = require('../logger') var logger = require('../logger')
@ -97,9 +99,9 @@ module.exports = function (sequelize, DataTypes) {
this.setDataValue('authorship', value ? JSON.stringify(value) : value) this.setDataValue('authorship', value ? JSON.stringify(value) : value)
} }
} }
}, { })
classMethods: {
associate: function (models) { Revision.associate = function (models) {
Revision.belongsTo(models.Note, { Revision.belongsTo(models.Note, {
foreignKey: 'noteId', foreignKey: 'noteId',
as: 'note', as: 'note',
@ -107,8 +109,8 @@ module.exports = function (sequelize, DataTypes) {
onDelete: 'CASCADE', onDelete: 'CASCADE',
hooks: true hooks: true
}) })
}, }
getNoteRevisions: function (note, callback) { Revision.getNoteRevisions = function (note, callback) {
Revision.findAll({ Revision.findAll({
where: { where: {
noteId: note.id noteId: note.id
@ -127,8 +129,8 @@ module.exports = function (sequelize, DataTypes) {
}).catch(function (err) { }).catch(function (err) {
callback(err, null) callback(err, null)
}) })
}, }
getPatchedNoteRevisionByTime: function (note, time, callback) { Revision.getPatchedNoteRevisionByTime = function (note, time, callback) {
// find all revisions to prepare for all possible calculation // find all revisions to prepare for all possible calculation
Revision.findAll({ Revision.findAll({
where: { where: {
@ -142,7 +144,7 @@ module.exports = function (sequelize, DataTypes) {
where: { where: {
noteId: note.id, noteId: note.id,
createdAt: { createdAt: {
$gte: time [Op.gte]: time
} }
}, },
order: [['createdAt', 'DESC']] order: [['createdAt', 'DESC']]
@ -159,8 +161,8 @@ module.exports = function (sequelize, DataTypes) {
}).catch(function (err) { }).catch(function (err) {
return callback(err, null) return callback(err, null)
}) })
}, }
checkAllNotesRevision: function (callback) { Revision.checkAllNotesRevision = function (callback) {
Revision.saveAllNotesRevision(function (err, notes) { Revision.saveAllNotesRevision(function (err, notes) {
if (err) return callback(err, null) if (err) return callback(err, null)
if (!notes || notes.length <= 0) { if (!notes || notes.length <= 0) {
@ -169,28 +171,28 @@ module.exports = function (sequelize, DataTypes) {
Revision.checkAllNotesRevision(callback) Revision.checkAllNotesRevision(callback)
} }
}) })
}, }
saveAllNotesRevision: function (callback) { Revision.saveAllNotesRevision = function (callback) {
sequelize.models.Note.findAll({ sequelize.models.Note.findAll({
// query all notes that need to save for revision // query all notes that need to save for revision
where: { where: {
$and: [ [Op.and]: [
{ {
lastchangeAt: { lastchangeAt: {
$or: { [Op.or]: {
$eq: null, [Op.eq]: null,
$and: { [Op.and]: {
$ne: null, [Op.ne]: null,
$gt: sequelize.col('createdAt') [Op.gt]: sequelize.col('createdAt')
} }
} }
} }
}, },
{ {
savedAt: { savedAt: {
$or: { [Op.or]: {
$eq: null, [Op.eq]: null,
$lt: sequelize.col('lastchangeAt') [Op.lt]: sequelize.col('lastchangeAt')
} }
} }
} }
@ -228,8 +230,8 @@ module.exports = function (sequelize, DataTypes) {
}).catch(function (err) { }).catch(function (err) {
return callback(err, null) return callback(err, null)
}) })
}, }
saveNoteRevision: function (note, callback) { Revision.saveNoteRevision = function (note, callback) {
Revision.findAll({ Revision.findAll({
where: { where: {
noteId: note.id noteId: note.id
@ -293,8 +295,8 @@ module.exports = function (sequelize, DataTypes) {
}).catch(function (err) { }).catch(function (err) {
return callback(err, null) return callback(err, null)
}) })
}, }
finishSaveNoteRevision: function (note, revision, callback) { Revision.finishSaveNoteRevision = function (note, revision, callback) {
note.update({ note.update({
savedAt: revision.updatedAt savedAt: revision.updatedAt
}).then(function () { }).then(function () {
@ -303,8 +305,6 @@ module.exports = function (sequelize, DataTypes) {
return callback(err, null) return callback(err, null)
}) })
} }
}
})
return Revision return Revision
} }

View File

@ -5,7 +5,7 @@ var scrypt = require('scrypt')
// core // core
var logger = require('../logger') var logger = require('../logger')
var {generateAvatarURL} = require('../letter-avatars') var { generateAvatarURL } = require('../letter-avatars')
module.exports = function (sequelize, DataTypes) { module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', { var User = sequelize.define('User', {
@ -47,18 +47,17 @@ module.exports = function (sequelize, DataTypes) {
this.setDataValue('password', hash) this.setDataValue('password', hash)
} }
} }
}, { })
instanceMethods: {
verifyPassword: function (attempt) { User.prototype.verifyPassword = function (attempt) {
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) { if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
return this return this
} else { } else {
return false return false
} }
} }
},
classMethods: { User.associate = function (models) {
associate: function (models) {
User.hasMany(models.Note, { User.hasMany(models.Note, {
foreignKey: 'ownerId', foreignKey: 'ownerId',
constraints: false constraints: false
@ -67,14 +66,14 @@ module.exports = function (sequelize, DataTypes) {
foreignKey: 'lastchangeuserId', foreignKey: 'lastchangeuserId',
constraints: false constraints: false
}) })
}, }
getProfile: function (user) { User.getProfile = function (user) {
if (!user) { if (!user) {
return null return null
} }
return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null) return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null)
}, }
parseProfile: function (profile) { User.parseProfile = function (profile) {
try { try {
profile = JSON.parse(profile) profile = JSON.parse(profile)
} catch (err) { } catch (err) {
@ -89,8 +88,8 @@ module.exports = function (sequelize, DataTypes) {
} }
} }
return profile return profile
}, }
parsePhotoByProfile: function (profile, bigger) { User.parsePhotoByProfile = function (profile, bigger) {
var photo = null var photo = null
switch (profile.provider) { switch (profile.provider) {
case 'facebook': case 'facebook':
@ -142,16 +141,14 @@ module.exports = function (sequelize, DataTypes) {
break break
} }
return photo return photo
}, }
parseProfileByEmail: function (email) { User.parseProfileByEmail = function (email) {
return { return {
name: email.substring(0, email.lastIndexOf('@')), name: email.substring(0, email.lastIndexOf('@')),
photo: generateAvatarURL('', email, false), photo: generateAvatarURL('', email, false),
biggerphoto: generateAvatarURL('', email, true) biggerphoto: generateAvatarURL('', email, true)
} }
} }
}
})
return User return User
} }

View File

@ -18,7 +18,7 @@ var utils = require('./utils')
// public // public
var response = { var response = {
errorForbidden: function (res) { errorForbidden: function (res) {
const {req} = res const { req } = res
if (req.user) { if (req.user) {
responseError(res, '403', 'Forbidden', 'oh no.') responseError(res, '403', 'Forbidden', 'oh no.')
} else { } else {

View File

@ -4,7 +4,7 @@ const Router = require('express').Router
const passport = require('passport') const passport = require('passport')
const DropboxStrategy = require('passport-dropbox-oauth2').Strategy const DropboxStrategy = require('passport-dropbox-oauth2').Strategy
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let dropboxAuth = module.exports = Router() let dropboxAuth = module.exports = Router()

View File

@ -7,8 +7,8 @@ const LocalStrategy = require('passport-local').Strategy
const config = require('../../../config') const config = require('../../../config')
const models = require('../../../models') const models = require('../../../models')
const logger = require('../../../logger') const logger = require('../../../logger')
const {setReturnToFromReferer} = require('../utils') const { setReturnToFromReferer } = require('../utils')
const {urlencodedParser} = require('../../utils') const { urlencodedParser } = require('../../utils')
const response = require('../../../response') const response = require('../../../response')
let emailAuth = module.exports = Router() let emailAuth = module.exports = Router()

View File

@ -5,7 +5,7 @@ const passport = require('passport')
const FacebookStrategy = require('passport-facebook').Strategy const FacebookStrategy = require('passport-facebook').Strategy
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let facebookAuth = module.exports = Router() let facebookAuth = module.exports = Router()

View File

@ -5,7 +5,7 @@ const passport = require('passport')
const GithubStrategy = require('passport-github').Strategy const GithubStrategy = require('passport-github').Strategy
const config = require('../../../config') const config = require('../../../config')
const response = require('../../../response') const response = require('../../../response')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let githubAuth = module.exports = Router() let githubAuth = module.exports = Router()

View File

@ -5,7 +5,7 @@ const passport = require('passport')
const GitlabStrategy = require('passport-gitlab2').Strategy const GitlabStrategy = require('passport-gitlab2').Strategy
const config = require('../../../config') const config = require('../../../config')
const response = require('../../../response') const response = require('../../../response')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let gitlabAuth = module.exports = Router() let gitlabAuth = module.exports = Router()

View File

@ -4,7 +4,7 @@ const Router = require('express').Router
const passport = require('passport') const passport = require('passport')
var GoogleStrategy = require('passport-google-oauth20').Strategy var GoogleStrategy = require('passport-google-oauth20').Strategy
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let googleAuth = module.exports = Router() let googleAuth = module.exports = Router()
@ -12,14 +12,14 @@ passport.use(new GoogleStrategy({
clientID: config.google.clientID, clientID: config.google.clientID,
clientSecret: config.google.clientSecret, clientSecret: config.google.clientSecret,
callbackURL: config.serverURL + '/auth/google/callback', callbackURL: config.serverURL + '/auth/google/callback',
userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo" userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo'
}, passportGeneralCallback)) }, passportGeneralCallback))
googleAuth.get('/auth/google', function (req, res, next) { googleAuth.get('/auth/google', function (req, res, next) {
setReturnToFromReferer(req) setReturnToFromReferer(req)
passport.authenticate('google', { scope: ['profile'] })(req, res, next) passport.authenticate('google', { scope: ['profile'] })(req, res, next)
}) })
// google auth callback // google auth callback
googleAuth.get('/auth/google/callback', googleAuth.get('/auth/google/callback',
passport.authenticate('google', { passport.authenticate('google', {
successReturnToOrRedirect: config.serverURL + '/', successReturnToOrRedirect: config.serverURL + '/',

View File

@ -6,8 +6,8 @@ const LDAPStrategy = require('passport-ldapauth')
const config = require('../../../config') const config = require('../../../config')
const models = require('../../../models') const models = require('../../../models')
const logger = require('../../../logger') const logger = require('../../../logger')
const {setReturnToFromReferer} = require('../utils') const { setReturnToFromReferer } = require('../utils')
const {urlencodedParser} = require('../../utils') const { urlencodedParser } = require('../../utils')
const response = require('../../../response') const response = require('../../../response')
let ldapAuth = module.exports = Router() let ldapAuth = module.exports = Router()

View File

@ -5,7 +5,7 @@ const passport = require('passport')
const Mattermost = require('mattermost') const Mattermost = require('mattermost')
const OAuthStrategy = require('passport-oauth2').Strategy const OAuthStrategy = require('passport-oauth2').Strategy
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
const mattermost = new Mattermost.Client() const mattermost = new Mattermost.Client()

View File

@ -4,7 +4,7 @@ const Router = require('express').Router
const passport = require('passport') const passport = require('passport')
const { Strategy, InternalOAuthError } = require('passport-oauth2') const { Strategy, InternalOAuthError } = require('passport-oauth2')
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let oauth2Auth = module.exports = Router() let oauth2Auth = module.exports = Router()

View File

@ -6,8 +6,8 @@ const OpenIDStrategy = require('@passport-next/passport-openid').Strategy
const config = require('../../../config') const config = require('../../../config')
const models = require('../../../models') const models = require('../../../models')
const logger = require('../../../logger') const logger = require('../../../logger')
const {urlencodedParser} = require('../../utils') const { urlencodedParser } = require('../../utils')
const {setReturnToFromReferer} = require('../utils') const { setReturnToFromReferer } = require('../utils')
let openIDAuth = module.exports = Router() let openIDAuth = module.exports = Router()

View File

@ -6,7 +6,7 @@ const SamlStrategy = require('passport-saml').Strategy
const config = require('../../../config') const config = require('../../../config')
const models = require('../../../models') const models = require('../../../models')
const logger = require('../../../logger') const logger = require('../../../logger')
const {urlencodedParser} = require('../../utils') const { urlencodedParser } = require('../../utils')
const fs = require('fs') const fs = require('fs')
const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) } const intersection = function (array1, array2) { return array1.filter((n) => array2.includes(n)) }

View File

@ -5,7 +5,7 @@ const passport = require('passport')
const TwitterStrategy = require('passport-twitter').Strategy const TwitterStrategy = require('passport-twitter').Strategy
const config = require('../../../config') const config = require('../../../config')
const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
let twitterAuth = module.exports = Router() let twitterAuth = module.exports = Router()

View File

@ -2,7 +2,7 @@
const Router = require('express').Router const Router = require('express').Router
const {urlencodedParser} = require('./utils') const { urlencodedParser } = require('./utils')
const history = require('../history') const history = require('../history')
const historyRouter = module.exports = Router() const historyRouter = module.exports = Router()

View File

@ -2,7 +2,7 @@
const config = require('../../config') const config = require('../../config')
const logger = require('../../logger') const logger = require('../../logger')
const imgur = require('imgur') const imgur = require('@hackmd/imgur')
exports.uploadImage = function (imagePath, callback) { exports.uploadImage = function (imagePath, callback) {
if (!imagePath || typeof imagePath !== 'string') { if (!imagePath || typeof imagePath !== 'string') {

View File

@ -3,7 +3,7 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const config = require('../../config') const config = require('../../config')
const {getImageMimeType} = require('../../utils') const { getImageMimeType } = require('../../utils')
const logger = require('../../logger') const logger = require('../../logger')
const Minio = require('minio') const Minio = require('minio')

View File

@ -3,7 +3,7 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const config = require('../../config') const config = require('../../config')
const {getImageMimeType} = require('../../utils') const { getImageMimeType } = require('../../utils')
const logger = require('../../logger') const logger = require('../../logger')
const AWS = require('aws-sdk') const AWS = require('aws-sdk')

View File

@ -4,7 +4,7 @@ const Router = require('express').Router
const response = require('../response') const response = require('../response')
const {markdownParser} = require('./utils') const { markdownParser } = require('./utils')
const noteRouter = module.exports = Router() const noteRouter = module.exports = Router()

View File

@ -8,7 +8,7 @@ const config = require('../config')
const models = require('../models') const models = require('../models')
const logger = require('../logger') const logger = require('../logger')
const {urlencodedParser} = require('./utils') const { urlencodedParser } = require('./utils')
const statusRouter = module.exports = Router() const statusRouter = module.exports = Router()

View File

@ -8,7 +8,7 @@ const response = require('../response')
const config = require('../config') const config = require('../config')
const models = require('../models') const models = require('../models')
const logger = require('../logger') const logger = require('../logger')
const {generateAvatar} = require('../letter-avatars') const { generateAvatar } = require('../letter-avatars')
const UserRouter = module.exports = Router() const UserRouter = module.exports = Router()

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
// external modules // external modules
var DiffMatchPatch = require('diff-match-patch') var DiffMatchPatch = require('@hackmd/diff-match-patch')
var dmp = new DiffMatchPatch() var dmp = new DiffMatchPatch()
// core // core

View File

@ -1,5 +1,5 @@
{ {
"name": "CodiMD", "name": "codimd",
"version": "1.3.1", "version": "1.3.1",
"description": "Realtime collaborative markdown notes on all platforms.", "description": "Realtime collaborative markdown notes on all platforms.",
"main": "app.js", "main": "app.js",
@ -16,9 +16,13 @@
"doctoc": "doctoc --title='# Table of Contents' README.md" "doctoc": "doctoc --title='# Table of Contents' README.md"
}, },
"dependencies": { "dependencies": {
"@hackmd/codemirror": "^5.41.2",
"@hackmd/diff-match-patch": "^1.1.1",
"@hackmd/idle-js": "^1.0.1",
"@hackmd/imgur": "^0.4.1",
"@hackmd/js-sequence-diagrams": "^0.0.1-alpha.2", "@hackmd/js-sequence-diagrams": "^0.0.1-alpha.2",
"@hackmd/lz-string": "1.4.4",
"@passport-next/passport-openid": "^1.0.0", "@passport-next/passport-openid": "^1.0.0",
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js",
"archiver": "^2.1.1", "archiver": "^2.1.1",
"async": "^2.1.4", "async": "^2.1.4",
"aws-sdk": "^2.345.0", "aws-sdk": "^2.345.0",
@ -29,14 +33,12 @@
"bootstrap-validator": "^0.11.8", "bootstrap-validator": "^0.11.8",
"chance": "^1.0.4", "chance": "^1.0.4",
"cheerio": "^0.22.0", "cheerio": "^0.22.0",
"codemirror": "git+https://github.com/hackmdio/CodeMirror.git",
"compression": "^1.6.2", "compression": "^1.6.2",
"connect-flash": "^0.1.1", "connect-flash": "^0.1.1",
"connect-session-sequelize": "^4.1.0", "connect-session-sequelize": "^6.0.0",
"cookie": "0.3.1", "cookie": "0.3.1",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",
"diff-match-patch": "git+https://github.com/hackmdio/diff-match-patch.git",
"ejs": "^2.5.5", "ejs": "^2.5.5",
"emojify.js": "~1.1.0", "emojify.js": "~1.1.0",
"express": ">=4.14", "express": ">=4.14",
@ -51,19 +53,16 @@
"helmet": "^3.13.0", "helmet": "^3.13.0",
"highlight.js": "~9.12.0", "highlight.js": "~9.12.0",
"i18n": "^0.8.3", "i18n": "^0.8.3",
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
"ionicons": "~2.0.1", "ionicons": "~2.0.1",
"jquery": "^3.1.1", "jquery": "^3.1.1",
"jquery-mousewheel": "^3.1.13", "jquery-mousewheel": "^3.1.13",
"jquery-ui": "^1.12.1", "jquery-ui": "^1.12.1",
"js-cookie": "^2.1.3", "js-cookie": "^2.1.3",
"js-url": "^2.3.0",
"js-yaml": "^3.7.0", "js-yaml": "^3.7.0",
"jsdom-nogyp": "^0.8.3", "jsdom-nogyp": "^0.8.3",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"list.js": "^1.5.0", "list.js": "^1.5.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"lz-string": "git+https://github.com/hackmdio/lz-string.git",
"markdown-it": "^8.2.2", "markdown-it": "^8.2.2",
"markdown-it-abbr": "^1.0.4", "markdown-it-abbr": "^1.0.4",
"markdown-it-container": "^2.0.0", "markdown-it-container": "^2.0.0",
@ -105,21 +104,19 @@
"pg-hstore": "^2.3.2", "pg-hstore": "^2.3.2",
"prismjs": "^1.6.0", "prismjs": "^1.6.0",
"randomcolor": "^0.5.3", "randomcolor": "^0.5.3",
"raphael": "git+https://github.com/dmitrybaranovskiy/raphael", "raphael": "^2.2.8",
"readline-sync": "^1.4.7", "readline-sync": "^1.4.7",
"request": "^2.88.0", "request": "^2.88.0",
"reveal.js": "~3.7.0", "reveal.js": "~3.7.0",
"scrypt": "^6.0.3", "scrypt": "^6.0.3",
"select2": "^3.5.2-browserify", "select2": "^3.5.2-browserify",
"sequelize": "^3.28.0", "sequelize": "5.3.2",
"sequelize-cli": "^2.5.1",
"shortid": "2.2.8", "shortid": "2.2.8",
"socket.io": "~2.1.1", "socket.io": "~2.1.1",
"socket.io-client": "~2.1.1", "socket.io-client": "~2.1.1",
"spin.js": "^2.3.2", "spin.js": "^2.3.2",
"sqlite3": "^4.0.1", "sqlite3": "^4.0.1",
"store": "^2.0.12", "store": "^2.0.12",
"string": "^3.3.3",
"tedious": "^1.14.0", "tedious": "^1.14.0",
"toobusy-js": "^0.5.1", "toobusy-js": "^0.5.1",
"turndown": "^5.0.1", "turndown": "^5.0.1",
@ -130,6 +127,7 @@
"viz.js": "^1.7.0", "viz.js": "^1.7.0",
"winston": "^3.1.0", "winston": "^3.1.0",
"ws": "^6.0.0", "ws": "^6.0.0",
"wurl": "^2.5.3",
"xss": "^1.0.3" "xss": "^1.0.3"
}, },
"resolutions": { "resolutions": {
@ -138,7 +136,7 @@
"**/request": "^2.88.0" "**/request": "^2.88.0"
}, },
"engines": { "engines": {
"node": ">=6.x" "node": ">=8.0.0"
}, },
"bugs": "https://github.com/hackmdio/codimd/issues", "bugs": "https://github.com/hackmdio/codimd/issues",
"keywords": [ "keywords": [
@ -185,13 +183,14 @@
"html-webpack-plugin": "4.0.0-beta.2", "html-webpack-plugin": "4.0.0-beta.2",
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
"jsonlint": "^1.6.2", "jsonlint": "^1.6.2",
"less": "^2.7.1", "less": "^3.9.0",
"less-loader": "^4.1.0", "less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.4.1", "mini-css-extract-plugin": "^0.4.1",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mock-require": "^3.0.3", "mock-require": "^3.0.3",
"optimize-css-assets-webpack-plugin": "^5.0.0", "optimize-css-assets-webpack-plugin": "^5.0.0",
"script-loader": "^0.7.2", "script-loader": "^0.7.2",
"sequelize-cli": "^5.4.0",
"string-loader": "^0.0.1", "string-loader": "^0.0.1",
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"uglifyjs-webpack-plugin": "^1.2.7", "uglifyjs-webpack-plugin": "^1.2.7",

View File

@ -12,7 +12,6 @@ module.exports = {
"ui": false, "ui": false,
"Spinner": false, "Spinner": false,
"modeType": false, "modeType": false,
"Idle": false,
"serverurl": false, "serverurl": false,
"key": false, "key": false,
"gapi": false, "gapi": false,

View File

@ -1,11 +1,6 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global moment, serverurl */ /* global moment, serverurl */
require('./locale')
require('../css/cover.css')
require('../css/site.css')
import { import {
checkIfAuth, checkIfAuth,
clearLoginState, clearLoginState,
@ -30,7 +25,12 @@ import {
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import List from 'list.js' import List from 'list.js'
import S from 'string' import unescapeHTML from 'lodash/unescape'
require('./locale')
require('../css/cover.css')
require('../css/site.css')
const options = { const options = {
valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'], valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags', 'pinned'],
@ -397,7 +397,7 @@ function buildTagsFilter (tags) {
for (let i = 0; i < tags.length; i++) { for (let i = 0; i < tags.length; i++) {
tags[i] = { tags[i] = {
id: i, id: i,
text: S(tags[i]).unescapeHTML().s text: unescapeHTML(tags[i])
} }
} }
filtertags = tags filtertags = tags

View File

@ -1,6 +1,24 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global moment, serverurl */ /* global moment, serverurl */
import Prism from 'prismjs'
import hljs from 'highlight.js'
import PDFObject from 'pdfobject'
import { saveAs } from 'file-saver'
import escapeHTML from 'lodash/escape'
import unescapeHTML from 'lodash/unescape'
import { stripTags } from '../../utils/string'
import getUIElements from './lib/editor/ui-elements'
import markdownit from 'markdown-it'
import markdownitContainer from 'markdown-it-container'
/* Defined regex markdown it plugins */
import Plugin from 'markdown-it-regexp'
require('prismjs/themes/prism.css') require('prismjs/themes/prism.css')
require('prismjs/components/prism-wiki') require('prismjs/components/prism-wiki')
require('prismjs/components/prism-haskell') require('prismjs/components/prism-haskell')
@ -10,17 +28,9 @@ require('prismjs/components/prism-jsx')
require('prismjs/components/prism-makefile') require('prismjs/components/prism-makefile')
require('prismjs/components/prism-gherkin') require('prismjs/components/prism-gherkin')
import Prism from 'prismjs'
import hljs from 'highlight.js'
import PDFObject from 'pdfobject'
import S from 'string'
import { saveAs } from 'file-saver'
require('./lib/common/login') require('./lib/common/login')
require('../vendor/md-toc') require('../vendor/md-toc')
var Viz = require('viz.js') var Viz = require('viz.js')
import getUIElements from './lib/editor/ui-elements'
const ui = getUIElements() const ui = getUIElements()
// auto update last change // auto update last change
@ -157,7 +167,7 @@ export function renderTags (view) {
function slugifyWithUTF8 (text) { function slugifyWithUTF8 (text) {
// remove html tags and trim spaces // remove html tags and trim spaces
let newText = S(text).trim().stripTags().s let newText = stripTags(text.toString().trim())
// replace all spaces in between to dashes // replace all spaces in between to dashes
newText = newText.replace(/\s+/g, '-') newText = newText.replace(/\s+/g, '-')
// slugify string to make it valid for attribute // slugify string to make it valid for attribute
@ -259,9 +269,9 @@ export function finishView (view) {
li.innerHTML = html li.innerHTML = html
let disabled = 'disabled' let disabled = 'disabled'
if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' } if (typeof editor !== 'undefined' && window.havePermission()) { disabled = '' }
if (/^\s*\[[x ]\]\s*/.test(html)) { if (/^\s*\[[x ]]\s*/.test(html)) {
li.innerHTML = html.replace(/^\s*\[ \]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`) li.innerHTML = html.replace(/^\s*\[ ]\s*/, `<input type="checkbox" class="task-list-item-checkbox "${disabled}><label></label>`)
.replace(/^\s*\[x\]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`) .replace(/^\s*\[x]\s*/, `<input type="checkbox" class="task-list-item-checkbox" checked ${disabled}><label></label>`)
if (li.tagName.toLowerCase() !== 'li') { if (li.tagName.toLowerCase() !== 'li') {
li.parentElement.setAttribute('class', 'task-list-item') li.parentElement.setAttribute('class', 'task-list-item')
} else { } else {
@ -492,22 +502,22 @@ export function finishView (view) {
value: code value: code
} }
} else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') { } else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') {
code = S(code).unescapeHTML().s code = unescapeHTML(code)
result = { result = {
value: Prism.highlight(code, Prism.languages[reallang]) value: Prism.highlight(code, Prism.languages[reallang])
} }
} else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') { } else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
code = S(code).unescapeHTML().s code = unescapeHTML(code)
result = { result = {
value: Prism.highlight(code, Prism.languages.wiki) value: Prism.highlight(code, Prism.languages.wiki)
} }
} else if (reallang === 'cmake') { } else if (reallang === 'cmake') {
code = S(code).unescapeHTML().s code = unescapeHTML(code)
result = { result = {
value: Prism.highlight(code, Prism.languages.makefile) value: Prism.highlight(code, Prism.languages.makefile)
} }
} else { } else {
code = S(code).unescapeHTML().s code = unescapeHTML(code)
const languages = hljs.listLanguages() const languages = hljs.listLanguages()
if (!languages.includes(reallang)) { if (!languages.includes(reallang)) {
result = hljs.highlightAuto(code) result = hljs.highlightAuto(code)
@ -902,7 +912,7 @@ export function scrollToHash () {
function highlightRender (code, lang) { function highlightRender (code, lang) {
if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return } if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
code = S(code).escapeHTML().s code = escapeHTML(code)
if (lang === 'sequence') { if (lang === 'sequence') {
return `<div class="sequence-diagram raw">${code}</div>` return `<div class="sequence-diagram raw">${code}</div>`
} else if (lang === 'flow') { } else if (lang === 'flow') {
@ -934,9 +944,6 @@ function highlightRender (code, lang) {
return result.value return result.value
} }
import markdownit from 'markdown-it'
import markdownitContainer from 'markdown-it-container'
export let md = markdownit('default', { export let md = markdownit('default', {
html: true, html: true,
breaks: true, breaks: true,
@ -1034,9 +1041,6 @@ md.renderer.rules.fence = (tokens, idx, options, env, self) => {
return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n` return `<pre><code${self.renderAttrs(token)}>${highlighted}</code></pre>\n`
} }
/* Defined regex markdown it plugins */
import Plugin from 'markdown-it-regexp'
// youtube // youtube
const youtubePlugin = new Plugin( const youtubePlugin = new Plugin(
// regexp to match // regexp to match

View File

@ -2,30 +2,29 @@
/* global serverurl, moment */ /* global serverurl, moment */
import store from 'store' import store from 'store'
import S from 'string' import LZString from '@hackmd/lz-string'
import LZString from 'lz-string'
import escapeHTML from 'lodash/escape'
import wurl from 'wurl'
import { import {
checkNoteIdValid, checkNoteIdValid,
encodeNoteId encodeNoteId
} from './utils' } from './utils'
import { import { checkIfAuth } from './lib/common/login'
checkIfAuth
} from './lib/common/login'
import { import { urlpath } from './lib/config'
urlpath
} from './lib/config'
window.migrateHistoryFromTempCallback = null window.migrateHistoryFromTempCallback = null
migrateHistoryFromTemp() migrateHistoryFromTemp()
function migrateHistoryFromTemp () { function migrateHistoryFromTemp () {
if (window.url('#tempid')) { if (wurl('#tempid')) {
$.get(`${serverurl}/temp`, { $.get(`${serverurl}/temp`, {
tempid: window.url('#tempid') tempid: wurl('#tempid')
}) })
.done(data => { .done(data => {
if (data && data.temp) { if (data && data.temp) {
@ -274,8 +273,8 @@ function parseToHistory (list, notehistory, callback) {
notehistory[i].fromNow = timestamp.fromNow() notehistory[i].fromNow = timestamp.fromNow()
notehistory[i].time = timestamp.format('llll') notehistory[i].time = timestamp.format('llll')
// prevent XSS // prevent XSS
notehistory[i].text = S(notehistory[i].text).escapeHTML().s notehistory[i].text = escapeHTML(notehistory[i].text)
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : [] notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHTML(notehistory[i].tags).split(',') : []
// add to list // add to list
if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) } if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
} }

View File

@ -1,16 +1,7 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global CodeMirror, Cookies, moment, Spinner, Idle, serverurl, /* global CodeMirror, Cookies, moment, Spinner, serverurl,
key, Dropbox, ot, hex2rgb, Visibility */ key, Dropbox, ot, hex2rgb, Visibility */
require('../vendor/showup/showup')
require('../css/index.css')
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
import TurndownService from 'turndown' import TurndownService from 'turndown'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
@ -20,8 +11,12 @@ import hljs from 'highlight.js'
import _ from 'lodash' import _ from 'lodash'
import wurl from 'wurl'
import List from 'list.js' import List from 'list.js'
import Idle from '@hackmd/idle-js'
import { import {
checkLoginStateChanged, checkLoginStateChanged,
setloginStateChangeEvent setloginStateChangeEvent
@ -82,6 +77,15 @@ import getUIElements from './lib/editor/ui-elements'
import modeType from './lib/modeType' import modeType from './lib/modeType'
import appState from './lib/appState' import appState from './lib/appState'
require('../vendor/showup/showup')
require('../css/index.css')
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
var defaultTextHeight = 20 var defaultTextHeight = 20
var viewportMargin = 20 var viewportMargin = 20
var defaultEditorMode = 'gfm' var defaultEditorMode = 'gfm'
@ -1388,12 +1392,12 @@ $('#gistImportModalConfirm').click(function () {
if (!isValidURL(gisturl)) { 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 { } else {
var hostname = window.url('hostname', gisturl) var hostname = wurl('hostname', gisturl)
if (hostname !== 'gist.github.com') { 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 { } else {
ui.spinner.show() ui.spinner.show()
$.get('https://api.github.com/gists/' + window.url('-1', gisturl)) $.get('https://api.github.com/gists/' + wurl('-1', gisturl))
.done(function (data) { .done(function (data) {
if (data.files) { if (data.files) {
var contents = '' var contents = ''

View File

@ -51,7 +51,7 @@ export function insertText (cm, text, cursorEnd = 0) {
let cursor = cm.getCursor() let cursor = cm.getCursor()
cm.replaceSelection(text, cursor, cursor) cm.replaceSelection(text, cursor, cursor)
cm.focus() cm.focus()
cm.setCursor({line: cursor.line, ch: cursor.ch + cursorEnd}) cm.setCursor({ line: cursor.line, ch: cursor.ch + cursorEnd })
} }
export function insertLink (cm, isImage) { export function insertLink (cm, isImage) {
@ -80,7 +80,7 @@ export function insertLink (cm, isImage) {
cm.setSelections(ranges) cm.setSelections(ranges)
} else { } else {
cm.replaceRange(symbol + linkEnd, cursor, cursor) cm.replaceRange(symbol + linkEnd, cursor, cursor)
cm.setCursor({line: cursor.line, ch: cursor.ch + symbol.length + linkEnd.length}) cm.setCursor({ line: cursor.line, ch: cursor.ch + symbol.length + linkEnd.length })
} }
} }
cm.focus() cm.focus()
@ -88,8 +88,8 @@ export function insertLink (cm, isImage) {
export function insertHeader (cm) { export function insertHeader (cm) {
let cursor = cm.getCursor() let cursor = cm.getCursor()
let startOfLine = {line: cursor.line, ch: 0} let startOfLine = { line: cursor.line, ch: 0 }
let startOfLineText = cm.getRange(startOfLine, {line: cursor.line, ch: 1}) let startOfLineText = cm.getRange(startOfLine, { line: cursor.line, ch: 1 })
// See if it is already a header // See if it is already a header
if (startOfLineText === '#') { if (startOfLineText === '#') {
cm.replaceRange('#', startOfLine, startOfLine) cm.replaceRange('#', startOfLine, startOfLine)
@ -108,14 +108,14 @@ export function insertOnStartOfLines (cm, symbol) {
if (!range.empty()) { if (!range.empty()) {
const from = range.from() const from = range.from()
const to = range.to() const to = range.to()
let selection = cm.getRange({line: from.line, ch: 0}, to) let selection = cm.getRange({ line: from.line, ch: 0 }, to)
selection = selection.replace(/\n/g, '\n' + symbol) selection = selection.replace(/\n/g, '\n' + symbol)
selection = symbol + selection selection = symbol + selection
cm.replaceRange(selection, from, to) cm.replaceRange(selection, from, to)
} else { } else {
cm.replaceRange(symbol, {line: cursor.line, ch: 0}, {line: cursor.line, ch: 0}) cm.replaceRange(symbol, { line: cursor.line, ch: 0 }, { line: cursor.line, ch: 0 })
} }
} }
cm.setCursor({line: cursor.line, ch: cursor.ch + symbol.length}) cm.setCursor({ line: cursor.line, ch: cursor.ch + symbol.length })
cm.focus() cm.focus()
} }

View File

@ -1,12 +1,6 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global refreshView */ /* global refreshView */
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
import { import {
autoLinkify, autoLinkify,
deduplicatedHeaderId, deduplicatedHeaderId,
@ -24,6 +18,12 @@ import {
import { preventXSS } from './render' import { preventXSS } from './render'
require('../css/extra.css')
require('../css/slide-preview.css')
require('../css/site.css')
require('highlight.js/styles/github-gist.css')
const markdown = $('#doc.markdown-body') const markdown = $('#doc.markdown-body')
const text = markdown.text() const text = markdown.text()
const lastMeta = md.meta const lastMeta = md.meta

View File

@ -1,12 +1,12 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global serverurl, Reveal, RevealMarkdown */ /* global serverurl, Reveal, RevealMarkdown */
require('../css/extra.css')
require('../css/site.css')
import { preventXSS } from './render' import { preventXSS } from './render'
import { md, updateLastChange, removeDOMEvents, finishView } from './extra' import { md, updateLastChange, removeDOMEvents, finishView } from './extra'
require('../css/extra.css')
require('../css/site.css')
const body = preventXSS($('.slides').text()) const body = preventXSS($('.slides').text())
window.createtime = window.lastchangeui.time.attr('data-createtime') window.createtime = window.lastchangeui.time.attr('data-createtime')

7
utils/string.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
function stripTags (s) {
return s.replace(RegExp(`</?[^<>]*>`, 'gi'), '')
}
exports.stripTags = stripTags

View File

@ -195,15 +195,11 @@ module.exports = {
'bootstrap-validator', 'bootstrap-validator',
'expose-loader?select2!select2', 'expose-loader?select2!select2',
'expose-loader?moment!moment', 'expose-loader?moment!moment',
'script-loader!js-url',
path.join(__dirname, 'public/js/cover.js') path.join(__dirname, 'public/js/cover.js')
], ],
index: [ index: [
'babel-polyfill', 'babel-polyfill',
'script-loader!jquery-ui-resizable', 'script-loader!jquery-ui-resizable',
'script-loader!js-url',
'script-loader!Idle.Js',
'expose-loader?LZString!lz-string',
'script-loader!codemirror', 'script-loader!codemirror',
'script-loader!inlineAttachment', 'script-loader!inlineAttachment',
'script-loader!jqueryTextcomplete', 'script-loader!jqueryTextcomplete',
@ -218,16 +214,16 @@ module.exports = {
'index-styles': [ 'index-styles': [
path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'), path.join(__dirname, 'public/vendor/jquery-ui/jquery-ui.min.css'),
path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'), path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.css'),
path.join(__dirname, 'node_modules/codemirror/lib/codemirror.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/lib/codemirror.css'),
path.join(__dirname, 'node_modules/codemirror/addon/fold/foldgutter.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/addon/fold/foldgutter.css'),
path.join(__dirname, 'node_modules/codemirror/addon/display/fullscreen.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/addon/display/fullscreen.css'),
path.join(__dirname, 'node_modules/codemirror/addon/dialog/dialog.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/addon/dialog/dialog.css'),
path.join(__dirname, 'node_modules/codemirror/addon/scroll/simplescrollbars.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/addon/scroll/simplescrollbars.css'),
path.join(__dirname, 'node_modules/codemirror/addon/search/matchesonscrollbar.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/addon/search/matchesonscrollbar.css'),
path.join(__dirname, 'node_modules/codemirror/theme/monokai.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/theme/monokai.css'),
path.join(__dirname, 'node_modules/codemirror/theme/one-dark.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/theme/one-dark.css'),
path.join(__dirname, 'node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/mode/tiddlywiki/tiddlywiki.css'),
path.join(__dirname, 'node_modules/codemirror/mode/mediawiki/mediawiki.css'), path.join(__dirname, 'node_modules/@hackmd/codemirror/mode/mediawiki/mediawiki.css'),
path.join(__dirname, 'public/css/github-extract.css'), path.join(__dirname, 'public/css/github-extract.css'),
path.join(__dirname, 'public/vendor/showup/showup.css'), path.join(__dirname, 'public/vendor/showup/showup.css'),
path.join(__dirname, 'public/css/mermaid.css'), path.join(__dirname, 'public/css/mermaid.css'),
@ -248,13 +244,10 @@ module.exports = {
'expose-loader?jsyaml!js-yaml', 'expose-loader?jsyaml!js-yaml',
'script-loader!mermaid', 'script-loader!mermaid',
'expose-loader?moment!moment', 'expose-loader?moment!moment',
'script-loader!js-url',
'script-loader!handlebars', 'script-loader!handlebars',
'expose-loader?hljs!highlight.js', 'expose-loader?hljs!highlight.js',
'expose-loader?emojify!emojify.js', 'expose-loader?emojify!emojify.js',
'script-loader!Idle.Js',
'script-loader!gist-embed', 'script-loader!gist-embed',
'expose-loader?LZString!lz-string',
'script-loader!codemirror', 'script-loader!codemirror',
'script-loader!inlineAttachment', 'script-loader!inlineAttachment',
'script-loader!jqueryTextcomplete', 'script-loader!jqueryTextcomplete',
@ -355,7 +348,7 @@ module.exports = {
modules: ['node_modules'], modules: ['node_modules'],
extensions: ['.js'], extensions: ['.js'],
alias: { alias: {
codemirror: path.join(__dirname, 'node_modules/codemirror/codemirror.min.js'), codemirror: path.join(__dirname, 'node_modules/@hackmd/codemirror/codemirror.min.js'),
inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'), inlineAttachment: path.join(__dirname, 'public/vendor/inlineAttachment/inline-attachment.js'),
jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'), jqueryTextcomplete: path.join(__dirname, 'public/vendor/jquery-textcomplete/jquery.textcomplete.js'),
codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'), codemirrorSpellChecker: path.join(__dirname, 'public/vendor/codemirror-spell-checker/spell-checker.min.js'),

1980
yarn.lock

File diff suppressed because it is too large Load Diff