Skip to content

Commit 80f33d8

Browse files
authored
Add the Markdown-It KaTeX plugin (#266)
* add katex plugin * Delete markdown-it/test.js * Update authors and description * overwrite mdHtml rather than returning out * Revert "overwrite mdHtml rather than returning out" This reverts commit cac4c27. * fix the path problem * fix formatting * fix more formatting
1 parent 1297e52 commit 80f33d8

3 files changed

Lines changed: 96 additions & 44 deletions

File tree

markdown-it/info.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"name": "Markdown-it markdown parser",
33
"identifier": "markdown-it",
44
"script": "markdown-it.qml",
5-
"resources": ["markdown-it.js", "markdown-it-deflist.js"],
6-
"authors": ["@milan-rusev", "@bessw"],
7-
"version": "1.4",
5+
"resources": ["markdown-it.js", "markdown-it-deflist.js", "markdown-it-katex.js"],
6+
"authors": ["@milan-rusev", "@bessw", "@cjendantix"],
7+
"version": "1.5",
88
"minAppVersion": "20.6.0",
9-
"description": "This script replaces the default markdown renderer with markdown-it.\n\n<b>Dependencies</b>\n<a href=\"https://github.com/markdown-it/markdown-it\">markdown-it.js</a> (v8.4.2 bundled with the script)\n\n<b>Usage</b>\nFor the possible configuration options check <a href=\"https://github.com/markdown-it/markdown-it/tree/master/lib/presets\">here</a>.\n\n<b>Important</b>\nThis script currently only works with <a href=\"https://github.com/qownnotes/scripts/issues/77\"><b>legacy media links</b></a>. You can turn them on in the <i>General Settings</i>.\n\nImportant note: You need to use legacy image linking with this script, otherwise there will be no images shown in the preview!"
9+
"description": "This script replaces the default markdown renderer with markdown-it and allows for optional LaTeX rendering support with the Markdown-It KaTeX plugin. (NOTE: LaTeX defaults to rendering with MathML ONLY). \n\n<b>Dependencies</b>\n<a href=\"https://github.com/markdown-it/markdown-it\">markdown-it.js</a> (v8.4.2 bundled with the script)\n<a href=\"https://github.com/mdit-plugins/mdit-plugins/tree/main/packages/katex\">Markdown-It KaTeX plugin</a> (v0.18.0 bundled with the script)\n\n<b>Usage</b>\nFor the possible configuration options check <a href=\"https://github.com/markdown-it/markdown-it/tree/master/lib/presets\">here</a>.\n\n<b>Important</b>\nThis script currently only works with <a href=\"https://github.com/qownnotes/scripts/issues/77\"><b>legacy media links</b></a>. You can turn them on in the <i>General Settings</i>.\n\nImportant note: You need to use legacy image linking with this script, otherwise there will be no images shown in the preview!"
1010
}

markdown-it/markdown-it-katex.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

markdown-it/markdown-it.qml

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,85 @@
1-
import QtQml 2.0
21
import QOwnNotesTypes 1.0
3-
4-
import "markdown-it.js" as MarkdownIt
2+
import QtQml 2.0
53
import "markdown-it-deflist.js" as MarkdownItDeflist
4+
import "markdown-it-katex.js" as MarkdownItKatex
5+
import "markdown-it.js" as MarkdownIt
66

77
QtObject {
88
property string customStylesheet
99
property variant md
1010
property string options
11-
property variant settingsVariables: [
12-
{
13-
"identifier": "options",
14-
"name": "Markdown-it options",
15-
"description": "For available options and default values see <a href='https://github.com/markdown-it/markdown-it/blob/master/lib/presets'>markdown-it presets</a>.",
16-
"type": "text",
17-
"default": "{" + "\n" + " //html: false, // Enable HTML tags in source" + "\n" + " //xhtmlOut: false, // Use '/' to close single tags (<br />)" + "\n" + " //breaks: false, // Convert '\\n' in paragraphs into <br>" + "\n" + " //langPrefix: 'language-', // CSS language prefix for fenced blocks" + "\n" + " //linkify: false, // autoconvert URL-like texts to links" + "\n" + "" + "\n" + " // Enable some language-neutral replacements + quotes beautification" + "\n" + " //typographer: false," + "\n" + "" + "\n" + " // Double + single quotes replacement pairs, when typographer enabled," + "\n" + " // and smartquotes on. Could be either a String or an Array." + "\n" + " //" + "\n" + " // For example, you can use '«»„“' for Russian, '„“‚‘' for German," + "\n" + " // and ['«\\xA0', '\\xA0»', '‹\\xA0', '\\xA0›'] for French (including nbsp)." + "\n" + " //quotes: '\\u201c\\u201d\\u2018\\u2019', /* “”‘’ */" + "\n" + "" + "\n" + " // Highlighter function. Should return escaped HTML," + "\n" + " // or '' if the source string is not changed and should be escaped externaly." + "\n" + " // If result starts with <pre... internal wrapper is skipped." + "\n" + " //" + "\n" + " // function (/*str, lang*/) { return ''; }" + "\n" + " //" + "\n" + " //highlight: null," + "\n" + "" + "\n" + " //maxNesting: 100 // Internal protection, recursion limit" + "\n" + "}"
18-
},
19-
{
20-
"identifier": "useDeflistPlugin",
21-
"name": "Definition lists",
22-
"text": "Enable the Mardown-it definition list (<dl>) plugin",
23-
"type": "boolean",
24-
"default": false
25-
},
26-
{
27-
"identifier": "customStylesheet",
28-
"name": "Custom stylesheet",
29-
"description": "Please enter your custom stylesheet:",
30-
"type": "text",
31-
"default": null
32-
},
33-
]
11+
property variant settingsVariables: [{
12+
"identifier": "options",
13+
"name": "Markdown-it options",
14+
"description": "For available options and default values see <a href='https://github.com/markdown-it/markdown-it/blob/master/lib/presets'>markdown-it presets</a>.",
15+
"type": "text",
16+
"default": "{" + "\n" + " //html: false, // Enable HTML tags in source" + "\n" + " //xhtmlOut: false, // Use '/' to close single tags (<br />)" + "\n" + " //breaks: false, // Convert '\\n' in paragraphs into <br>" + "\n" + " //langPrefix: 'language-', // CSS language prefix for fenced blocks" + "\n" + " //linkify: false, // autoconvert URL-like texts to links" + "\n" + "" + "\n" + " // Enable some language-neutral replacements + quotes beautification" + "\n" + " //typographer: false," + "\n" + "" + "\n" + " // Double + single quotes replacement pairs, when typographer enabled," + "\n" + " // and smartquotes on. Could be either a String or an Array." + "\n" + " //" + "\n" + " // For example, you can use '«»„“' for Russian, '„“‚‘' for German," + "\n" + " // and ['«\\xA0', '\\xA0»', '‹\\xA0', '\\xA0›'] for French (including nbsp)." + "\n" + " //quotes: '\\u201c\\u201d\\u2018\\u2019', /* “”‘’ */" + "\n" + "" + "\n" + " // Highlighter function. Should return escaped HTML," + "\n" + " // or '' if the source string is not changed and should be escaped externaly." + "\n" + " // If result starts with <pre... internal wrapper is skipped." + "\n" + " //" + "\n" + " // function (/*str, lang*/) { return ''; }" + "\n" + " //" + "\n" + " //highlight: null," + "\n" + "" + "\n" + " //maxNesting: 100 // Internal protection, recursion limit" + "\n" + "}"
17+
}, {
18+
"identifier": "useDeflistPlugin",
19+
"name": "Definition lists",
20+
"text": "Enable the Markdown-it definition list (<dl>) plugin",
21+
"type": "boolean",
22+
"default": false
23+
}, {
24+
"identifier": "useKatexPlugin",
25+
"name": "LaTeX Support",
26+
"text": "Enable the Markdown-it definition list KaTeX plugin",
27+
"type": "boolean",
28+
"default": false
29+
}, {
30+
"identifier": "customStylesheet",
31+
"name": "Custom stylesheet",
32+
"description": "Please enter your custom stylesheet:",
33+
"type": "text",
34+
"default": null
35+
}]
3436
property bool useDeflistPlugin
37+
property bool useKatexPlugin
3538

3639
function init() {
3740
var optionsObj = eval("(" + options + ")");
38-
// md = new MarkdownIt.markdownit(optionsObj);
39-
md = new this.markdownit(optionsObj); // workaround because its a node module and qml-browserify didn't work
41+
md = new this.markdownit(optionsObj);
42+
if (useDeflistPlugin)
43+
md.use(this.markdownitDeflist);
4044

41-
if (useDeflistPlugin) {
42-
// md.use(MarkdownItDeflist.markdownitDeflist);
43-
md.use(this.markdownitDeflist); // workaround because its a node module and qml-browserify didn't work
44-
}
45+
if (useKatexPlugin)
46+
this.markdownItKatex(md, {
47+
"output": "mathml"
48+
});
4549

4650
//Allow file:// url scheme
4751
var validateLinkOrig = md.validateLink;
4852
var GOOD_PROTO_RE = /^(file):/;
49-
md.validateLink = function (url) {
53+
md.validateLink = function(url) {
5054
var str = url.trim().toLowerCase();
5155
return GOOD_PROTO_RE.test(str) ? true : validateLinkOrig(url);
5256
};
5357
}
5458

59+
function resolvePath(base, relative) {
60+
const baseParts = base.replace(/\/+$/, '').split('/');
61+
const relParts = relative.replace(/^\.\/+/, '').split('/');
62+
for (const part of relParts) {
63+
if (part === '..')
64+
baseParts.pop();
65+
else if (part !== '.' && part !== '')
66+
baseParts.push(part);
67+
}
68+
return baseParts.join('/');
69+
}
70+
71+
function isProtocolUrl(url) {
72+
return /^[a-zA-Z][\w+.-]*:\/\//.test(url);
73+
}
74+
75+
function isWindowsAbsolute(path) {
76+
return /^[a-zA-Z]:[\\/]/.test(path);
77+
}
78+
79+
function isUnixAbsolute(path) {
80+
return path.startsWith('/');
81+
}
82+
5583
/**
5684
* This function is called when the markdown html of a note is generated
5785
*
@@ -67,22 +95,45 @@ QtObject {
6795
*/
6896
function noteToMarkdownHtmlHook(note, html, forExport) {
6997
var mdHtml = md.render(note.noteText);
70-
7198
//Insert root folder in attachments and media relative urls
7299
var path = script.currentNoteFolderPath();
73-
if (script.platformIsWindows()) {
100+
if (script.platformIsWindows())
74101
path = "/" + path;
75-
}
76-
mdHtml = mdHtml.replace(new RegExp("href=\"file://attachments/", "gi"), "href=\"file://" + path + "/attachments/");
77-
mdHtml = mdHtml.replace(new RegExp("src=\"file://media/", "gi"), "src=\"file://" + path + "/media/");
102+
103+
mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => {
104+
// Convert backslashes to forward slashes for URL
105+
106+
if (isProtocolUrl(rawPath))
107+
return `${prefix}${rawPath}"`;
108+
109+
let finalPath;
110+
if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath))
111+
// Absolute path (Unix or Windows)
112+
finalPath = rawPath.replace(/\\/g, '/');
113+
else
114+
// Relative path → resolve against base
115+
finalPath = resolvePath(basePath, rawPath.replace(/^\.\/+/, ''));
116+
return `${prefix}file://${finalPath}"`;
117+
});
118+
// Don't attempt to render in the preview, it doesn't support mathml or complex css
119+
if (!forExport && useKatexPlugin)
120+
mdHtml = mdHtml.replace(/(<math\b[^>]*>)([\s\S]*?)(<\/math>)/gi, (fullMatch, openMathTag, mathInner, closeMathTag) => {
121+
let blockPresent = /\bdisplay="block"/i.test(openMathTag);
122+
let out = blockPresent ? '<br><i>' + openMathTag : '&nbsp;<i>' + openMathTag;
123+
out += mathInner.replace(/(<semantics\b[^>]*>)([\s\S]*?)(<\/semantics>)/gi, (semiMatch, openSemi, semiInner, closeSemi) => {
124+
const cleaned = semiInner.replace(/<mrow\b[^>]*>[\s\S]*?<\/mrow>/gi, '');
125+
return openSemi + cleaned + closeSemi;
126+
});
127+
out += blockPresent ? closeMathTag + '</i><br>' : closeMathTag + '</i>&nbsp;';
128+
return out;
129+
});
78130

79131
//Get original styles
80132
var head = html.match(new RegExp("<head>(?:.|\n)*?</head>"))[0];
81133
//Add custom styles
82134
head = head.replace("</style>", "table {border-spacing: 0; border-style: solid; border-width: 1px; border-collapse: collapse; margin-top: 0.5em;} th, td {padding: 0 5px;}" + customStylesheet + "</style>");
83-
84135
mdHtml = "<html>" + head + "<body>" + mdHtml + "</body></html>";
85-
86136
return mdHtml;
87137
}
138+
88139
}

0 commit comments

Comments
 (0)