From f729b1d68b0c96aaf83a3c2670480bc9f6b59be7 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Fri, 19 Jun 2026 09:04:47 -0700 Subject: [PATCH 1/4] Improved sentence case. --- src/background/modules/AutocorrectHandler.js | 28 ++++++++++--------- src/background/modules/ContextMenu.js | 2 +- .../modules/UnicodeTransformationHandler.js | 12 +++++++- src/common/modules/data/Fonts.js | 10 +++---- src/content_scripts/autocorrect.js | 4 +-- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/background/modules/AutocorrectHandler.js b/src/background/modules/AutocorrectHandler.js index d50d892..d5e4a51 100644 --- a/src/background/modules/AutocorrectHandler.js +++ b/src/background/modules/AutocorrectHandler.js @@ -256,18 +256,20 @@ BrowserCommunication.addListener(COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_BACKGROU browser.runtime.onMessage.addListener((message) => { // console.log(message); - if (message.type === COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT) { - const response = { - type: COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT, - enabled: settings.enabled, - quotes: settings.quotes, - fracts: settings.fracts, - numbers: settings.numbers, - autocorrections, - longest, - symbolpatterns: IS_CHROME ? symbolpatterns.source : symbolpatterns, - antipatterns: IS_CHROME ? antipatterns.source : antipatterns - }; - return Promise.resolve(response); + if (message.type !== COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT) { + return; } + + const response = { + type: COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT, + enabled: settings.enabled, + quotes: settings.quotes, + fracts: settings.fracts, + numbers: settings.numbers, + autocorrections, + longest, + symbolpatterns: IS_CHROME ? symbolpatterns.source : symbolpatterns, + antipatterns: IS_CHROME ? antipatterns.source : antipatterns + }; + return Promise.resolve(response); }); diff --git a/src/background/modules/ContextMenu.js b/src/background/modules/ContextMenu.js index eb211b9..cf95df3 100644 --- a/src/background/modules/ContextMenu.js +++ b/src/background/modules/ContextMenu.js @@ -304,7 +304,7 @@ export async function init() { } menus.onClicked.addListener(handleMenuChoosen); - pasteSymbol = platformInfo.os === "mac" ? "\u2318" : browser.i18n.getMessage("menuCtrlKey"); + pasteSymbol = platformInfo.os === "mac" ? "\u{2318}" : browser.i18n.getMessage("menuCtrlKey"); } BrowserCommunication.addListener(COMMUNICATION_MESSAGE_TYPE.UNICODE_FONT, async (request) => { diff --git a/src/common/modules/UnicodeTransformationHandler.js b/src/common/modules/UnicodeTransformationHandler.js index ac1d533..2605aae 100644 --- a/src/common/modules/UnicodeTransformationHandler.js +++ b/src/common/modules/UnicodeTransformationHandler.js @@ -87,7 +87,17 @@ function capitalizeEachWord(text) { * @returns {string} */ function sentenceCase(text) { - return Array.from(segmenterSentence.segment(text), ({ segment: [head, ...tail] }) => head.toLocaleUpperCase() + tail.join("")).join(""); + return Array.from(segmenterSentence.segment(text), ({ segment }) => { + let found = false; + return Array.from(segmenterWord.segment(segment), ({ segment, isWordLike }) => { + if (!found && isWordLike) { + found = true; + const [head, ...tail] = segment; + return head.toLocaleUpperCase() + tail.join(""); + } + return segment; + }).join(""); + }).join(""); } /** diff --git a/src/common/modules/data/Fonts.js b/src/common/modules/data/Fonts.js index e804cda..e3dad1f 100644 --- a/src/common/modules/data/Fonts.js +++ b/src/common/modules/data/Fonts.js @@ -224,9 +224,9 @@ export const fontLetters = Object.freeze( * @type {Object.} */ export const formats = Object.freeze({ - Overlined: "\u0305", - DoubleOverlined: "\u033F", - Strikethrough: "\u0336", - Underlined: "\u0332", - DoubleUnderlined: "\u0333" + Overlined: "\u{305}", + DoubleOverlined: "\u{33F}", + Strikethrough: "\u{336}", + Underlined: "\u{332}", + DoubleUnderlined: "\u{333}" }); diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js index 13d53c7..54896a1 100644 --- a/src/content_scripts/autocorrect.js +++ b/src/content_scripts/autocorrect.js @@ -148,7 +148,7 @@ function insertIntoPage(atext) { */ function countChars(str) { // removing the Unicode joiner chars \u200D - return Array.from(segmenter.segment(str.replaceAll("\u200D", ""))).length; + return Array.from(segmenter.segment(str.replaceAll("\u{200D}", ""))).length; } /** @@ -333,7 +333,7 @@ function autocorrect(event) { [label] = result; } else { // Fraction slash character: https://en.wikipedia.org/wiki/Numerals_in_Unicode#Fractions - label = `${numerator}\u2044${denominator}`; + label = `${numerator}\u{2044}${denominator}`; } const index = firstDifferenceIndex(label, fraction); if (index >= 0) { From dd5a650547cf843846686c92c8f020758f8630ae Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Sat, 20 Jun 2026 08:18:55 -0700 Subject: [PATCH 2/4] Reimplemented Unicode autocorrect. --- src/background/modules/AutocorrectHandler.js | 43 +-- src/content_scripts/autocorrect.js | 327 ++++++++++++------- 2 files changed, 222 insertions(+), 148 deletions(-) diff --git a/src/background/modules/AutocorrectHandler.js b/src/background/modules/AutocorrectHandler.js index d5e4a51..92e4704 100644 --- a/src/background/modules/AutocorrectHandler.js +++ b/src/background/modules/AutocorrectHandler.js @@ -117,19 +117,13 @@ function applySettings() { } // Longest autocorrection - longest = 0; - - for (const symbol in autocorrections) { - if (symbol.length > longest) { - longest = symbol.length; - } - } + longest = Math.max(...Object.keys(autocorrections).map((s) => s.length), 0); console.log("Longest autocorrection", longest); - symbolpatterns = createTree(Object.keys(autocorrections)); + const symbolpatternsRegexpString = createTree(Object.keys(autocorrections)); // Do not autocorrect for these patterns - antipatterns = []; + let antipatternsList = []; for (const x in autocorrections) { let length = 0; let index = x.length; @@ -152,31 +146,21 @@ function applySettings() { if (length) { length = x.length - (index + length); if (length > 1) { - antipatterns.push(x.slice(0, -(length - 1))); + antipatternsList.push(x.slice(0, -(length - 1))); } } } - antipatterns = antipatterns.filter((item, pos) => antipatterns.indexOf(item) === pos); - console.log("Do not autocorrect for these patterns", antipatterns); + antipatternsList = antipatternsList.filter((item, pos) => antipatternsList.indexOf(item) === pos); + console.log("Do not autocorrect for these patterns", antipatternsList); - antipatterns = createTree(antipatterns); + const antipatternsRegexpString = createTree(antipatternsList); - symbolpatterns = new RegExp(`(${symbolpatterns})$`, "u"); - antipatterns = new RegExp(`(${antipatterns})$`, "u"); + symbolpatterns = new RegExp(`(${symbolpatternsRegexpString})$`, "u"); + antipatterns = new RegExp(`(${antipatternsRegexpString})$`, "u"); const end = performance.now(); console.log(`The new autocorrect settings were applied in ${end - start} ms.`); } -/** - * On error. - * - * @param {string} error - * @returns {void} - */ -function onError(error) { - console.error(`Error: ${error}`); -} - /** * Set autocorrect settings. * @@ -206,7 +190,6 @@ function sendSettings(autocorrect) { browser.tabs.query({}).then((tabs) => { for (const tab of tabs) { - // This requires Thunderbird 78.4: https://bugzilla.mozilla.org/show_bug.cgi?id=1641576 browser.tabs.sendMessage( tab.id, { @@ -220,9 +203,13 @@ function sendSettings(autocorrect) { symbolpatterns: IS_CHROME ? symbolpatterns.source : symbolpatterns, antipatterns: IS_CHROME ? antipatterns.source : antipatterns } - ).catch(onError); + ).catch((error) => { + console.error(`Error: ${error}`); + }); } - }).catch(onError); + }).catch((error) => { + console.error(`Error: ${error}`); + }); } /** diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js index 54896a1..f28e71b 100644 --- a/src/content_scripts/autocorrect.js +++ b/src/content_scripts/autocorrect.js @@ -1,4 +1,3 @@ - const afractions = Object.freeze({ "¼": [1, 4], "½": [1, 2], @@ -32,8 +31,6 @@ const constants = Object.freeze({ const AUTOCORRECT_CONTENT = "autocorrectContent"; const INSERT = "insert"; -const segmenter = new Intl.Segmenter(); - let insertedText; // Last insert text let deletedText; // Last deleted text let lastTarget; // Last target @@ -60,73 +57,112 @@ let running = false; const IS_CHROME = Object.getPrototypeOf(browser) !== Object.prototype; /** - * Get caret position. + * Get the root editable element for a contenteditable/designMode edit. * - * @param {HTMLElement} target - * @returns {number|null} + * @param {EventTarget} target + * @returns {HTMLElement|null} */ -function getCaretPosition(target) { - // ContentEditable elements - if (target.isContentEditable || document.designMode === "on") { - target.focus(); - const selection = document.getSelection(); - if (selection.rangeCount !== 1) { - return null; +function getEditingRoot(target) { + if (document.designMode === "on") { + return document.body || document.documentElement; + } + + if (!target.isContentEditable) { + return null; + } + + let element = target; + while (element.parentElement?.isContentEditable) { + element = element.parentElement; + } + return element; +} + +/** + * Get a stable collapsed caret range without mutating the editing host. + * + * @param {InputEvent} event + * @returns {Range|null} + */ +function getCaretRange(event) { + if (event.inputType.startsWith("insert") && event.getTargetRanges) { + const ranges = event.getTargetRanges(); + if (ranges.length === 1) { + const [range] = ranges; + const arange = document.createRange(); + arange.setStart(range.startContainer, range.startOffset); + arange.setEnd(range.endContainer, range.endOffset); + if (!arange.collapsed) { + return null; + } + return arange; } - const arange = selection.getRangeAt(0); - if (!arange.collapsed) { + } + + const selection = document.getSelection(); + if (!selection || selection.rangeCount !== 1) { + return null; + } + + const range = selection.getRangeAt(0); + if (!range.collapsed) { + return null; + } + + return range.cloneRange(); +} + +/** + * Get the text before the caret without mutating the page DOM. + * + * @param {HTMLElement|HTMLInputElement|HTMLTextAreaElement} target + * @param {InputEvent} event + * @returns {string|null} + */ +function getTextBeforeCaret(target, event) { + // ContentEditable elements + const root = getEditingRoot(target); + if (root) { + const caretRange = getCaretRange(event); + if (!caretRange) { return null; } - const range = arange.cloneRange(); - const temp = document.createTextNode("\0"); - range.insertNode(temp); - const caretposition = target.innerText.indexOf("\0"); - temp.remove(); - return caretposition; + + const range = document.createRange(); + range.selectNodeContents(root); + range.setEnd(caretRange.endContainer, caretRange.endOffset); + return range.toString(); } // input and textarea fields if (target.selectionStart !== target.selectionEnd) { return null; } - return target.selectionStart; + return target.value.slice(0, target.selectionStart); } /** - * Insert at caret in the given element. + * Insert at the current selection/caret in the given element. * Adapted from: https://www.everythingfrontend.com/posts/insert-text-into-textarea-at-cursor-position.html * * @param {HTMLElement} target * @param {string} atext - * @throws {Error} if nothing is selected - * @returns {void} + * @returns {boolean} */ function insertAtCaret(target, atext) { // document.execCommand is deprecated, although there is not yet an alternative: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand // insertReplacementText if (document.execCommand("insertText", false, atext)) { - return; + return true; } // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 - if (target.setRangeText) { - const start = target.selectionStart; - const end = target.selectionEnd; - - if (start != null && end != null) { - target.setRangeText(atext); - - target.selectionStart = target.selectionEnd = start + atext.length; - - // Notify any possible listeners of the change - const event = document.createEvent("UIEvent"); - event.initEvent("input", true, false); - target.dispatchEvent(event); - - return; - } + if (target.setRangeText && target.selectionStart != null && target.selectionEnd != null) { + target.setRangeText(atext, target.selectionStart, target.selectionEnd, "end"); + target.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: atext })); + return true; } - throw new Error("nothing selected"); + return false; } /** @@ -136,51 +172,112 @@ function insertAtCaret(target, atext) { * @returns {void} */ function insertIntoPage(atext) { - return insertAtCaret(document.activeElement, atext); + if (!insertAtCaret(document.activeElement, atext)) { + throw new Error("nothing selected"); + } } /** - * Count Unicode characters. - * Adapted from: https://blog.jonnew.com/posts/poo-dot-length-equals-two + * Return a DOM range covering the given number of UTF-16 code units before the caret. * - * @param {string} str - * @returns {number} + * @param {HTMLElement} root + * @param {Range} caretRange + * @param {number} length + * @returns {Range|null} */ -function countChars(str) { - // removing the Unicode joiner chars \u200D - return Array.from(segmenter.segment(str.replaceAll("\u{200D}", ""))).length; +function getRangeBeforeCaret(root, caretRange, length) { + const range = document.createRange(); + range.setEnd(caretRange.startContainer, caretRange.startOffset); + + if (!length) { + range.collapse(false); + return range; + } + + let remaining = length; + const textNodes = []; + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + + for (let node = walker.nextNode(); node; node = walker.nextNode()) { + let endOffset; + if (node === caretRange.startContainer) { + endOffset = caretRange.startOffset; + } else { + const position = caretRange.comparePoint(node, node.length); + if (position > 0) { + break; + } + endOffset = node.length; + } + + if (endOffset > 0) { + textNodes.push([node, endOffset]); + } + } + + for (const [node, endOffset] of textNodes.reverse()) { + const take = Math.min(remaining, endOffset); + remaining -= take; + if (!remaining) { + range.setStart(node, endOffset - take); + return range; + } + } + + return null; } /** - * Delete at caret. + * Apply a prepared text replacement. * - * @param {HTMLElement} target - * @param {string} atext - * @returns {void} + * @param {HTMLElement|HTMLInputElement|HTMLTextAreaElement} target + * @param {InputEvent} event + * @param {string} deleteText + * @param {string} insertText + * @returns {boolean} */ -function deleteCaret(target, atext) { - const count = countChars(atext); - if (count > 0) { - // document.execCommand is deprecated, although there is not yet an alternative: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand - if (document.execCommand("delete", false)) { - for (let i = 0; i < count - 1; ++i) { - document.execCommand("delete", false); - } +function applyReplacement(target, event, deleteText, insertText) { + const root = getEditingRoot(target); + if (root) { + const caretRange = getCaretRange(event); + if (!caretRange) { + return false; + } + + const range = getRangeBeforeCaret(root, caretRange, deleteText.length); + if (!range || range.toString() !== deleteText) { + return false; } - // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 - else if (target.setRangeText) { - const start = target.selectionStart; - - target.selectionStart = start - atext.length; - target.selectionEnd = start; - target.setRangeText(""); - - // Notify any possible listeners of the change - const e = document.createEvent("UIEvent"); - e.initEvent("input", true, false); - target.dispatchEvent(e); + + const selection = document.getSelection(); + if (!selection) { + return false; } + + event.preventDefault(); + + selection.removeAllRanges(); + selection.addRange(range); + return insertAtCaret(root, insertText); + } + + if (target.selectionStart !== target.selectionEnd) { + return false; + } + + const start = target.selectionStart - deleteText.length; + if (start < 0 || target.value.slice(start, target.selectionStart) !== deleteText) { + return false; } + + event.preventDefault(); + + const end = target.selectionStart; + target.selectionStart = start; + target.selectionEnd = end; + + // "insertReplacementText" + return insertAtCaret(target, insertText); } /** @@ -271,7 +368,7 @@ function firstDifferenceIndex(a, b) { */ function autocorrect(event) { // console.log('beforeinput', event.inputType, event.data); - if (!["insertText", "insertCompositionText", "insertParagraph", "insertLineBreak"].includes(event.inputType)) { + if (event.isComposing || event.cancelable === false || !["insertText", "insertParagraph", "insertLineBreak"].includes(event.inputType)) { return; } if (!symbolpatterns) { @@ -281,17 +378,20 @@ function autocorrect(event) { return; } running = true; - const { target } = event; - const caretposition = getCaretPosition(target); - if (caretposition != null) { - const value = target.value || target.innerText; + try { + const { target } = event; + const value = getTextBeforeCaret(target, event); + if (value == null) { + return; + } + const caretposition = value.length; let deletecount = 0; let insert = ["insertLineBreak", "insertParagraph"].includes(event.inputType) ? "\n" : event.data; const inserted = insert; let output = false; // Use Unicode smart quotes if (quotes && (insert === "'" || insert === '"')) { - const previouschar = value.slice(caretposition < 1 ? 0 : caretposition - 1, caretposition); + const previouschar = value.slice(-1); // White space const re = /^\s*$/u; if (insert === "'") { @@ -301,12 +401,12 @@ function autocorrect(event) { } output = true; } - const previousText = value.slice(caretposition < longest ? 0 : caretposition - longest, caretposition); + const previousText = value.slice(-longest); const regexResult = symbolpatterns.exec(previousText); // Autocorrect Unicode Symbols if (regexResult) { const length = longest - 1; - const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; + const text = value.slice(-length) + inserted; const aregexResult = symbolpatterns.exec(text); if (!antipatterns.test(text) && (!aregexResult || (caretposition <= longest ? regexResult.index < aregexResult.index : regexResult.index <= aregexResult.index))) { const [autocorrection] = regexResult; @@ -319,10 +419,10 @@ function autocorrect(event) { if (!output && fracts) { // Fractions regular expression: https://regex101.com/r/RtUMrA/1 const fractionRegex = /(?\d+)\/(?\d+)$/u; - const previousText = value.slice(0, caretposition); + const previousText = value; const regexResult = fractionRegex.exec(previousText); if (regexResult && insert !== "/") { - const text = value.slice(0, caretposition) + inserted; + const text = value + inserted; if (!fractionRegex.test(text)) { const [fraction] = regexResult; const numerator = Number.parseInt(regexResult.groups.numerator, 10); @@ -349,10 +449,10 @@ function autocorrect(event) { // Numbers regular expression: https://regex101.com/r/7jUaSP/11 // Do not match version numbers: https://github.com/rugk/unicodify/issues/40 const numberRegex = /(?\.\d+)?$/u; - const previousText = value.slice(0, caretposition); + const previousText = value; const regexResult = numberRegex.exec(previousText); if (regexResult && insert !== ".") { - const text = value.slice(0, caretposition) + inserted; + const text = value + inserted; if (!numberRegex.test(text)) { const [number] = regexResult; const label = outputLabel(number, regexResult.groups.fractionpart); @@ -367,14 +467,10 @@ function autocorrect(event) { } } if (output) { - event.preventDefault(); - - const text = deletecount ? value.slice(caretposition - deletecount, caretposition) : ""; - if (text) { - lastTarget = null; - deleteCaret(target, text); + const text = deletecount ? value.slice(caretposition - deletecount) : ""; + if (!applyReplacement(target, event, text, insert)) { + return; } - insertAtCaret(target, insert); insertedText = insert; deletedText = text + inserted; @@ -383,8 +479,9 @@ function autocorrect(event) { lastTarget = target; lastCaretPosition = caretposition - deletecount + insert.length; } + } finally { + running = false; } - running = false; } /** @@ -396,32 +493,30 @@ function autocorrect(event) { function undoAutocorrect(event) { // console.log('beforeinput', event.inputType, event.data); // Backspace - if (event.inputType !== "deleteContentBackward") { + if (event.isComposing || event.cancelable === false || event.inputType !== "deleteContentBackward") { return; } if (running) { return; } running = true; - const { target } = event; - const caretposition = getCaretPosition(target); - if (caretposition != null) { - if (target === lastTarget && caretposition === lastCaretPosition) { - event.preventDefault(); - - if (insertedText) { - lastTarget = null; - deleteCaret(target, insertedText); - } - if (deletedText) { - insertAtCaret(target, deletedText); + try { + const { target } = event; + const value = getTextBeforeCaret(target, event); + if (value == null) { + return; + } + const caretposition = value.length; + if (target === lastTarget && caretposition === lastCaretPosition && (!insertedText || value.endsWith(insertedText))) { + if (applyReplacement(target, event, insertedText, deletedText)) { + console.debug("Undo autocorrect: “%s” was replaced with “%s”.", insertedText, deletedText); } - console.debug("Undo autocorrect: “%s” was replaced with “%s”.", insertedText, deletedText); } lastTarget = null; + } finally { + running = false; } - running = false; } /** @@ -459,15 +554,7 @@ function handleResponse(message, _sender) { } } -/** - * Handle errors from messages and responses. - * - * @param {string} error - * @returns {void} - */ -function handleError(error) { +browser.runtime.sendMessage({ type: AUTOCORRECT_CONTENT }).then(handleResponse, (error) => { console.error(`Error: ${error}`); -} - -browser.runtime.sendMessage({ type: AUTOCORRECT_CONTENT }).then(handleResponse, handleError); +}); browser.runtime.onMessage.addListener(handleResponse); From b4d7c140a63cdc566cb533e99396b351e9627f55 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Fri, 26 Jun 2026 05:10:30 -0700 Subject: [PATCH 3/4] Enabled fallback for doing conversions on blocked pages. --- src/background/modules/ContextMenu.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/background/modules/ContextMenu.js b/src/background/modules/ContextMenu.js index cf95df3..78832ee 100644 --- a/src/background/modules/ContextMenu.js +++ b/src/background/modules/ContextMenu.js @@ -7,6 +7,9 @@ import * as Notifications from "/common/modules/Notifications.js"; import { COMMUNICATION_MESSAGE_TYPE } from "/common/modules/data/BrowserCommunicationTypes.js"; import { menuStructure, SEPARATOR_ID_PREFIX, TRANSFORMATION_TYPE } from "/common/modules/data/Fonts.js"; +// Thunderbird +// https://bugzilla.mozilla.org/show_bug.cgi?id=1641573 +const IS_THUNDERBIRD = Boolean(globalThis.messenger); const menus = browser.menus || browser.contextMenus; // fallback for Thunderbird const PREVIEW_STRING_CUT_LENGTH = 100; // a setting that may improve performance by not calculating invisible parts of the context menu @@ -20,15 +23,15 @@ let pasteSymbol = null; * Thunderbird workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1641575 * * @param {string} text - * @param {string} fieldId + * @param {string} [fieldId] * @returns {void} */ function fallback(text, fieldId) { navigator.clipboard.writeText(text); - const fieldName = fieldId.startsWith("compose") ? fieldId.slice("compose".length) : fieldId; + const fieldName = fieldId?.startsWith("compose") ? fieldId.slice("compose".length) : fieldId; Notifications.showNotification( "menuNotificationPressCtrlVTitle", - "menuNotificationPressCtrlVContent", + IS_THUNDERBIRD ? "menuNotificationPressCtrlVContent" : `The add-on was unable to access this tab directly, so the transformed text has been copied to your clipboard.\nPlease press ${pasteSymbol}-V to do the transformation.`, [ pasteSymbol, fieldName @@ -46,21 +49,20 @@ function fallback(text, fieldId) { * @throws {Error} */ async function handleMenuChoosen(info, tab) { - let text = info.selectionText; + const text = info.selectionText; if (!text) { return; } - text = text.normalize(); - const output = UnicodeTransformationHandler.transformText(text, info.menuItemId); + const output = UnicodeTransformationHandler.transformText(text.normalize(), info.menuItemId); // Thunderbird workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1641575 if (info.fieldId) { if (info.fieldId === "composeSubject") { const details = await browser.compose.getComposeDetails(tab.id); - if (details.subject.split(info.selectionText).length === 2) { - browser.compose.setComposeDetails(tab.id, { subject: details.subject.replace(info.selectionText, output) }); + if (details.subject.split(text).length === 2) { + browser.compose.setComposeDetails(tab.id, { subject: details.subject.replace(text, output) }); return; } } @@ -71,7 +73,10 @@ async function handleMenuChoosen(info, tab) { browser.tabs.sendMessage(tab.id, { type: COMMUNICATION_MESSAGE_TYPE.INSERT, text: output - }, { frameId: info.frameId }); + }, { frameId: info.frameId }).catch((error) => { + console.error(`Error: ${error}`); + fallback(output); + }); } /** From 24934e5b29e539382869721b2ff003764d0c98e9 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Fri, 19 Jun 2026 09:08:58 -0700 Subject: [PATCH 4/4] Updated version to 0.9. --- scripts/manifests/chromemanifest.json | 2 +- scripts/manifests/dev.json | 2 +- scripts/manifests/firefox.json | 2 +- scripts/manifests/thunderbirdmanifest.json | 2 +- src/manifest.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/manifests/chromemanifest.json b/scripts/manifests/chromemanifest.json index 0cf76ba..76bca51 100644 --- a/scripts/manifests/chromemanifest.json +++ b/scripts/manifests/chromemanifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extensionName__", "short_name": "__MSG_extensionNameShort__", - "version": "0.8", + "version": "0.9", "author": "Teal Dulcet, rugk", "description": "__MSG_extensionDescription__", diff --git a/scripts/manifests/dev.json b/scripts/manifests/dev.json index cba1246..65643e1 100644 --- a/scripts/manifests/dev.json +++ b/scripts/manifests/dev.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Unicodify DEV VERSION", "short_name": "__MSG_extensionNameShort__", - "version": "0.8", + "version": "0.9", "author": "Teal Dulcet, rugk", "description": "__MSG_extensionDescription__", diff --git a/scripts/manifests/firefox.json b/scripts/manifests/firefox.json index 0bf7869..d21d6d5 100644 --- a/scripts/manifests/firefox.json +++ b/scripts/manifests/firefox.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extensionName__", "short_name": "__MSG_extensionNameShort__", - "version": "0.8", + "version": "0.9", "author": "Teal Dulcet, rugk", "description": "__MSG_extensionDescription__", diff --git a/scripts/manifests/thunderbirdmanifest.json b/scripts/manifests/thunderbirdmanifest.json index 7c4bd79..c6a32bf 100644 --- a/scripts/manifests/thunderbirdmanifest.json +++ b/scripts/manifests/thunderbirdmanifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extensionName__", "short_name": "__MSG_extensionNameShort__", - "version": "0.8", + "version": "0.9", "author": "Teal Dulcet, rugk", "description": "__MSG_extensionDescription__", diff --git a/src/manifest.json b/src/manifest.json index cba1246..65643e1 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Unicodify DEV VERSION", "short_name": "__MSG_extensionNameShort__", - "version": "0.8", + "version": "0.9", "author": "Teal Dulcet, rugk", "description": "__MSG_extensionDescription__",