forgeplus-react/src/common/marked.js

200 lines
4.6 KiB
JavaScript

import marked from 'marked'
import { escape, rtrim } from 'marked/src/helpers'
import { renderToString } from 'katex'
function unescape(str) {
str = str
.replace(/( |\u00a0| )/g, '')
.replace(/>/g, '>')
.replace(/&lt;/g, '<')
.replace(/\\$/g, '')
.replace(/^\\(?:{)/, '\\\\{')
if (!str.match(/\S/)) {
return '';
}
return str
}
function toKatex(str) {
return renderToString(unescape(str), {
throwOnError: false
})
}
function indentCodeCompensation(raw, text) {
const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
if (matchIndentToCode === null) {
return text;
}
const indentToCode = matchIndentToCode[1];
return text
.split('\n')
.map(node => {
const matchIndentInNode = node.match(/^\s+/);
if (matchIndentInNode === null) {
return node;
}
const [indentInNode] = matchIndentInNode;
if (indentInNode.length >= indentToCode.length) {
return node.slice(indentToCode.length);
}
return node;
})
.join('\n');
}
const latexRegex = /\`?\${2}([^\$\n]+?)\${2}\`?/g
//兼容之前的 ##标题式写法
const headingRegex = /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/
let toc = []
let ctx = ["<ul>"]
export function cleanToc() {
toc = []
ctx = ["<ul>"]
}
function buildToc(coll, k, level, ctx) {
if (k >= coll.length || coll[k].level <= level) { return k }
var node = coll[k]
ctx.push("<li><a href='#" + node.anchor + "'>" + node.text + "</a>")
k++
var childCtx = []
k = buildToc(coll, k, node.level, childCtx)
if (childCtx.length > 0) {
ctx.push("<ul>")
childCtx.forEach(function (idm) {
ctx.push(idm)
});
ctx.push("</ul>")
}
ctx.push("</li>")
k = buildToc(coll, k, level, ctx)
return k
}
export function getTocContent() {
buildToc(toc, 0, 0, ctx)
ctx.push("</ul>")
return ctx.join("")
}
const tokenizer = {
heading(src) {
const cap = headingRegex.exec(src)
if (cap) {
return {
type: 'heading',
raw: cap[0],
depth: cap[1].length,
text: cap[2]
}
}
},
paragraph(src) {
const cap = this.rules.block.paragraph.exec(src)
let text = cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
let match = text.match(latexRegex)
if (match) {
text = text.replace(latexRegex, (_, $1) => {
return toKatex($1)
})
}
if (cap) {
return {
type: 'paragraph',
raw: cap[0],
text
};
}
},
code(src, tokens) {
const cap = this.rules.block.code.exec(src)
if (cap) {
const lastToken = tokens[tokens.length - 1];
// An indented code block cannot interrupt a paragraph.
if (lastToken && lastToken.type === 'paragraph') {
return {
raw: cap[0],
text: cap[0].trimRight()
}
}
let text = cap[0].replace(/^ {4}/gm, '')
text = !this.options.pedantic ? rtrim(text, '\n') : text
let match = text.match(latexRegex)
if (match) {
text = text.replace(latexRegex, (_, $1) => {
return toKatex($1)
})
}
return {
type: 'code',
raw: cap[0],
codeBlockStyle: 'indented',
text
}
}
},
fences(src) {
const cap = this.rules.block.fences.exec(src)
if (cap) {
const raw = cap[0]
let text = indentCodeCompensation(raw, cap[3] || '')
const lang = cap[2] ? cap[2].trim() : cap[2]
if (['latex', 'katex', 'math'].indexOf(lang) >= 0) {
text = toKatex(text)
}
return {
type: 'code',
raw,
lang,
text
}
}
}
}
const renderer = {
code(code, infostring, escaped) {
const lang = (infostring || '').match(/\S*/)[0];
if (!lang) {
return '<pre class="prettyprint linenums"><code>'
+ (escaped ? code : escape(code, true))
+ '</code></pre>';
}
if (['latex', 'katex', 'math'].indexOf(lang) >= 0) {
return `<p class='editormd-tex'>${code}</p>`
} else {
return `<pre class="prettyprint linenums"><code class="language-${infostring}">${escaped ? code : escape(code, true)}</code></pre>\n`
}
},
heading(text, level, raw, slugger) {
let anchor = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w\\u4e00-\\u9fa5]]+/g, '-');
toc.push({
anchor: anchor,
level: level,
text: text
})
return '<h' + level + ' id="' + anchor + '">' + text + '</h' + level + '>'
}
}
marked.setOptions({
silent: true,
smartypants: true,
gfm: true,
pedantic: false
})
marked.use({ tokenizer, renderer });
export default marked