diff --git a/README.md b/README.md index 406cba3a..a232b926 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ In VNote, almost everything is configurable, such as background color, font, and - [markdown-it-front-matter](https://github.com/craigdmckenna/markdown-it-front-matter) (MIT License) - [markdown-it-imsize](https://github.com/tatsy/markdown-it-imsize) (Unknown) (Thanks @Kinka for help) - [markdown-it-emoji](https://github.com/markdown-it/markdown-it-emoji) (MIT License) +- [markdown-it-texmath](https://github.com/goessner/markdown-it-texmath) (MIT License) - [mermaid 7.0.0](https://github.com/knsv/mermaid) (MIT License) - [MathJax](https://www.mathjax.org/) (Apache-2.0) - [showdown](https://github.com/showdownjs/showdown) (Unknown) diff --git a/README_zh.md b/README_zh.md index 046d2a1b..7d424b02 100644 --- a/README_zh.md +++ b/README_zh.md @@ -200,6 +200,7 @@ VNote中,几乎一切都是可以定制的,例如背景颜色、字体以及 - [markdown-it-front-matter](https://github.com/craigdmckenna/markdown-it-front-matter) (MIT License) - [markdown-it-imsize](https://github.com/tatsy/markdown-it-imsize) (Unknown) (Thanks @Kinka for help) - [markdown-it-emoji](https://github.com/markdown-it/markdown-it-emoji) (MIT License) +- [markdown-it-texmath](https://github.com/goessner/markdown-it-texmath) (MIT License) - [mermaid 7.0.0](https://github.com/knsv/mermaid) (MIT License) - [MathJax](https://www.mathjax.org/) (Apache-2.0) - [showdown](https://github.com/showdownjs/showdown) (Unknown) diff --git a/src/resources/markdown-it.js b/src/resources/markdown-it.js index 4948cd13..b0d0f964 100644 --- a/src/resources/markdown-it.js +++ b/src/resources/markdown-it.js @@ -108,6 +108,8 @@ mdit = mdit.use(window.markdownitFootnote); mdit = mdit.use(window["markdown-it-imsize.js"]); +mdit = mdit.use(texmath, { delimiters: 'dollars' }); + var mdHasTocSection = function(markdown) { var n = markdown.search(/(\n|^)\[toc\]/i); return n != -1; @@ -150,8 +152,20 @@ var updateText = function(text) { // If you add new logics after handling MathJax, please pay attention to // finishLoading logic. if (VEnableMathjax) { + var texToRender = document.getElementsByClassName('tex-to-render'); + var nrTex = texToRender.length; + if (nrTex == 0) { + finishOneAsyncJob(); + return; + } + + var eles = []; + for (var i = 0; i < nrTex; ++i) { + eles.push(texToRender[i]); + } + try { - MathJax.Hub.Queue(["Typeset", MathJax.Hub, contentDiv, postProcessMathJax]); + MathJax.Hub.Queue(["Typeset", MathJax.Hub, eles, postProcessMathJax]); } catch (err) { content.setLog("err: " + err); finishOneAsyncJob(); @@ -196,3 +210,49 @@ var handleMetaData = function() { pre.appendChild(code); contentDiv.insertAdjacentElement('afterbegin', pre); }; + +var postProcessMathJaxWhenMathjaxReady = function() { + var all = MathJax.Hub.getAllJax(); + for (var i = 0; i < all.length; ++i) { + var node = all[i].SourceElement().parentNode; + if (VRemoveMathjaxScript) { + // Remove the SourceElement. + try { + node.removeChild(all[i].SourceElement()); + } catch (err) { + content.setLog("err: " + err); + } + } + + if (node.tagName.toLowerCase() == 'code') { + var pre = node.parentNode; + var p = document.createElement('p'); + p.innerHTML = node.innerHTML; + pre.parentNode.replaceChild(p, pre); + } + } +}; + +var handleMathjaxReady = function() { + if (!VEnableMathjax) { + return; + } + + var texToRender = document.getElementsByClassName('tex-to-render'); + var nrTex = texToRender.length; + if (nrTex == 0) { + return; + } + + var eles = []; + for (var i = 0; i < nrTex; ++i) { + eles.push(texToRender[i]); + } + + try { + MathJax.Hub.Queue(["Typeset", MathJax.Hub, eles, postProcessMathJaxWhenMathjaxReady]); + } catch (err) { + content.setLog("err: " + err); + finishOneAsyncJob(); + } +}; diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js index 25c179fe..74f543f8 100644 --- a/src/resources/markdown_template.js +++ b/src/resources/markdown_template.js @@ -92,6 +92,10 @@ if (typeof VOS == 'undefined') { VOS = 'win'; } +if (typeof handleMathjaxReady == 'undefined') { + var handleMathjaxReady = function() {}; +} + // Whether highlight special blocks like puml, flowchart. var highlightSpecialBlocks = false; @@ -1181,6 +1185,7 @@ var addClassToCodeBlock = function() { // Add the class to pre. pare.classList.add("lang-mathjax"); pare.classList.add("language-mathjax"); + pare.classList.add("tex-to-render"); } } } @@ -1268,7 +1273,11 @@ var postProcessMathJax = function() { var node = all[i].SourceElement().parentNode; if (VRemoveMathjaxScript) { // Remove the SourceElement. - node.removeChild(all[i].SourceElement()); + try { + node.removeChild(all[i].SourceElement()); + } catch (err) { + content.setLog("err: " + err); + } } if (node.tagName.toLowerCase() == 'code') { diff --git a/src/resources/mathjax_preview.js b/src/resources/mathjax_preview.js index 4efb2af5..b00faa91 100644 --- a/src/resources/mathjax_preview.js +++ b/src/resources/mathjax_preview.js @@ -43,6 +43,11 @@ var previewMathJax = function(identifier, id, timeStamp, text, isHtml) { p = htmlToElement(text); if (isEmptyMathJax(p.textContent)) { p = null; + } else if (p.tagName.toLowerCase() != 'p') { + // Need to wrap it in a

, or domtoimage won't work. + var tp = document.createElement('p'); + tp.appendChild(p); + p = tp; } } else { p = document.createElement('p'); diff --git a/src/utils/markdown-it/README.md b/src/utils/markdown-it/README.md index ac1c1e0a..c1480bae 100644 --- a/src/utils/markdown-it/README.md +++ b/src/utils/markdown-it/README.md @@ -35,3 +35,7 @@ Tatsuya Yatagawa # [markdown-it-emoji](https://github.com/markdown-it/markdown-it-emoji) v1.4.0 Vitaly Puzrin + +# [markdown-it-texmath](https://github.com/goessner/markdown-it-texmath) +v0.0.0 +Stefan Goessner diff --git a/src/utils/markdown-it/markdown-it-texmath.js b/src/utils/markdown-it/markdown-it-texmath.js new file mode 100644 index 00000000..bd259589 --- /dev/null +++ b/src/utils/markdown-it/markdown-it-texmath.js @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Stefan Goessner - 2017-18. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * Modified by Le Tan for MathJax support in VNote. + * We mark all the formulas and enclose them with $ in class 'tex-to-render' for + * further processing by MathJax. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +function texmath(md, options) { + let delimiters = options && options.delimiters || 'dollars'; + + if (delimiters in texmath.rules) { + for (let rule of texmath.rules[delimiters].inline) { + md.inline.ruler.before('escape', rule.name, texmath.inline(rule)); // ! important + md.renderer.rules[rule.name] = (tokens, idx) => rule.tmpl.replace(/\$1/,texmath.render(tokens[idx].content,false)); + } + + for (let rule of texmath.rules[delimiters].block) { + md.block.ruler.before('fence', rule.name, texmath.block(rule)); + md.renderer.rules[rule.name] = (tokens, idx) => rule.tmpl.replace(/\$1/,texmath.render(tokens[idx].content,true)); + } + } +} + +texmath.applyRule = function(rule, str, beg) { + let pre, match, post; + rule.rex.lastIndex = beg; + pre = str.startsWith(rule.tag,beg) && (!rule.pre || rule.pre(str,beg)); + match = pre && rule.rex.exec(str); + if (match) { + match.lastIndex = rule.rex.lastIndex; + post = !rule.post || rule.post(str, match.lastIndex-1); + } + rule.rex.lastIndex = 0; + return post && match; +} + +texmath.inline = (rule) => + function(state, silent) { + let res = texmath.applyRule(rule, state.src, state.pos); + if (res) { + if (!silent) { + let token = state.push(rule.name, 'math', 0); + token.content = res[1]; // group 1 from regex .. + token.markup = rule.tag; + } + state.pos = res.lastIndex; + } + return !!res; + } + +texmath.block = (rule) => + function(state, begLine, endLine, silent) { + let res = texmath.applyRule(rule, state.src, state.bMarks[begLine] + state.tShift[begLine]); + if (res) { + if (!silent) { + let token = state.push(rule.name, 'math', 0); + token.block = true; + token.content = res[1]; + token.markup = rule.tag; + } + for (let line=begLine, endpos=res.lastIndex-1; line < endLine; line++) + if (endpos >= state.bMarks[line] && endpos <= state.eMarks[line]) { // line for end of block math found ... + state.line = line+1; + break; + } + state.pos = res.lastIndex; + } + return !!res; + } + +texmath.render = function(tex, isblock) { + let res; + if (isblock) { + res = '$$$$' + tex + '$$$$'; + } else { + res = '$$' + tex + '$$'; + } + + return res; +} + +texmath.$_pre = (str,beg) => { + let prv = beg > 0 ? str[beg-1].charCodeAt(0) : false; + return !prv || prv !== 0x5c // no backslash, + && (prv < 0x30 || prv > 0x39); // no decimal digit .. before opening '$' +} +texmath.$_post = (str,end) => { + let nxt = str[end+1] && str[end+1].charCodeAt(0); + return !nxt || nxt < 0x30 || nxt > 0x39; // no decimal digit .. after closing '$' +} + +texmath.rules = { + brackets: { + inline: [ + { name: 'math_inline', + rex: /\\\((.+?)\\\)/gy, + tmpl: '$1', + tag: '\\(' + } + ], + block: [ + { name: 'math_block', + rex: /\\\[(.+?)\\\]/gmy, + tmpl: '$1', + tag: '\\[' + } + ] + }, + gitlab: { + inline: [ + { name: 'math_inline', + rex: /\$`(.+?)`\$/gy, + tmpl: '$1', + tag: '$`' + } + ], + block: [ + { name: 'math_block', + rex: /`{3}math\s+?([^`]+?)\s+?`{3}/gmy, + tmpl: '$1', + tag: '```math' + } + ] + }, + dollars: { + inline: [ + { name: 'math_inline', + rex: /\$(\S[^$\r\n]*?[^\s\\]{1}?)\$/gy, + tmpl: '$1', + tag: '$', + pre: texmath.$_pre, + post: texmath.$_post + }, + { name: 'math_single', + rex: /\$([^$\s\\]{1}?)\$/gy, + tmpl: '$1', + tag: '$', + pre: texmath.$_pre, + post: texmath.$_post + } + ], + block: [ + { name: 'math_block', + rex: /\${2}([^$]*?)\${2}/gmy, + tmpl: '$1', + tag: '$$' + } + ] + }, +}; + +if (typeof module === "object" && module.exports) + module.exports = texmath; diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 8e3c789d..04a896c0 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -639,6 +639,8 @@ QString VUtils::generateHtmlTemplate(const QString &p_template, bool p_wkhtmltopdf, bool p_addToc) { + bool mathjaxTypeSetOnLoad = true; + QString jsFile, extraFile; switch (p_conType) { case MarkdownConverterType::Marked: @@ -659,7 +661,8 @@ QString VUtils::generateHtmlTemplate(const QString &p_template, "\n" + "\n" + "\n" + - "\n"; + "\n" + + "\n"; const MarkdownitOption &opt = g_config->getMarkdownitOption(); @@ -696,6 +699,8 @@ QString VUtils::generateHtmlTemplate(const QString &p_template, .arg(opt.m_metadata ? QStringLiteral("true") : QStringLiteral("false")) .arg(opt.m_emoji ? QStringLiteral("true") : QStringLiteral("false")); extraFile += optJs; + + mathjaxTypeSetOnLoad = false; break; } @@ -734,10 +739,12 @@ QString VUtils::generateHtmlTemplate(const QString &p_template, extraFile += "\n" "\n" + "\n"; diff --git a/src/vnote.cpp b/src/vnote.cpp index f2dacb5d..8a240c9c 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -42,6 +42,7 @@ const QString VNote::c_markdownitFootnoteExtraFile = ":/utils/markdown-it/markdo const QString VNote::c_markdownitFrontMatterExtraFile = ":/utils/markdown-it/markdown-it-front-matter.js"; const QString VNote::c_markdownitImsizeExtraFile = ":/utils/markdown-it/markdown-it-imsize.min.js"; const QString VNote::c_markdownitEmojiExtraFile = ":/utils/markdown-it/markdown-it-emoji.min.js"; +const QString VNote::c_markdownitTexMathExtraFile = ":/utils/markdown-it/markdown-it-texmath.js"; const QString VNote::c_showdownJsFile = ":/resources/showdown.js"; const QString VNote::c_showdownExtraFile = ":/utils/showdown/showdown.min.js"; diff --git a/src/vnote.h b/src/vnote.h index 43cc1f0b..3f1f1f53 100644 --- a/src/vnote.h +++ b/src/vnote.h @@ -53,6 +53,7 @@ public: static const QString c_markdownitFrontMatterExtraFile; static const QString c_markdownitImsizeExtraFile; static const QString c_markdownitEmojiExtraFile; + static const QString c_markdownitTexMathExtraFile; // Showdown static const QString c_showdownJsFile; diff --git a/src/vnote.qrc b/src/vnote.qrc index 4d47895c..5b592c8b 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -265,5 +265,6 @@ resources/view_image.css resources/icons/decrease_outline_level.svg resources/icons/increase_outline_level.svg + utils/markdown-it/markdown-it-texmath.js