From fc5007a3aa176f3482b47075cd619494fec89a13 Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Sun, 21 Jul 2024 18:59:39 +0800 Subject: [PATCH 1/2] Use to-jyutping Package, Strict Typing --- .github/workflows/build.yml | 57 +++++++++++++++-------------- .github/workflows/lint.yml | 25 +++++++++++++ .gitignore | 10 +++--- LICENSE | 2 +- README.md | 10 ++---- _locales/en/messages.json | 2 +- _locales/ja/messages.json | 8 ++--- _locales/ko/messages.json | 20 +++++++++++ _locales/zh_CN/messages.json | 2 +- _locales/zh_HK/messages.json | 2 +- _locales/zh_TW/messages.json | 2 +- background_scripts/main.js | 69 +++++++++++------------------------- build.py | 22 ------------ content_scripts/main.js | 47 +++++++++++++++--------- lib/MessageManager.js | 61 +++++++++++++++++++++---------- lib/Trie.js | 50 -------------------------- manifest.json | 4 +-- package-lock.json | 49 +++++++++++++++++++++++++ package.json | 17 +++++++++ popup/index.css | 11 +++++- popup/index.html | 20 ++++++++--- popup/index.js | 20 +++++++---- tsconfig.json | 27 ++++++++++++++ 23 files changed, 316 insertions(+), 221 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 _locales/ko/messages.json delete mode 100644 build.py delete mode 100644 lib/Trie.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ce2f70..abd3970 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,34 +1,33 @@ name: Build on: - push: - release: - types: - - created + push: + release: + types: + - created jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install opencc - - name: Build - run: python build.py - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: inject-jyutping - path: | - _locales - background_scripts - content_scripts - icons - lib - popup - manifest.json + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Install dependencies + run: npm i + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: inject-jyutping + path: | + _locales + background_scripts + content_scripts + icons + lib + popup + manifest.json diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a81e8d7 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Install dependencies + run: npm i + + - name: Run lint + run: npm run lint diff --git a/.gitignore b/.gitignore index b087f0b..369937b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -/jyut6ping3.dict.yaml -/jyut6ping3.simple.dict.yaml -/background_scripts/dictionary.json.txt -/preprocess.py -/lib/browser-polyfill.js -/inject-jyutping.zip +inject-jyutping.zip +node_modules +DS_Store +dist diff --git a/LICENSE b/LICENSE index d0e8a13..cad56c5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2020, Cantonese Computational Linguistics Infrastructure Development Workgroup +Copyright (c) 2024, Cantonese Computational Linguistics Infrastructure Development Workgroup All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index f729e5e..f0123d6 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,10 @@ A browser extension for Mozilla Firefox and Google Chrome that adds Cantonese pronunciation (Jyutping) on Chinese characters, a powerful tool for learning Cantonese and Jyutping. -項目靈感來自 [EYHN/Furigana](https://github.com/EYHN/Furigana)。 - -This project is inspired by [EYHN/Furigana](https://github.com/EYHN/Furigana). -

Install on1 zong1

-- [Chrome Web Store](https://chrome.google.com/webstore/detail/inject-jyutping/lfgpgjkjglogbndlkikjgbbfoiofbdjp) -- [Firefox Browser Add-On](https://addons.mozilla.org/en-US/firefox/addon/inject-jyutping/) +- [Chrome Web Store](https://chrome.google.com/webstore/detail/inject-jyutping/lfgpgjkjglogbndlkikjgbbfoiofbdjp) +- [Firefox Browser Add-On](https://addons.mozilla.org/en-US/firefox/addon/inject-jyutping/)

Build pin1 jik6

@@ -19,4 +15,4 @@ See [`.github/workflows/build.yml`](.github/workflows/build.yml).

Screenshot zit6 tou4

-![](./demo.jpg) +![Demo](./demo.jpg) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1cfc658..d49f249 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -12,7 +12,7 @@ "message": "Inject Jyutping" }, "popupCheckboxText": { - "message": "Inject automatically((zi6)(dung6)(zyu3)(jap6))" + "message": "Inject automatically" }, "refreshPromptText": { "message": "Please refresh the page for the change to take effect." diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 0ebbbfc..1ee870e 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -3,16 +3,16 @@ "message": "ja" }, "extensionName": { - "message": "粵拼を注入" + "message": "粤拼を注入" }, "extensionDescription": { - "message": "漢字に広東語の発音(粵拼)を付ける。" + "message": "漢字に広東語の発音(粤拼)を付けます。" }, "contextMenuItemDoInjectJyutping": { - "message": "粵拼を注入" + "message": "粤拼を注入" }, "popupCheckboxText": { - "message": "オート注入((zi6)(dung6)(zyu3)(jap6))" + "message": "自動で注入" }, "refreshPromptText": { "message": "変更を有効にするには、ページを再読み込みしてください。" diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json new file mode 100644 index 0000000..1a29fcf --- /dev/null +++ b/_locales/ko/messages.json @@ -0,0 +1,20 @@ +{ + "langCode": { + "message": "ko" + }, + "extensionName": { + "message": "월병(粵拼)을 주입" + }, + "extensionDescription": { + "message": "한자에 광동어의 발음(월병/粵拼)을 붙인다." + }, + "contextMenuItemDoInjectJyutping": { + "message": "월병(粵拼)을 주입" + }, + "popupCheckboxText": { + "message": "자동으로 주입" + }, + "refreshPromptText": { + "message": "변경사항을 적용하려면 페이지를 다시 로드하세요." + } +} diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 57749d0..f9ef711 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -12,7 +12,7 @@ "message": "注入粤拼" }, "popupCheckboxText": { - "message": "(zi6)(dung6)(zyu3)(jap6)" + "message": "自动注入" }, "refreshPromptText": { "message": "请刷新页面使更改生效。" diff --git a/_locales/zh_HK/messages.json b/_locales/zh_HK/messages.json index 6cfbcd5..00f08a8 100644 --- a/_locales/zh_HK/messages.json +++ b/_locales/zh_HK/messages.json @@ -12,7 +12,7 @@ "message": "注入粵拼" }, "popupCheckboxText": { - "message": "(zi6)(dung6)(zyu3)(jap6)" + "message": "" }, "refreshPromptText": { "message": "請刷新頁面使更改生效。" diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index aa7db8a..3945701 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -12,7 +12,7 @@ "message": "注入粵拼" }, "popupCheckboxText": { - "message": "(zi6)(dung6)(zyu3)(jap6)" + "message": "" }, "refreshPromptText": { "message": "請刷新頁面使更改生效。" diff --git a/background_scripts/main.js b/background_scripts/main.js index e9dbdf6..aa88342 100644 --- a/background_scripts/main.js +++ b/background_scripts/main.js @@ -1,54 +1,25 @@ -/** - * 轉換一個字串,取得字串中每個字及其讀音。 - * @param {Trie} t Trie 樹 - * @param {String} s 鍵字串 - * @return {Array} 二維陣列。每個元素為一個字及其讀音。 - */ -function convert(t, s) { - const res = []; - while (s.length) { - const prefix = t.longestPrefix(s); - if (typeof prefix !== 'undefined') { - const [cs, rs] = prefix; - const zipped_cs_rs = cs.map((c, i) => [c, rs[i]]); - res.push(...zipped_cs_rs); - s = s.slice(cs.reduce((acc, x) => acc + x.length, 0)); // total length of strings in array cs - } else { - const k = s[Symbol.iterator]().next().value; // Unicode-aware version of s[0] - res.push([k, null]); - s = s.slice(k.length); - } - } - return res; -} - -(async () => { - /* Dictionary */ - - const t = new Trie(); +import Browser from 'webextension-polyfill'; +import MessageManager from '../lib/MessageManager.js'; +import { getJyutpingList } from 'to-jyutping'; - for (const [k, v] of await (await fetch(browser.runtime.getURL('background_scripts/dictionary.json.txt'))).json()) { - t.addWord(k, v); - } - - /* Communicate with content script */ +/* Communicate with content script */ - browser.runtime.onConnect.addListener(port => { - const mm = new MessageManager(port); - mm.registerHandler('convert', s => convert(t, s)); - }); +Browser.runtime.onConnect.addListener(port => { + /** @type { MessageManager<{ convert(msg: string): [string, string | null][] }> } */ + const mm = new MessageManager(port); + mm.registerHandler('convert', getJyutpingList); +}); - /* Context Menu */ +/* Context Menu */ - browser.contextMenus.onClicked.addListener((info, tab) => { - if (info.menuItemId === 'do-inject-jyutping') { - browser.tabs.sendMessage(tab.id, { name: 'do-inject-jyutping' }); - } - }); +Browser.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId === 'do-inject-jyutping') { + Browser.tabs.sendMessage(tab?.id || 0, { name: 'do-inject-jyutping' }); + } +}); - browser.contextMenus.create({ - id: 'do-inject-jyutping', - title: browser.i18n.getMessage('contextMenuItemDoInjectJyutping'), - contexts: ['page'], - }); -})(); +Browser.contextMenus.create({ + id: 'do-inject-jyutping', + title: Browser.i18n.getMessage('contextMenuItemDoInjectJyutping'), + contexts: ['page'], +}); diff --git a/build.py b/build.py deleted file mode 100644 index 0bfd09f..0000000 --- a/build.py +++ /dev/null @@ -1,22 +0,0 @@ -import json -import os - -# Library -os.system('wget -nc -O lib/browser-polyfill.js https://unpkg.com/webextension-polyfill@0.8.0/dist/browser-polyfill.js') - -# Preprocess -os.system('wget -nc https://raw.githubusercontent.com/CanCLID/ToJyutping/74f8e9c/preprocess.py') -os.system("sed -i 's/src\/ToJyutping\/jyut6ping3.simple.dict.yaml/jyut6ping3.simple.dict.yaml/' preprocess.py") -os.system('python preprocess.py') - -l = [] - -with open('jyut6ping3.simple.dict.yaml') as f: - for line in f: - k, v = line.rstrip('\n').split('\t') - l.append((k, v)) - -# *.json.txt: See mozilla/addons-linter#1700 -with open('background_scripts/dictionary.json.txt', 'w') as f: - f.write(json.dumps(l, ensure_ascii=False).replace('], [', '],\n[')) - f.write('\n') # Add line break at the end of file diff --git a/content_scripts/main.js b/content_scripts/main.js index 25812b8..ed0218e 100644 --- a/content_scripts/main.js +++ b/content_scripts/main.js @@ -1,19 +1,20 @@ +import Browser from 'webextension-polyfill'; +import MessageManager from '../lib/MessageManager.js'; + /** * Check if a string contains Chinese characters. - * @param {String} s The string to be checked - * @return {Boolean} If the string contains at least one Chinese character, - * returns true. Otherwise returns false. + * @param {string} s The string to be checked + * @return {boolean} Whether the string contains at least one Chinese character. */ function hasHanChar(s) { - const r = /[〆〇一-鿿㐀-䶿𠀀-𪛟𪜀-𫜿𫝀-𫠟𫠠-𬺯𬺰-𮯯𰀀-𱍏]/u; - return Boolean(s.match(r)); + return /[〆〇一-鿿㐀-䶿𠀀-𪛟𪜀-𫜿𫝀-𫠟𫠠-𬺯𬺰-𮯯𰀀-𱍏]/u.test(s); } /** * Determine whether an HTML element should be handled by inject-jyutping * by checking its lang tag. - * @param {String} lang The lang tag of an HTML element - * @return {Boolean} If the lang tag is reasonable to be handled, returns + * @param {string} lang The lang tag of an HTML element + * @return {boolean} If the lang tag is reasonable to be handled, returns * true. Otherwise returns false. */ function isTargetLang(lang) { @@ -22,8 +23,8 @@ function isTargetLang(lang) { /** * Create a ruby element with the character and the pronunciation. - * @param {String} ch The character in a ruby element - * @param {String} pronunciation The pronunciation in a ruby element + * @param {string} ch The character in a ruby element + * @param {string} pronunciation The pronunciation in a ruby element * @return {Element} The ruby element */ function makeRuby(ch, pronunciation) { @@ -47,23 +48,29 @@ function makeRuby(ch, pronunciation) { return ruby; } -const port = browser.runtime.connect(); +const port = Browser.runtime.connect(); +/** @type { MessageManager<{ convert(msg: string): [string, string | null][] }> } */ const mm = new MessageManager(port); const mo = new MutationObserver(changes => { for (const change of changes) { for (const node of change.addedNodes) { const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode; - forEachText(node, convertText, element?.closest('[lang]')?.lang); + forEachText(node, convertText, /** @type {HTMLElement} */ (/** @type {Element} */ (element)?.closest?.('[lang]'))?.lang); } } }); +/** + * @param {Node} node + * @param {(node: Node) => void} callback + * @param {string} [lang = ''] + */ function forEachText(node, callback, lang = '') { if (!isTargetLang(lang)) { return; } if (node.nodeType === Node.TEXT_NODE) { - if (hasHanChar(node.nodeValue)) { + if (hasHanChar(node.nodeValue || '')) { callback(node); } } else if (node.nodeType === Node.ELEMENT_NODE) { @@ -72,22 +79,28 @@ function forEachText(node, callback, lang = '') { return; } for (const child of node.childNodes) { - forEachText(child, callback, node.lang); + forEachText(child, callback, /** @type {HTMLElement} */ (node).lang); } } } +/** + * @param {Node} node + */ async function convertText(node) { - const conversionResults = await mm.sendMessage('convert', node.nodeValue); + const conversionResults = await mm.sendMessage('convert', node.nodeValue || ''); const newNodes = document.createDocumentFragment(); for (const [k, v] of conversionResults) { newNodes.appendChild(v === null ? document.createTextNode(k) : makeRuby(k, v)); } if (node.isConnected && node.nodeValue !== newNodes.textContent) { - node.parentNode.replaceChild(newNodes, node); + node.parentNode?.replaceChild(newNodes, node); } } +/** + * @param {() => void} fn + */ function once(fn) { let called = false; return () => { @@ -107,14 +120,14 @@ const init = once(() => { }); }); -browser.runtime.onMessage.addListener(msg => { +Browser.runtime.onMessage.addListener(msg => { if (msg.name === 'do-inject-jyutping') { init(); } }); async function autoInit() { - if ((await browser.storage.local.get('enabled'))['enabled'] !== false) { + if ((await Browser.storage.local.get('enabled'))['enabled'] !== false) { init(); } } diff --git a/lib/MessageManager.js b/lib/MessageManager.js index 3a8c458..8a65ae8 100644 --- a/lib/MessageManager.js +++ b/lib/MessageManager.js @@ -1,31 +1,51 @@ -/* Usage example: - -In background script: - -browser.runtime.onConnect.addListener(port => { - const mm = new MessageManager(port); - mm.registerHandler('double', s => s + s); - mm.registerHandler('triple', s => s + s + s); -}); - -In content script: - -const port = browser.runtime.connect(); -const mm = new MessageManager(port); -mm.sendMessage('double', '你好').then(f => alert(f)); // Will alert 你好你好 -mm.sendMessage('triple', '你好').then(f => alert(f)); // Will alert 你好你好你好 -*/ +/** @import { Runtime } from 'webextension-polyfill'; */ const getUniqueId = ( id => () => id++ )(0); -class MessageManager { +/** + * A class to manage messages between background script and content script. + * @template {Record any>} T + * @example + * In background script: + * + * ```js + * Browser.runtime.onConnect.addListener(port => { + * const mm = new MessageManager(port); + * mm.registerHandler('double', s => s + s); + * mm.registerHandler('triple', s => s + s + s); + * }); + * ``` + * + * In content script: + * + * ```js + * const port = Browser.runtime.connect(); + * const mm = new MessageManager(port); + * mm.sendMessage('double', '你好').then(alert); // Will alert 你好你好 + * mm.sendMessage('triple', '你好').then(alert); // Will alert 你好你好你好 + * ``` + */ +export default class MessageManager { + /** + * @param {Runtime.Port} port + */ constructor(port) { + /** + * @type {Runtime.Port} + * @private + */ this.port = port; } + /** + * @template {keyof T} K + * @param {K} handlerName + * @param {Parameters[0]} msg + * @returns {Promise>} + */ sendMessage(handlerName, msg) { const { port } = this; const id = getUniqueId(); @@ -40,6 +60,11 @@ class MessageManager { }); } + /** + * @template {keyof T} K + * @param {K} handlerName + * @param {T[K]} f + */ registerHandler(handlerName, f) { const { port } = this; port.onMessage.addListener(msg => { diff --git a/lib/Trie.js b/lib/Trie.js deleted file mode 100644 index 4f22b12..0000000 --- a/lib/Trie.js +++ /dev/null @@ -1,50 +0,0 @@ -class Trie { - constructor() { - /** - * Trie 的每個節點為一個 Map 物件。 - * key 為 code point,value 為子節點(也是一個 Map)。 - * 如果 Map 物件有 __trie_val 屬性,則該屬性為值字串,代表替換的字詞。 - */ - this.t = new Map(); - } - - /** - * 將一組資料加入字典樹 - * @param {String} k 鍵字串 - * @param {String} v 值字串,代表替換的字詞 - */ - addWord(k, v) { - let t = this.t; - for (const c of k) { - const cp = c.codePointAt(0); - if (!t.has(cp)) { - t.set(cp, new Map()); - } - t = t.get(cp); - } - t.__trie_val = v; - } - - longestPrefix(s) { - const totalBreadcrumbs = []; - let currentBreadcrumbs = [], - currentTarget, - { t } = this; - for (const c of s) { - const cp = c.codePointAt(0); - if (!t.has(cp)) { - break; - } - currentBreadcrumbs.push(c); - t = t.get(cp); - if (typeof t.__trie_val !== 'undefined') { - currentTarget = t.__trie_val; - totalBreadcrumbs.push(...currentBreadcrumbs); - currentBreadcrumbs = []; - } - } - if (totalBreadcrumbs.length) { - return [totalBreadcrumbs, currentTarget.split(' ')]; // chars, romanization of each char - } - } -} diff --git a/manifest.json b/manifest.json index ddd2c8b..0dbd842 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "__MSG_extensionName__", - "version": "0.3.0", + "version": "0.4.0", "description": "__MSG_extensionDescription__", "icons": { "48": "icons/48.png", @@ -18,7 +18,7 @@ } ], "background": { - "scripts": ["lib/browser-polyfill.js", "lib/MessageManager.js", "lib/Trie.js", "background_scripts/main.js"], + "scripts": ["lib/browser-polyfill.js", "lib/MessageManager.js", "background_scripts/main.js"], "persistent": true }, "permissions": ["contextMenus", "storage"], diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..143be9d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,49 @@ +{ + "name": "inject-jyutping", + "version": "0.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "inject-jyutping", + "version": "0.4.0", + "dependencies": { + "to-jyutping": "2.0.0", + "webextension-polyfill": "^0.12.0" + }, + "devDependencies": { + "@types/webextension-polyfill": "^0.10.7", + "typescript": "^5.5.3" + } + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.10.7.tgz", + "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==", + "dev": true + }, + "node_modules/to-jyutping": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-jyutping/-/to-jyutping-2.0.0.tgz", + "integrity": "sha512-Y8ClOMXVEshH3XGAnswo8gG2NviIgpGYsVwhfejvW1QVoQT8OqwMzka1Ydpvex87Ce1763aNXwsYunKPuGnhEQ==" + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/webextension-polyfill": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", + "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b55b836 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "inject-jyutping", + "version": "0.4.0", + "description": "A browser extension that adds Cantonese pronunciation (Jyutping) on Chinese characters", + "type": "module", + "scripts": { + "lint": "tsc" + }, + "dependencies": { + "to-jyutping": "2.0.0", + "webextension-polyfill": "^0.12.0" + }, + "devDependencies": { + "@types/webextension-polyfill": "^0.10.7", + "typescript": "^5.5.3" + } +} diff --git a/popup/index.css b/popup/index.css index fa9884c..5209a7f 100644 --- a/popup/index.css +++ b/popup/index.css @@ -1,4 +1,4 @@ -.middle { +.middle > * { vertical-align: middle; } @@ -21,6 +21,15 @@ ruby.inject-jyutping > rt { margin: 1rem; } +.semibold { + font-weight: 600; +} + +#nativeCheckboxText { + font-size: 0.85em; + color: darkgrey; +} + #refreshPromptText { color: red; margin: 1rem; diff --git a/popup/index.html b/popup/index.html index 2abce36..a9ea664 100644 --- a/popup/index.html +++ b/popup/index.html @@ -8,11 +8,21 @@
-   - +
+
+
+ (zi6)(dung6)(zyu3)(jap6) +
+
+
+ +

diff --git a/popup/index.js b/popup/index.js index fb5017d..4100315 100644 --- a/popup/index.js +++ b/popup/index.js @@ -1,12 +1,20 @@ +import Browser from 'webextension-polyfill'; + +const i = Browser.i18n.getMessage; + +const nativeCheckboxText = /** @type {HTMLDivElement} */ (document.getElementById('nativeCheckboxText')); +const extensionEnabled = /** @type {HTMLInputElement} */ (document.getElementById('extensionEnabled')); +const refreshPromptText = /** @type {HTMLParagraphElement} */ (document.getElementById('refreshPromptText')); + /* Initialize state */ (async () => { - document.documentElement.lang = browser.i18n.getMessage('langCode'); - document.getElementById('checkboxText').innerHTML = browser.i18n.getMessage('popupCheckboxText'); - document.getElementById('extensionEnabled').checked = (await browser.storage.local.get('enabled'))['enabled'] !== false; + document.documentElement.lang = i('langCode'); + nativeCheckboxText.textContent = i('popupCheckboxText'); + extensionEnabled.checked = (await Browser.storage.local.get('enabled'))['enabled'] !== false; })(); /* Handle state change */ -document.getElementById('extensionEnabled').addEventListener('click', () => { - browser.storage.local.set({ enabled: document.getElementById('extensionEnabled').checked }); - document.getElementById('refreshPromptText').innerHTML = browser.i18n.getMessage('refreshPromptText'); +extensionEnabled.addEventListener('click', () => { + Browser.storage.local.set({ enabled: extensionEnabled.checked }); + refreshPromptText.innerHTML = i('refreshPromptText'); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e11711c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "moduleDetection": "force", + + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + + "checkJs": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "useDefineForClassFields": true, + "skipLibCheck": true, + "incremental": true, + "noEmit": true + } +} From 15adb3b83413e86a30c644668cca368f727fad96 Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Mon, 22 Jul 2024 01:05:20 +0800 Subject: [PATCH 2/2] Migrate to Manifest Version 3 --- .github/workflows/build.yml | 13 +- .github/workflows/lint.yml | 25 -- .gitignore | 2 +- README.md | 14 +- _locales/zh_HK/messages.json | 2 +- _locales/zh_TW/messages.json | 2 +- background_scripts/index.js | 25 ++ background_scripts/main.js | 25 -- content_scripts/{main.css => index.css} | 0 content_scripts/{main.js => index.js} | 11 +- global.d.ts | 3 + tsconfig.json => jsconfig.json | 5 +- lib/MessageManager.js | 10 +- manifest.json | 22 +- package-lock.json | 424 ++++++++++++++++++++++++ package.json | 4 +- popup/index.css | 76 ++--- popup/index.html | 19 +- popup/index.js | 18 +- 19 files changed, 554 insertions(+), 146 deletions(-) delete mode 100644 .github/workflows/lint.yml create mode 100644 background_scripts/index.js delete mode 100644 background_scripts/main.js rename content_scripts/{main.css => index.css} (100%) rename content_scripts/{main.js => index.js} (91%) create mode 100644 global.d.ts rename tsconfig.json => jsconfig.json (90%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abd3970..97e231e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,12 @@ -name: Build +name: Lint & Build on: push: + branches: + - main + pull_request: + branches: + - main release: types: - created @@ -19,7 +24,11 @@ jobs: - name: Install dependencies run: npm i + - name: Run lint + run: npm run lint + - name: Upload artifact + if: ${{ github.event_name == 'release' }} uses: actions/upload-artifact@v4 with: name: inject-jyutping @@ -31,3 +40,5 @@ jobs: lib popup manifest.json + node_modules/webextension-polyfill/dist/browser-polyfill.min.js + node_modules/to-jyutping/dist/index.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index a81e8d7..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Lint - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - - - name: Install dependencies - run: npm i - - - name: Run lint - run: npm run lint diff --git a/.gitignore b/.gitignore index 369937b..43a5949 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ inject-jyutping.zip node_modules DS_Store -dist +jsconfig.tsbuildinfo diff --git a/README.md b/README.md index f0123d6..1a18e8e 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,14 @@

Inject Jyutping bong1 hon3 zi6 biu1 jyut6 ping3

-呢個係一個可以幫網頁上面嘅漢字自動標註粵拼嘅 Chrome 同 Firefox 插件,係學習粵拼同粵語嘅強大工具。 +呢個係一個可以幫網頁上面嘅漢字自動標註粵拼嘅 Chrome、Firefox 同 Edge 擴充功能,係學習粵拼同粵語嘅強大工具。 -A browser extension for Mozilla Firefox and Google Chrome that adds Cantonese pronunciation (Jyutping) on Chinese characters, a powerful tool for learning Cantonese and Jyutping. +A browser extension for Google Chrome, Mozilla Firefox, and Microsoft Edge that adds Cantonese pronunciation (Jyutping) on Chinese characters, a powerful tool for learning Cantonese and Jyutping. -

Install on1 zong1

+

Install on1 zong1

- [Chrome Web Store](https://chrome.google.com/webstore/detail/inject-jyutping/lfgpgjkjglogbndlkikjgbbfoiofbdjp) -- [Firefox Browser Add-On](https://addons.mozilla.org/en-US/firefox/addon/inject-jyutping/) +- [Firefox Browser Add-On](https://addons.mozilla.org/firefox/addon/inject-jyutping/) -

Build pin1 jik6

- -See [`.github/workflows/build.yml`](.github/workflows/build.yml). - -

Screenshot zit6 tou4

+

Preview jyu6 laam5

![Demo](./demo.jpg) diff --git a/_locales/zh_HK/messages.json b/_locales/zh_HK/messages.json index 00f08a8..7d8ba2f 100644 --- a/_locales/zh_HK/messages.json +++ b/_locales/zh_HK/messages.json @@ -15,6 +15,6 @@ "message": "" }, "refreshPromptText": { - "message": "請刷新頁面使更改生效。" + "message": "請重新載入頁面以使變更生效。" } } diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 3945701..9df3e66 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -15,6 +15,6 @@ "message": "" }, "refreshPromptText": { - "message": "請刷新頁面使更改生效。" + "message": "請重新載入頁面以使變更生效。" } } diff --git a/background_scripts/index.js b/background_scripts/index.js new file mode 100644 index 0000000..ca88246 --- /dev/null +++ b/background_scripts/index.js @@ -0,0 +1,25 @@ +import '/node_modules/webextension-polyfill/dist/browser-polyfill.min.js'; +import '/node_modules/to-jyutping/dist/index.js'; +import '/lib/MessageManager.js'; + +/* Communicate with content script */ + +browser.runtime.onConnect.addListener(port => { + /** @type { MessageManager<{ convert(msg: string): [string, string | null][] }> } */ + const mm = new MessageManager(port); + mm.registerHandler('convert', ToJyutping.getJyutpingList); +}); + +/* Context Menu */ + +browser.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId === 'do-inject-jyutping') { + browser.tabs.sendMessage(tab?.id || 0, { name: 'do-inject-jyutping' }); + } +}); + +browser.contextMenus.create({ + id: 'do-inject-jyutping', + title: browser.i18n.getMessage('contextMenuItemDoInjectJyutping'), + contexts: ['page'], +}); diff --git a/background_scripts/main.js b/background_scripts/main.js deleted file mode 100644 index aa88342..0000000 --- a/background_scripts/main.js +++ /dev/null @@ -1,25 +0,0 @@ -import Browser from 'webextension-polyfill'; -import MessageManager from '../lib/MessageManager.js'; -import { getJyutpingList } from 'to-jyutping'; - -/* Communicate with content script */ - -Browser.runtime.onConnect.addListener(port => { - /** @type { MessageManager<{ convert(msg: string): [string, string | null][] }> } */ - const mm = new MessageManager(port); - mm.registerHandler('convert', getJyutpingList); -}); - -/* Context Menu */ - -Browser.contextMenus.onClicked.addListener((info, tab) => { - if (info.menuItemId === 'do-inject-jyutping') { - Browser.tabs.sendMessage(tab?.id || 0, { name: 'do-inject-jyutping' }); - } -}); - -Browser.contextMenus.create({ - id: 'do-inject-jyutping', - title: Browser.i18n.getMessage('contextMenuItemDoInjectJyutping'), - contexts: ['page'], -}); diff --git a/content_scripts/main.css b/content_scripts/index.css similarity index 100% rename from content_scripts/main.css rename to content_scripts/index.css diff --git a/content_scripts/main.js b/content_scripts/index.js similarity index 91% rename from content_scripts/main.js rename to content_scripts/index.js index ed0218e..4d295fa 100644 --- a/content_scripts/main.js +++ b/content_scripts/index.js @@ -1,13 +1,10 @@ -import Browser from 'webextension-polyfill'; -import MessageManager from '../lib/MessageManager.js'; - /** * Check if a string contains Chinese characters. * @param {string} s The string to be checked * @return {boolean} Whether the string contains at least one Chinese character. */ function hasHanChar(s) { - return /[〆〇一-鿿㐀-䶿𠀀-𪛟𪜀-𫜿𫝀-𫠟𫠠-𬺯𬺰-𮯯𰀀-𱍏]/u.test(s); + return /[\p{Unified_Ideograph}\u3006\u3007]/u.test(s); } /** @@ -48,7 +45,7 @@ function makeRuby(ch, pronunciation) { return ruby; } -const port = Browser.runtime.connect(); +const port = browser.runtime.connect(); /** @type { MessageManager<{ convert(msg: string): [string, string | null][] }> } */ const mm = new MessageManager(port); const mo = new MutationObserver(changes => { @@ -120,14 +117,14 @@ const init = once(() => { }); }); -Browser.runtime.onMessage.addListener(msg => { +browser.runtime.onMessage.addListener(msg => { if (msg.name === 'do-inject-jyutping') { init(); } }); async function autoInit() { - if ((await Browser.storage.local.get('enabled'))['enabled'] !== false) { + if ((await browser.storage.local.get('enabled'))['enabled'] !== false) { init(); } } diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..46c8333 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,3 @@ +declare const browser: import('webextension-polyfill').Browser; +declare const ToJyutping: import('to-jyutping').default; +declare const MessageManager: import('./lib/MessageManager').MessageManager; diff --git a/tsconfig.json b/jsconfig.json similarity index 90% rename from tsconfig.json rename to jsconfig.json index e11711c..4d264f0 100644 --- a/tsconfig.json +++ b/jsconfig.json @@ -4,7 +4,6 @@ "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "NodeNext", "moduleResolution": "NodeNext", - "moduleDetection": "force", "strict": true, "allowUnusedLabels": false, @@ -16,12 +15,12 @@ "isolatedModules": true, "verbatimModuleSyntax": true, - "checkJs": true, + "checkJs": true, "esModuleInterop": true, "resolveJsonModule": true, "useDefineForClassFields": true, "skipLibCheck": true, "incremental": true, - "noEmit": true + "noEmit": true } } diff --git a/lib/MessageManager.js b/lib/MessageManager.js index 8a65ae8..fc6492f 100644 --- a/lib/MessageManager.js +++ b/lib/MessageManager.js @@ -12,7 +12,7 @@ const getUniqueId = ( * In background script: * * ```js - * Browser.runtime.onConnect.addListener(port => { + * browser.runtime.onConnect.addListener(port => { * const mm = new MessageManager(port); * mm.registerHandler('double', s => s + s); * mm.registerHandler('triple', s => s + s + s); @@ -22,13 +22,13 @@ const getUniqueId = ( * In content script: * * ```js - * const port = Browser.runtime.connect(); + * const port = browser.runtime.connect(); * const mm = new MessageManager(port); * mm.sendMessage('double', '你好').then(alert); // Will alert 你好你好 * mm.sendMessage('triple', '你好').then(alert); // Will alert 你好你好你好 * ``` */ -export default class MessageManager { +class MessageManager { /** * @param {Runtime.Port} port */ @@ -75,3 +75,7 @@ export default class MessageManager { }); } } + +Object.assign(globalThis, { MessageManager }); + +/** @type {typeof MessageManager} MessageManager */ diff --git a/manifest.json b/manifest.json index 0dbd842..4df8018 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "__MSG_extensionName__", "version": "0.4.0", "description": "__MSG_extensionDescription__", @@ -11,19 +11,29 @@ "content_scripts": [ { "matches": [""], - "js": ["lib/browser-polyfill.js", "lib/MessageManager.js", "content_scripts/main.js"], - "css": ["content_scripts/main.css"], + "js": ["node_modules/webextension-polyfill/dist/browser-polyfill.min.js", "lib/MessageManager.js", "content_scripts/index.js"], + "css": ["content_scripts/index.css"], "all_frames": true, "run_at": "document_end" } ], "background": { - "scripts": ["lib/browser-polyfill.js", "lib/MessageManager.js", "background_scripts/main.js"], - "persistent": true + "service_worker": "background_scripts/index.js", + "type": "module" }, + "web_accessible_resources": [ + { + "matches": [""], + "resources": [ + "node_modules/webextension-polyfill/dist/browser-polyfill.min.js", + "node_modules/to-jyutping/dist/index.js", + "lib/MessageManager.js" + ] + } + ], "permissions": ["contextMenus", "storage"], "default_locale": "en", - "browser_action": { + "action": { "default_icon": "icons/96.png", "default_title": "__MSG_extensionName__", "default_popup": "popup/index.html" diff --git a/package-lock.json b/package-lock.json index 143be9d..4f3a893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,15 +13,439 @@ }, "devDependencies": { "@types/webextension-polyfill": "^0.10.7", + "esbuild": "^0.23.0", "typescript": "^5.5.3" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@types/webextension-polyfill": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.10.7.tgz", "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==", "dev": true }, + "node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/to-jyutping": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-jyutping/-/to-jyutping-2.0.0.tgz", diff --git a/package.json b/package.json index b55b836..42eaf00 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,8 @@ "name": "inject-jyutping", "version": "0.4.0", "description": "A browser extension that adds Cantonese pronunciation (Jyutping) on Chinese characters", - "type": "module", "scripts": { - "lint": "tsc" + "lint": "tsc -p jsconfig.json" }, "dependencies": { "to-jyutping": "2.0.0", @@ -12,6 +11,7 @@ }, "devDependencies": { "@types/webextension-polyfill": "^0.10.7", + "esbuild": "^0.23.0", "typescript": "^5.5.3" } } diff --git a/popup/index.css b/popup/index.css index 5209a7f..37008de 100644 --- a/popup/index.css +++ b/popup/index.css @@ -1,8 +1,17 @@ -.middle > * { - vertical-align: middle; +body { + font-size: 16px; + margin: 1em; } -ruby.inject-jyutping > rt { +#auto-inject { + display: flex; + align-items: center; + justify-content: center; + gap: 1em; + margin-bottom: 0.5em; +} + +rt { font-size: 0.74em; font-variant: initial; margin-left: 0.1em; @@ -10,29 +19,22 @@ ruby.inject-jyutping > rt { text-transform: initial; } -#container { - display: flex; - flex-direction: column; - align-items: center; -} - -#checkboxText { +#auto-inject-text { white-space: nowrap; - margin: 1rem; } -.semibold { +#auto-inject-cantonese-text { font-weight: 600; } -#nativeCheckboxText { - font-size: 0.85em; - color: darkgrey; +#auto-inject-native-text { + font-size: 0.75em; + color: dimgrey; } -#refreshPromptText { +#refresh-prompt-text { + font-size: 0.75em; color: red; - margin: 1rem; } /* Create a "toggle switch" (on/off button) with CSS. @@ -40,7 +42,7 @@ ruby.inject-jyutping > rt { */ /* The switch - the box around the slider */ -.switch { +#auto-inject-switch { position: relative; display: inline-block; width: 49px; @@ -48,53 +50,41 @@ ruby.inject-jyutping > rt { } /* Hide default HTML checkbox */ -.switch input { +#auto-inject-checkbox { opacity: 0; width: 0; height: 0; } /* The slider */ -.slider { +#auto-inject-slider { position: absolute; cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ebf7fc; + inset: 0; + background-color: #8ac8f5; + border-radius: 28px; } -.slider:before { +#auto-inject-slider:before { position: absolute; content: ''; - height: 20px; width: 20px; + height: 20px; left: 4px; bottom: 4px; background-color: white; + border-radius: 50%; transition: 0.3s cubic-bezier(0.18, 0.89, 0.35, 1.15) all; } -input:checked + .slider { - background-color: #03a9f4; +#auto-inject-checkbox:checked + #auto-inject-slider { + background-color: #1592ec; } -input:focus + .slider { - box-shadow: 0 0 1px #03a9f4; +#auto-inject-checkbox:focus + #auto-inject-slider { + box-shadow: 0 0 1px #1592ec; } -input:checked + .slider:before { - -webkit-transform: translateX(20px); - -ms-transform: translateX(20px); +#auto-inject-checkbox:checked + #auto-inject-slider:before { transform: translateX(20px); } - -/* Rounded sliders */ -.slider.round { - border-radius: 28px; -} - -.slider.round:before { - border-radius: 50%; -} diff --git a/popup/index.html b/popup/index.html index a9ea664..f178eef 100644 --- a/popup/index.html +++ b/popup/index.html @@ -3,27 +3,26 @@ - - +
-
-
-
+
+
+
(zi6)(dung6)(zyu3)(jap6)
-
+
-
-

+
diff --git a/popup/index.js b/popup/index.js index 4100315..f26afeb 100644 --- a/popup/index.js +++ b/popup/index.js @@ -1,20 +1,20 @@ -import Browser from 'webextension-polyfill'; +import '/node_modules/webextension-polyfill/dist/browser-polyfill.min.js'; -const i = Browser.i18n.getMessage; +const i = browser.i18n.getMessage; -const nativeCheckboxText = /** @type {HTMLDivElement} */ (document.getElementById('nativeCheckboxText')); -const extensionEnabled = /** @type {HTMLInputElement} */ (document.getElementById('extensionEnabled')); -const refreshPromptText = /** @type {HTMLParagraphElement} */ (document.getElementById('refreshPromptText')); +const autoInjectNativeText = /** @type {HTMLDivElement} */ (document.getElementById('auto-inject-native-text')); +const autoInjectCheckbox = /** @type {HTMLInputElement} */ (document.getElementById('auto-inject-checkbox')); +const refreshPromptText = /** @type {HTMLParagraphElement} */ (document.getElementById('refresh-prompt-text')); /* Initialize state */ (async () => { document.documentElement.lang = i('langCode'); - nativeCheckboxText.textContent = i('popupCheckboxText'); - extensionEnabled.checked = (await Browser.storage.local.get('enabled'))['enabled'] !== false; + autoInjectNativeText.textContent = i('popupCheckboxText'); + autoInjectCheckbox.checked = (await browser.storage.local.get('enabled'))['enabled'] !== false; })(); /* Handle state change */ -extensionEnabled.addEventListener('click', () => { - Browser.storage.local.set({ enabled: extensionEnabled.checked }); +autoInjectCheckbox.addEventListener('click', () => { + browser.storage.local.set({ enabled: autoInjectCheckbox.checked }); refreshPromptText.innerHTML = i('refreshPromptText'); });