From b6f417cd75effb5b3f170517c2818593f178e550 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Tue, 8 Aug 2023 07:13:12 -0700 Subject: [PATCH 1/4] Added emoji autocorrect. --- .github/FUNDING.yml | 1 + assets/texts/en/privacy.txt | 2 +- scripts/manifests/chromemanifest.json | 10 + scripts/manifests/dev.json | 11 +- scripts/manifests/firefox.json | 14 +- scripts/manifests/thunderbirdmanifest.json | 6 +- src/_locales/en/messages.json | 64 ++-- src/background/background.js | 2 + src/background/modules/ContextMenu.js | 3 +- src/background/modules/InstallUpgrade.js | 2 +- src/common/modules/AutocorrectHandler.js | 293 +++++++++++++++ src/common/modules/EmojiInteraction.js | 2 +- src/common/modules/LanguageHelper.js | 2 +- src/common/modules/MobileHelper.js | 2 +- src/common/modules/PageHandler.js | 8 +- .../modules/data/BrowserCommunicationTypes.js | 2 + src/common/modules/data/DefaultSettings.js | 7 + src/common/modules/data/Symbols.js | 168 +++++++++ src/common/modules/data/Tips.js | 2 +- src/content_scripts/autocorrect.js | 351 ++++++++++++++++++ src/manifest.json | 14 +- src/options/modules/CustomOptionTriggers.js | 64 +++- src/options/modules/ManualAdjustments.js | 3 +- src/options/options.html | 74 +++- src/options/options.js | 2 +- src/popup/index.js | 6 +- src/popup/module/ConfirmationHint.js | 2 +- 27 files changed, 1050 insertions(+), 67 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 src/common/modules/AutocorrectHandler.js create mode 100644 src/common/modules/data/Symbols.js create mode 100644 src/content_scripts/autocorrect.js diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..e947178 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [rugk, tdulcet] diff --git a/assets/texts/en/privacy.txt b/assets/texts/en/privacy.txt index 4779d47..d77a94d 100644 --- a/assets/texts/en/privacy.txt +++ b/assets/texts/en/privacy.txt @@ -5,5 +5,5 @@ An explanation of all permissions, this add-on requests, can be found at https:/ == THIRD-PARTY SERVICES == This ADD-ON uses the โ€œsync storageโ€ of your browser to store the settings. If the USER enables โ€œSyncโ€ in the browser, the settings are uploaded and synchronized across your devices connected to your account. If you do not do, the data is only stored locally on your device. -In Mozilla Firefox the data is end-to-end encrypted before getting uploaded and stored on servers by Mozilla. +In Mozilla Firefox, the data is end-to-end encrypted before being uploaded and stored on servers by Mozilla. See https://accounts.firefox.com/legal/privacy and https://www.mozilla.org/privacy/firefox/#c-privacy-topic-8 for Mozilla's privacy policies on that topic. diff --git a/scripts/manifests/chromemanifest.json b/scripts/manifests/chromemanifest.json index 0fa8d94..5272040 100644 --- a/scripts/manifests/chromemanifest.json +++ b/scripts/manifests/chromemanifest.json @@ -36,6 +36,16 @@ "background": { "page": "background/background.html" }, + "content_scripts": [ + { + "matches": [""], + "all_frames": true, + "js": [ + "browser-polyfill.js", + "content_scripts/autocorrect.js" + ] + } + ], "commands": { "_execute_browser_action": { "suggested_key": { diff --git a/scripts/manifests/dev.json b/scripts/manifests/dev.json index 47ec2cc..d796e75 100644 --- a/scripts/manifests/dev.json +++ b/scripts/manifests/dev.json @@ -34,6 +34,13 @@ "background": { "page": "background/background.html" }, + "content_scripts": [ + { + "matches": [""], + "all_frames": true, + "js": ["content_scripts/autocorrect.js"] + } + ], "commands": { "_execute_browser_action": { "suggested_key": { @@ -65,10 +72,10 @@ ], // "search" currently not requested though, see https://discourse.mozilla.org/t/why-do-we-need-an-extra-permission-simply-for-starting-a-search/41174?u=rugkx - "applications": { + "browser_specific_settings": { "gecko": { "id": "awesome-emoji-picker@rugk.github.io", - "strict_min_version": "74.0" + "strict_min_version": "87.0" } } } diff --git a/scripts/manifests/firefox.json b/scripts/manifests/firefox.json index f7fecc0..d8c9289 100644 --- a/scripts/manifests/firefox.json +++ b/scripts/manifests/firefox.json @@ -34,6 +34,13 @@ "background": { "page": "background/background.html" }, + "content_scripts": [ + { + "matches": [""], + "all_frames": true, + "js": ["content_scripts/autocorrect.js"] + } + ], "commands": { "_execute_browser_action": { "suggested_key": { @@ -59,14 +66,15 @@ "optional_permissions": [ "clipboardWrite", - "search" + "search", + "tabs" ], // "search" currently not requested though, see https://discourse.mozilla.org/t/why-do-we-need-an-extra-permission-simply-for-starting-a-search/41174?u=rugkx - "applications": { + "browser_specific_settings": { "gecko": { "id": "awesome-emoji-picker@rugk.github.io", - "strict_min_version": "74.0" + "strict_min_version": "87.0" } } } diff --git a/scripts/manifests/thunderbirdmanifest.json b/scripts/manifests/thunderbirdmanifest.json index 0256ff1..b1fe38d 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.9.1", + "version": "0.9.4", "author": "rugk, Teal Dulcet", "description": "__MSG_extensionDescription__", @@ -60,10 +60,10 @@ "clipboardWrite" ], - "applications": { + "browser_specific_settings": { "gecko": { "id": "awesome-emoji-picker@rugk.github.io", - "strict_min_version": "87.0" + "strict_min_version": "91.0" } } } diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 314f957..6980d8f 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -9,7 +9,7 @@ "description": "Short name of the extension. I suggest not to translate it." }, "extensionDescription": { - "message": "This add-on provides you with a modern emoji picker that you can use to find emojis. You can then copy it to the clipboard or insert emoji into the active web page.", + "message": "This add-on provides you with a modern emoji picker that you can use to find emojis. You can then copy it to the clipboard or insert emoji into the active web page.", "description": "Description of the extension." }, "browserActionButtonTitle": { @@ -90,11 +90,11 @@ // tips "tipYouLikeAddon": { "message": "You like this add-on?", - "description": "A tip shown to remind the user to rate the addon." + "description": "A tip shown to remind the user to rate the add-on." }, "tipYouLikeAddonButton": { "message": "Rate it", - "description": "Button for the tip shown to remind the user to rate the addon." + "description": "Button for the tip shown to remind the user to rate the add-on." }, "tipPopupCodeHotkey": { "message": "Remember the hot key for this add-on is Ctrl+Shift+Period.", @@ -144,7 +144,7 @@ "description": "The title for a settings group." }, "titleBehaviour": { - "message": "Behaviour", + "message": "Behavior", "description": "The title for a settings group." }, @@ -190,23 +190,23 @@ }, "optionEmojiSize16px": { "message": "Small (16px)", - "description": "Option of the emoji size. Do mention a 'human-readbable' description first, followed by the technical pixel size." + "description": "Option of the emoji size. Do mention a 'human-readable' description first, followed by the technical pixel size." }, "optionEmojiSize24px": { "message": "Medium (24px)", - "description": "Option of the emoji size. Do mention a 'human-readbable' description first, followed by the technical pixel size." + "description": "Option of the emoji size. Do mention a 'human-readable' description first, followed by the technical pixel size." }, "optionEmojiSize32px": { "message": "Large (32px)", - "description": "Option of the emoji size. Do mention a 'human-readbable' description first, followed by the technical pixel size." + "description": "Option of the emoji size. Do mention a 'human-readable' description first, followed by the technical pixel size." }, "optionEmojiSize40px": { "message": "Very Large (40px)", - "description": "Option of the emoji size. Do mention a 'human-readbable' description first, followed by the technical pixel size." + "description": "Option of the emoji size. Do mention a 'human-readable' description first, followed by the technical pixel size." }, "optionEmojiSize48px": { "message": "Extra Large (48px)", - "description": "Option of the emoji size. Do mention a 'human-readbable' description first, followed by the technical pixel size." + "description": "Option of the emoji size. Do mention a 'human-readable' description first, followed by the technical pixel size." }, "optionEmojiPickerPerLine": { "message": "Width of picker:", @@ -259,7 +259,7 @@ }, "optionCopyEmojiColons": { - "message": "Use the $COLON$ code instead of the Emoji unicode.", + "message": "Use the $COLON$ shortcode instead of the Unicode Emoji.", "description": "This is an option shown in the add-on settings. You can also translate 'code' with 'syntax'.", "placeholders": { "colon": { @@ -414,91 +414,91 @@ // please DO READ this section for translating the emoji-specific terms: https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc "emojiMartSearch": { "message": "Search", - "description": "Localisation of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartClear": { "message": "Clear", - "description": "Localisation of emoji-mart, the emoji-picker. Accessible label on \"clear\" button. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of emoji-mart, the emoji-picker. Accessible label on \"clear\" button. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartNoEmojiFound": { "message": "No Emoji Found", - "description": "Localisation of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartSkinText": { "message": "Choose your default skin tone", - "description": "Localisation of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartCategorySearch": { "message": "Search Results", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartCategoryRecent": { "message": "Frequently Used", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartCategoryPeople": { "message": "Smileys & People", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryNature": { "message": "Animals & Nature", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryFoods": { "message": "Food & Drink", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryActivity": { "message": "Activity", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryPlaces": { "message": "Travel & Places", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryObjects": { "message": "Objects", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategorySymbols": { "message": "Symbols", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryFlags": { "message": "Flags", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartCategoryCustom": { "message": "Custom", - "description": "Localisation of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of an emoji category option of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartCategoriesLabel": { "message": "Emoji categories", - "description": "Localisation of emoji-mart, the emoji-picker. Accessible title for the list of categories. See: https://github.com/missive/emoji-mart#i18n" + "description": "Localization of emoji-mart, the emoji-picker. Accessible title for the list of categories. See: https://github.com/missive/emoji-mart#i18n" }, "emojiMartSkintone1": { "message": "Default Skin Tone", - "description": "Localisation of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartSkintone2": { "message": "Light Skin Tone", - "description": "Localisation of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartSkintone3": { "message": "Medium-Light Skin Tone", - "description": "Localisation of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartSkintone4": { "message": "Medium Skin Tone", - "description": "Localisation of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartSkintone5": { "message": "Medium-Dark Skin Tone", - "description": "Localisation of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, "emojiMartSkintone6": { "message": "Dark Skin Tone", - "description": "Localisation of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" + "description": "Localization of the skin tone of emoji-mart, the emoji-picker. See: https://github.com/missive/emoji-mart#i18n and https://github.com/rugk/awesome-emoji-picker/blob/master/CONTRIBUTING.md#translating-emoji-terms-categories-skin-names-etc" }, // ARIA labels/descriptions diff --git a/src/background/background.js b/src/background/background.js index b4ccf31..8a15378 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,8 +1,10 @@ import * as IconHandler from "/common/modules/IconHandler.js"; +import * as AutocorrectHandler from "/common/modules/AutocorrectHandler.js"; import * as ContextMenu from "./modules/ContextMenu.js"; import * as OmniboxSearch from "./modules/OmniboxSearch.js"; // init modules IconHandler.init(); +AutocorrectHandler.init(); ContextMenu.init(); OmniboxSearch.init(); diff --git a/src/background/modules/ContextMenu.js b/src/background/modules/ContextMenu.js index 441829f..3c15e50 100644 --- a/src/background/modules/ContextMenu.js +++ b/src/background/modules/ContextMenu.js @@ -56,8 +56,7 @@ async function applySettings(contextMenu) { id: EMOJI, title: menuText, command: commandToFind, - // Remove IS_THUNDERBIRD once https://bugzilla.mozilla.org/show_bug.cgi?id=1716976 is fixed - contexts: IS_THUNDERBIRD ? ["editable", "selection", "page"] : ["editable"] + contexts: ["editable"] }); } } diff --git a/src/background/modules/InstallUpgrade.js b/src/background/modules/InstallUpgrade.js index 2cf4a4a..af655d3 100644 --- a/src/background/modules/InstallUpgrade.js +++ b/src/background/modules/InstallUpgrade.js @@ -1,7 +1,7 @@ /** * Upgrades user data on installation of new updates. * - * Attention: Currently you must not include this script asyncronously. See + * Attention: Currently you must not include this script asynchronously. See * https://bugzilla.mozilla.org/show_bug.cgi?id=1506464 for details. * * @module InstallUpgrade diff --git a/src/common/modules/AutocorrectHandler.js b/src/common/modules/AutocorrectHandler.js new file mode 100644 index 0000000..1ea4a7f --- /dev/null +++ b/src/common/modules/AutocorrectHandler.js @@ -0,0 +1,293 @@ +"use strict"; + +import * as AddonSettings from "/common/modules/AddonSettings/AddonSettings.js"; +import * as BrowserCommunication from "/common/modules/BrowserCommunication/BrowserCommunication.js"; + +import { COMMUNICATION_MESSAGE_TYPE } from "/common/modules/data/BrowserCommunicationTypes.js"; +import * as symbols from "/common/modules/data/Symbols.js"; +// Not actually a module +import * as emojimart from "/common/lib/emoji-mart-embed/dist/emoji-mart.js"; + +const settings = { + enabled: null, + autocorrectEmojis: null, + autocorrectEmojiShortcodes: null, + autocomplete: null, + autocompleteSelect: null +}; + +// Leaf node +const LEAF = Symbol("leaf"); + +let autocorrections = {}; + +// Longest autocorrection +let longest = 0; + +let symbolpatterns = []; +// Exceptions, do not autocorrect for these patterns +let antipatterns = []; + +const emojiShortcodes = {}; + +// Chrome +// Adapted from: https://github.com/mozilla/webextension-polyfill/blob/master/src/browser-polyfill.js +const IS_CHROME = Object.getPrototypeOf(browser) !== Object.prototype; + +/** + * Traverse Trie tree of objects to create RegEx. + * + * @param {Object.} tree + * @returns {string} + */ +function createRegEx(tree) { + const alternatives = []; + const characterClass = []; + + // Escape special characters + const regExSpecialChars = /[.*+?^${}()|[\]\\]/gu; + + for (const char in tree) { + if (char) { + const escaptedChar = char.replace(regExSpecialChars, "\\$&"); + + const atree = tree[char]; + if (!(LEAF in atree && Object.keys(atree).length === 1)) { + const recurse = createRegEx(atree); + alternatives.push(recurse + escaptedChar); + // alternatives.push(escaptedChar + recurse); + } else { + characterClass.push(escaptedChar); + } + } + } + + if (characterClass.length) { + alternatives.push(characterClass.length === 1 ? characterClass[0] : `[${characterClass.join("")}]`); + } + + let result = alternatives.length === 1 ? alternatives[0] : `(?:${alternatives.join("|")})`; + + if (LEAF in tree) { + if (characterClass.length || alternatives.length > 1) { + result += "?"; + } else { + result = `(?:${result})?`; + } + } + + return result; +} + +/** + * Convert autocorrections into Trie tree of objects. + * + * @param {string[]} arr + * @returns {string} + */ +function createTree(arr) { + const tree = {}; + + arr.sort((a, b) => b.length - a.length); + + for (const str of arr) { + let temp = tree; + + for (const char of Array.from(str).reverse()) { + // for (const char of str) { + if (!(char in temp)) { + temp[char] = {}; + } + temp = temp[char]; + } + + // Leaf node + temp[LEAF] = true; + } + + Object.freeze(tree); + return createRegEx(tree); +} + +/** + * Apply new autocorrect settings and create regular expressions. + * + * @returns {void} + */ +function applySettings() { + const start = performance.now(); + autocorrections = {}; + + // Add all symbols to our autocorrections map, we want to replace + if (settings.autocorrectEmojis) { + Object.assign(autocorrections, symbols.emojis); + } + if (settings.autocorrectEmojiShortcodes) { + Object.assign(autocorrections, emojiShortcodes); + } + + // Longest autocorrection + longest = 0; + + for (const symbol in autocorrections) { + if (symbol.length > longest) { + longest = symbol.length; + } + } + console.log("Longest autocorrection", longest); + + symbolpatterns = createTree(Object.keys(autocorrections)); + + // Do not autocorrect for these patterns + antipatterns = []; + for (const x in autocorrections) { + let length = 0; + let index = x.length; + + for (const y in autocorrections) { + if (x === y) { + continue; + } + const aindex = x.indexOf(y); + if (aindex >= 0) { + if (aindex < index) { + index = aindex; + length = y.length; + } else if (aindex === index && y.length > length) { + length = y.length; + } + } + } + + if (length) { + length = x.length - (index + length); + if (length > 1) { + antipatterns.push(x.slice(0, -(length - 1))); + } + } + } + antipatterns = antipatterns.filter((item, pos) => antipatterns.indexOf(item) === pos); + console.log("Do not autocorrect for these patterns", antipatterns); + + antipatterns = createTree(antipatterns); + + symbolpatterns = new RegExp(`(${symbolpatterns})$`, "u"); + antipatterns = new RegExp(`(${antipatterns})$`, "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. + * + * @param {Object} autocorrect + * @returns {void} + */ +function setSettings(autocorrect) { + settings.enabled = autocorrect.enabled; + settings.autocorrectEmojis = autocorrect.autocorrectEmojis; + settings.autocorrectEmojiShortcodes = autocorrect.autocorrectEmojiShortcodes; + settings.autocomplete = autocorrect.autocompleteEmojiShortcodes; + settings.autocompleteSelect = autocorrect.autocompleteSelect; + + if (settings.enabled) { + applySettings(); + } +} + +/** + * Send autocorrect settings to content scripts. + * + * @param {Object} autocorrect + * @returns {void} + */ +function sendSettings(autocorrect) { + setSettings(autocorrect); + + browser.tabs.query({}).then((tabs) => { + for (const tab of tabs) { + browser.tabs.sendMessage( + tab.id, + { + type: COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT, + enabled: settings.enabled, + autocomplete: settings.autocomplete, + autocompleteSelect: settings.autocompleteSelect, + autocorrections: autocorrections, + longest: longest, + symbolpatterns: IS_CHROME ? symbolpatterns.source : symbolpatterns, + antipatterns: IS_CHROME ? antipatterns.source : antipatterns, + emojiShortcodes: emojiShortcodes + } + ).catch(onError); + } + }).catch(onError); +} + +/** + * Init autocorrect module. + * + * @public + * @returns {Promise} + */ +export async function init() { + const autocorrect = await AddonSettings.get("autocorrect"); + + for (const emoji of Object.values(emojiMart.emojiIndex.emojis)) { + if (!emoji.native) { + emojiShortcodes[emoji[1].colons] = emoji[1].native; + } else { + emojiShortcodes[emoji.colons] = emoji.native; + } + } + + Object.freeze(emojiShortcodes); + + setSettings(autocorrect); + + browser.runtime.onMessage.addListener((message, sender) => { + // console.log(message); + if (message.type === COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT) { + const response = { + type: COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_CONTENT, + enabled: settings.enabled, + autocomplete: settings.autocomplete, + autocompleteSelect: settings.autocompleteSelect, + autocorrections: autocorrections, + longest: longest, + symbolpatterns: IS_CHROME ? symbolpatterns.source : symbolpatterns, + antipatterns: IS_CHROME ? antipatterns.source : antipatterns, + emojiShortcodes: emojiShortcodes + }; + // console.log(response); + return Promise.resolve(response); + } + }); + + // Thunderbird + // Remove if part 3 of https://bugzilla.mozilla.org/show_bug.cgi?id=1630786#c4 is ever done + if (typeof messenger !== "undefined") { + browser.composeScripts.register({ + js: [ + { file: "/content_scripts/autocorrect.js" } + ] + }); + } +} + +BrowserCommunication.addListener(COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_BACKGROUND, (request) => { + // clear cache by reloading all options + // await AddonSettings.loadOptions(); + + return sendSettings(request.optionValue); +}); diff --git a/src/common/modules/EmojiInteraction.js b/src/common/modules/EmojiInteraction.js index b12387c..46f476e 100644 --- a/src/common/modules/EmojiInteraction.js +++ b/src/common/modules/EmojiInteraction.js @@ -7,7 +7,7 @@ import * as PageHandler from "./PageHandler.js"; * @param {string} text * @param {Object} options * @param {boolean} options.insertIntoPage whether to try to insert it into the active page - * @param {boolean} options.copyOnlyOnFallback whether to fallback to copying emojis (alos requires copyToClipboard=true) + * @param {boolean} options.copyOnlyOnFallback whether to fallback to copying emojis (also requires copyToClipboard=true) * @param {boolean} options.copyToClipboard whether the text should be copied into the page * @returns {Promise} * @throws {Error} diff --git a/src/common/modules/LanguageHelper.js b/src/common/modules/LanguageHelper.js index 1a45091..4c18170 100644 --- a/src/common/modules/LanguageHelper.js +++ b/src/common/modules/LanguageHelper.js @@ -8,7 +8,7 @@ /** * The list of languages the add-on is already translated into. * - * If the add-on is only partially translated into one of these langauges and + * If the add-on is only partially translated into one of these languages and * there may be a lot of strings that are missing it's localized version, it may * be removed from that list here. * diff --git a/src/common/modules/MobileHelper.js b/src/common/modules/MobileHelper.js index 7f7089a..c7937e0 100644 --- a/src/common/modules/MobileHelper.js +++ b/src/common/modules/MobileHelper.js @@ -1,5 +1,5 @@ /** - * Checks whether the user is running a mobvile version of the browser. + * Checks whether the user is running a mobile version of the browser. * * @public * @module MobileHelper diff --git a/src/common/modules/PageHandler.js b/src/common/modules/PageHandler.js index dd9abd6..62aec31 100644 --- a/src/common/modules/PageHandler.js +++ b/src/common/modules/PageHandler.js @@ -1,5 +1,5 @@ /** - * Handling fetures for current page. + * Handling features for current page. * */ @@ -18,8 +18,8 @@ export async function insertIntoPage(text) { active: true }); - const promises = tabs.map(async (tab) => { - // make sure content script is inserted + const promises = tabs.map(/* async */ (tab) => { + /* // make sure content script is inserted await browser.tabs.executeScript(tab.id, { code: "insertIntoPage;", allFrames: true, @@ -32,7 +32,7 @@ export async function insertIntoPage(text) { allFrames: true, runAt: "document_end" }); - }); + }); */ // send request to insert emoji // This will not work in Manifest V3: https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#executing-arbitrary-strings diff --git a/src/common/modules/data/BrowserCommunicationTypes.js b/src/common/modules/data/BrowserCommunicationTypes.js index dac565c..541ac91 100644 --- a/src/common/modules/data/BrowserCommunicationTypes.js +++ b/src/common/modules/data/BrowserCommunicationTypes.js @@ -14,5 +14,7 @@ */ export const COMMUNICATION_MESSAGE_TYPE = Object.freeze({ OMNIBAR_TOGGLE: "omnibarToggle", + AUTOCORRECT_BACKGROUND: "autocorrectBackground", + AUTOCORRECT_CONTENT: "autocorrectContent", CONTEXT_MENU: "contextMenu" }); diff --git a/src/common/modules/data/DefaultSettings.js b/src/common/modules/data/DefaultSettings.js index d8a84a9..c6ced10 100644 --- a/src/common/modules/data/DefaultSettings.js +++ b/src/common/modules/data/DefaultSettings.js @@ -31,6 +31,13 @@ const defaultSettings = { showConfirmationMessage: true, closePopup: true }, + autocorrect: { + enabled: false, + autocorrectEmojis: true, + autocorrectEmojiShortcodes: true, + autocompleteEmojiShortcodes: true, + autocompleteSelect: false + }, contextMenu: { insertEmoji: true }, diff --git a/src/common/modules/data/Symbols.js b/src/common/modules/data/Symbols.js new file mode 100644 index 0000000..08fbcc4 --- /dev/null +++ b/src/common/modules/data/Symbols.js @@ -0,0 +1,168 @@ +"use strict"; + +/** + * Emojis + * + * @public + * @const + * @type {Object.} + */ +export const emojis = Object.freeze({ + 100: "๐Ÿ’ฏ", + ZZZ: "๐Ÿ’ค", + "": "โš ๏ธ", + "(-)": "โ›”", + "(\\)": "๐Ÿšซ", // (\) + // "??": "โ‡", + "[]": "โฌœ", + "()": "โšช", + // "!?": "โ‰๏ธ", + COOL: "๐Ÿ†’", + "[COOL]": "๐Ÿ†’", + FREE: "๐Ÿ†“", + "[FREE]": "๐Ÿ†“", + NEW: "๐Ÿ†•", + "[NEW]": "๐Ÿ†•", + SOS: "๐Ÿ†˜", + "[SOS]": "๐Ÿ†˜", + "UP!": "๐Ÿ†™", + "[UP!]": "๐Ÿ†™", + "[BACK]": "๐Ÿ”™", + "[END]": "๐Ÿ”š", + "[ON!]": "๐Ÿ”›", + "[SOON]": "๐Ÿ”œ", + "[TOP]": "๐Ÿ”", + "[i]": "โ„น๏ธ", + "[OK]": "๐Ÿ†—", + "[VS]": "๐Ÿ†š", + "[x]": "โŽ", + "[*]": "*๏ธโƒฃ", + // ":+1:": "๐Ÿ‘", + // ":-1:": "๐Ÿ‘Ž", + "[->]": "โžก๏ธ", + "[<-]": "โฌ…๏ธ", + "[<->]": "โ†”๏ธ", + "[#]": "#๏ธโƒฃ", + "[0]": "0๏ธโƒฃ", + "[1]": "1๏ธโƒฃ", + "[2]": "2๏ธโƒฃ", + "[3]": "3๏ธโƒฃ", + "[4]": "4๏ธโƒฃ", + "[5]": "5๏ธโƒฃ", + "[6]": "6๏ธโƒฃ", + "[7]": "7๏ธโƒฃ", + "[8]": "8๏ธโƒฃ", + "[9]": "9๏ธโƒฃ", + "[10]": "๐Ÿ”Ÿ", + + // Slack (https://github.com/needim/wdt-emoji-bundle/blob/master/emoji.js#L1846-L1895) + ":o)": "๐Ÿต", + "=)": "๐Ÿ˜ƒ", + "=-)": "๐Ÿ˜ƒ", + ":>": "๐Ÿ˜†", + ":->": "๐Ÿ˜†", + ";p": "๐Ÿ˜œ", + ";-p": "๐Ÿ˜œ", + ";b": "๐Ÿ˜œ", + ";-b": "๐Ÿ˜œ", + + "(c)": "ยฉ๏ธ", + "(r)": "ยฎ๏ธ", + "(tm)": "โ„ข๏ธ", + "!!": "โ€ผ๏ธ", + ":(": "โ˜น๏ธ", // Frown + ":-(": "โ˜น๏ธ", // Frown + ":|": "๐Ÿ˜", + ":-|": "๐Ÿ˜", + ":D": "๐Ÿ˜ƒ", // Laughing + ":-D": "๐Ÿ˜ƒ", // Laughing + // ":o": "๐Ÿ˜ฒ", + ":-o": "๐Ÿ˜ฒ", + // ":p": "๐Ÿ˜", // Tongue-out + ":-p": "๐Ÿ˜", // Tongue-out + // ":b": "๐Ÿ˜", // Tongue-out + ":-b": "๐Ÿ˜", // Tongue-out + ";)": "๐Ÿ˜‰", // Wink + ";-)": "๐Ÿ˜‰", // Wink + ":o": "๐Ÿ˜ซ", // Yell + "8-)": "๐Ÿ˜Ž", // Cool + ":$": "๐Ÿค‘", // Money-Mouth + ":-$": "๐Ÿค‘", // Money-Mouth + ":!": "๐Ÿ˜ฌ", // Foot-in-Mouth + ":-!": "๐Ÿ˜ฌ", // Foot-in-Mouth + "O:)": "๐Ÿ˜‡", // Innocent + "O:-)": "๐Ÿ˜‡", // Innocent + ":'(": "๐Ÿ˜ข", // Cry + ":'-(": "๐Ÿ˜ข", // Cry + ":X": "๐Ÿค", // Lips-are-Sealed + ":-X": "๐Ÿค", // Lips-are-Sealed + + // Pidgin + ">:(": "๐Ÿ˜ ", + ">:-(": "๐Ÿ˜ ", + "@->--": "๐ŸŒน", + + // https://github.com/wooorm/emoticon/blob/master/support.md + ":,(": "๐Ÿ˜ข", + ":,-(": "๐Ÿ˜ข", + "]:(": "๐Ÿ‘ฟ", + "]:-(": "๐Ÿ‘ฟ", + "o:)": "๐Ÿ˜‡", + "o:-)": "๐Ÿ˜‡", + "0:)": "๐Ÿ˜‡", + "0:-)": "๐Ÿ˜‡", + ":,)": "๐Ÿ˜‚", + ":,-)": "๐Ÿ˜‚", + ":')": "๐Ÿ˜‚", + ":'-)": "๐Ÿ˜‚", + "X-)": "๐Ÿ˜†", + ":-": "๐Ÿ˜ถ", + // ":O": "๐Ÿ˜ฒ", + ":-O": "๐Ÿ˜ฒ", + // ":0": "๐Ÿ˜ฒ", + ":-0": "๐Ÿ˜ฒ", + ":@": "๐Ÿ˜ก", + ":-@": "๐Ÿ˜ก", + "]:)": "๐Ÿ˜ˆ", + "]:-)": "๐Ÿ˜ˆ", + ":,'(": "๐Ÿ˜ญ", + ":,'-(": "๐Ÿ˜ญ", + ":',(": "๐Ÿ˜ญ", + ":',-(": "๐Ÿ˜ญ", + // ":P": "๐Ÿ˜", + ":-P": "๐Ÿ˜", + ";P": "๐Ÿ˜œ", + ";-P": "๐Ÿ˜œ", + ",:(": "๐Ÿ˜“", + ",:-(": "๐Ÿ˜“", + "':(": "๐Ÿ˜“", + "':-(": "๐Ÿ˜“", + ",:)": "๐Ÿ˜…", + ",:-)": "๐Ÿ˜…", + "':)": "๐Ÿ˜…", + "':-)": "๐Ÿ˜…", + // ":s": "๐Ÿ˜’", + ":-s": "๐Ÿ˜’", + // ":z": "๐Ÿ˜’", + ":-z": "๐Ÿ˜’", + // ":S": "๐Ÿ˜’", + ":-S": "๐Ÿ˜’", + // ":Z": "๐Ÿ˜’", + ":-Z": "๐Ÿ˜’" +}); diff --git a/src/common/modules/data/Tips.js b/src/common/modules/data/Tips.js index 083af07..8ef5216 100644 --- a/src/common/modules/data/Tips.js +++ b/src/common/modules/data/Tips.js @@ -111,7 +111,7 @@ const tipArray = [ tipSpec.actionButton.action = await getBrowserValue({ firefox: "https://addons.mozilla.org/firefox/addon/awesome-emoji-picker/reviews/?utm_source=addon-tips&utm_medium=addon&utm_content=addon-tips-tipYouLikeAddon&utm_campaign=addon-tips", thunderbird: "https://addons.thunderbird.net/thunderbird/addon/awesome-emoji-picker/reviews/?utm_source=addon-tips&utm_medium=addon&utm_content=addon-tips-tipYouLikeAddon&utm_campaign=addon-tips", - chrome: "https://chrome.google.com/webstore/detail/awesome-emoji-picker/?utm_source=addon-tips&utm_medium=addon&utm_content=addon-tips-tipYouLikeAddon&utm_campaign=addon-tips", + chrome: "https://chrome.google.com/webstore/detail/awesome-emoji-picker/reviews/?utm_source=addon-tips&utm_medium=addon&utm_content=addon-tips-tipYouLikeAddon&utm_campaign=addon-tips" }); return null; } diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js new file mode 100644 index 0000000..02fb8b4 --- /dev/null +++ b/src/content_scripts/autocorrect.js @@ -0,0 +1,351 @@ +"use strict"; + +// communication type +// directly include magic constant as a workaround as we cannot import modules in content scripts due to https://bugzilla.mozilla.org/show_bug.cgi?id=1451545 +const AUTOCORRECT_CONTENT = "autocorrectContent"; + +let insertedText; // Last insert text +let deletedText; // Last deleted text +let lastTarget; // Last target +let lastCaretPosition; // Last caret position + +let enabled = false; +let autocomplete = true; +let autocompleteSelect = false; + +let autocorrections = {}; + +let longest = 0; + +// Regular expressions +let symbolpatterns = null; +// Exceptions, do not autocorrect for these patterns +let antipatterns = null; + +let emojiShortcodes = {}; + +let running = false; + +// Chrome +// Adapted from: https://github.com/mozilla/webextension-polyfill/blob/master/src/browser-polyfill.js +const IS_CHROME = Object.getPrototypeOf(browser) !== Object.prototype; + +/** + * Get caret position. + * + * @param {HTMLElement} target + * @returns {number|null} + */ +function getCaretPosition(target) { + // ContentEditable elements + if (target.isContentEditable || document.designMode === "on") { + target.focus(); + const _range = document.getSelection().getRangeAt(0); + if (!_range.collapsed) { + return null; + } + const range = _range.cloneRange(); + const temp = document.createTextNode("\0"); + range.insertNode(temp); + const caretposition = target.innerText.indexOf("\0"); + temp.remove(); + return caretposition; + } + // input and textarea fields + if (target.selectionStart !== target.selectionEnd) { + return null; + } + return target.selectionStart; +} + +/** + * Insert at 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} + */ +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; + } + + // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 + if (typeof target.setRangeText === "function") { + 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; + } + } + + throw new Error("nothing selected"); +} + +/** + * Insert at caret in the given element and select. + * + * @param {HTMLElement} target + * @param {string} atext + * @returns {void} + */ +function insertAndSelect(target, atext) { + insertAtCaret(target, atext); + // ContentEditable elements + if (target.isContentEditable || document.designMode === "on") { + const range = document.getSelection().getRangeAt(0); + range.setStart(range.startContainer, range.startOffset - atext.length); + } + // input and textarea fields + else { + target.selectionStart -= atext.length; + } +} + +/** + * Insert into page. + * + * @param {string} atext + * @returns {void} + */ +function insertIntoPage(atext) { + return insertAtCaret(document.activeElement, atext); +} + +/** + * Count Unicode characters. + * Adapted from: https://blog.jonnew.com/posts/poo-dot-length-equals-two + * Intl.Segmenter is not yet supported by Firefox/Thunderbird: https://bugzilla.mozilla.org/show_bug.cgi?id=1423593 + * + * @param {string} str + * @returns {number} + */ +function countChars(str) { + // removing the joiners + const split = str.split("\u{200D}"); + let count = 0; + + for (const s of split) { + // removing the variation selectors + count += Array.from(s.replaceAll(/[\uFE00-\uFE0F]/gu, "")).length; + } + + return count; +} + +/** + * Delete at caret. + * + * @param {HTMLElement} target + * @param {string} atext + * @returns {void} + */ +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); + } + } + // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 + else if (typeof target.setRangeText === "function") { + 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); + } + } +} + +/** + * Autocorrect on text input even by evaluating the keys and replacing the characters/string. + * + * @param {InputEvent} event + * @returns {void} + */ +function autocorrect(event) { + // console.log('beforeinput', event.inputType, event.data); + if (!(event.inputType === "insertText" || event.inputType === "insertCompositionText" || event.inputType === "insertParagraph" || event.inputType === "insertLineBreak")) { + return; + } + if (!symbolpatterns) { + throw new Error("Emoji autocorrect settings have not been received. Do not autocorrect."); + } + if (running) { + return; + } + running = true; + const target = event.target; + const caretposition = getCaretPosition(target); + if (caretposition) { + const value = target.value || target.innerText; + let deletecount = 0; + let insert = event.inputType === "insertLineBreak" || event.inputType === "insertParagraph" ? "\n" : event.data; + const inserted = insert; + let output = false; + const previousText = value.slice(caretposition < longest ? 0 : caretposition - longest, caretposition); + const regexResult = symbolpatterns.exec(previousText); + // Autocorrect :colon: Emoji Shortcodes and/or Emoticon Emojis and/or Unicode Symbols + if (regexResult) { + const length = longest - 1; + const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; + const aregexResult = symbolpatterns.exec(text); + const aaregexResult = antipatterns.exec(text); + if (!aaregexResult && (!aregexResult || (caretposition <= longest ? regexResult.index < aregexResult.index : regexResult.index <= aregexResult.index))) { + insert = autocorrections[regexResult[0]] + inserted; + deletecount = regexResult[0].length; + output = true; + } + } else { + // Autocomplete :colon: Emoji Shortcodes + if (autocomplete) { + // Emoji Shortcode + const re = /:[a-z0-9-+_]+$/u; + const length = longest - 2; + const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; + const regexResult = re.exec(text); + if (regexResult) { + const aregexResult = Object.keys(emojiShortcodes).filter((item) => item.indexOf(regexResult[0]) === 0); + if (aregexResult.length >= 1 && (regexResult[0].length > 2 || aregexResult[0].length === 3)) { + const ainsert = aregexResult[0].slice(regexResult[0].length); + if (autocompleteSelect || aregexResult.length > 1) { + event.preventDefault(); + + insertAtCaret(target, inserted); + insertAndSelect(target, ainsert); + } else { + insert = inserted + ainsert; + output = true; + } + } + } + } + } + if (output) { + event.preventDefault(); + + const text = deletecount ? value.slice(caretposition - deletecount, caretposition) : ""; + if (text) { + lastTarget = null; + deleteCaret(target, text); + } + insertAtCaret(target, insert); + + insertedText = insert; + deletedText = text + inserted; + console.debug("Autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", deletedText, insertedText); + + lastTarget = target; + lastCaretPosition = caretposition - deletecount + insert.length; + + if (deletedText && insertedText.startsWith(deletedText)) { + insertedText = insertedText.slice(deletedText.length); + deletedText = ""; + } + } + } + running = false; +} + +/** + * Undo autocorrect in case the backspace has been pressed. + * + * @param {InputEvent} event + * @returns {void} + */ +function undoAutocorrect(event) { + // console.log('beforeinput', event.inputType, event.data); + // Backspace + if (event.inputType !== "deleteContentBackward") { + return; + } + if (running) { + return; + } + running = true; + const target = event.target; + const caretposition = getCaretPosition(target); + if (caretposition) { + if (target === lastTarget && caretposition === lastCaretPosition) { + event.preventDefault(); + + if (insertedText) { + lastTarget = null; + deleteCaret(target, insertedText); + } + if (deletedText) { + insertAtCaret(target, deletedText); + } + console.debug("Undo autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", insertedText, deletedText); + } + + lastTarget = null; + } + running = false; +} + +/** + * Handle response from the autocorrect module. + * + * @param {Object} message + * @param {Object} sender + * @returns {void} + */ +function handleResponse(message, sender) { + if (message.type !== AUTOCORRECT_CONTENT) { + return; + } + enabled = message.enabled; + autocomplete = message.autocomplete; + autocompleteSelect = message.autocompleteSelect; + autocorrections = message.autocorrections; + longest = message.longest; + symbolpatterns = IS_CHROME ? new RegExp(message.symbolpatterns, "u") : message.symbolpatterns; + antipatterns = IS_CHROME ? new RegExp(message.antipatterns, "u") : message.antipatterns; + emojiShortcodes = message.emojiShortcodes; + // console.log(message); + + if (enabled) { + addEventListener("beforeinput", undoAutocorrect, true); + addEventListener("beforeinput", autocorrect, true); + } else { + removeEventListener("beforeinput", undoAutocorrect, true); + removeEventListener("beforeinput", autocorrect, true); + } +} + +/** + * Handle errors from messages and responses. + * + * @param {string} error + * @returns {void} + */ +function handleError(error) { + console.error(`Error: ${error}`); +} + +browser.runtime.sendMessage({ type: AUTOCORRECT_CONTENT }).then(handleResponse, handleError); +browser.runtime.onMessage.addListener(handleResponse); +console.log("AwesomeEmoji autocorrect module loaded."); diff --git a/src/manifest.json b/src/manifest.json index 8cc4edc..edf919e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -34,6 +34,13 @@ "background": { "page": "background/background.html" }, + "content_scripts": [ + { + "matches": [""], + "all_frames": true, + "js": ["content_scripts/autocorrect.js"] + } + ], "commands": { "_execute_browser_action": { "suggested_key": { @@ -60,14 +67,15 @@ "optional_permissions": [ "clipboardWrite", - "search" + "search", + "tabs" ], // "search" currently not requested though, see https://discourse.mozilla.org/t/why-do-we-need-an-extra-permission-simply-for-starting-a-search/41174?u=rugkx - "applications": { + "browser_specific_settings": { "gecko": { "id": "awesome-emoji-picker@rugk.github.io", - "strict_min_version": "74.0" + "strict_min_version": "87.0" } } } diff --git a/src/options/modules/CustomOptionTriggers.js b/src/options/modules/CustomOptionTriggers.js index 0bcb390..70093d0 100644 --- a/src/options/modules/CustomOptionTriggers.js +++ b/src/options/modules/CustomOptionTriggers.js @@ -16,12 +16,19 @@ import * as IconHandler from "/common/modules/IconHandler.js"; const CLIPBOARD_WRITE_PERMISSION = { permissions: ["clipboardWrite"] }; +const TABS_PERMISSION = { + permissions: ["tabs"] +}; const MESSAGE_EMOJI_COPY_PERMISSION_SEARCH = "searchActionCopyPermissionInfo"; +const MESSAGE_TABS_PERMISSION = "tabsPermissionInfo"; + +// Thunderbird +// https://bugzilla.mozilla.org/show_bug.cgi?id=1641573 +const IS_THUNDERBIRD = typeof messenger !== "undefined"; /** * Adjust UI if QR code size option is changed. * - * @function * @private * @param {boolean} optionValue * @param {string} [option] @@ -70,6 +77,50 @@ function applyPickerResultPermissions(optionValue) { return retPromise; } +/** + * Requests the permission for autocorrect settings. + * + * @private + * @param {Object} optionValue + * @param {string} [option] + * @param {Object} [event] + * @returns {Promise} + */ +function applyAutocorrectPermissions(optionValue, option, event) { + if (optionValue.enabled) { + document.getElementById("autocorrectEmojiShortcodes").disabled = false; + document.getElementById("autocorrectEmojis").disabled = false; + document.getElementById("autocompleteEmojiShortcodes").disabled = false; + document.getElementById("autocompleteSelect").disabled = false; + } else { + document.getElementById("autocorrectEmojiShortcodes").disabled = true; + document.getElementById("autocorrectEmojis").disabled = true; + document.getElementById("autocompleteEmojiShortcodes").disabled = true; + document.getElementById("autocompleteSelect").disabled = true; + } + + let retPromise; + + if (!PermissionRequest.isPermissionGranted(TABS_PERMISSION) // and not already granted + ) { + retPromise = PermissionRequest.requestPermission( + TABS_PERMISSION, + MESSAGE_TABS_PERMISSION, + event + ); + } else { + PermissionRequest.cancelPermissionPrompt(TABS_PERMISSION, MESSAGE_TABS_PERMISSION); + } + + // trigger update for current session + browser.runtime.sendMessage({ + type: COMMUNICATION_MESSAGE_TYPE.AUTOCORRECT_BACKGROUND, + optionValue: optionValue + }); + + return retPromise; +} + /** * Apply the new context menu settings. * @@ -331,7 +382,6 @@ function applyEmojiSearch(optionValue, option, event = {}) { * * This is basically the "init" method. * - * @function * @returns {Promise} */ export async function registerTrigger() { @@ -345,12 +395,13 @@ export async function registerTrigger() { // update slider status AutomaticSettings.Trigger.registerSave("pickerResult", applyPickerResultPermissions); + AutomaticSettings.Trigger.registerSave("autocorrect", applyAutocorrectPermissions); AutomaticSettings.Trigger.registerSave("contextMenu", applyContextMenuSettings); AutomaticSettings.Trigger.registerSave("popupIconColored", applyPopupIconColor); AutomaticSettings.Trigger.registerSave("emojiPicker", updatePerLineStatus); AutomaticSettings.Trigger.registerSave("emojiPicker", updateEmojiPerLineMaxViaEmojiSize); // Thunderbird - if (typeof messenger !== "undefined") { + if (IS_THUNDERBIRD) { document.getElementById("browser").style.display = "none"; } else { AutomaticSettings.Trigger.registerSave("emojiSearch", applyEmojiSearch); @@ -366,4 +417,11 @@ export async function registerTrigger() { document.getElementById("searchActionCopyPermissionInfo"), "permissionRequiredClipboardWrite" ); + await PermissionRequest.registerPermissionMessageBox( + TABS_PERMISSION, + MESSAGE_TABS_PERMISSION, + document.getElementById("tabsPermissionInfo"), + // "permissionRequiredTabs" // TODO: This will need to be localized + "Permission to send any updated options to your open tabs is required to prevent you having to reload all of them manually." + ); } diff --git a/src/options/modules/ManualAdjustments.js b/src/options/modules/ManualAdjustments.js index 81d70c7..5cd9e1a 100644 --- a/src/options/modules/ManualAdjustments.js +++ b/src/options/modules/ManualAdjustments.js @@ -13,7 +13,6 @@ export function init() { thunderbird: "https://addons.thunderbird.net/thunderbird/addon/unicodify-text-transformer/?utm_source=awesomeEmojiPicker-addon&utm_medium=addon&utm_content=awesomeEmojiPicker-addon-settings-inline&utm_campaign=awesomeEmojiPicker-addon-settings-inline", chrome: "https://chrome.google.com/webstore/detail/unicodify-text-transformer/?utm_source=awesomeEmojiPicker-addon&utm_medium=addon&utm_content=awesomeEmojiPicker-addon-settings-inline&utm_campaign=awesomeEmojiPicker-addon-settings-inline" }).then((browserUrl) => { - // Uncomment this line after https://github.com/rugk/awesome-emoji-picker/pull/93 is merged. - // document.getElementById("link-unicodify").href = browserUrl; + document.getElementById("link-unicodify").href = browserUrl; }); } diff --git a/src/options/options.html b/src/options/options.html index f5bc9e5..007d05d 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -101,7 +101,7 @@

Appearance

- How many emojis should be shown per line in the emoji picker. This affects the width of the popup. + How many emojis should be shown per line in the emoji picker? This affects the width of the popup.
  • @@ -141,7 +141,7 @@

    Behavior

  • - +
    Only useful to enable on specific sites that support this kind of text.
  • @@ -161,6 +161,76 @@

    Behavior

    +
    +

    Emoji autocorrection

    + With the exception of the smart quotes, all the autocorrections are done after typing the first nonmatching character following the character sequence, such as a space (โฃ). Press Backspace (โŒซ) to undo an autocorrection.
    + + For Unicode Symbol autocorrection and Unicode font conversion from the context menu, please try our ฯ… Unicodify add-on. + +
    +
    + The Emoji autocorrect feature is experimental and may negatively affect some web pages. + + + +
    +
    + +
      +
      + +
      + +
    • +
      + + +
      +
    • + +
    • +
        +
      • +
        + + +
        + For example, this will replace :smiley:with ๐Ÿ˜ƒ and :apple:with ๐ŸŽ. +
      • + +
      • +
        + + +
        + For example, this will replace :)with โ˜บ๏ธ, :Dwith ๐Ÿ˜ƒ and :(with โ˜น๏ธ. +
      • + +
      • +
        + + +
        + For example, this will replace :apwith :apple:. +
          +
        • +
          + + +
          +
        • +
        +
      • +
      +
    • +
    +
    diff --git a/src/options/options.js b/src/options/options.js index eaaa670..9e9fe66 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -1,5 +1,5 @@ /** - * Starter module for addon settings site. + * Starter module for add-on settings site. * * @requires modules/OptionHandler */ diff --git a/src/popup/index.js b/src/popup/index.js index ef35f11..b74a5b3 100644 --- a/src/popup/index.js +++ b/src/popup/index.js @@ -52,14 +52,14 @@ export async function focusElement(element, retries = 20, delay = 50) { await wait(); - // if element is focussed, we are lucky + // if element is focused, we are lucky if (document.activeElement === element) { - console.log(element, "focussed with", retries, "retries left, at delay", delay); + console.log(element, "focused with", retries, "retries left, at delay", delay); return; } if (retries <= 0) { - throw new TypeError("no re-tries left for focussing"); // will be converted into rejected promise + throw new TypeError("no re-tries left for focusing"); // will be converted into rejected promise } await wait(delay); diff --git a/src/popup/module/ConfirmationHint.js b/src/popup/module/ConfirmationHint.js index 352e0ad..bb1de60 100644 --- a/src/popup/module/ConfirmationHint.js +++ b/src/popup/module/ConfirmationHint.js @@ -23,7 +23,7 @@ const elPanelTemplate = elTemplate.content.getElementById("confirmation-hint"); * @param {number} position.top manual position * @param {number} height the height of the popup * @param {number} width the width of the popup - * @returns {Object} the ficed position + * @returns {Object} the fixed position */ function keepMessageInsideOfPopup(position, height, width) { const TOLERANCE = 3; // px From e6a369a2d176ca6ee942e147985aac15dfa5ae83 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Thu, 10 Aug 2023 03:10:41 -0700 Subject: [PATCH 2/4] Fixed indentation of autocorrect content script. --- src/content_scripts/autocorrect.js | 420 ++++++++++++++--------------- 1 file changed, 210 insertions(+), 210 deletions(-) diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js index 02fb8b4..f9c4819 100644 --- a/src/content_scripts/autocorrect.js +++ b/src/content_scripts/autocorrect.js @@ -37,25 +37,25 @@ const IS_CHROME = Object.getPrototypeOf(browser) !== Object.prototype; * @returns {number|null} */ function getCaretPosition(target) { - // ContentEditable elements - if (target.isContentEditable || document.designMode === "on") { - target.focus(); - const _range = document.getSelection().getRangeAt(0); - if (!_range.collapsed) { - return null; - } - const range = _range.cloneRange(); - const temp = document.createTextNode("\0"); - range.insertNode(temp); - const caretposition = target.innerText.indexOf("\0"); - temp.remove(); - return caretposition; - } - // input and textarea fields - if (target.selectionStart !== target.selectionEnd) { - return null; - } - return target.selectionStart; + // ContentEditable elements + if (target.isContentEditable || document.designMode === "on") { + target.focus(); + const _range = document.getSelection().getRangeAt(0); + if (!_range.collapsed) { + return null; + } + const range = _range.cloneRange(); + const temp = document.createTextNode("\0"); + range.insertNode(temp); + const caretposition = target.innerText.indexOf("\0"); + temp.remove(); + return caretposition; + } + // input and textarea fields + if (target.selectionStart !== target.selectionEnd) { + return null; + } + return target.selectionStart; } /** @@ -68,32 +68,32 @@ function getCaretPosition(target) { * @returns {void} */ 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; - } + // 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; + } - // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 - if (typeof target.setRangeText === "function") { - const start = target.selectionStart; - const end = target.selectionEnd; + // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 + if (typeof target.setRangeText === "function") { + const start = target.selectionStart; + const end = target.selectionEnd; - if (start != null && end != null) { - target.setRangeText(atext); + if (start != null && end != null) { + target.setRangeText(atext); - target.selectionStart = target.selectionEnd = start + atext.length; + 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); + // Notify any possible listeners of the change + const event = document.createEvent("UIEvent"); + event.initEvent("input", true, false); + target.dispatchEvent(event); - return; - } - } + return; + } + } - throw new Error("nothing selected"); + throw new Error("nothing selected"); } /** @@ -104,16 +104,16 @@ function insertAtCaret(target, atext) { * @returns {void} */ function insertAndSelect(target, atext) { - insertAtCaret(target, atext); - // ContentEditable elements - if (target.isContentEditable || document.designMode === "on") { - const range = document.getSelection().getRangeAt(0); - range.setStart(range.startContainer, range.startOffset - atext.length); - } - // input and textarea fields - else { - target.selectionStart -= atext.length; - } + insertAtCaret(target, atext); + // ContentEditable elements + if (target.isContentEditable || document.designMode === "on") { + const range = document.getSelection().getRangeAt(0); + range.setStart(range.startContainer, range.startOffset - atext.length); + } + // input and textarea fields + else { + target.selectionStart -= atext.length; + } } /** @@ -123,7 +123,7 @@ function insertAndSelect(target, atext) { * @returns {void} */ function insertIntoPage(atext) { - return insertAtCaret(document.activeElement, atext); + return insertAtCaret(document.activeElement, atext); } /** @@ -135,16 +135,16 @@ function insertIntoPage(atext) { * @returns {number} */ function countChars(str) { - // removing the joiners - const split = str.split("\u{200D}"); - let count = 0; + // removing the joiners + const split = str.split("\u{200D}"); + let count = 0; - for (const s of split) { - // removing the variation selectors - count += Array.from(s.replaceAll(/[\uFE00-\uFE0F]/gu, "")).length; - } + for (const s of split) { + // removing the variation selectors + count += Array.from(s.replaceAll(/[\uFE00-\uFE0F]/gu, "")).length; + } - return count; + return count; } /** @@ -155,28 +155,28 @@ function countChars(str) { * @returns {void} */ 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); - } - } - // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 - else if (typeof target.setRangeText === "function") { - 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 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); + } + } + // Firefox input and textarea fields: https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 + else if (typeof target.setRangeText === "function") { + 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); + } + } } /** @@ -186,87 +186,87 @@ function deleteCaret(target, atext) { * @returns {void} */ function autocorrect(event) { - // console.log('beforeinput', event.inputType, event.data); - if (!(event.inputType === "insertText" || event.inputType === "insertCompositionText" || event.inputType === "insertParagraph" || event.inputType === "insertLineBreak")) { - return; - } - if (!symbolpatterns) { - throw new Error("Emoji autocorrect settings have not been received. Do not autocorrect."); - } - if (running) { - return; - } - running = true; - const target = event.target; - const caretposition = getCaretPosition(target); - if (caretposition) { - const value = target.value || target.innerText; - let deletecount = 0; - let insert = event.inputType === "insertLineBreak" || event.inputType === "insertParagraph" ? "\n" : event.data; - const inserted = insert; - let output = false; - const previousText = value.slice(caretposition < longest ? 0 : caretposition - longest, caretposition); - const regexResult = symbolpatterns.exec(previousText); - // Autocorrect :colon: Emoji Shortcodes and/or Emoticon Emojis and/or Unicode Symbols - if (regexResult) { - const length = longest - 1; - const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; - const aregexResult = symbolpatterns.exec(text); - const aaregexResult = antipatterns.exec(text); - if (!aaregexResult && (!aregexResult || (caretposition <= longest ? regexResult.index < aregexResult.index : regexResult.index <= aregexResult.index))) { - insert = autocorrections[regexResult[0]] + inserted; - deletecount = regexResult[0].length; - output = true; - } - } else { - // Autocomplete :colon: Emoji Shortcodes - if (autocomplete) { - // Emoji Shortcode - const re = /:[a-z0-9-+_]+$/u; - const length = longest - 2; - const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; - const regexResult = re.exec(text); - if (regexResult) { - const aregexResult = Object.keys(emojiShortcodes).filter((item) => item.indexOf(regexResult[0]) === 0); - if (aregexResult.length >= 1 && (regexResult[0].length > 2 || aregexResult[0].length === 3)) { - const ainsert = aregexResult[0].slice(regexResult[0].length); - if (autocompleteSelect || aregexResult.length > 1) { - event.preventDefault(); - - insertAtCaret(target, inserted); - insertAndSelect(target, ainsert); - } else { - insert = inserted + ainsert; - output = true; - } - } - } - } - } - if (output) { - event.preventDefault(); - - const text = deletecount ? value.slice(caretposition - deletecount, caretposition) : ""; - if (text) { - lastTarget = null; - deleteCaret(target, text); - } - insertAtCaret(target, insert); - - insertedText = insert; - deletedText = text + inserted; - console.debug("Autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", deletedText, insertedText); - - lastTarget = target; - lastCaretPosition = caretposition - deletecount + insert.length; - - if (deletedText && insertedText.startsWith(deletedText)) { - insertedText = insertedText.slice(deletedText.length); - deletedText = ""; - } - } - } - running = false; + // console.log('beforeinput', event.inputType, event.data); + if (!(event.inputType === "insertText" || event.inputType === "insertCompositionText" || event.inputType === "insertParagraph" || event.inputType === "insertLineBreak")) { + return; + } + if (!symbolpatterns) { + throw new Error("Emoji autocorrect settings have not been received. Do not autocorrect."); + } + if (running) { + return; + } + running = true; + const target = event.target; + const caretposition = getCaretPosition(target); + if (caretposition) { + const value = target.value || target.innerText; + let deletecount = 0; + let insert = event.inputType === "insertLineBreak" || event.inputType === "insertParagraph" ? "\n" : event.data; + const inserted = insert; + let output = false; + const previousText = value.slice(caretposition < longest ? 0 : caretposition - longest, caretposition); + const regexResult = symbolpatterns.exec(previousText); + // Autocorrect :colon: Emoji Shortcodes and/or Emoticon Emojis and/or Unicode Symbols + if (regexResult) { + const length = longest - 1; + const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; + const aregexResult = symbolpatterns.exec(text); + const aaregexResult = antipatterns.exec(text); + if (!aaregexResult && (!aregexResult || (caretposition <= longest ? regexResult.index < aregexResult.index : regexResult.index <= aregexResult.index))) { + insert = autocorrections[regexResult[0]] + inserted; + deletecount = regexResult[0].length; + output = true; + } + } else { + // Autocomplete :colon: Emoji Shortcodes + if (autocomplete) { + // Emoji Shortcode + const re = /:[a-z0-9-+_]+$/u; + const length = longest - 2; + const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; + const regexResult = re.exec(text); + if (regexResult) { + const aregexResult = Object.keys(emojiShortcodes).filter((item) => item.indexOf(regexResult[0]) === 0); + if (aregexResult.length >= 1 && (regexResult[0].length > 2 || aregexResult[0].length === 3)) { + const ainsert = aregexResult[0].slice(regexResult[0].length); + if (autocompleteSelect || aregexResult.length > 1) { + event.preventDefault(); + + insertAtCaret(target, inserted); + insertAndSelect(target, ainsert); + } else { + insert = inserted + ainsert; + output = true; + } + } + } + } + } + if (output) { + event.preventDefault(); + + const text = deletecount ? value.slice(caretposition - deletecount, caretposition) : ""; + if (text) { + lastTarget = null; + deleteCaret(target, text); + } + insertAtCaret(target, insert); + + insertedText = insert; + deletedText = text + inserted; + console.debug("Autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", deletedText, insertedText); + + lastTarget = target; + lastCaretPosition = caretposition - deletecount + insert.length; + + if (deletedText && insertedText.startsWith(deletedText)) { + insertedText = insertedText.slice(deletedText.length); + deletedText = ""; + } + } + } + running = false; } /** @@ -276,34 +276,34 @@ function autocorrect(event) { * @returns {void} */ function undoAutocorrect(event) { - // console.log('beforeinput', event.inputType, event.data); - // Backspace - if (event.inputType !== "deleteContentBackward") { - return; - } - if (running) { - return; - } - running = true; - const target = event.target; - const caretposition = getCaretPosition(target); - if (caretposition) { - if (target === lastTarget && caretposition === lastCaretPosition) { - event.preventDefault(); - - if (insertedText) { - lastTarget = null; - deleteCaret(target, insertedText); - } - if (deletedText) { - insertAtCaret(target, deletedText); - } - console.debug("Undo autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", insertedText, deletedText); - } - - lastTarget = null; - } - running = false; + // console.log('beforeinput', event.inputType, event.data); + // Backspace + if (event.inputType !== "deleteContentBackward") { + return; + } + if (running) { + return; + } + running = true; + const target = event.target; + const caretposition = getCaretPosition(target); + if (caretposition) { + if (target === lastTarget && caretposition === lastCaretPosition) { + event.preventDefault(); + + if (insertedText) { + lastTarget = null; + deleteCaret(target, insertedText); + } + if (deletedText) { + insertAtCaret(target, deletedText); + } + console.debug("Undo autocorrect: โ€œ%sโ€ was replaced with โ€œ%sโ€.", insertedText, deletedText); + } + + lastTarget = null; + } + running = false; } /** @@ -314,26 +314,26 @@ function undoAutocorrect(event) { * @returns {void} */ function handleResponse(message, sender) { - if (message.type !== AUTOCORRECT_CONTENT) { - return; - } - enabled = message.enabled; - autocomplete = message.autocomplete; - autocompleteSelect = message.autocompleteSelect; - autocorrections = message.autocorrections; - longest = message.longest; - symbolpatterns = IS_CHROME ? new RegExp(message.symbolpatterns, "u") : message.symbolpatterns; - antipatterns = IS_CHROME ? new RegExp(message.antipatterns, "u") : message.antipatterns; - emojiShortcodes = message.emojiShortcodes; - // console.log(message); - - if (enabled) { - addEventListener("beforeinput", undoAutocorrect, true); - addEventListener("beforeinput", autocorrect, true); - } else { - removeEventListener("beforeinput", undoAutocorrect, true); - removeEventListener("beforeinput", autocorrect, true); - } + if (message.type !== AUTOCORRECT_CONTENT) { + return; + } + enabled = message.enabled; + autocomplete = message.autocomplete; + autocompleteSelect = message.autocompleteSelect; + autocorrections = message.autocorrections; + longest = message.longest; + symbolpatterns = IS_CHROME ? new RegExp(message.symbolpatterns, "u") : message.symbolpatterns; + antipatterns = IS_CHROME ? new RegExp(message.antipatterns, "u") : message.antipatterns; + emojiShortcodes = message.emojiShortcodes; + // console.log(message); + + if (enabled) { + addEventListener("beforeinput", undoAutocorrect, true); + addEventListener("beforeinput", autocorrect, true); + } else { + removeEventListener("beforeinput", undoAutocorrect, true); + removeEventListener("beforeinput", autocorrect, true); + } } /** @@ -343,7 +343,7 @@ function handleResponse(message, sender) { * @returns {void} */ function handleError(error) { - console.error(`Error: ${error}`); + console.error(`Error: ${error}`); } browser.runtime.sendMessage({ type: AUTOCORRECT_CONTENT }).then(handleResponse, handleError); From 883cb07ab8fcfd9d51a3a5e416b32ee75869b5ff Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Thu, 10 Aug 2023 04:18:22 -0700 Subject: [PATCH 3/4] Removed dead code and updated comments. --- src/common/modules/PageHandler.js | 17 +---------------- src/content_scripts/autocorrect.js | 5 ++--- src/options/modules/CustomOptionTriggers.js | 2 +- src/options/options.html | 1 + 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/common/modules/PageHandler.js b/src/common/modules/PageHandler.js index 62aec31..0bb5a9b 100644 --- a/src/common/modules/PageHandler.js +++ b/src/common/modules/PageHandler.js @@ -18,22 +18,7 @@ export async function insertIntoPage(text) { active: true }); - const promises = tabs.map(/* async */ (tab) => { - /* // make sure content script is inserted - await browser.tabs.executeScript(tab.id, { - code: "insertIntoPage;", - allFrames: true, - runAt: "document_end" - }).catch(() => { - // ends here if insertIntoPage == undefined - // insert function, if needed - return browser.tabs.executeScript(tab.id, { - file: "/content_scripts/insertIntoPage.js", - allFrames: true, - runAt: "document_end" - }); - }); */ - + const promises = tabs.map(tab) => { // send request to insert emoji // This will not work in Manifest V3: https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#executing-arbitrary-strings return browser.tabs.executeScript(tab.id, { diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js index f9c4819..8b55dbf 100644 --- a/src/content_scripts/autocorrect.js +++ b/src/content_scripts/autocorrect.js @@ -187,7 +187,7 @@ function deleteCaret(target, atext) { */ function autocorrect(event) { // console.log('beforeinput', event.inputType, event.data); - if (!(event.inputType === "insertText" || event.inputType === "insertCompositionText" || event.inputType === "insertParagraph" || event.inputType === "insertLineBreak")) { + if (!["insertText", "insertCompositionText", "insertParagraph", "insertLineBreak"].includes(event.inputType)) { return; } if (!symbolpatterns) { @@ -202,7 +202,7 @@ function autocorrect(event) { if (caretposition) { const value = target.value || target.innerText; let deletecount = 0; - let insert = event.inputType === "insertLineBreak" || event.inputType === "insertParagraph" ? "\n" : event.data; + let insert = ["insertLineBreak", "insertParagraph"].includes(event.inputType) ? "\n" : event.data; const inserted = insert; let output = false; const previousText = value.slice(caretposition < longest ? 0 : caretposition - longest, caretposition); @@ -325,7 +325,6 @@ function handleResponse(message, sender) { symbolpatterns = IS_CHROME ? new RegExp(message.symbolpatterns, "u") : message.symbolpatterns; antipatterns = IS_CHROME ? new RegExp(message.antipatterns, "u") : message.antipatterns; emojiShortcodes = message.emojiShortcodes; - // console.log(message); if (enabled) { addEventListener("beforeinput", undoAutocorrect, true); diff --git a/src/options/modules/CustomOptionTriggers.js b/src/options/modules/CustomOptionTriggers.js index 70093d0..0cc2173 100644 --- a/src/options/modules/CustomOptionTriggers.js +++ b/src/options/modules/CustomOptionTriggers.js @@ -421,7 +421,7 @@ export async function registerTrigger() { TABS_PERMISSION, MESSAGE_TABS_PERMISSION, document.getElementById("tabsPermissionInfo"), - // "permissionRequiredTabs" // TODO: This will need to be localized + // "permissionRequiredTabs" // TODO(to: 'rugk'): This will need to be localized "Permission to send any updated options to your open tabs is required to prevent you having to reload all of them manually." ); } diff --git a/src/options/options.html b/src/options/options.html index 007d05d..9aab647 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -162,6 +162,7 @@

    Behavior

    +

    Emoji autocorrection

    With the exception of the smart quotes, all the autocorrections are done after typing the first nonmatching character following the character sequence, such as a space (โฃ). Press Backspace (โŒซ) to undo an autocorrection.
    From 2cf1237160cc1d8cbaf759f6bc70f53fca84b6cb Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Mon, 25 Sep 2023 04:16:07 -0700 Subject: [PATCH 4/4] Simplified content script and fixed typo. --- src/common/modules/PageHandler.js | 2 +- src/content_scripts/autocorrect.js | 32 ++++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/common/modules/PageHandler.js b/src/common/modules/PageHandler.js index 0bb5a9b..71a599c 100644 --- a/src/common/modules/PageHandler.js +++ b/src/common/modules/PageHandler.js @@ -18,7 +18,7 @@ export async function insertIntoPage(text) { active: true }); - const promises = tabs.map(tab) => { + const promises = tabs.map((tab) => { // send request to insert emoji // This will not work in Manifest V3: https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/#executing-arbitrary-strings return browser.tabs.executeScript(tab.id, { diff --git a/src/content_scripts/autocorrect.js b/src/content_scripts/autocorrect.js index 8b55dbf..350c302 100644 --- a/src/content_scripts/autocorrect.js +++ b/src/content_scripts/autocorrect.js @@ -214,8 +214,9 @@ function autocorrect(event) { const aregexResult = symbolpatterns.exec(text); const aaregexResult = antipatterns.exec(text); if (!aaregexResult && (!aregexResult || (caretposition <= longest ? regexResult.index < aregexResult.index : regexResult.index <= aregexResult.index))) { - insert = autocorrections[regexResult[0]] + inserted; - deletecount = regexResult[0].length; + const [autocorrection] = regexResult; + insert = autocorrections[autocorrection] + inserted; + deletecount = autocorrection.length; output = true; } } else { @@ -227,9 +228,10 @@ function autocorrect(event) { const text = value.slice(caretposition < length ? 0 : caretposition - length, caretposition) + inserted; const regexResult = re.exec(text); if (regexResult) { - const aregexResult = Object.keys(emojiShortcodes).filter((item) => item.indexOf(regexResult[0]) === 0); - if (aregexResult.length >= 1 && (regexResult[0].length > 2 || aregexResult[0].length === 3)) { - const ainsert = aregexResult[0].slice(regexResult[0].length); + const [shortcode] = regexResult; + const aregexResult = Object.keys(emojiShortcodes).filter((item) => item.indexOf(shortcode) === 0); + if (aregexResult.length >= 1 && (shortcode.length > 2 || aregexResult[0].length === 3)) { + const ainsert = aregexResult[0].slice(shortcode.length); if (autocompleteSelect || aregexResult.length > 1) { event.preventDefault(); @@ -317,14 +319,18 @@ function handleResponse(message, sender) { if (message.type !== AUTOCORRECT_CONTENT) { return; } - enabled = message.enabled; - autocomplete = message.autocomplete; - autocompleteSelect = message.autocompleteSelect; - autocorrections = message.autocorrections; - longest = message.longest; - symbolpatterns = IS_CHROME ? new RegExp(message.symbolpatterns, "u") : message.symbolpatterns; - antipatterns = IS_CHROME ? new RegExp(message.antipatterns, "u") : message.antipatterns; - emojiShortcodes = message.emojiShortcodes; + ({ + enabled, + autocomplete, + autocompleteSelect, + autocorrections, + longest, + symbolpatterns, + antipatterns, + emojiShortcodes + } = message); + symbolpatterns = IS_CHROME ? new RegExp(symbolpatterns, "u") : symbolpatterns; + antipatterns = IS_CHROME ? new RegExp(antipatterns, "u") : antipatterns; if (enabled) { addEventListener("beforeinput", undoAutocorrect, true);