mirror of
https://github.com/status-im/codimd.git
synced 2025-01-11 16:34:33 +00:00
Merge pull request #1515 from hackmdio/release-2.1.0
This commit is contained in:
commit
89a0de4205
@ -1,7 +1,6 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "lts/carbon"
|
||||
- "lts/dubnium"
|
||||
- "11"
|
||||
- "12"
|
||||
@ -12,7 +11,6 @@ cache: npm
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- node_js: lts/carbon
|
||||
- node_js: lts/dubnium
|
||||
allow_failures:
|
||||
- node_js: "11"
|
||||
|
8
app.js
8
app.js
@ -17,6 +17,7 @@ var passportSocketIo = require('passport.socketio')
|
||||
var helmet = require('helmet')
|
||||
var i18n = require('i18n')
|
||||
var flash = require('connect-flash')
|
||||
var apiMetrics = require('prometheus-api-metrics')
|
||||
|
||||
// core
|
||||
var config = require('./lib/config')
|
||||
@ -56,6 +57,12 @@ function createHttpServer () {
|
||||
var app = express()
|
||||
var server = createHttpServer()
|
||||
|
||||
// API and process monitoring with Prometheus for Node.js micro-service
|
||||
app.use(apiMetrics({
|
||||
metricsPath: '/metrics/router',
|
||||
excludeRoutes: ['/metrics/codimd']
|
||||
}))
|
||||
|
||||
// logger
|
||||
app.use(morgan('combined', {
|
||||
stream: logger.stream
|
||||
@ -131,6 +138,7 @@ app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.st
|
||||
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime }))
|
||||
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime }))
|
||||
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
|
||||
app.use(require('./lib/metrics').router)
|
||||
|
||||
// session
|
||||
app.use(session({
|
||||
|
@ -1,4 +1,6 @@
|
||||
FROM hackmdio/buildpack:1.0.4 as BUILD
|
||||
ARG RUNTIME
|
||||
|
||||
FROM hackmdio/buildpack:node-10-0baafb79 as BUILD
|
||||
|
||||
COPY --chown=hackmd:hackmd . .
|
||||
|
||||
@ -12,11 +14,12 @@ RUN set -xe && \
|
||||
rm -rf .git .gitignore .travis.yml .dockerignore .editorconfig .babelrc .mailmap .sequelizerc.example \
|
||||
test docs contribute \
|
||||
package-lock.json webpack.prod.js webpack.htmlexport.js webpack.dev.js webpack.common.js \
|
||||
config.json.example README.md CONTRIBUTING.md AUTHORS
|
||||
config.json.example README.md CONTRIBUTING.md AUTHORS node_modules
|
||||
|
||||
FROM hackmdio/runtime:1.0.6
|
||||
FROM $RUNTIME
|
||||
USER hackmd
|
||||
WORKDIR /home/hackmd/app
|
||||
COPY --chown=1500:1500 --from=BUILD /home/hackmd/app .
|
||||
RUN npm install --production && npm cache clean --force && rm -rf /tmp/{core-js-banners,phantomjs}
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["/home/hackmd/app/docker-entrypoint.sh"]
|
||||
|
@ -1,5 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
set -x
|
||||
|
||||
CURRENT_DIR=$(dirname "$BASH_SOURCE")
|
||||
|
||||
docker build -t hackmdio/codimd -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."
|
||||
GIT_SHA1="$(git rev-parse HEAD)"
|
||||
GIT_SHORT_ID="${GIT_SHA1:0:8}"
|
||||
GIT_TAG=$(git describe --exact-match --tags $(git log -n1 --pretty='%h') 2>/dev/null || echo "")
|
||||
|
||||
DOCKER_TAG="${GIT_TAG:-$GIT_SHORT_ID}"
|
||||
|
||||
docker build --build-arg RUNTIME=hackmdio/runtime:node-10-d27854ef -t "hackmdio/hackmd:$DOCKER_TAG" -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."
|
||||
|
||||
docker build --build-arg RUNTIME=hackmdio/runtime:node-10-cjk-d27854ef -t "hackmdio/hackmd:$DOCKER_TAG-cjk" -f "$CURRENT_DIR/Dockerfile" "$CURRENT_DIR/.."
|
||||
|
@ -15,50 +15,56 @@ const emailAuth = module.exports = Router()
|
||||
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'email'
|
||||
}, function (email, password, done) {
|
||||
}, async function (email, password, done) {
|
||||
if (!validator.isEmail(email)) return done(null, false)
|
||||
models.User.findOne({
|
||||
where: {
|
||||
email: email
|
||||
}
|
||||
}).then(function (user) {
|
||||
|
||||
try {
|
||||
const user = await models.User.findOne({
|
||||
where: {
|
||||
email: email
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) return done(null, false)
|
||||
if (!user.verifyPassword(password)) return done(null, false)
|
||||
if (!await user.verifyPassword(password)) return done(null, false)
|
||||
return done(null, user)
|
||||
}).catch(function (err) {
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
return done(err)
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
if (config.allowEmailRegister) {
|
||||
emailAuth.post('/register', urlencodedParser, function (req, res, next) {
|
||||
emailAuth.post('/register', urlencodedParser, async function (req, res, next) {
|
||||
if (!req.body.email || !req.body.password) return response.errorBadRequest(req, res)
|
||||
if (!validator.isEmail(req.body.email)) return response.errorBadRequest(req, res)
|
||||
models.User.findOrCreate({
|
||||
where: {
|
||||
email: req.body.email
|
||||
},
|
||||
defaults: {
|
||||
password: req.body.password
|
||||
}
|
||||
}).spread(function (user, created) {
|
||||
if (user) {
|
||||
if (created) {
|
||||
logger.debug('user registered: ' + user.id)
|
||||
req.flash('info', "You've successfully registered, please signin.")
|
||||
} else {
|
||||
logger.debug('user found: ' + user.id)
|
||||
req.flash('error', 'This email has been used, please try another one.')
|
||||
try {
|
||||
const [user, created] = await models.User.findOrCreate({
|
||||
where: {
|
||||
email: req.body.email
|
||||
},
|
||||
defaults: {
|
||||
password: req.body.password
|
||||
}
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
req.flash('error', 'Failed to register your account, please try again.')
|
||||
return res.redirect(config.serverURL + '/')
|
||||
}
|
||||
req.flash('error', 'Failed to register your account, please try again.')
|
||||
|
||||
if (created) {
|
||||
logger.debug('user registered: ' + user.id)
|
||||
req.flash('info', "You've successfully registered, please signin.")
|
||||
} else {
|
||||
logger.debug('user found: ' + user.id)
|
||||
req.flash('error', 'This email has been used, please try another one.')
|
||||
}
|
||||
return res.redirect(config.serverURL + '/')
|
||||
}).catch(function (err) {
|
||||
} catch (err) {
|
||||
logger.error('auth callback failed: ' + err)
|
||||
return response.errorInternalError(req, res)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ passport.use(new OAuth2CustomStrategy({
|
||||
clientSecret: config.oauth2.clientSecret,
|
||||
callbackURL: config.serverURL + '/auth/oauth2/callback',
|
||||
userProfileURL: config.oauth2.userProfileURL,
|
||||
state: config.oauth2.state,
|
||||
scope: config.oauth2.scope
|
||||
}, passportGeneralCallback))
|
||||
|
||||
|
@ -100,6 +100,7 @@ module.exports = {
|
||||
userProfileDisplayNameAttr: 'displayName',
|
||||
userProfileEmailAttr: 'email',
|
||||
userProfilePhotoAttr: 'photo',
|
||||
state: true,
|
||||
scope: 'email'
|
||||
},
|
||||
facebook: {
|
||||
|
@ -94,6 +94,7 @@ module.exports = {
|
||||
tokenURL: process.env.CMD_OAUTH2_TOKEN_URL,
|
||||
userProfileURL: process.env.CMD_OAUTH2_USER_PROFILE_URL,
|
||||
scope: process.env.CMD_OAUTH2_SCOPE,
|
||||
state: process.env.CMD_OAUTH2_STATE,
|
||||
userProfileUsernameAttr: process.env.CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
|
||||
userProfileDisplayNameAttr: process.env.CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
|
||||
userProfileEmailAttr: process.env.CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
|
||||
|
@ -2,7 +2,7 @@
|
||||
const config = require('../config')
|
||||
const logger = require('../logger')
|
||||
|
||||
const lutim = require('lib/imageRouter/lutim')
|
||||
const lutim = require('lutim')
|
||||
|
||||
exports.uploadImage = function (imagePath, callback) {
|
||||
if (!imagePath || typeof imagePath !== 'string') {
|
||||
|
@ -6,9 +6,10 @@ const config = require('../config')
|
||||
const { getImageMimeType } = require('../utils')
|
||||
const logger = require('../logger')
|
||||
|
||||
const AWS = require('aws-sdk')
|
||||
const awsConfig = new AWS.Config(config.s3)
|
||||
const s3 = new AWS.S3(awsConfig)
|
||||
const { S3Client } = require('@aws-sdk/client-s3-node/S3Client')
|
||||
const { PutObjectCommand } = require('@aws-sdk/client-s3-node/commands/PutObjectCommand')
|
||||
|
||||
const s3 = new S3Client(config.s3)
|
||||
|
||||
exports.uploadImage = function (imagePath, callback) {
|
||||
if (!imagePath || typeof imagePath !== 'string') {
|
||||
@ -32,16 +33,12 @@ exports.uploadImage = function (imagePath, callback) {
|
||||
Body: buffer,
|
||||
ACL: 'public-read'
|
||||
}
|
||||
|
||||
const mimeType = getImageMimeType(imagePath)
|
||||
if (mimeType) { params.ContentType = mimeType }
|
||||
|
||||
s3.putObject(params, function (err, data) {
|
||||
if (err) {
|
||||
callback(new Error(err), null)
|
||||
return
|
||||
}
|
||||
const command = new PutObjectCommand(params)
|
||||
|
||||
s3.send(command).then(data => {
|
||||
let s3Endpoint = 's3.amazonaws.com'
|
||||
if (config.s3.endpoint) {
|
||||
s3Endpoint = config.s3.endpoint
|
||||
@ -49,6 +46,10 @@ exports.uploadImage = function (imagePath, callback) {
|
||||
s3Endpoint = `s3-${config.s3.region}.amazonaws.com`
|
||||
}
|
||||
callback(null, `https://${s3Endpoint}/${config.s3bucket}/${params.Key}`)
|
||||
}).catch(err => {
|
||||
if (err) {
|
||||
callback(new Error(err), null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
15
lib/metrics.js
Normal file
15
lib/metrics.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const { Router } = require('express')
|
||||
|
||||
const { wrap } = require('./utils')
|
||||
|
||||
// load controller
|
||||
const statusController = require('./status')
|
||||
const appRouter = Router()
|
||||
|
||||
// register route
|
||||
appRouter.get('/status', wrap(statusController.getStatus))
|
||||
appRouter.get('/metrics/codimd', wrap(statusController.getMetrics))
|
||||
|
||||
exports.router = appRouter
|
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
// external modules
|
||||
var Sequelize = require('sequelize')
|
||||
var scrypt = require('scrypt')
|
||||
var Scrypt = require('scrypt-kdf')
|
||||
|
||||
// core
|
||||
var logger = require('../logger')
|
||||
@ -41,22 +41,34 @@ module.exports = function (sequelize, DataTypes) {
|
||||
}
|
||||
},
|
||||
password: {
|
||||
type: Sequelize.TEXT,
|
||||
set: function (value) {
|
||||
var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
|
||||
this.setDataValue('password', hash)
|
||||
}
|
||||
type: Sequelize.TEXT
|
||||
}
|
||||
})
|
||||
|
||||
User.prototype.verifyPassword = function (attempt) {
|
||||
if (scrypt.verifyKdfSync(Buffer.from(this.password, 'hex'), attempt)) {
|
||||
return this
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
User.hashPassword = async function (plain) {
|
||||
return (await Scrypt.kdf(plain, await Scrypt.pickParams(0.1))).toString('hex')
|
||||
}
|
||||
|
||||
User.prototype.verifyPassword = async function (attempt) {
|
||||
if (await Scrypt.verify(Buffer.from(this.password, 'hex'), attempt)) {
|
||||
return this
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
User.addHook('beforeCreate', async function (user) {
|
||||
// only do hash when password is presented
|
||||
if (user.password) {
|
||||
user.password = await User.hashPassword(user.password)
|
||||
}
|
||||
})
|
||||
User.addHook('beforeUpdate', async function (user) {
|
||||
if (user.changed('password')) {
|
||||
user.password = await User.hashPassword(user.password)
|
||||
}
|
||||
})
|
||||
|
||||
User.associate = function (models) {
|
||||
User.hasMany(models.Note, {
|
||||
foreignKey: 'ownerId',
|
||||
@ -103,10 +115,11 @@ module.exports = function (sequelize, DataTypes) {
|
||||
else photo += '?size=bigger'
|
||||
break
|
||||
case 'github':
|
||||
if (profile.photos && profile.photos[0]) photo = profile.photos[0].value.replace('?', '')
|
||||
else photo = 'https://avatars.githubusercontent.com/u/' + profile.id
|
||||
if (bigger) photo += '?s=400'
|
||||
else photo += '?s=96'
|
||||
const photoURL = new URL(profile.photos && profile.photos[0]
|
||||
? profile.photos[0].value
|
||||
: `https://avatars.githubusercontent.com/u/${profile.id}`)
|
||||
photoURL.searchParams.set('s', bigger ? 400 : 96)
|
||||
photo = photoURL.toString()
|
||||
break
|
||||
case 'gitlab':
|
||||
photo = profile.avatarUrl
|
||||
|
@ -61,6 +61,8 @@ async function showNote (req, res) {
|
||||
// if allow free url enable, auto create note
|
||||
if (!config.allowFreeURL || config.forbiddenNoteIDs.includes(noteId)) {
|
||||
return errorNotFound(req, res)
|
||||
} else if (!config.allowAnonymous && !userId) {
|
||||
return errorForbidden(req, res)
|
||||
}
|
||||
note = await createNote(userId, noteId)
|
||||
}
|
||||
|
@ -298,10 +298,12 @@ function getStatus () {
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
return logger.error('count user failed: ' + err)
|
||||
logger.error('count user failed: ' + err)
|
||||
return Promise.reject(new Error('count user failed: ' + err))
|
||||
})
|
||||
}).catch(function (err) {
|
||||
return logger.error('count note failed: ' + err)
|
||||
logger.error('count note failed: ' + err)
|
||||
return Promise.reject(new Error('count note failed: ' + err))
|
||||
})
|
||||
}
|
||||
|
||||
@ -772,8 +774,7 @@ function queueForConnect (socket) {
|
||||
const noteId = socket.noteId
|
||||
logger.info('SERVER connected a client to [' + noteId + ']:')
|
||||
logger.info(JSON.stringify(user))
|
||||
// logger.info(notes);
|
||||
getStatus(function (data) {
|
||||
getStatus().then(function (data) {
|
||||
logger.info(JSON.stringify(data))
|
||||
})
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ appRouter.get('/404', errorPageController.errorNotFound)
|
||||
// get 500 internal error
|
||||
appRouter.get('/500', errorPageController.errorInternalError)
|
||||
|
||||
appRouter.get('/status', wrap(statusController.getStatus))
|
||||
appRouter.get('/config', statusController.getConfig)
|
||||
|
||||
// register auth module
|
||||
|
@ -4,14 +4,30 @@ const realtime = require('../realtime/realtime')
|
||||
const config = require('../config')
|
||||
|
||||
exports.getStatus = async (req, res) => {
|
||||
const data = await realtime.getStatus()
|
||||
|
||||
res.set({
|
||||
'Cache-Control': 'private', // only cache by client
|
||||
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
res.send(data)
|
||||
|
||||
try {
|
||||
const data = await realtime.getStatus()
|
||||
res.send(data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
res.status(500).send(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
exports.getMetrics = async (req, res) => {
|
||||
const data = await realtime.getStatus()
|
||||
|
||||
res.set({
|
||||
'Cache-Control': 'private', // only cache by client
|
||||
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
|
||||
'Content-Type': 'text/plain; charset=utf-8'
|
||||
})
|
||||
res.render('../js/lib/common/metrics.ejs', data)
|
||||
}
|
||||
|
||||
exports.getConfig = (req, res) => {
|
||||
|
2247
package-lock.json
generated
2247
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
123
package.json
123
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codimd",
|
||||
"version": "2.0.1",
|
||||
"version": "2.1.0",
|
||||
"description": "Realtime collaborative markdown notes on all platforms.",
|
||||
"keywords": [
|
||||
"Collaborative",
|
||||
@ -30,25 +30,19 @@
|
||||
"postinstall": "bin/heroku"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hackmd/codemirror": "~5.49.5",
|
||||
"@aws-sdk/client-s3-node": "0.1.0-preview.2",
|
||||
"@hackmd/diff-match-patch": "~1.1.3",
|
||||
"@hackmd/idle-js": "~1.0.1",
|
||||
"@hackmd/imgur": "~0.5.0",
|
||||
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
|
||||
"@hackmd/lz-string": "~1.4.4",
|
||||
"@hackmd/meta-marked": "~0.4.4",
|
||||
"@hackmd/pandoc.js": "^0.1.9",
|
||||
"@passport-next/passport-openid": "~1.0.0",
|
||||
"@susisu/mte-kernel": "^2.1.0",
|
||||
"archiver": "~3.1.1",
|
||||
"async": "~3.1.0",
|
||||
"aws-sdk": "~2.503.0",
|
||||
"azure-storage": "~2.10.3",
|
||||
"babel-polyfill": "~6.26.0",
|
||||
"base64url": "~3.0.1",
|
||||
"body-parser": "~1.19.0",
|
||||
"bootstrap": "~3.4.0",
|
||||
"bootstrap-validator": "~0.11.8",
|
||||
"chance": "~1.0.18",
|
||||
"cheerio": "~0.22.0",
|
||||
"connect-flash": "~0.1.1",
|
||||
@ -59,46 +53,17 @@
|
||||
"ejs": "~2.6.2",
|
||||
"express": "~4.17.1",
|
||||
"express-session": "~1.16.2",
|
||||
"file-saver": "~2.0.2",
|
||||
"flowchart.js": "~1.12.2",
|
||||
"fork-awesome": "~1.1.7",
|
||||
"formidable": "~1.2.1",
|
||||
"gist-embed": "~2.6.0",
|
||||
"graceful-fs": "~4.2.1",
|
||||
"handlebars": "~4.1.2",
|
||||
"helmet": "~3.20.0",
|
||||
"highlight.js": "~9.15.9",
|
||||
"https-proxy-agent": "^3.0.1",
|
||||
"i18n": "~0.8.3",
|
||||
"ionicons": "~2.0.1",
|
||||
"isomorphic-fetch": "~2.2.1",
|
||||
"jquery": "~3.4.1",
|
||||
"jquery-mousewheel": "~3.1.13",
|
||||
"jquery-ui": "~1.12.1",
|
||||
"js-cookie": "~2.2.0",
|
||||
"js-yaml": "~3.13.1",
|
||||
"jsdom-nogyp": "~0.8.3",
|
||||
"keymaster": "~1.6.2",
|
||||
"list.js": "~1.5.0",
|
||||
"lodash": "~4.17.15",
|
||||
"lutim": "~1.0.2",
|
||||
"markdown-it": "~10.0.0",
|
||||
"markdown-it-abbr": "~1.0.4",
|
||||
"markdown-it-container": "~2.0.0",
|
||||
"markdown-it-deflist": "~2.0.3",
|
||||
"markdown-it-footnote": "~3.0.2",
|
||||
"markdown-it-imsize": "~2.0.1",
|
||||
"markdown-it-ins": "~2.0.0",
|
||||
"markdown-it-mark": "~2.0.0",
|
||||
"markdown-it-mathjax": "~2.0.0",
|
||||
"markdown-it-regexp": "~0.4.0",
|
||||
"markdown-it-sub": "~1.0.0",
|
||||
"markdown-it-sup": "~1.0.0",
|
||||
"markdown-pdf": "~9.0.0",
|
||||
"markdownlint": "^0.17.0",
|
||||
"mathjax": "~2.7.5",
|
||||
"mattermost-redux": "~5.13.0",
|
||||
"mermaid": "~8.4.8",
|
||||
"method-override": "~3.0.0",
|
||||
"minimist": "~1.2.0",
|
||||
"minio": "^7.0.12",
|
||||
@ -119,50 +84,38 @@
|
||||
"passport-saml": "~1.0.0",
|
||||
"passport-twitter": "~1.0.4",
|
||||
"passport.socketio": "~3.7.0",
|
||||
"pdfobject": "~2.1.1",
|
||||
"pg": "~6.1.2",
|
||||
"pg-hstore": "~2.3.2",
|
||||
"plantuml-encoder": "^1.2.5",
|
||||
"prismjs": "~1.17.1",
|
||||
"prom-client": "^12.0.0",
|
||||
"prometheus-api-metrics": "^2.2.5",
|
||||
"randomcolor": "~0.5.4",
|
||||
"raphael": "~2.2.8",
|
||||
"readline-sync": "~1.4.7",
|
||||
"request": "~2.88.0",
|
||||
"reveal.js": "~3.9.2",
|
||||
"scrypt": "~6.0.3",
|
||||
"select2": "~3.5.2-browserify",
|
||||
"scrypt-kdf": "^2.0.1",
|
||||
"sequelize": "5.21.3",
|
||||
"sequelize-cli": "~5.5.1",
|
||||
"shortid": "~2.2.14",
|
||||
"socket.io": "~2.2.0",
|
||||
"socket.io-client": "~2.2.0",
|
||||
"spin.js": "~4.0.0",
|
||||
"sqlite3": "~4.0.9",
|
||||
"store": "~2.0.12",
|
||||
"tedious": "~6.2.0",
|
||||
"toobusy-js": "~0.5.1",
|
||||
"turndown": "~5.0.3",
|
||||
"uuid": "~3.3.2",
|
||||
"validator": "~11.1.0",
|
||||
"vega": "~5.9.1",
|
||||
"vega-embed": "~6.2.2",
|
||||
"vega-lite": "~4.4.0",
|
||||
"velocity-animate": "~1.5.2",
|
||||
"visibilityjs": "~2.0.2",
|
||||
"viz.js": "~2.1.2",
|
||||
"winston": "~3.2.1",
|
||||
"ws": "~7.1.1",
|
||||
"wurl": "~2.5.3",
|
||||
"xss": "~1.0.6"
|
||||
"ws": "~7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hackmd/codemirror": "~5.49.5",
|
||||
"@hackmd/emojify.js": "^2.1.0",
|
||||
"@hackmd/idle-js": "~1.0.1",
|
||||
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
|
||||
"@susisu/mte-kernel": "^2.1.0",
|
||||
"acorn": "~6.1.1",
|
||||
"babel-core": "~6.26.3",
|
||||
"babel-loader": "~7.1.4",
|
||||
"babel-plugin-transform-runtime": "~6.23.0",
|
||||
"babel-preset-env": "~1.7.0",
|
||||
"babel-runtime": "~6.26.0",
|
||||
"bootstrap": "~3.4.0",
|
||||
"bootstrap-validator": "~0.11.8",
|
||||
"copy-webpack-plugin": "~4.5.2",
|
||||
"css-loader": "~1.0.0",
|
||||
"dictionary-de": "^2.0.3",
|
||||
@ -173,39 +126,87 @@
|
||||
"exports-loader": "~0.7.0",
|
||||
"expose-loader": "~0.7.5",
|
||||
"file-loader": "~2.0.0",
|
||||
"file-saver": "~2.0.2",
|
||||
"flowchart.js": "~1.12.2",
|
||||
"fork-awesome": "~1.1.7",
|
||||
"gist-embed": "~2.6.0",
|
||||
"graceful-fs": "~4.2.1",
|
||||
"handlebars": "~4.1.2",
|
||||
"highlight.js": "~9.15.9",
|
||||
"html-webpack-plugin": "~4.0.0-beta.2",
|
||||
"imports-loader": "~0.8.0",
|
||||
"intelli-espower-loader": "~1.0.1",
|
||||
"ionicons": "~2.0.1",
|
||||
"jquery": "~3.4.1",
|
||||
"jquery-mousewheel": "~3.1.13",
|
||||
"jquery-ui": "~1.12.1",
|
||||
"js-cookie": "~2.2.0",
|
||||
"js-yaml": "~3.13.1",
|
||||
"jsonlint": "~1.6.2",
|
||||
"keymaster": "~1.6.2",
|
||||
"leaflet": "~1.6.0",
|
||||
"less": "~3.9.0",
|
||||
"less-loader": "~4.1.0",
|
||||
"list.js": "~1.5.0",
|
||||
"markdown-it-abbr": "~1.0.4",
|
||||
"markdown-it-container": "~2.0.0",
|
||||
"markdown-it-deflist": "~2.0.3",
|
||||
"markdown-it-footnote": "~3.0.2",
|
||||
"markdown-it-imsize": "~2.0.1",
|
||||
"markdown-it-ins": "~2.0.0",
|
||||
"markdown-it-mark": "~2.0.0",
|
||||
"markdown-it-mathjax": "~2.0.0",
|
||||
"markdown-it-regexp": "~0.4.0",
|
||||
"markdown-it-ruby": "^0.1.1",
|
||||
"markdown-it-sub": "~1.0.0",
|
||||
"markdown-it-sup": "~1.0.0",
|
||||
"markdownlint": "^0.17.0",
|
||||
"mathjax": "~2.7.5",
|
||||
"mermaid": "~8.4.8",
|
||||
"mini-css-extract-plugin": "~0.4.1",
|
||||
"mocha": "~5.2.0",
|
||||
"mock-require": "~3.0.3",
|
||||
"nyc": "~14.0.0",
|
||||
"optimize-css-assets-webpack-plugin": "~5.0.0",
|
||||
"pdfobject": "~2.1.1",
|
||||
"plantuml-encoder": "^1.2.5",
|
||||
"power-assert": "~1.6.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"raphael": "~2.2.8",
|
||||
"reveal.js": "~3.9.2",
|
||||
"script-loader": "~0.7.2",
|
||||
"select2": "~3.5.2-browserify",
|
||||
"sinon": "~7.3.2",
|
||||
"socket.io-client": "~2.2.0",
|
||||
"spin.js": "~4.0.0",
|
||||
"standard": "~13.1.0",
|
||||
"store": "~2.0.12",
|
||||
"string-loader": "~0.0.1",
|
||||
"style-loader": "~0.23.1",
|
||||
"tedious": "~6.2.0",
|
||||
"turndown": "~5.0.3",
|
||||
"typo-js": "^1.0.3",
|
||||
"uglifyjs-webpack-plugin": "~1.2.7",
|
||||
"url-loader": "~1.0.1",
|
||||
"vega": "~5.9.1",
|
||||
"vega-embed": "~6.2.2",
|
||||
"vega-lite": "~4.4.0",
|
||||
"velocity-animate": "~1.5.2",
|
||||
"visibilityjs": "~2.0.2",
|
||||
"viz.js": "~2.1.2",
|
||||
"webpack": "~4.39.0",
|
||||
"webpack-cli": "~3.3.6",
|
||||
"webpack-merge": "~4.2.1",
|
||||
"webpack-parallel-uglify-plugin": "~1.1.0"
|
||||
"webpack-parallel-uglify-plugin": "~1.1.0",
|
||||
"wurl": "~2.5.3",
|
||||
"xss": "~1.0.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "~4.0.0",
|
||||
"utf-8-validate": "~5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0 <12.0.0"
|
||||
"node": ">=10.0.0 <13.0.0"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ import { stripTags } from '../../utils/string'
|
||||
|
||||
import getUIElements from './lib/editor/ui-elements'
|
||||
import { emojifyImageDir } from './lib/editor/constants'
|
||||
import { parseFenceCodeParams, serializeParamToAttribute } from './lib/markdown/utils'
|
||||
|
||||
import markdownit from 'markdown-it'
|
||||
import markdownitContainer from 'markdown-it-container'
|
||||
@ -1028,24 +1029,30 @@ export function scrollToHash () {
|
||||
location.hash = hash
|
||||
}
|
||||
|
||||
const fenceCodeAlias = {
|
||||
sequence: 'sequence-diagram',
|
||||
flow: 'flow-chart',
|
||||
graphviz: 'graphviz',
|
||||
mermaid: 'mermaid',
|
||||
abc: 'abc',
|
||||
vega: 'vega',
|
||||
geo: 'geo'
|
||||
}
|
||||
|
||||
function highlightRender (code, lang) {
|
||||
if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
|
||||
|
||||
const params = parseFenceCodeParams(lang)
|
||||
const attr = serializeParamToAttribute(params)
|
||||
lang = lang.split(/\s+/g)[0]
|
||||
|
||||
code = escapeHTML(code)
|
||||
if (lang === 'sequence') {
|
||||
return `<div class="sequence-diagram raw">${code}</div>`
|
||||
} else if (lang === 'flow') {
|
||||
return `<div class="flow-chart raw">${code}</div>`
|
||||
} else if (lang === 'graphviz') {
|
||||
return `<div class="graphviz raw">${code}</div>`
|
||||
} else if (lang === 'mermaid') {
|
||||
return `<div class="mermaid raw">${code}</div>`
|
||||
} else if (lang === 'abc') {
|
||||
return `<div class="abc raw">${code}</div>`
|
||||
} else if (lang === 'vega') {
|
||||
return `<div class="vega raw">${code}</div>`
|
||||
} else if (lang === 'geo') {
|
||||
return `<div class="geo raw">${code}</div>`
|
||||
|
||||
const langAlias = fenceCodeAlias[lang]
|
||||
if (langAlias) {
|
||||
return `<div class="${langAlias} raw"${attr}>${code}</div>`
|
||||
}
|
||||
|
||||
const result = {
|
||||
value: code
|
||||
}
|
||||
@ -1167,7 +1174,7 @@ md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||
}
|
||||
|
||||
if (options.highlight) {
|
||||
highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
|
||||
highlighted = options.highlight(token.content, info) || md.utils.escapeHtml(token.content)
|
||||
} else {
|
||||
highlighted = md.utils.escapeHtml(token.content)
|
||||
}
|
||||
|
11
public/js/lib/common/metrics.ejs
Normal file
11
public/js/lib/common/metrics.ejs
Normal file
@ -0,0 +1,11 @@
|
||||
online_notes <%- onlineNotes %>
|
||||
online_users <%- onlineUsers %>
|
||||
distinct_online_users <%- distinctOnlineUsers %>
|
||||
notes_count <%- notesCount %>
|
||||
registered_users <%- registeredUsers %>
|
||||
online_registered_users <%- onlineRegisteredUsers %>
|
||||
distinct_online_registered_users <%- distinctOnlineRegisteredUsers %>
|
||||
is_connection_busy <%- isConnectionBusy ? 1 : 0 %>
|
||||
connection_socket_queue_length <%- connectionSocketQueueLength %>
|
||||
is_disconnect_busy <%- isDisconnectBusy ? 1: 0 %>
|
||||
disconnect_socket_queue_length <%- disconnectSocketQueueLength %>
|
53
public/js/lib/markdown/utils.js
Normal file
53
public/js/lib/markdown/utils.js
Normal file
@ -0,0 +1,53 @@
|
||||
export function parseFenceCodeParams (lang) {
|
||||
const attrMatch = lang.match(/{(.*)}/)
|
||||
const params = {}
|
||||
if (attrMatch && attrMatch.length >= 2) {
|
||||
const attrs = attrMatch[1]
|
||||
const paraMatch = attrs.match(/([#.](\S+?)\s)|((\S+?)\s*=\s*("(.+?)"|'(.+?)'|\[[^\]]*\]|\{[}]*\}|(\S+)))/g)
|
||||
paraMatch && paraMatch.forEach(param => {
|
||||
param = param.trim()
|
||||
if (param[0] === '#') {
|
||||
params['id'] = param.slice(1)
|
||||
} else if (param[0] === '.') {
|
||||
if (params['class']) params['class'] = []
|
||||
params['class'] = params['class'].concat(param.slice(1))
|
||||
} else {
|
||||
const offset = param.indexOf('=')
|
||||
const id = param.substring(0, offset).trim().toLowerCase()
|
||||
let val = param.substring(offset + 1).trim()
|
||||
const valStart = val[0]
|
||||
const valEnd = val[val.length - 1]
|
||||
if (['"', "'"].indexOf(valStart) !== -1 && ['"', "'"].indexOf(valEnd) !== -1 && valStart === valEnd) {
|
||||
val = val.substring(1, val.length - 1)
|
||||
}
|
||||
if (id === 'class') {
|
||||
if (params['class']) params['class'] = []
|
||||
params['class'] = params['class'].concat(val)
|
||||
} else {
|
||||
params[id] = val
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
export function serializeParamToAttribute (params) {
|
||||
if (Object.getOwnPropertyNames(params).length === 0) {
|
||||
return ''
|
||||
} else {
|
||||
return ` data-params="${escape(JSON.stringify(params))}"`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} elem
|
||||
*/
|
||||
export function deserializeParamAttributeFromElement (elem) {
|
||||
const params = elem.getAttribute('data-params')
|
||||
if (params) {
|
||||
return JSON.parse(unescape(params))
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
@ -73,7 +73,7 @@ md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||
}
|
||||
|
||||
if (options.highlight) {
|
||||
highlighted = options.highlight(token.content, langName) || md.utils.escapeHtml(token.content)
|
||||
highlighted = options.highlight(token.content, info) || md.utils.escapeHtml(token.content)
|
||||
} else {
|
||||
highlighted = md.utils.escapeHtml(token.content)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user