diff --git a/.eslintignore b/.eslintignore
index 510f2b3d..206e3a31 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,6 @@
**/node_modules/*
dist/*
website/build/*
+website/lib/remarkable-katex.js
**/*.min.js
contrib/mhchem/*
diff --git a/docs/supported.md b/docs/supported.md
index 2487f30d..1d5b5a4d 100644
--- a/docs/supported.md
+++ b/docs/supported.md
@@ -542,8 +542,10 @@ Other KaTeX color functions expect the content to be a function argument:
$\textcolor{blue}{F=ma}$ `\textcolor{blue}{F=ma}`
$\textcolor{#228B22}{F=ma}$ `\textcolor{#228B22}{F=ma}`
-$\colorbox{aqua}{A}$ `\colorbox{aqua}{A}`
-$\fcolorbox{red}{aqua}{A}$ `\fcolorbox{red}{aqua}{A}`
+$\colorbox{aqua}{$F=ma$}$ `\colorbox{aqua}{$F=ma$}`
+$\fcolorbox{red}{aqua}{$F=ma$}$ `\fcolorbox{red}{aqua}{$F=ma$}`
+
+Note that, as in LaTeX, `\colorbox` & `\fcolorbox` renders its third argument as text, so you may want to switch back to math mode with `$` as in the examples above.
For color definition, KaTeX color functions will accept the standard HTML [predefined color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords). They will also accept an RGB argument in CSS hexadecimal style. The "#" is optional before a six-digit specification.
diff --git a/website/lib/remarkable-katex.js b/website/lib/remarkable-katex.js
index d6007354..6ea12482 100644
--- a/website/lib/remarkable-katex.js
+++ b/website/lib/remarkable-katex.js
@@ -1,3 +1,6 @@
+// https://github.com/bradhowes/remarkable-katex/blob/master/index.js
+// Modified here to require("../..") instead of require("katex").
+
/* MIT License
Copyright (c) 2017 Brad Howes
@@ -22,179 +25,135 @@ SOFTWARE.
*/
/**
- * Plugin for Remarkable Markdown processor which transforms $..$ and $$..$$
- * sequences into math HTML using the KaTeX package.
+ * Plugin for Remarkable Markdown processor which transforms $..$ and $$..$$ sequences into math HTML using the
+ * Katex package.
*/
-module.exports = function(md, options) {
- const katex = require("../../");
+module.exports = (md, options) => {
+ const dollar = '$';
+ const opts = options || {};
+ const delimiter = opts.delimiter || dollar;
+ if (delimiter.length !== 1) throw 'invalid delimiter';
+ const katex = require("../../");
- function renderKatex(source, displayMode) {
- return katex.renderToString(source,
- {displayMode, throwOnError: false, trust: true, strict: false});
+ /**
+ * Render the contents as KaTeX
+ */
+ const renderKatex = (source, displayMode) => katex.renderToString(source,
+ {displayMode: displayMode,
+ throwOnError: false});
+
+ /**
+ * Parse '$$' as a block. Based off of similar method in remarkable.
+ */
+ const parseBlockKatex = (state, startLine, endLine) => {
+ let haveEndMarker = false,
+ pos = state.bMarks[startLine] + state.tShift[startLine],
+ max = state.eMarks[startLine];
+
+ if (pos + 1 > max) return false;
+
+ const marker = state.src.charAt(pos);
+ if (marker !== delimiter) return false;
+
+ // scan marker length
+ const mem = pos;
+ pos = state.skipChars(pos, marker);
+ let len = pos - mem;
+
+ if (len != 2) return false;
+
+ // search end of block
+ let nextLine = startLine;
+
+ for (;;) {
+ ++nextLine;
+ if (nextLine >= endLine) break;
+
+ pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
+ max = state.eMarks[nextLine];
+
+ if (pos < max && state.tShift[nextLine] < state.blkIndent) break;
+ if (state.src.charAt(pos) !== delimiter) continue;
+ if (state.tShift[nextLine] - state.blkIndent >= 4) continue;
+
+ pos = state.skipChars(pos, marker);
+ if (pos - mem < len) continue;
+
+ pos = state.skipSpaces(pos);
+ if (pos < max) continue;
+
+ haveEndMarker = true;
+ break;
}
- /**
- * Parse '$$' as a block. Based off of similar method in remarkable.
- */
- function parseBlockKatex(state, startLine, endLine) {
- let len;
- let params;
- let nextLine;
- let mem;
- let haveEndMarker = false;
- let pos = state.bMarks[startLine] + state.tShift[startLine];
- let max = state.eMarks[startLine];
- const dollar = 0x24;
+ // If a fence has heading spaces, they should be removed from its inner block
+ len = state.tShift[startLine];
+ state.line = nextLine + (haveEndMarker ? 1 : 0);
+ const content = state.getLines(startLine + 1, nextLine, len, true)
+ .replace(/[ \n]+/g, ' ')
+ .trim();
- if (pos + 1 > max) { return false; }
+ state.tokens.push({type: 'katex', params: null, content: content, lines: [startLine, state.line],
+ level: state.level, block: true});
+ return true;
+ };
- const marker = state.src.charCodeAt(pos);
- if (marker !== dollar) { return false; }
+ /**
+ * Look for '$' or '$$' spans in Markdown text. Based off of the 'fenced' parser in remarkable.
+ */
+ const parseInlineKatex = (state, silent) => {
+ const start = state.pos, max = state.posMax;
+ let pos = start, marker;
- // scan marker length
- mem = pos;
- pos = state.skipChars(pos, marker);
- len = pos - mem;
+ // Unexpected starting character
+ if (state.src.charAt(pos) !== delimiter) return false;
- if (len !== 2) { return false; }
+ ++pos;
+ while (pos < max && state.src.charAt(pos) === delimiter) ++pos;
- // search end of block
- nextLine = startLine;
+ // Capture the length of the starting delimiter -- closing one must match in size
+ marker = state.src.slice(start, pos);
+ if (marker.length > 2) return false;
- for (;;) {
- ++nextLine;
- if (nextLine >= endLine) {
- // unclosed block should be autoclosed by end of document.
- // also block seems to be autoclosed by end of parent
- break;
- }
+ let spanStart = pos;
+ let escapedDepth = 0;
+ while (pos < max) {
+ let char = state.src.charAt(pos);
+ if (char === '{') {
+ escapedDepth += 1;
+ }
+ else if (char === '}') {
+ escapedDepth -= 1;
+ if (escapedDepth < 0) return false;
+ }
+ else if (char === delimiter && escapedDepth == 0) {
+ let matchStart = pos;
+ let matchEnd = pos + 1;
+ while (matchEnd < max && state.src.charAt(matchEnd) === delimiter)
+ ++matchEnd;
- pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
- max = state.eMarks[nextLine];
-
- if (pos < max && state.tShift[nextLine] < state.blkIndent) {
- // non-empty line with negative indent should stop the list:
- // - ```
- // test
- break;
- }
-
- if (state.src.charCodeAt(pos) !== dollar) { continue; }
-
- if (state.tShift[nextLine] - state.blkIndent >= 4) {
-
- // closing fence should be indented less than 4 spaces
- continue;
- }
-
- pos = state.skipChars(pos, marker);
-
- // closing code fence must be at least as long as the opening one
- if (pos - mem < len) { continue; }
-
- // make sure tail has spaces only
- pos = state.skipSpaces(pos);
-
- if (pos < max) { continue; }
-
- haveEndMarker = true;
-
- // found!
- break;
+ if (matchEnd - matchStart == marker.length) {
+ if (!silent) {
+ const content = state.src.slice(spanStart, matchStart)
+ .replace(/[ \n]+/g, ' ')
+ .trim();
+ state.push({type: 'katex', content: content, block: marker.length > 1, level: state.level});
+ }
+ state.pos = matchEnd;
+ return true;
}
-
- // If a fence has heading spaces, they should be removed from
- // its inner block
- len = state.tShift[startLine];
-
- state.line = nextLine + (haveEndMarker ? 1 : 0);
-
- const content = state.getLines(startLine + 1, nextLine, len, true)
- .replace(/[ \n]+/g, ' ')
- .trim();
-
- state.tokens.push({
- type: 'katex',
- params,
- content,
- lines: [startLine, state.line],
- level: state.level,
- block: true,
- });
-
- return true;
+ }
+ pos += 1;
}
- /**
- * Look for '$' or '$$' spans in Markdown text.
- * Based off of the 'fenced' parser in remarkable.
- */
- function parseInlineKatex(state, silent) {
- const dollar = 0x24;
- const backslash = 0x5c;
- let pos = state.pos;
- const start = pos;
- const max = state.posMax;
- let matchStart;
- let matchEnd;
- let esc;
+ if (! silent) state.pending += marker;
+ state.pos += marker.length;
- if (state.src.charCodeAt(pos) !== dollar) { return false; }
- ++pos;
+ return true;
+ };
- while (pos < max && state.src.charCodeAt(pos) === dollar) {
- ++pos;
- }
-
- const marker = state.src.slice(start, pos);
- if (marker.length > 2) { return false; }
-
- matchStart = matchEnd = pos;
-
- while ((matchStart = state.src.indexOf('$', matchEnd)) !== -1) {
- matchEnd = matchStart + 1;
-
- // bypass escaped delimiters
- esc = matchStart - 1;
- while (state.src.charCodeAt(esc) === backslash) {
- --esc;
- }
- if ((matchStart - esc) % 2 === 0) { continue; }
-
- while (matchEnd < max && state.src.charCodeAt(matchEnd) === dollar) {
- ++matchEnd;
- }
-
- if (matchEnd - matchStart === marker.length) {
- if (!silent) {
- const content = state.src.slice(pos, matchStart)
- .replace(/[ \n]+/g, ' ')
- .trim();
-
- state.push({
- type: 'katex',
- content,
- block: marker.length > 1,
- level: state.level,
- });
- }
-
- state.pos = matchEnd;
- return true;
- }
- }
-
- if (!silent) {
- state.pending += marker;
- }
- state.pos += marker.length;
-
- return true;
- }
-
- md.inline.ruler.push('katex', parseInlineKatex, options);
- md.block.ruler.push('katex', parseBlockKatex, options);
- md.renderer.rules.katex = function(tokens, idx) {
- return renderKatex(tokens[idx].content, tokens[idx].block);
- };
+ md.inline.ruler.push('katex', parseInlineKatex, options);
+ md.block.ruler.push('katex', parseBlockKatex, options);
+ md.renderer.rules.katex = (tokens, idx) => renderKatex(tokens[idx].content, tokens[idx].block);
+ md.renderer.rules.katex.delimiter = delimiter;
};