marked集成katex数学公式展示
简要介绍
利用marked,生成预览的过程中,缺少数学公式的支持。目前(2021.07)对数学公式支持比较好的库是katex。
以下实现主要参考两个地方:
实现原理:拦截marked的Renderer实现,然后调用katex.renderToString方法实现。
注意点:
- 拦截时,需要保存原来的实现类。见:
const unchanged = new marked.Renderer()
- marked默认做了html encode,需要转一下,见方法:
htmldecode()
- markdown的写法上
- 使用`
$E=mc^2$
`表示嵌入行内 - 使用`
$$E=mc^2$$
`表示在新段落中显示。
- 使用`
源代码示例
JavaScript部分(文件名:marked_with_katex.js)
// ============= add katex support =====================
// refer: https://github.com/markedjs/marked/issues/1538
// =====================================================
const renderer = new marked.Renderer();
var options = {
renderer: renderer,
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
},
pedantic: false,
gfm: true,
breaks: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false
};
marked.setOptions(options);
function htmldecode(text){
var temp = document.createElement("div");
temp.innerHTML = text;
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
function mathsExpression(expr) {
if (expr.match(/^\$\$[\s\S]*\$\$$/)) {
expr = expr.substr(2, expr.length - 4);
return katex.renderToString(expr, { displayMode: true });
} else if (expr.match(/^\$[\s\S]*\$$/)) {
expr = htmldecode(expr); // temp solution
expr = expr.substr(1, expr.length - 2);
return katex.renderToString(expr, { displayMode: false });
}
}
const unchanged = new marked.Renderer()
renderer.code = function(code, lang, escaped) {
if (!lang) {
const math = mathsExpression(code);
if (math) {
return math;
}
}
return unchanged.code(code, lang, escaped);
};
renderer.codespan = function(text) {
const math = mathsExpression(text);
if (math) {
return math;
}
return unchanged.codespan(text);
};
function markedWithKatex(text) {
return marked(text, options);
}
//const md = "hello```\n$$c=\sqrt{a^2 + b^2}$$\n```";
//console.log(marked(md, { renderer: renderer }));
完整的html(文件名demo2.html)
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Marked in the browser</title>
<link rel="stylesheet" href="./demo.css" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/marked@2.1.3/marked.min.js"></script>
<link rel="stylesheet"
href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.0.1/build/styles/default.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.0.1/build/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" crossorigin="anonymous"></script>
<script src="marked_with_katex.js"></script>
</head>
<body>
<div id="main">
<div class="containers">
<div class="container">
<div class="label">Markdown <input id="button" type="button" value="Click" > </div>
<textarea id="markdown" class="inputPane">
markdown
</textarea>
</div>
<div class="container">
<div class="label">
<select id="outputType">
<option value="preview">Preview</option>
<option value="html">HTML Source</option>
<option value="lexer">Lexer Data</option>
</select>
</div>
<div id="preview" class="pane">
<iframe src="preview.html" frameborder="0" sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts"></iframe>
</div>
<!--
<textarea id="html" class="pane" readonly="readonly"></textarea>
<textarea id="lexer" class="pane" readonly="readonly"></textarea>
<textarea id="quickref" class="pane" readonly="readonly"></textarea>
-->
</div> <!-- container -->
</div> <!-- containers -->
</div> <!-- main -->
<script>
var previewIframe = document.querySelector('#preview iframe');
var markdownElem = document.querySelector('#markdown');
var delayTime = 1000;
var inputDirty = true;
function handleInput() {
inputDirty = true;
}
markdownElem.addEventListener('change', handleInput, false);
markdownElem.addEventListener('keyup', handleInput, false);
markdownElem.addEventListener('keypress', handleInput, false);
markdownElem.addEventListener('keydown', handleInput, false);
function doParse() {
if(inputDirty) {
inputDirty = false;
previewIframe.contentDocument.body.innerHTML = markedWithKatex(markdownElem.value);
}
checkChangeTimeout = window.setTimeout(doParse, delayTime);
}
var btn = document.getElementById("button");
btn.onclick = function(){
doParse();
}
window.onload=function(){
doParse();
}
</script>
</body>
</html>
iframe预览部分(文件名:preview.html)
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>marked preview</title>
<link rel="stylesheet" href="./demo.css" type="text/css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css">
<link rel="stylesheet"
href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.0.1/build/styles/default.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.0.1/build/highlight.min.js"></script>
</head>
<body>
hello marked
</body
</html>
css部分(仅供参考)(文件名:demo.css)
html, body {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
background-color: #fbfbfb;
height: 100%;
}
textarea {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 12px;
resize: none;
}
header {
padding-top: 10px;
display: flex;
height: 58px;
}
header h1 {
margin: 0;
}
.containers {
display: flex;
height: calc(100vh - 68px);
}
.container {
flex-basis: 50%;
padding: 5px;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
}
.pane, .inputPane {
margin-top: 5px;
padding: 0.6em;
border: 1px solid #ccc;
overflow: auto;
flex-grow: 1;
flex-shrink: 1;
}
#preview {
display: flex;
}
#preview iframe {
flex-grow: 1;
}
.error {
border-color: red;
background-color: #FEE
}