mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-30 05:19:57 +00:00
Add optional KaTeX support to markdown plugin (#5846)
* Add optional KaTeX support to the tiddlywiki/markdown plugin. Uses the remarkable-katex plugin 1.1.8 by Brad Howes to enable KaTeX support if the tiddlywiki/katex plugin is installed. Fixes #2984. TESTED: Created a test wiki with: ``` $ node tiddlywiki.js test --init markdowndemo $ node tiddlywiki.js test --listen ``` * Verified markdown support works without the tiddlywiki/katex plugin enabled. * Verified markdown support works with the tiddlywiki/katex plugin enabled. * Verified KaTeX (both inline and blocks) work as expected when the tiddlywiki/katex plugin is enabled. * Mention remarkable-katex plugin usage in the readme Tiddler. * Include the remarkable-katex license as a tiddler. * Include the Remarkable license. * Include unminified original source of remarkable-katex 1.1.8.
This commit is contained in:
parent
0b71f25f74
commit
a1d9464011
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Brad Howes
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
137
plugins/tiddlywiki/markdown/files/remarkable-katex.js
Normal file
137
plugins/tiddlywiki/markdown/files/remarkable-katex.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin for Remarkable Markdown processor which transforms $..$ and $$..$$ sequences into math HTML using the
|
||||||
|
* Katex package.
|
||||||
|
*/
|
||||||
|
const rkatex = (md, options) => {
|
||||||
|
const backslash = '\\';
|
||||||
|
const dollar = '$';
|
||||||
|
const opts = options || {};
|
||||||
|
const delimiter = opts.delimiter || dollar;
|
||||||
|
if (delimiter.length !== 1) { throw new Error('invalid delimiter'); }
|
||||||
|
|
||||||
|
const katex = require("katex");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
let pos = state.bMarks[startLine] + state.tShift[startLine];
|
||||||
|
let max = state.eMarks[startLine];
|
||||||
|
|
||||||
|
if (pos + 1 > max) { return false; }
|
||||||
|
|
||||||
|
const marker = state.src.charAt(pos);
|
||||||
|
if (marker !== delimiter) { return false; }
|
||||||
|
|
||||||
|
// scan marker length
|
||||||
|
let 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: null, content: content, lines: [startLine, state.line],
|
||||||
|
level: state.level, block: true});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for '$' or '$$' spans in Markdown text. Based off of the 'fenced' parser in remarkable.
|
||||||
|
*/
|
||||||
|
const parseInlineKatex = (state, silent) => {
|
||||||
|
const start = state.pos;
|
||||||
|
const max = state.posMax;
|
||||||
|
let pos = start;
|
||||||
|
|
||||||
|
// Unexpected starting character
|
||||||
|
if (state.src.charAt(pos) !== delimiter) { return false; }
|
||||||
|
|
||||||
|
++pos;
|
||||||
|
while (pos < max && state.src.charAt(pos) === delimiter) { ++pos; }
|
||||||
|
|
||||||
|
// Capture the length of the starting delimiter -- closing one must match in size
|
||||||
|
const marker = state.src.slice(start, pos);
|
||||||
|
if (marker.length > 2) { return false; }
|
||||||
|
|
||||||
|
const spanStart = pos;
|
||||||
|
let escapedDepth = 0;
|
||||||
|
while (pos < max) {
|
||||||
|
const char = state.src.charAt(pos);
|
||||||
|
if (char === '{' && (pos == 0 || state.src.charAt(pos - 1) != backslash)) {
|
||||||
|
escapedDepth += 1;
|
||||||
|
} else if (char === '}' && (pos == 0 || state.src.charAt(pos - 1) != backslash)) {
|
||||||
|
escapedDepth -= 1;
|
||||||
|
if (escapedDepth < 0) { return false; }
|
||||||
|
} else if (char === delimiter && escapedDepth === 0) {
|
||||||
|
const matchStart = pos;
|
||||||
|
let matchEnd = pos + 1;
|
||||||
|
while (matchEnd < max && state.src.charAt(matchEnd) === delimiter) { ++matchEnd; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (tokens, idx) => renderKatex(tokens[idx].content, tokens[idx].block);
|
||||||
|
md.renderer.rules.katex.delimiter = delimiter;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = rkatex;
|
20
plugins/tiddlywiki/markdown/files/remarkable-katex.min.js
vendored
Normal file
20
plugins/tiddlywiki/markdown/files/remarkable-katex.min.js
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";const rkatex=(md,options)=>{const backslash='\\';const dollar='$';const opts=options||{};const delimiter=opts.delimiter||dollar;if(delimiter.length!==1){throw new Error('invalid delimiter');}
|
||||||
|
const katex=require("katex");const renderKatex=(source,displayMode)=>katex.renderToString(source,{displayMode:displayMode,throwOnError:false});const parseBlockKatex=(state,startLine,endLine)=>{let haveEndMarker=false;let pos=state.bMarks[startLine]+state.tShift[startLine];let max=state.eMarks[startLine];if(pos+1>max){return false;}
|
||||||
|
const marker=state.src.charAt(pos);if(marker!==delimiter){return false;}
|
||||||
|
let mem=pos;pos=state.skipChars(pos,marker);let len=pos-mem;if(len!==2){return false;}
|
||||||
|
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;}
|
||||||
|
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:null,content:content,lines:[startLine,state.line],level:state.level,block:true});return true;};const parseInlineKatex=(state,silent)=>{const start=state.pos;const max=state.posMax;let pos=start;if(state.src.charAt(pos)!==delimiter){return false;}
|
||||||
|
++pos;while(pos<max&&state.src.charAt(pos)===delimiter){++pos;}
|
||||||
|
const marker=state.src.slice(start,pos);if(marker.length>2){return false;}
|
||||||
|
const spanStart=pos;let escapedDepth=0;while(pos<max){const char=state.src.charAt(pos);if(char==='{'&&(pos==0||state.src.charAt(pos-1)!=backslash)){escapedDepth+=1;}else if(char==='}'&&(pos==0||state.src.charAt(pos-1)!=backslash)){escapedDepth-=1;if(escapedDepth<0){return false;}}else if(char===delimiter&&escapedDepth===0){const matchStart=pos;let matchEnd=pos+1;while(matchEnd<max&&state.src.charAt(matchEnd)===delimiter){++matchEnd;}
|
||||||
|
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;}}
|
||||||
|
pos+=1;}
|
||||||
|
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=(tokens,idx)=>renderKatex(tokens[idx].content,tokens[idx].block);md.renderer.rules.katex.delimiter=delimiter;};module.exports=rkatex;
|
22
plugins/tiddlywiki/markdown/files/remarkable-license.txt
Normal file
22
plugins/tiddlywiki/markdown/files/remarkable-license.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2016, Jon Schlinkert
|
||||||
|
Copyright (c) 2014 Jon Schlinkert, Vitaly Puzrin.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@ -7,6 +7,30 @@
|
|||||||
"title": "$:/plugins/tiddlywiki/markdown/remarkable.js",
|
"title": "$:/plugins/tiddlywiki/markdown/remarkable.js",
|
||||||
"module-type": "library"
|
"module-type": "library"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "remarkable-license.txt",
|
||||||
|
"fields": {
|
||||||
|
"type": "text/plain",
|
||||||
|
"title": "$:/plugins/tiddlywiki/markdown/remarkable-license"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "remarkable-katex.min.js",
|
||||||
|
"fields": {
|
||||||
|
"type": "application/javascript",
|
||||||
|
"title": "$:/plugins/tiddlywiki/markdown/remarkable-katex.js",
|
||||||
|
"module-type": "library"
|
||||||
|
},
|
||||||
|
"prefix": "(function(realRequire) {var require = function(m) {if(m===\"katex\"){m = \"$:/plugins/tiddlywiki/katex/katex.min.js\"};return realRequire(m);};",
|
||||||
|
"suffix": "})(require);\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "remarkable-katex-license.txt",
|
||||||
|
"fields": {
|
||||||
|
"type": "text/plain",
|
||||||
|
"title": "$:/plugins/tiddlywiki/markdown/remarkable-katex-license"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "$:/plugins/tiddlywiki/markdown",
|
"title": "$:/plugins/tiddlywiki/markdown",
|
||||||
"name": "Markdown",
|
"name": "Markdown",
|
||||||
"description": "Markdown parser based on remarkable by Jon Schlinkert",
|
"description": "Markdown parser based on remarkable by Jon Schlinkert and remarkable-katex by Brad Howes",
|
||||||
"list": "readme usage"
|
"list": "readme usage remarkable-license remarkable-katex-license"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
title: $:/plugins/tiddlywiki/markdown/readme
|
title: $:/plugins/tiddlywiki/markdown/readme
|
||||||
|
|
||||||
This is a TiddlyWiki plugin for parsing Markdown text, using the [[Remarkable|https://github.com/jonschlinkert/remarkable]] library.
|
This is a TiddlyWiki plugin for parsing Markdown text, using the [[Remarkable|https://github.com/jonschlinkert/remarkable]] library. If the KaTeX TiddlyWiki plugin is installed, KaTeX support is enabled using the [[remarkable-katex|https://github.com/bradhowes/remarkable-katex]] Remarkable plugin.
|
||||||
|
|
||||||
It is completely self-contained, and doesn't need an Internet connection in order to work. It works both in the browser and under Node.js.
|
It is completely self-contained, and doesn't need an Internet connection in order to work. It works both in the browser and under Node.js.
|
||||||
|
|
||||||
|
@ -39,6 +39,12 @@ var accumulatingTypes = {
|
|||||||
|
|
||||||
var md = new Remarkable(remarkableOpts);
|
var md = new Remarkable(remarkableOpts);
|
||||||
|
|
||||||
|
// If tiddlywiki/katex plugin is present, use remarkable-katex to enable katex support.
|
||||||
|
if ($tw.modules.titles["$:/plugins/tiddlywiki/katex/katex.min.js"]) {
|
||||||
|
var rk = require("$:/plugins/tiddlywiki/markdown/remarkable-katex.js");
|
||||||
|
md = md.use(rk);
|
||||||
|
}
|
||||||
|
|
||||||
if (parseAsBoolean("$:/config/markdown/linkify")) {
|
if (parseAsBoolean("$:/config/markdown/linkify")) {
|
||||||
md = md.use(linkify);
|
md = md.use(linkify);
|
||||||
}
|
}
|
||||||
@ -223,6 +229,16 @@ function convertNodes(remarkableTree, isStartOfInline) {
|
|||||||
accumulatedText = accumulatedText + currentNode.content;
|
accumulatedText = accumulatedText + currentNode.content;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "katex":
|
||||||
|
out.push({
|
||||||
|
type: "latex",
|
||||||
|
attributes: {
|
||||||
|
text: { type: "text", value: currentNode.content },
|
||||||
|
displayMode: { type: "text", value: currentNode.block ? "true" : "false" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (currentNode.type.substr(currentNode.type.length - 5) === "_open") {
|
if (currentNode.type.substr(currentNode.type.length - 5) === "_open") {
|
||||||
var tagName = currentNode.type.substr(0, currentNode.type.length - 5);
|
var tagName = currentNode.type.substr(0, currentNode.type.length - 5);
|
||||||
|
Loading…
Reference in New Issue
Block a user