Browse Source

markdown-it: use markdown-it-texmath to get rid of escape of MathJax

Le Tan 7 years ago
parent
commit
35be1516ed

+ 1 - 0
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)

+ 1 - 0
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)

+ 61 - 1
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();
+    }
+};

+ 10 - 1
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') {

+ 5 - 0
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 <p>, or domtoimage won't work.
+            var tp = document.createElement('p');
+            tp.appendChild(p);
+            p = tp;
         }
     } else {
         p = document.createElement('p');

+ 4 - 0
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

+ 155 - 0
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: '<x-eq class="tex-to-render">$1</x-eq>',
+                tag: '\\('
+            }
+        ],
+        block: [
+            {   name: 'math_block',
+                rex: /\\\[(.+?)\\\]/gmy,
+                tmpl: '<x-eqn class="tex-to-render">$1</x-eqn>',
+                tag: '\\['
+            }
+        ]
+    },
+    gitlab: {
+        inline: [
+            {   name: 'math_inline',
+                rex: /\$`(.+?)`\$/gy,
+                tmpl: '<x-eq class="tex-to-render">$1</x-eq>',
+                tag: '$`'
+            }
+        ],
+        block: [
+            {   name: 'math_block',
+                rex: /`{3}math\s+?([^`]+?)\s+?`{3}/gmy,
+                tmpl: '<x-eqn class="tex-to-render">$1</x-eqn>',
+                tag: '```math'
+            }
+        ]
+    },
+    dollars: {
+        inline: [
+            {   name: 'math_inline',
+                rex: /\$(\S[^$\r\n]*?[^\s\\]{1}?)\$/gy,
+                tmpl: '<x-eq class="tex-to-render">$1</x-eq>',
+                tag: '$',
+                pre: texmath.$_pre,
+                post: texmath.$_post
+            },
+            {   name: 'math_single',
+                rex: /\$([^$\s\\]{1}?)\$/gy,
+                tmpl: '<x-eq class="tex-to-render">$1</x-eq>',
+                tag: '$',
+                pre: texmath.$_pre,
+                post: texmath.$_post
+            }
+        ],
+        block: [
+            {   name: 'math_block',
+                rex: /\${2}([^$]*?)\${2}/gmy,
+                tmpl: '<x-eqn class="tex-to-render">$1</x-eqn>',
+                tag: '$$'
+            }
+        ]
+    },
+};
+
+if (typeof module === "object" && module.exports)
+   module.exports = texmath;

+ 10 - 3
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,
                     "<script src=\"qrc" + VNote::c_markdownitAnchorExtraFile + "\"></script>\n" +
                     "<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n" +
                     "<script src=\"qrc" + VNote::c_markdownitImsizeExtraFile + "\"></script>\n" +
-                    "<script src=\"qrc" + VNote::c_markdownitFootnoteExtraFile + "\"></script>\n";
+                    "<script src=\"qrc" + VNote::c_markdownitFootnoteExtraFile + "\"></script>\n" +
+                    "<script src=\"qrc" + VNote::c_markdownitTexMathExtraFile + "\"></script>\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 += "<script type=\"text/x-mathjax-config\">"
                      "MathJax.Hub.Config({\n"
                      "                    tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n"
-                                                   "processEscapes: true,\n"
-                                                   "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
+                                                    "processEscapes: true,\n"
+                                                    "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
                      "                    showProcessingMessages: false,\n"
+                     "                    skipStartupTypeset: " + QString("%1,\n").arg(mathjaxTypeSetOnLoad ? "false" : "true") +
                      "                    messageStyle: \"none\"});\n"
+                     "MathJax.Hub.Register.StartupHook(\"End\", function() { handleMathjaxReady(); });\n"
                      "</script>\n"
                      "<script type=\"text/javascript\" async src=\"" + mj + "\"></script>\n" +
                      "<script>var VEnableMathjax = true;</script>\n";

+ 1 - 0
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";

+ 1 - 0
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;

+ 1 - 0
src/vnote.qrc

@@ -265,5 +265,6 @@
         <file>resources/view_image.css</file>
         <file>resources/icons/decrease_outline_level.svg</file>
         <file>resources/icons/increase_outline_level.svg</file>
+        <file>utils/markdown-it/markdown-it-texmath.js</file>
     </qresource>
 </RCC>