From 805d2da71b8901eb1b32fa99e9cc9992c8fdd5ec Mon Sep 17 00:00:00 2001 From: luginf Date: Fri, 22 May 2026 11:49:09 +0200 Subject: [PATCH] =?UTF-8?q?Bug=20fix=20=E2=80=94=20keyboard=20focus=20lost?= =?UTF-8?q?=20after=20custom=20actions=20(txt2tags-it.qml)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After invoking a custom action via keyboard shortcut (e.g. "Heading 2"), the editor lost focus, forcing the user to click with the mouse before typing again. - Root cause: the toolbar/menu button steals focus when the action fires. - First attempt: script.noteTextEditSetFocus() — does not exist, throws TypeError on v26.5.14. - Fix: mainWindow.focusNoteTextEdit() added at the end of customActionInvoked(). This is the only official API method for restoring editor focus. noteToMarkdownHtmlHook optimisations (hot path, called on every keystroke): - _isWindows — platformIsWindows() cached in init() (avoids an external call on every render) - _headRe — regex /[\s\S]*?<\/head>/ pre-compiled in init() (no more new RegExp(...) on each call + (?:.|\n)*? replaced by [\s\S]*? which is significantly faster on large HTML) - _urlAttrRe — URL attribute regex pre-compiled in init() + lastIndex = 0 reset before each replace() - _cssInject — constant CSS string extracted as a property txt2tags_autolink optimisation (inline rule, called at every position): - charCode fast-fail before src.slice(pos) + exec() — avoids creating a substring for every / and any non-letter character --- txt2tags-it/info.json | 2 +- txt2tags-it/markdown-it-txt2tags.js | 5 ++++- txt2tags-it/txt2tags-it.qml | 20 ++++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/txt2tags-it/info.json b/txt2tags-it/info.json index 1337404..17e86e2 100755 --- a/txt2tags-it/info.json +++ b/txt2tags-it/info.json @@ -4,7 +4,7 @@ "script": "txt2tags-it.qml", "resources": ["markdown-it.js", "markdown-it-txt2tags.js"], "authors": ["@luginf"], - "version": "0.1", + "version": "0.1.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.\n\nDependencies\nmarkdown-it.js (v8.4.2 bundled with the script)\n\nUsage\nFor the possible configuration options check here.\n\nImportant\nThis script currently only works with legacy media links. You can turn them on in the General Settings.\n\nImportant note: You need to use legacy image linking with this script, otherwise there will be no images shown in the preview!" } diff --git a/txt2tags-it/markdown-it-txt2tags.js b/txt2tags-it/markdown-it-txt2tags.js index c10ab5d..28a354c 100755 --- a/txt2tags-it/markdown-it-txt2tags.js +++ b/txt2tags-it/markdown-it-txt2tags.js @@ -283,7 +283,10 @@ var markdownitTxt2tags; function (state, silent) { var pos = state.pos; var src = state.src; - // Must start with a URL scheme (letters then "://") + // Fast-fail: scheme must start with an ASCII letter + var ch = src.charCodeAt(pos); + if (!((ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a))) + return false; var match = /^[a-zA-Z][\w+\-.]*:\/\/[^\s\]]*/.exec(src.slice(pos)); if (!match) return false; var url = match[0]; diff --git a/txt2tags-it/txt2tags-it.qml b/txt2tags-it/txt2tags-it.qml index a732d56..032d2ad 100644 --- a/txt2tags-it/txt2tags-it.qml +++ b/txt2tags-it/txt2tags-it.qml @@ -47,6 +47,10 @@ QtObject { property bool useTxt2tagsPlugin property bool useEditorHighlighting property bool useSetextHeadings + property bool _isWindows: false + property variant _headRe + property variant _urlAttrRe + property string _cssInject: "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;}" function init() { var optionsObj = eval("(" + options + ")"); @@ -85,6 +89,10 @@ QtObject { script.addHighlightingRule("^%.*$", "%", 11); } + _isWindows = script.platformIsWindows(); + _headRe = /[\s\S]*?<\/head>/; + _urlAttrRe = /(\b(?:src|href|data-[\w-]+)\s*=\s*)(["'])([^"']+)\2/gi; + //Allow file:// url scheme var validateLinkOrig = md.validateLink; var GOOD_PROTO_RE = /^(file):/; @@ -160,6 +168,7 @@ QtObject { applyHeading(3); break; } + mainWindow.focusNoteTextEdit(); } /** @@ -179,10 +188,11 @@ QtObject { var mdHtml = md.render(note.noteText); //Insert root folder in attachments and media relative urls var path = script.currentNoteFolderPath(); - if (script.platformIsWindows()) + if (_isWindows) path = "/" + path; - mdHtml = mdHtml.replace(/(\b(?:src|href|data-[\w-]+)\s*=\s*)(["'])([^"']+)\2/gi, (_, attr, quote, rawPath) => { + _urlAttrRe.lastIndex = 0; + mdHtml = mdHtml.replace(_urlAttrRe, (_, attr, quote, rawPath) => { if (isProtocolUrl(rawPath)) return `${attr}${quote}${rawPath}${quote}`; @@ -194,10 +204,8 @@ QtObject { return `${attr}${quote}file://${finalPath}${quote}`; }); - //Get original styles - var head = html.match(new RegExp("(?:.|\n)*?"))[0]; - //Add custom styles - head = head.replace("", "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 || "") + ""); + var head = html.match(_headRe)[0]; + head = head.replace("", _cssInject + (customStylesheet || "") + ""); mdHtml = "" + head + "" + mdHtml + ""; return mdHtml; }