Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
vanabel opened this issue Nov 15, 2013 · 9 comments

Comments

@vanabel
Copy link

vanabel commented Nov 15, 2013

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());
%>
@vanabel
Copy link
Author

vanabel commented Nov 15, 2013

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

@kasperpeulen
Copy link

@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>'));
};

@benweet
Copy link
Owner

benweet commented Nov 16, 2013

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

@benweet
Copy link
Owner

benweet commented Nov 24, 2013

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));
    };

@benweet
Copy link
Owner

benweet commented Nov 24, 2013

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

@vanabel
Copy link
Author

vanabel commented Nov 25, 2013

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...

@benweet
Copy link
Owner

benweet commented Nov 25, 2013

  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.

@kasperpeulen
Copy link

@benweet 👍

@netw0rkf10w
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants