Conversation
- Enable html:true by default so HTML comments (<!-- -->) are passed
through as-is instead of being escaped as visible text
- Fix --strikethrough-- to use <s> tag instead of <del>, consistent
with markdown ~~strikethrough~~ rendering
- Remove + item ordered list editor highlighting rule (was applying
an unwanted heading style to list lines)
- Fix basePath undefined variable → path in relative URL resolution
- Register txt2tags_link before adding autolink/wikilink rules:
ruler.before("txt2tags_link", ...) threw "Parser rule not found"
when called before txt2tags_link was itself registered, aborting
the entire plugin initialisation and breaking all txt2tags rendering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…namespace In Qt5, importing a JS file in QML binds the module's top-level `this` to the QML global scope, so UMD modules export their constructors onto the component via `g = this; g.markdownit = f()`, and `this.markdownit` works in init(). In Qt6, the module's top-level `this` is the module namespace object (e.g. MarkdownIt, MarkdownItTxt2tags), not the QML component scope. So the constructors land on `MarkdownIt.markdownit` etc., and `this.markdownit` in init() is undefined — causing silent init failure and a blank preview. Fix: resolve each constructor with `Namespace.name || this.name` so that Qt6 uses the module namespace and Qt5 falls back to the component scope. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In Qt6 QML, JavaScript files imported via 'import "foo.js" as Foo' run in strict mode where `this` at the top level is undefined. The UMD wrappers in markdown-it, markdown-it-deflist, markdown-it-katex and markdown-it-txt2tags all fell back to `g = this` when window/global/self were undefined, then threw TypeError setting a property on undefined. Fix: insert `globalThis` before the `this` fallback in each UMD chain. globalThis is always the real global object in Qt6's JS engine. In init(), resolve constructors via `_g = globalThis || this` so that Qt6 uses globalThis (where the modules now export) and Qt5 falls back to the component scope (where the modules previously exported via this). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The root cause of Qt6 breakage: UMD modules used `g = this; g.markdownit = f()` where `this` at module top-level is undefined in Qt6 strict mode, so the assignment threw and nothing was exported. Fix: declare `var markdownit` and `var markdownitTxt2tags` at the top level of each JS file (outside the IIFE). Top-level vars are always accessible via the QML module qualifier (MarkdownIt.markdownit, MarkdownItTxt2tags.markdownitTxt2tags) in both Qt5 and Qt6 — no runtime this/globalThis lookup needed. Also removed the unused deflist and katex plugins to simplify the script. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new QOwnNotes script (txt2tags-it) that uses markdown-it and a custom markdown-it plugin to support txt2tags syntax in rendered preview (and optional editor highlighting), intended to replace the earlier attempt from PR #282.
Changes:
- Introduces a new renderer QML script that wires markdown-it + a txt2tags plugin and adds editor highlighting rules.
- Adds a markdown-it plugin implementing several txt2tags block/inline syntaxes (headings, comments, underline, strike, links, wikilinks, autolinks).
- Adds script metadata (
info.json) and a short README for distribution/use.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| txt2tags-it/txt2tags-it.qml | New renderer script: initializes markdown-it + txt2tags plugin, adds highlighting rules, rewrites relative media links to file://, injects custom CSS. |
| txt2tags-it/markdown-it-txt2tags.js | New markdown-it plugin implementing txt2tags parsing rules (headings/comments/lists/inline markup/links). |
| txt2tags-it/info.json | Script metadata and long-form description/dependency notes. |
| txt2tags-it/README.md | Minimal usage/context README. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "name": "Markdown-it options", | ||
| "description": "For available options and default values see <a href='https://github.com/markdown-it/markdown-it/blob/main/lib/presets'>markdown-it presets</a>.", | ||
| "type": "text", | ||
| "default": "{" + "\n" + " html: true, // 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" + " //maxNesting: 100 // Internal protection, recursion limit" + "\n" + "}" |
There was a problem hiding this comment.
The default markdown-it options enable html: true, which allows raw HTML in note content to be rendered. That can introduce XSS/script-injection risk in the preview/export (especially since this script also rewrites links). Consider defaulting to html: false (matching the existing markdown-it script) and letting users opt in via settings if needed.
| "default": "{" + "\n" + " html: true, // 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" + " //maxNesting: 100 // Internal protection, recursion limit" + "\n" + "}" | |
| "default": "{" + "\n" + " html: false, // Disable HTML tags in source by default" + "\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" + " //maxNesting: 100 // Internal protection, recursion limit" + "\n" + "}" |
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => { | ||
| if (isProtocolUrl(rawPath)) | ||
| return `${prefix}${rawPath}"`; | ||
|
|
||
| let finalPath; | ||
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | ||
| finalPath = rawPath.replace(/\\/g, '/'); | ||
| else | ||
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | ||
| return `${prefix}file://${finalPath}"`; |
There was a problem hiding this comment.
The replacement callback hardcodes a double-quote at the end of the attribute value (e.g. ...${rawPath}"). If the original HTML used single quotes (possible when html: true is enabled), this will generate malformed HTML with mismatched quotes. Preserve the original quote character (e.g. capture it separately or derive it from prefix) when rebuilding the attribute string.
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => { | |
| if (isProtocolUrl(rawPath)) | |
| return `${prefix}${rawPath}"`; | |
| let finalPath; | |
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | |
| finalPath = rawPath.replace(/\\/g, '/'); | |
| else | |
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | |
| return `${prefix}file://${finalPath}"`; | |
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*)(["'])([^"']+)\2/gi, (_, prefix, quote, rawPath) => { | |
| if (isProtocolUrl(rawPath)) | |
| return `${prefix}${quote}${rawPath}${quote}`; | |
| let finalPath; | |
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | |
| finalPath = rawPath.replace(/\\/g, '/'); | |
| else | |
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | |
| return `${prefix}${quote}file://${finalPath}${quote}`; |
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => { | ||
| if (isProtocolUrl(rawPath)) | ||
| return `${prefix}${rawPath}"`; | ||
|
|
||
| let finalPath; | ||
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | ||
| finalPath = rawPath.replace(/\\/g, '/'); | ||
| else | ||
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | ||
| return `${prefix}file://${finalPath}"`; |
There was a problem hiding this comment.
This return also hardcodes a double-quote terminator, which can break attributes that were originally single-quoted. Use the same closing quote as the opening quote captured by the regex to avoid producing invalid HTML.
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*["'])([^"']+)["']/gi, (_, prefix, rawPath) => { | |
| if (isProtocolUrl(rawPath)) | |
| return `${prefix}${rawPath}"`; | |
| let finalPath; | |
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | |
| finalPath = rawPath.replace(/\\/g, '/'); | |
| else | |
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | |
| return `${prefix}file://${finalPath}"`; | |
| mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*)(["'])([^"']+)\2/gi, (_, prefix, quote, rawPath) => { | |
| if (isProtocolUrl(rawPath)) | |
| return `${prefix}${quote}${rawPath}${quote}`; | |
| let finalPath; | |
| if (isUnixAbsolute(rawPath) || isWindowsAbsolute(rawPath)) | |
| finalPath = rawPath.replace(/\\/g, '/'); | |
| else | |
| finalPath = resolvePath(path, rawPath.replace(/^\.\/+/, '')); | |
| return `${prefix}${quote}file://${finalPath}${quote}`; |
| //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;} del {text-decoration: line-through;}" + customStylesheet + "</style>"); |
There was a problem hiding this comment.
customStylesheet defaults to null, and concatenating it here will insert the literal string "null" into the generated <style> block. Coerce to an empty string when unset (e.g., customStylesheet || "") to avoid emitting invalid/unexpected CSS.
| 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;} del {text-decoration: line-through;}" + customStylesheet + "</style>"); | |
| 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;} del {text-decoration: line-through;}" + (customStylesheet || "") + "</style>"); |
| var match = /^[a-zA-Z][\w+\-.]*:\/\/[^\s\]]*/.exec(src.slice(pos)); | ||
| if (!match) return false; | ||
| var url = match[0]; | ||
| // Strip trailing punctuation that is unlikely to be part of the URL | ||
| url = url.replace(/[.,;:!?)]+$/, ""); | ||
| if (!url) return false; | ||
| if (!silent) { | ||
| var token = state.push("link_open", "a", 1); | ||
| token.attrs = [["href", url]]; | ||
| token.markup = "autolink"; | ||
| state.push("text", "", 0).content = url; | ||
| state.push("link_close", "a", -1).markup = "autolink"; | ||
| } |
There was a problem hiding this comment.
The bare-URL autolink rule accepts any scheme and sets href directly without calling md.validateLink() / md.normalizeLink(). This can allow unsafe schemes (e.g. javascript://...) to be rendered as clickable links, bypassing markdown-it’s safety checks. Validate and normalize the URL before emitting link_open, and reject untrusted protocols.
There was a problem hiding this comment.
Done. The autolink rule now calls md.validateLink() (rejects javascript: and other unsafe schemes) and
md.normalizeLink() (percent-encodes the URL) before emitting the link_open token — consistent with the
txt2tags_link rule.
| state.push("txt2tags_s_open", "s", 1); | ||
| state.push("text", "", 0).content = src.slice(start, end); | ||
| state.push("txt2tags_s_close", "s", -1); |
There was a problem hiding this comment.
The plugin documentation (and the QML stylesheet) describe --strikethrough-- rendering as <del>…</del>, but the implementation emits <s>…</s> (txt2tags_s_open/close with tag s). Align the implementation and docs/styles (either emit del tokens or update the docs and CSS selectors accordingly).
| state.push("txt2tags_s_open", "s", 1); | |
| state.push("text", "", 0).content = src.slice(start, end); | |
| state.push("txt2tags_s_close", "s", -1); | |
| state.push("txt2tags_s_open", "del", 1); | |
| state.push("text", "", 0).content = src.slice(start, end); | |
| state.push("txt2tags_s_close", "del", -1); |
There was a problem hiding this comment.
Strikethrough now emits …, matching the docs and the CSS rule in the QML stylesheet.
| "authors": ["@luginf"], | ||
| "version": "0.1", | ||
| "minAppVersion": "26.4.11", | ||
| "description": "This script, based on markdown-it, replaces the default markdown renderer with markdown-it AND also with the txt2tags syntax. It also 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/main/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!" |
There was a problem hiding this comment.
description claims optional LaTeX rendering via the Markdown-It KaTeX plugin, but this script’s resources list does not include markdown-it-katex.js and the QML code doesn’t expose a setting or initialize KaTeX. Update the description to match actual functionality, or add the missing resource + wiring if KaTeX support is intended.
| "description": "This script, based on markdown-it, replaces the default markdown renderer with markdown-it AND also with the txt2tags syntax. It also 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/main/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!" | |
| "description": "This script, based on markdown-it, replaces the default markdown renderer with markdown-it AND also with the txt2tags syntax.\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/main/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!" |
replaces #282