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 toUserCustom extension
to obtain the following function:
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 aTeX
code, this is quite useful when you use stackedit as a mid-step (as an WYSIWYG editor) , but finally you want to obtain atex
code. add the following toButton 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] + '. </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 + '. </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 + '. </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...
- You can set the id directly on the
div
. The link will be pointing to thatdiv
. That's what I did, it should work. - You can control that with css. In
.latex_title
if you removefloat:left;
there will be a line break afterTheorem 2.
.
NOTE: I've updated my code about half an hour ago.
@benweet 👍