benweet / stackedit

In-browser Markdown editor

Home Page:https://stackedit.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Improved mathjax process: add the theorem environments and beautify HTML code preview

vanabel opened this issue · comments

I have add the following code as an extension of mathjax.

  • Extension of Theorem environments
    You can add the following code to UserCustom extension to obtain the following function:
    thm
   userCustom.onPagedownConfigure = function(editor) {
        editor.hooks.chain("onPreviewRefresh", function() {
            MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]);
        });
    };

userCustom.onPagedownConfigure = function (editor) {
    var converter = editor.getConverter();
    var preConversion = converter.hooks.preConversion;
    var array = {
        "thm": "Theorem",
        "lem": "Lemmma",
        "cor": "Corollary",
        "prop": "Property",
        "defn": "Definition",
        "rem": "Remark",
        "prob": "Problem",
        "excs": "Exercise",
        "examp": "Example",
        "proof": "Proof"
    };
    converter.hooks.preConversion = function (text) {
        text = text.replace(/\\begin{(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof)}\n*([\s\S]*?)\\end{\1}/g, function (wholeMatch, m1, m2) {
            return '<' + m1 + '>' + m2 + '</' + m1 + '>';
        });
        text = text.replace(/\\ref{(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof):([\s\S]*?)}/g, function (wholeMatch, m1, m2) {
            return '<a class="latex_ref" href="#' + m1 + ':' + m2 + '">' + array[m1] + ' ' + m2 + '</a><span class="latex_ref_nodisplay">' + m1 + ':' + m2 + '</span>';
        });
        text = text.replace(/\\(section|subsection|subsubsection|title|author|date){([\s\S]*?)}/g, function (wholeMatch, m1, m2) {
            if (m1 == 'section') {
                return '###' + m2;
            } else if (m1 == 'subsection') {
                return '####' + m2;
            } else if (m1 == 'subsubsection') {
                return '#####' + m2;
            } else if (m1 == 'title') {
                return '##' + m2;
            } else if (m1 == 'author') {
                return '<div class="latex_author">' + m2 + '</div>';
            } else if (m1 == 'date') {
                return '<div class="latex_date">' + m2 + '</div>';
            }
        });
        return preConversion(text);
    };
    converter.hooks.chain("preBlockGamut", function (text, blockGamutHookCallback) {

        var num_thm = 0;
        var num_excs = 0;

        return text.replace(/<(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof)>([\s\S]*?)<\/\1>/g, function (wholeMatch, m1, m2) {
            var strreturn;
            if (m1 == "proof") {
                strreturn = '<div class="latex_proof"><span class="latex_strong"><strong>' + array[m1] + '.</strong></span>' + m2 + '<span class="latex_proofend" style="float:right">□</span></div>';
                strreturn = blockGamutHookCallback(strreturn);
            } else if (m1 == "excs" | m1 == "examp" | m1 == "prob") {
                strreturn = '<a  class="latex_link"  id="' + m1 + ':' + ++num_excs + '"></a>\n<div class="latex_' + m1 + '"><span class="latex_strong"><strong>' + array[m1] + ' ' + num_excs + '. </strong></span>' + m2 + '</div>';
                strreturn = blockGamutHookCallback(strreturn);
            } else {
                strreturn = '<a  class="latex_link"  id="' + m1 + ':' + ++num_thm + '"></a>\n<div class="latex_' + m1 + '"><span class="latex_strong"><strong>' + array[m1] + ' ' + num_thm + '. </strong></span>' + m2 + '</div>';
                strreturn = blockGamutHookCallback(strreturn);
            }
            return strreturn;
        });
    });
};

userCustom.onReady = function () {
    $("head")
        .append($(
            '<style type="text/css">\n@import url("https://yandex.st/highlightjs/7.3/styles/solarized_light.min.css");\n\
        .latex_ref_nodisplay{display:none;}\n\
        .latex_thm, .latex_lem, .latex_cor, .latex_defn, .latex_prop, .latex_rem{\n\
        border:solid 1px #ccc;\n\
        font-style:normal;\n\
        margin:15px 0;\n\
        padding:5px;\n\
        background: lightcyan;\n\
        border: solid 3px green;\n\
        -moz-border-radius: 1.0em;\n\
        -webkit-border-radius: 7px;\n\
        box-shadow: 0 0 0 green;\n\
        }\n\
        .latex_prob, .latex_examp, .latex_excs {\
        font-style:normal;\n\
        margin:10px 0;\n\
        padding:5px;\n\
        background: lightgoldenrodyellow;\n\
        border: solid 3px rgb(255, 203, 136);\n\
        -moz-border-radius: 1.0em; \n\
        -webkit-border-radius: 7px; \n\
        box-shadow: 0 0 0 green;\n\
        }\n\
</style>'));
};
  • Beautify the HTML code of preview
    You can make the output HTML code almost the same as it should be in a TeX code, this is quite useful when you use stackedit as a mid-step (as an WYSIWYG editor) , but finally you want to obtain a tex code. add the following to Button HTML code
<% var output=$ ( "<div>").html(documentHTML); 
output.find( ".MathJax, .MathJax_SVG_Display, .MathJax_Display, .MathJax_SVG, .MathJax_Preview, .latex_link, .latex_ref, .latex_strong, .latex_proofend").remove(); 
output.find( 'script[type="math/tex"]').each(function() { $(this).replaceWith( '$' + this.innerHTML
+ '$'); }); 
output.find( 'script[type="math/tex; mode=display"]').each(function() { $(this).replaceWith( '$$' + this.innerHTML + '$$'); }); 
output.find( 'h2').each(function() { $(this).replaceWith( '\\title{' + this.innerHTML + '}'); }); 
output.find( 'h3').each(function() { $(this).replaceWith( '\\section{' + this.innerHTML + '}'); }); 
output.find( 'h4').each(function() { $(this).replaceWith( '\\subsection{' + this.innerHTML + '}'); }); 
output.find( 'h5').each(function() { $(this).replaceWith( '\\subsubsection{' + this.innerHTML + '}'); }); 
output.find( 'p').each(function() { $(this).replaceWith(  this.innerHTML); }); 
output.find( ".latex_author").each(function() { $(this).replaceWith( '\\author{'+ this.innerHTML+'}'); }); 
output.find( ".latex_date").each(function() { $(this).replaceWith( '\\date{'+ this.innerHTML+'}'); }); 
output.find( ".latex_ref").each(function() { $(this).replaceWith( '\\ref{' +this.innerHTML+'}'); }); 
output.find( ".latex_ref_nodisplay").each(function() { $(this).replaceWith( '\\ref{' +this.innerHTML+'}'); }); 
output.find( ".latex_thm").each(function() { $(this).replaceWith( '\\begin{thm}' +this.innerHTML+'\\end{thm}'); }); 
output.find( ".latex_lem").each(function() { $(this).replaceWith( '\\begin{lem}' +this.innerHTML+'\\end{lem}'); }); 
output.find( ".latex_prop").each(function() { $(this).replaceWith( '\\begin{prop}' +this.innerHTML+'\\end{prop}'); }); 
output.find( ".latex_rem").each(function() { $(this).replaceWith( '\\begin{rem}' +this.innerHTML+'\\end{rem}'); }); 
output.find( ".latex_cor").each(function() { $(this).replaceWith( '\\begin{cor}' +this.innerHTML+'\\end{cor}'); }); 
output.find( ".latex_defn").each(function() { $(this).replaceWith( '\\begin{defn}' +this.innerHTML+'\\end{defn}'); }); 
output.find( ".latex_prob").each(function() { $(this).replaceWith( '\\begin{prob}' +this.innerHTML+'\\end{prob}'); }); 
output.find( ".latex_examp").each(function() { $(this).replaceWith( '\\begin{examp}' +this.innerHTML+'\\end{examp}'); }); 
output.find( ".latex_excs").each(function() { $(this).replaceWith( '\\begin{excs}' +this.innerHTML+'\\end{excs}'); }); 
output.find( ".latex_proof").each(function() { $(this).replaceWith( '\\begin{proof}' +this.innerHTML+'\\end{proof}'); });  
var str=output.html();
output.html(str.replace(/\n+/gi, '\n')
.replace(/\\begin{(.*)?}(\[.*\])?(.*)\\end{\1}/, '\\begin{$1}$2\n$3\n\\end{$1}\n'));
print(output.html());
%>

I need guys to help me on complete this kind of functions.

@vanabel Thank you for sharing it. This could become an awesome extension. I think that mathjax never included the amsthm package because this is something a text editor should provide. Adding something like this to this editor seems logical to me.

I think you have one m too much here: "lem": "Lemmma",

This is more like the styling latex has:

userCustom.onPagedownConfigure = function(editor) {
        editor.hooks.chain("onPreviewRefresh", function() {
            MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]);
        });
    };

userCustom.onPagedownConfigure = function (editor) {
    var converter = editor.getConverter();
    var preConversion = converter.hooks.preConversion;
    var array = {
        "thm": "Theorem",
        "lem": "Lemma",
        "cor": "Corollary",
        "prop": "Property",
        "defn": "Definition",
        "rem": "Remark",
        "prob": "Problem",
        "excs": "Exercise",
        "examp": "Example",
        "proof": "Proof"
    };
    converter.hooks.preConversion = function (text) {
        text = text.replace(/\\begin{(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof)}\n*([\s\S]*?)\\end{\1}/g, function (wholeMatch, m1, m2) {
            return '<' + m1 + '>' + m2 + '</' + m1 + '>';
        });
        text = text.replace(/\\ref{(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof):([\s\S]*?)}/g, function (wholeMatch, m1, m2) {
            return '<a class="latex_ref" href="#' + m1 + ':' + m2 + '">' + array[m1] + ' ' + m2 + '</a><span class="latex_ref_nodisplay">' + m1 + ':' + m2 + '</span>';
        });
        text = text.replace(/\\(section|subsection|subsubsection|title|author|date){([\s\S]*?)}/g, function (wholeMatch, m1, m2) {
            if (m1 == 'section') {
                return '###' + m2;
            } else if (m1 == 'subsection') {
                return '####' + m2;
            } else if (m1 == 'subsubsection') {
                return '#####' + m2;
            } else if (m1 == 'title') {
                return '##' + m2;
            } else if (m1 == 'author') {
                return '<div class="latex_author">' + m2 + '</div>';
            } else if (m1 == 'date') {
                return '<div class="latex_date">' + m2 + '</div>';
            }
        });
        return preConversion(text);
    };
    converter.hooks.chain("preBlockGamut", function (text, blockGamutHookCallback) {

        var num_thm = 0;
        var num_excs = 0;

        return text.replace(/<(thm|lem|cor|prop|defn|rem|prob|examp|excs|proof)>([\s\S]*?)<\/\1>/g, function (wholeMatch, m1, m2) {
            var strreturn;
            if (m1 == "proof") {
                strreturn = '<div class="latex_proof"><span class="latex_strong"><em>' + array[m1] + '.&nbsp;&nbsp;</em></span>' + m2 + '<span class="latex_proofend" style="float:right">$■$</span></div>';
                strreturn = blockGamutHookCallback(strreturn);
            } else if (m1 == "excs" | m1 == "examp" | m1 == "prob") {
                strreturn = '<a  class="latex_link"  id="' + m1 + ':' + ++num_excs + '"></a>\n<div class="latex_' + m1 + '"><span class="latex_strong" ><strong>' + array[m1] + ' ' + num_excs + '.&nbsp;&nbsp;</strong></span>' + m2 + '</div>';
                strreturn = blockGamutHookCallback(strreturn);
            } else {
                strreturn = '<a  class="latex_link"  id="' + m1 + ':' + ++num_thm + '"></a>\n<div class="latex_' + m1 + '"><span class="latex_strong" style="font-style:normal ;"><strong>' + array[m1] + ' ' + num_thm + '.&nbsp;&nbsp;</strong></span>' + m2 + '</div>';
                strreturn = blockGamutHookCallback(strreturn);
            }
            return strreturn;
        });
    });
};

userCustom.onReady = function () {
    $("head")
        .append($(
            '<style type="text/css">\n@import url("https://yandex.st/highlightjs/7.3/styles/solarized_light.min.css");\n\
        .latex_ref_nodisplay{display:none;}\n\
        .latex_thm, .latex_lem, .latex_cor, .latex_defn, .latex_prop, .latex_rem{\n\
        font-style:italic;\n\
        display: block; \n\
        margin:15px 0;\n\
        }\n\
        .latex_prob, .latex_examp, .latex_excs, .latex_proof {\n\
        font-style:normal;\n\
        margin: 10px 0;\n\
        display: block; \n\
        }\n\
</style>'));
};

This part is useless:

   userCustom.onPagedownConfigure = function(editor) {
        editor.hooks.chain("onPreviewRefresh", function() {
            MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]);
        });
    };

You define userCustom.onPagedownConfigure twice.
As stated in #180 replace it by:

    userCustom.onAsyncPreview = function(callback) {
        MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]);
        callback();
    };

This is my revision.

I've removed the <a class="latex_link" id="' + m1 + ':' + ++num_excs/thm + '"></a> because it makes no sense to have a <a> element for that. I've moved the id in the <div> element. I actually set the id at the end of the rendering to globally update numbers in all modified and unmodified sections.

I've also removed <span class="latex_ref_nodisplay">...</span>. I didn't get the point.

If you don't want the content of \begin...\end to be interpreted as Markdown, you can replace every blockGamutHookCallback(m2) by m2.

    userCustom.onPagedownConfigure = function (editor) {
        var thmCounter  = { num: 0 };
        var excsCounter = { num: 0 };
        var environmentMap = {
            thm:   { title: "Theorem"    ,counter: thmCounter  },
            lem:   { title: "Lemma"      ,counter: thmCounter  },
            cor:   { title: "Corollary"  ,counter: thmCounter  },
            prop:  { title: "Property"   ,counter: thmCounter  },
            defn:  { title: "Definition" ,counter: thmCounter  },
            rem:   { title: "Remark"     ,counter: thmCounter  },
            prob:  { title: "Problem"    ,counter: excsCounter },
            excs:  { title: "Exercise"   ,counter: excsCounter },
            examp: { title: "Example"    ,counter: excsCounter },
            proof: { title: "Proof" }
        };
        var converter = editor.getConverter();
        // Save the preConversion callbacks stack
        var preConversion = converter.hooks.preConversion;
        converter.hooks.preConversion = function (text) {
            // Change \begin...\end to /begin.../end to avoid MathJax processing
            text = text.replace(/\\begin{(\w+)}([\s\S]*?)\\end{\1}/g, function (wholeMatch, m1, m2) {
                if(!environmentMap[m1]) return wholeMatch;
                // At this stage we need to keep the same number of characters for accurate section parsing
                return '/begin{' + m1 + '}' + m2 + '/end{' + m1 + '}';
            });
            // Transform \title and \section into markdown title to take benefit of partial rendering
            text = text.replace(/\\(\w+){([^\r\n}]+)}/g, function (wholeMatch, m1, m2) {
                // At this stage we need to keep the same number of characters for accurate section parsing
                if (m1 == 'section') {
                    // \section{} has to be replaced by 10 chars
                    return '\n###     ' + m2 + '\n';
                }
                if (m1 == 'subsection') {
                    // \subsection{} has to be replaced by 13 chars
                    return '\n####       ' + m2 + '\n';
                }
                if (m1 == 'subsubsection') {
                    // \subsubsection{} has to be replaced by 16 chars
                    return '\n#####         ' + m2 + '\n';
                }
                if (m1 == 'title') {
                    // \title{} has to be replaced by 8 chars
                    return '\n##    ' + m2 + '\n';
                }
                return wholeMatch;
            });
            // We are replacing the preConversion stack, call the other preConversion callbacks from the old stack
            return preConversion(text);
        };
        converter.hooks.chain("preBlockGamut", function (text, blockGamutHookCallback) {
            text = text.replace(/\\ref{(\w+):(\d+)}/g, function (wholeMatch, m1, m2) {
                if(!environmentMap[m1]) return wholeMatch;
                return '<a class="latex_ref" href="#' + m1 + ':' + m2 + '">' + environmentMap[m1].title + ' ' + m2 + '</a>';
            });
            text = text.replace(/\\(author|date){([\s\S]*?)}/g, '<div class="latex_$1">$2</div>');
            return text.replace(/\/begin{(\w+)}([\s\S]*?)\/end{\1}/g, function (wholeMatch, m1, m2) {
                if(!environmentMap[m1]) return wholeMatch;
                var result = '<div class="latex_' + m1 + '"><span class="latex_title"></span>' + blockGamutHookCallback(m2);
                if (m1 == "proof") {
                    result += '<span class="latex_proofend" style="float:right">$■$</span>';
                }
                return result + '</div>';
            });
        });
        var previewContentsElt = document.getElementById('preview-contents');
        editor.hooks.chain('onPreviewRefresh', function() {
            thmCounter.num = 0;
            excsCounter.num = 0;
            _.each(previewContentsElt.querySelectorAll('[class^="latex_"]'), function(elt) {
                var key = elt.className.match(/^latex_(\S+)/)[1];
                var environment = environmentMap[key];
                if(!environment) return;
                var title = environment.title;
                if(environment.counter) {
                    environment.counter.num++;
                    title += ' ' + environment.counter.num;
                    elt.id = key + ':' + environment.counter.num;
                }
                elt.querySelector('.latex_title').innerHTML = title + '.';
            });
        });
    };

    userCustom.onReady = function () {
        var style = [
            '.latex_thm, .latex_lem, .latex_cor, .latex_defn, .latex_prop, .latex_rem {',
            '    font-style:italic;',
            '    display: block;',
            '    margin:15px 0;',
            '}',
            '.latex_prob, .latex_examp, .latex_excs, .latex_proof {',
            '    font-style:normal;',
            '    margin: 10px 0;',
            '    display: block;',
            '}',
            '.latex_title {',
            '    float:left;',
            '    font-weight:bold;',
            '    padding-right: 10px;',
            '}',
            '.latex_proofend {',
            '    float:right;',
            '}',
        ].join('\n');
        $("head").append($('<style type="text/css">').html(style));
    };

Just reposted. I had to reimplement the 2 counters logic.

Two points: 1. The <a class="latex_link" id="' + m1 + ':' + ++num_excs/thm + '"></a> just for \ref{thm:1} to get a link of Theorem 1. 2. Why your environment head stay at a single line, e.g.

Theorem 2.  
If ...

rather than

Theorem 2. If...
  1. You can set the id directly on the div. The link will be pointing to that div. That's what I did, it should work.
  2. You can control that with css. In .latex_title if you remove float:left; there will be a line break after Theorem 2..

NOTE: I've updated my code about half an hour ago.

@vanabel Great extension! Any plan to port this to MathJax v3? Thanks.