Fixed index.js delimeter logic
* Since the code is suggesting using only $ - $ for inline, $$ - $$ for block math syntax, specific delimiter for $ - $ and $$ - $$ is required, not general escape logic. also it will fix the issue #2. * Some code optimization and variable name change was done. * I found that 'throwOnError:false' option doesn't actually work on katex. They will still throw an error if the syntax is invalid. So instead of sending this directly, I fixed this code to catch the error internally, return the original latex token, and log it on the console when throwOnError option is explicitly set to true.
This commit is contained in:
parent
1922f1d732
commit
c58b297f43
1 changed files with 111 additions and 208 deletions
319
index.js
319
index.js
|
@ -11,229 +11,129 @@ for rendering output.
|
||||||
|
|
||||||
var katex = require('katex');
|
var katex = require('katex');
|
||||||
|
|
||||||
|
//return if we have valid delimiter, '$', is in the position.
|
||||||
function scanDelims(state, start, delimLength) {
|
function isValidDelim(state) {
|
||||||
var pos = start, lastChar, nextChar, count, can_open, can_close,
|
var lastChar, secondLastChar, nextChar, pos = state.pos;
|
||||||
isLastWhiteSpace, isLastPunctChar,
|
|
||||||
isNextWhiteSpace, isNextPunctChar,
|
if(state.src[pos]!=='$'){
|
||||||
left_flanking = true,
|
return false; //the character $ must be in its position.
|
||||||
right_flanking = true,
|
|
||||||
max = state.posMax,
|
|
||||||
isWhiteSpace = state.md.utils.isWhiteSpace,
|
|
||||||
isPunctChar = state.md.utils.isPunctChar,
|
|
||||||
isMdAsciiPunct = state.md.utils.isMdAsciiPunct;
|
|
||||||
|
|
||||||
// treat beginning of the line as a whitespace
|
|
||||||
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20;
|
|
||||||
|
|
||||||
if (pos >= max) {
|
|
||||||
can_open = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += delimLength;
|
secondLastChar= pos > 1 ? state.src[pos-2] : ' '
|
||||||
|
lastChar = pos > 0 ? state.src[pos-1] : ' '
|
||||||
count = pos - start;
|
nextChar = pos + 1 < state.src.length ? state.src[pos+1] : ' '
|
||||||
|
|
||||||
// treat end of the line as a whitespace
|
if(
|
||||||
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20;
|
lastChar === '\\' //$ we found was escaped.
|
||||||
|
|| (lastChar === '$' && secondLastChar !== '\\') // $ we found was after $ but not escaped.
|
||||||
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar));
|
|| nextChar === '$' //$ we found was actually block delimiter $$.
|
||||||
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar));
|
)
|
||||||
|
{
|
||||||
isLastWhiteSpace = isWhiteSpace(lastChar);
|
return false;
|
||||||
isNextWhiteSpace = isWhiteSpace(nextChar);
|
|
||||||
|
|
||||||
if (isNextWhiteSpace) {
|
|
||||||
left_flanking = false;
|
|
||||||
} else if (isNextPunctChar) {
|
|
||||||
if (!(isLastWhiteSpace || isLastPunctChar)) {
|
|
||||||
left_flanking = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLastWhiteSpace) {
|
return true;
|
||||||
right_flanking = false;
|
|
||||||
} else if (isLastPunctChar) {
|
|
||||||
if (!(isNextWhiteSpace || isNextPunctChar)) {
|
|
||||||
right_flanking = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
can_open = left_flanking;
|
|
||||||
can_close = right_flanking;
|
|
||||||
|
|
||||||
return {
|
|
||||||
can_open: can_open,
|
|
||||||
can_close: can_close,
|
|
||||||
delims: count
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function math_inline(state, silent){
|
||||||
function makeMath_inline(open, close) {
|
var start, found = false, token;
|
||||||
return function math_inline(state, silent) {
|
if(silent) { return false; }
|
||||||
var startCount,
|
|
||||||
found,
|
start = state.pos;
|
||||||
res,
|
if(state.src[start] !== '$'){ return false; }
|
||||||
token,
|
if(!isValidDelim(state)){
|
||||||
closeDelim,
|
state.pos += 1;
|
||||||
max = state.posMax,
|
state.pending += '$';
|
||||||
start = state.pos,
|
|
||||||
openDelim = state.src.slice(start, start + open.length);
|
|
||||||
|
|
||||||
if (openDelim !== open) { return false; }
|
|
||||||
if (silent) { return false; } // Don’t run any pairs in validation mode
|
|
||||||
|
|
||||||
res = scanDelims(state, start, openDelim.length);
|
|
||||||
startCount = res.delims;
|
|
||||||
|
|
||||||
if (!res.can_open) {
|
|
||||||
state.pos += startCount;
|
|
||||||
// Earlier we checked !silent, but this implementation does not need it
|
|
||||||
state.pending += state.src.slice(start, state.pos);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.pos = start + open.length;
|
|
||||||
|
|
||||||
while (state.pos < max) {
|
|
||||||
closeDelim = state.src.slice(state.pos, state.pos + close.length);
|
|
||||||
if (closeDelim === close) {
|
|
||||||
res = scanDelims(state, state.pos, close.length);
|
|
||||||
if (res.can_close) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.md.inline.skipToken(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
// Parser failed to find ending tag, so it is not a valid math
|
|
||||||
state.pos = start;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Found!
|
|
||||||
state.posMax = state.pos;
|
|
||||||
state.pos = start + close.length;
|
|
||||||
|
|
||||||
// Earlier we checked !silent, but this implementation does not need it
|
|
||||||
token = state.push('math_inline', 'math', 0);
|
|
||||||
token.content = state.src.slice(state.pos, state.posMax);
|
|
||||||
token.markup = open;
|
|
||||||
|
|
||||||
state.pos = state.posMax + close.length;
|
|
||||||
state.posMax = max;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
|
state.pos+=1;
|
||||||
|
|
||||||
|
while(state.pos < state.posMax){
|
||||||
|
if(isValidDelim(state)){
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state.md.inline.skipToken(state);
|
||||||
|
}
|
||||||
|
if(!found){
|
||||||
|
// Parser failed to find closing delimiter, so it is not a valid math
|
||||||
|
state.pos = start;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//found the closing delimiter and state.pos is pointing it
|
||||||
|
token = state.push('math_inline','math',0);
|
||||||
|
token.content = state.src.slice(start+1,state.pos).trim();
|
||||||
|
token.markup = '$';
|
||||||
|
|
||||||
|
state.pos += 1;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMath_block(open, close) {
|
function math_block(state, start, end, silent){
|
||||||
return function math_block(state, startLine, endLine, silent) {
|
var firstLine, lastLine, next, lastPos, found = false, token,
|
||||||
var openDelim, len, params, nextLine, token, firstLine, lastLine, lastLinePos,
|
pos = state.bMarks[start] + state.tShift[start],
|
||||||
haveEndMarker = false,
|
max = state.eMarks[start]
|
||||||
pos = state.bMarks[startLine] + state.tShift[startLine],
|
|
||||||
max = state.eMarks[startLine];
|
if(pos + 2 > max){ return false; }
|
||||||
|
if(state.src.slice(pos,pos+2)!=='$$'){ return false; }
|
||||||
if (pos + open.length > max) { return false; }
|
|
||||||
|
pos += 2;
|
||||||
openDelim = state.src.slice(pos, pos + open.length);
|
firstLine = state.src.slice(pos,max);
|
||||||
|
|
||||||
if (openDelim !== open) { return false; }
|
if(silent){ return true; }
|
||||||
|
if(firstLine.trim().slice(-2)==='$$'){
|
||||||
pos += open.length;
|
// Single line expression
|
||||||
firstLine = state.src.slice(pos, max);
|
firstLine = firstLine.trim().slice(0, -2);
|
||||||
|
found = true;
|
||||||
// Since start is found, we can report success here in validation mode
|
}
|
||||||
if (silent) { return true; }
|
|
||||||
|
for(next = start; next < end; ){
|
||||||
if (firstLine.trim().slice(-close.length) === close) {
|
if(found){ break; }
|
||||||
// Single line expression
|
next++;
|
||||||
firstLine = firstLine.trim().slice(0, -close.length);
|
pos = state.bMarks[next]+state.tShift[next];
|
||||||
haveEndMarker = true;
|
max = state.eMarks[next];
|
||||||
|
if(pos < max && state.tShift[next] < state.blkIndent){
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
if(state.src.slice(pos,max).trim().slice(-2)==='$$'){
|
||||||
// search end of block
|
lastPos = state.src.slice(0,max).lastIndexOf('$$');
|
||||||
nextLine = startLine;
|
lastLine = state.src.slice(pos,lastPos);
|
||||||
|
found = true;
|
||||||
for (;;) {
|
|
||||||
if (haveEndMarker) { break; }
|
|
||||||
|
|
||||||
nextLine++;
|
|
||||||
|
|
||||||
if (nextLine >= endLine) {
|
|
||||||
// unclosed block should be autoclosed by end of document.
|
|
||||||
// also block seems to be autoclosed by end of parent
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = 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:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.src.slice(pos, max).trim().slice(-close.length) !== close) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.tShift[nextLine] - state.blkIndent >= 4) {
|
|
||||||
// closing block math should be indented less then 4 spaces
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastLinePos = state.src.slice(0, max).lastIndexOf(close);
|
|
||||||
lastLine = state.src.slice(pos, lastLinePos);
|
|
||||||
|
|
||||||
pos += lastLine.length + close.length;
|
|
||||||
|
|
||||||
// make sure tail has spaces only
|
|
||||||
pos = state.skipSpaces(pos);
|
|
||||||
|
|
||||||
if (pos < max) { continue; }
|
|
||||||
|
|
||||||
// found!
|
|
||||||
haveEndMarker = true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// If math block has heading spaces, they should be removed from its inner block
|
|
||||||
len = state.tShift[startLine];
|
if(!found){
|
||||||
|
return false;
|
||||||
state.line = nextLine + (haveEndMarker ? 1 : 0);
|
}
|
||||||
|
|
||||||
token = state.push('math_block', 'math', 0);
|
state.line = next + 1;
|
||||||
token.block = true;
|
|
||||||
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
|
token = state.push('math_block', 'math', 0);
|
||||||
state.getLines(startLine + 1, nextLine, len, true) +
|
token.block = true;
|
||||||
(lastLine && lastLine.trim() ? lastLine : '');
|
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '')
|
||||||
token.info = params;
|
+ state.getLines(start + 1, next, state.tShift[start], true)
|
||||||
token.map = [ startLine, state.line ];
|
+ (lastLine && lastLine.trim() ? lastLine : '');
|
||||||
token.markup = open;
|
token.map = [ start, state.line ];
|
||||||
|
token.markup = '$$';
|
||||||
return true;
|
return true;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = function math_plugin(md, options) {
|
module.exports = function math_plugin(md, options) {
|
||||||
// Default options
|
// Default options
|
||||||
|
|
||||||
var inlineOpen = '$',
|
|
||||||
inlineClose = '$',
|
|
||||||
blockOpen = '$$',
|
|
||||||
blockClose = '$$';
|
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
// set KaTeX as the renderer for markdown-it-simplemath
|
// set KaTeX as the renderer for markdown-it-simplemath
|
||||||
var katexInline = function(latex){
|
var katexInline = function(latex){
|
||||||
options.displayMode = false;
|
options.displayMode = false;
|
||||||
return katex.renderToString(latex, options);
|
try{
|
||||||
|
return katex.renderToString(latex, options);
|
||||||
|
}
|
||||||
|
catch(error){
|
||||||
|
if(options.throwOnError){ console.log(error); }
|
||||||
|
return latex;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var inlineRenderer = function(tokens, idx){
|
var inlineRenderer = function(tokens, idx){
|
||||||
|
@ -242,16 +142,19 @@ module.exports = function math_plugin(md, options) {
|
||||||
|
|
||||||
var katexBlock = function(latex){
|
var katexBlock = function(latex){
|
||||||
options.displayMode = true;
|
options.displayMode = true;
|
||||||
return katex.renderToString(latex, options);
|
try{
|
||||||
|
return katex.renderToString(latex, options);
|
||||||
|
}
|
||||||
|
catch(error){
|
||||||
|
if(options.throwOnError){ console.log(error); }
|
||||||
|
return latex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var blockRenderer = function(tokens, idx){
|
var blockRenderer = function(tokens, idx){
|
||||||
return katexBlock(tokens[idx].content) + '\n';
|
return katexBlock(tokens[idx].content) + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
var math_inline = makeMath_inline(inlineOpen, inlineClose);
|
|
||||||
var math_block = makeMath_block(blockOpen, blockClose);
|
|
||||||
|
|
||||||
md.inline.ruler.before('escape', 'math_inline', math_inline);
|
md.inline.ruler.before('escape', 'math_inline', math_inline);
|
||||||
md.block.ruler.after('blockquote', 'math_block', math_block, {
|
md.block.ruler.after('blockquote', 'math_block', math_block, {
|
||||||
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
|
||||||
|
|
Reference in a new issue