-
Notifications
You must be signed in to change notification settings - Fork 88
Add the Markdown-It KaTeX plugin #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a21f830
11cf0b2
ab471e8
cac4c27
02bca7e
da6453f
7cfe826
b695484
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,57 +1,85 @@ | ||
| import QtQml 2.0 | ||
| import QOwnNotesTypes 1.0 | ||
|
|
||
| import "markdown-it.js" as MarkdownIt | ||
| import QtQml 2.0 | ||
| import "markdown-it-deflist.js" as MarkdownItDeflist | ||
| import "markdown-it-katex.js" as MarkdownItKatex | ||
| import "markdown-it.js" as MarkdownIt | ||
|
|
||
| QtObject { | ||
| property string customStylesheet | ||
| property variant md | ||
| property string options | ||
| property variant settingsVariables: [ | ||
| { | ||
| "identifier": "options", | ||
| "name": "Markdown-it options", | ||
| "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>.", | ||
| "type": "text", | ||
| "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" + "}" | ||
| }, | ||
| { | ||
| "identifier": "useDeflistPlugin", | ||
| "name": "Definition lists", | ||
| "text": "Enable the Mardown-it definition list (<dl>) plugin", | ||
| "type": "boolean", | ||
| "default": false | ||
| }, | ||
| { | ||
| "identifier": "customStylesheet", | ||
| "name": "Custom stylesheet", | ||
| "description": "Please enter your custom stylesheet:", | ||
| "type": "text", | ||
| "default": null | ||
| }, | ||
| ] | ||
| property variant settingsVariables: [{ | ||
| "identifier": "options", | ||
| "name": "Markdown-it options", | ||
| "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>.", | ||
| "type": "text", | ||
| "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" + "}" | ||
| }, { | ||
| "identifier": "useDeflistPlugin", | ||
| "name": "Definition lists", | ||
| "text": "Enable the Markdown-it definition list (<dl>) plugin", | ||
| "type": "boolean", | ||
| "default": false | ||
| }, { | ||
| "identifier": "useKatexPlugin", | ||
| "name": "LaTeX Support", | ||
| "text": "Enable the Markdown-it definition list KaTeX plugin", | ||
| "type": "boolean", | ||
| "default": false | ||
| }, { | ||
| "identifier": "customStylesheet", | ||
| "name": "Custom stylesheet", | ||
| "description": "Please enter your custom stylesheet:", | ||
| "type": "text", | ||
| "default": null | ||
| }] | ||
| property bool useDeflistPlugin | ||
| property bool useKatexPlugin | ||
|
|
||
| function init() { | ||
| var optionsObj = eval("(" + options + ")"); | ||
| // md = new MarkdownIt.markdownit(optionsObj); | ||
| md = new this.markdownit(optionsObj); // workaround because its a node module and qml-browserify didn't work | ||
| md = new this.markdownit(optionsObj); | ||
| if (useDeflistPlugin) | ||
| md.use(this.markdownitDeflist); | ||
|
|
||
| if (useDeflistPlugin) { | ||
| // md.use(MarkdownItDeflist.markdownitDeflist); | ||
| md.use(this.markdownitDeflist); // workaround because its a node module and qml-browserify didn't work | ||
| } | ||
| if (useKatexPlugin) | ||
| this.markdownItKatex(md, { | ||
| "output": "mathml" | ||
| }); | ||
|
|
||
| //Allow file:// url scheme | ||
| var validateLinkOrig = md.validateLink; | ||
| var GOOD_PROTO_RE = /^(file):/; | ||
| md.validateLink = function (url) { | ||
| md.validateLink = function(url) { | ||
| var str = url.trim().toLowerCase(); | ||
| return GOOD_PROTO_RE.test(str) ? true : validateLinkOrig(url); | ||
| }; | ||
| } | ||
|
|
||
| function resolvePath(base, relative) { | ||
| const baseParts = base.replace(/\/+$/, '').split('/'); | ||
| const relParts = relative.replace(/^\.\/+/, '').split('/'); | ||
| for (const part of relParts) { | ||
| if (part === '..') | ||
| baseParts.pop(); | ||
| else if (part !== '.' && part !== '') | ||
| baseParts.push(part); | ||
| } | ||
| return baseParts.join('/'); | ||
| } | ||
|
|
||
| function isProtocolUrl(url) { | ||
| return /^[a-zA-Z][\w+.-]*:\/\//.test(url); | ||
| } | ||
|
|
||
| function isWindowsAbsolute(path) { | ||
| return /^[a-zA-Z]:[\\/]/.test(path); | ||
| } | ||
|
|
||
| function isUnixAbsolute(path) { | ||
| return path.startsWith('/'); | ||
| } | ||
|
|
||
| /** | ||
| * This function is called when the markdown html of a note is generated | ||
| * | ||
|
|
@@ -67,22 +95,45 @@ QtObject { | |
| */ | ||
| function noteToMarkdownHtmlHook(note, html, forExport) { | ||
| var mdHtml = md.render(note.noteText); | ||
|
|
||
| //Insert root folder in attachments and media relative urls | ||
| var path = script.currentNoteFolderPath(); | ||
| if (script.platformIsWindows()) { | ||
| if (script.platformIsWindows()) | ||
| path = "/" + path; | ||
| } | ||
| mdHtml = mdHtml.replace(new RegExp("href=\"file://attachments/", "gi"), "href=\"file://" + path + "/attachments/"); | ||
| mdHtml = mdHtml.replace(new RegExp("src=\"file://media/", "gi"), "src=\"file://" + path + "/media/"); | ||
|
|
||
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => { | ||
| // Convert backslashes to forward slashes for URL | ||
|
|
||
| if (isProtocolUrl(rawPath)) | ||
| return `${prefix}${rawPath}"`; | ||
|
|
||
| let finalPath; | ||
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | ||
| // Absolute path (Unix or Windows) | ||
| finalPath = rawPath.replace(/\\/g, '/'); | ||
| else | ||
| // Relative path → resolve against base | ||
| finalPath = resolvePath(basePath, rawPath.replace(/^\.\/+/, '')); | ||
| return `${prefix}file://${finalPath}"`; | ||
| }); | ||
| // Don't attempt to render in the preview, it doesn't support mathml or complex css | ||
| if (!forExport && useKatexPlugin) | ||
| mdHtml = mdHtml.replace(/(<math\b[^>]*>)([\s\S]*?)(<\/math>)/gi, (fullMatch, openMathTag, mathInner, closeMathTag) => { | ||
| let blockPresent = /\bdisplay="block"/i.test(openMathTag); | ||
| let out = blockPresent ? '<br><i>' + openMathTag : ' <i>' + openMathTag; | ||
| out += mathInner.replace(/(<semantics\b[^>]*>)([\s\S]*?)(<\/semantics>)/gi, (semiMatch, openSemi, semiInner, closeSemi) => { | ||
| const cleaned = semiInner.replace(/<mrow\b[^>]*>[\s\S]*?<\/mrow>/gi, ''); | ||
| return openSemi + cleaned + closeSemi; | ||
| }); | ||
| out += blockPresent ? closeMathTag + '</i><br>' : closeMathTag + '</i> '; | ||
| return out; | ||
| }); | ||
|
|
||
| //Get original styles | ||
| var head = html.match(new RegExp("<head>(?:.|\n)*?</head>"))[0]; | ||
| //Add custom styles | ||
| 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>"); | ||
|
|
||
| mdHtml = "<html>" + head + "<body>" + mdHtml + "</body></html>"; | ||
|
|
||
| return mdHtml; | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you add that on purpose?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, that was the QML formatter's doing, though I don't imagine it's a problem. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You changed a lot of whitespaces in this and the following lines. That's what I meant with code formatting changes.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. The diff looks cleaner in VSCode. That section all I did was add a boolean option titled LaTeX Support that would enable or disable KaTeX.