Skip to content

Txt2tags it#284

Open
luginf wants to merge 16 commits intoqownnotes:mainfrom
luginf:txt2tags-it
Open

Txt2tags it#284
luginf wants to merge 16 commits intoqownnotes:mainfrom
luginf:txt2tags-it

Conversation

@luginf
Copy link
Copy Markdown

@luginf luginf commented Apr 17, 2026

replaces #282

luginf and others added 10 commits April 11, 2026 23:21
- 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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread txt2tags-it/txt2tags-it.qml Outdated
"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" + "}"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"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" + "}"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread txt2tags-it/txt2tags-it.qml Outdated
Comment on lines +106 to +115
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}"`;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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}`;

Copilot uses AI. Check for mistakes.
Comment thread txt2tags-it/txt2tags-it.qml Outdated
Comment on lines +106 to +115
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}"`;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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}`;

Copilot uses AI. Check for mistakes.
Comment thread txt2tags-it/txt2tags-it.qml Outdated
//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>");
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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>");

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +254 to +266
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";
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread txt2tags-it/markdown-it-txt2tags.js Outdated
Comment on lines +323 to +325
state.push("txt2tags_s_open", "s", 1);
state.push("text", "", 0).content = src.slice(start, end);
state.push("txt2tags_s_close", "s", -1);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strikethrough now emits , matching the docs and the CSS rule in the QML stylesheet.

Comment thread txt2tags-it/info.json Outdated
"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!"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"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!"

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants