Browse Source

Use syzoj-renderer to render markdown, math and highlight

pull/6/head
Menci 6 years ago
parent
commit
3f062215c3
  1. 43
      libs/highlight.js
  2. 171
      libs/markdown.js
  3. 5
      package.json

43
libs/highlight.js

@ -1,44 +1,5 @@
/* const { highlight } = require('syzoj-renderer');
* This file is part of moemark-renderer.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* moemark-renderer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* moemark-renderer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with moemark-renderer. If not, see <http://www.gnu.org/licenses/>.
*/
const pygmentize = require('pygmentize-bundled-cached');
function escapeHTML(s) {
// Code from http://stackoverflow.com/questions/5251520/how-do-i-escape-some-html-in-javascript/5251551
return s.replace(/[^0-9A-Za-z ]/g, (c) => {
return "&#" + c.charCodeAt(0) + ";";
});
}
module.exports = (code, lang, cb) => { module.exports = (code, lang, cb) => {
pygmentize({ highlight(code, lang).then(cb);
lang: lang,
format: 'html',
options: {
nowrap: true,
classprefix: 'pl-'
}
}, code, (err, res) => {
if (err || res.toString() === 'undefined') {
cb(escapeHTML(code));
} else {
cb(res);
}
});
} }

171
libs/markdown.js

@ -1,27 +1,26 @@
/* const { markdown } = require('syzoj-renderer');
* This file is part of moemark-renderer. const XSS = require('xss');
* const CSSFilter = require('cssfilter');
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com> const xssWhiteList = Object.assign({}, require('xss/lib/default').whiteList);
*
* moemark-renderer is free software: you can redistribute it and/or modify delete xssWhiteList.audio;
* it under the terms of the GNU General Public License as published by delete xssWhiteList.video;
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. for (const tag in xssWhiteList) {
* xssWhiteList[tag] = xssWhiteList[tag].concat(['style', 'class']);
* moemark-renderer is distributed in the hope that it will be useful, }
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the const xss = new XSS.FilterXSS({
* GNU General Public License for more details. whiteList: xssWhiteList,
* stripIgnoreTag: true,
* You should have received a copy of the GNU General Public License onTagAttr: (tag, name, value, isWhiteAttr) => {
* along with moemark-renderer. If not, see <http://www.gnu.org/licenses/>. if (tag.toLowerCase() === 'img' && name.toLowerCase() === 'src' && value.startsWith('data:image/')) {
*/ return name + '="' + XSS.escapeAttrValue(value) + '"';
}
const MoeMark = require('moemark'); }
const katex = require('katex'); });
const mj = require('mathjax-node');
let defaultCache = { const defaultCache = {
data: {}, data: {},
get(key) { get(key) {
return this.data[key]; return this.data[key];
@ -31,124 +30,14 @@ let defaultCache = {
} }
}; };
let config = { function filter(html) {
highlight: require('./highlight') html = xss.process(html);
}; if (html) {
html = `<div style="position: relative; overflow: hidden; ">${html}</div>`;
let uuid = require('uuid'); }
return html;
function render(s, cb) {
if (!s.trim()) return cb('');
let mathCnt = 0, mathPending = 0, maths = new Array(), mathID = new Array(), hlCnt = 0, hlPending = 0, hls = new Array(), hlID = new Array(), res, callback, ss, cache = render.cache, cacheOption = render.cacheOption, finished = false;
if (cacheOption.result) {
let x = cache.get('RES_' + s);
if (x !== undefined) return cb(x);
}
MoeMark.setOptions({
lineNumber: false,
math: true,
highlight: function(code, lang) {
if (cacheOption.highlight) {
let x = cache.get('H_' + lang + '_' + code);
if (x !== undefined) return x;
}
let id = hlCnt;
hlCnt++, hlPending++;
config.highlight(code, lang, res => {
hls[id] = res;
if (cacheOption.highlight) cache.set('H_' + lang + '_' + code, res);
if (!--hlPending) finish();
});
return hlID[id] = uuid();
},
mathRenderer: function(str, display) {
let mathFinish = (error, result) => {
if (error) maths[id] = '<p><div style="display: inline-block; border: 1px solid #000; "><strong>' + error.toString() + '</strong></div></p>';
else if (display) maths[id] = '<p style="text-align: center; ">' + result + '</p>';
else maths[id] = result;
if (cacheOption.math) cache.set('M_' + display + '_' + str, maths[id]);
if (!--mathPending) finish();
}
const id = mathCnt;
mathCnt++, mathPending++;
try {
let x = cache.get('M_' + display + '_' + str);
if (x !== undefined) process.nextTick(() => mathFinish(null, x));
else {
let res = katex.renderToString(str, { displayMode: display });
process.nextTick(() => mathFinish(null, '<span style="zoom: 1.01; ">' + res + '</span>'));
}
} catch (e) {
mj.typeset({
math: str,
format: display ? 'TeX' : 'inline-TeX',
svg: true,
width: 0
}, data => {
mathFinish(data.errors, data.svg);
});
}
return mathID[id] = uuid();
}
});
function finish() {
if (finished || !res || mathPending || hlPending) return;
finished = true;
if (maths.length || hls.length) {
for (let i = 0; i < maths.length; i++) {
res = res.replace(mathID[i], maths[i]);
}
for (let i = 0; i < hls.length; i++) {
res = res.replace(hlID[i], hls[i]);
}
}
if (cacheOption.result) cache.set('RES_' + s, res);
cb(res);
}
try {
let XSS = require('xss');
let CSSFilter = require('cssfilter');
let whiteList = Object.assign({}, require('xss/lib/default').whiteList);
delete whiteList.audio;
delete whiteList.video;
for (let tag in whiteList) whiteList[tag] = whiteList[tag].concat(['style', 'class']);
let xss = new XSS.FilterXSS({
whiteList: whiteList,
stripIgnoreTag: true,
onTagAttr: (tag, name, value, isWhiteAttr) => {
if (tag.toLowerCase() === 'img' && name.toLowerCase() === 'src' && value.startsWith('data:image/')) return name + '="' + XSS.escapeAttrValue(value) + '"';
}
});
let replaceXSS = s => {
s = xss.process(s);
if (s) {
s = `<div style="position: relative; overflow: hidden; ">${s}</div>`;
}
return s;
};
res = replaceXSS(MoeMark(s));
if (mathPending == 0 && hlPending == 0) {
finish();
}
} catch(e) {
cb(e);
}
}; };
render.moemark = MoeMark; module.exports = (markdownCode, callback) => {
render.cache = defaultCache; markdown(markdownCode, defaultCache, filter).then(callback);
render.cacheOption = {
highlight: true,
math: true,
result: false
}; };
render.config = config;
module.exports = render;

5
package.json

@ -42,17 +42,13 @@
"js-yaml": "^3.9.0", "js-yaml": "^3.9.0",
"jsondiffpatch": "0.2.5", "jsondiffpatch": "0.2.5",
"jsonwebtoken": "^8.4.0", "jsonwebtoken": "^8.4.0",
"katex": "^0.10.1",
"mariadb": "^2.0.2-rc", "mariadb": "^2.0.2-rc",
"mathjax-node": "^2.1.1",
"moemark": "^0.3.10",
"moment": "^2.24.0", "moment": "^2.24.0",
"msgpack-lite": "^0.1.26", "msgpack-lite": "^0.1.26",
"multer": "^1.2.0", "multer": "^1.2.0",
"node-7z": "^0.4.0", "node-7z": "^0.4.0",
"nodemailer": "^4.7.0", "nodemailer": "^4.7.0",
"object-assign-deep": "^0.4.0", "object-assign-deep": "^0.4.0",
"pygmentize-bundled-cached": "^1.1.0",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"request": "^2.74.0", "request": "^2.74.0",
"request-promise": "^4.1.1", "request-promise": "^4.1.1",
@ -63,6 +59,7 @@
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
"stream-to-string": "^1.1.0", "stream-to-string": "^1.1.0",
"syzoj-divine": "^1.0.2", "syzoj-divine": "^1.0.2",
"syzoj-renderer": "syzoj/syzoj-renderer",
"tempfile": "^2.0.0", "tempfile": "^2.0.0",
"tmp-promise": "^1.0.3", "tmp-promise": "^1.0.3",
"waliyun": "^3.1.1", "waliyun": "^3.1.1",

Loading…
Cancel
Save