From dc9e3b6d039ba2dcddc0ff612a6645a1d2a7f67c Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Thu, 25 Jul 2024 09:59:32 +0800 Subject: [PATCH] Use to-jyutping Package, Strict Typing, Migrate to Manifest Version 3 (#8) * Use to-jyutping Package, Strict Typing * Migrate to Manifest Version 3 --- .github/workflows/build.yml | 70 ++-- .gitignore | 10 +- LICENSE | 2 +- README.md | 22 +- _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 | 4 +- _locales/zh_TW/messages.json | 4 +- background_scripts/index.js | 25 ++ background_scripts/main.js | 54 --- build.py | 22 -- content_scripts/{main.css => index.css} | 0 content_scripts/{main.js => index.js} | 38 +- global.d.ts | 3 + jsconfig.json | 26 ++ lib/MessageManager.js | 63 +++- lib/Trie.js | 50 --- manifest.json | 24 +- package-lock.json | 473 ++++++++++++++++++++++++ package.json | 17 + popup/index.css | 75 ++-- popup/index.html | 25 +- popup/index.js | 20 +- 25 files changed, 781 insertions(+), 278 deletions(-) create mode 100644 _locales/ko/messages.json create mode 100644 background_scripts/index.js delete mode 100644 background_scripts/main.js delete mode 100644 build.py rename content_scripts/{main.css => index.css} (100%) rename content_scripts/{main.js => index.js} (74%) create mode 100644 global.d.ts create mode 100644 jsconfig.json delete mode 100644 lib/Trie.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0ce2f70..97e231e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,34 +1,44 @@ -name: Build +name: Lint & Build on: - push: - release: - types: - - created + push: + branches: + - main + pull_request: + branches: + - main + 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: Run lint + run: npm run lint + + - name: Upload artifact + if: ${{ github.event_name == 'release' }} + uses: actions/upload-artifact@v4 + with: + name: inject-jyutping + path: | + _locales + background_scripts + content_scripts + icons + lib + popup + manifest.json + node_modules/webextension-polyfill/dist/browser-polyfill.min.js + node_modules/to-jyutping/dist/index.js diff --git a/.gitignore b/.gitignore index b087f0b..43a5949 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 +jsconfig.tsbuildinfo 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..1a18e8e 100644 --- a/README.md +++ b/README.md @@ -1,22 +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. -項目靈感來自 [EYHN/Furigana](https://github.com/EYHN/Furigana)。 +

Install on1 zong1

-This project is inspired by [EYHN/Furigana](https://github.com/EYHN/Furigana). +- [Chrome Web Store](https://chrome.google.com/webstore/detail/inject-jyutping/lfgpgjkjglogbndlkikjgbbfoiofbdjp) +- [Firefox Browser Add-On](https://addons.mozilla.org/firefox/addon/inject-jyutping/) -

Install on1 zong1

+

Preview jyu6 laam5

-- [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

- -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..7d8ba2f 100644 --- a/_locales/zh_HK/messages.json +++ b/_locales/zh_HK/messages.json @@ -12,9 +12,9 @@ "message": "注入粵拼" }, "popupCheckboxText": { - "message": "(zi6)(dung6)(zyu3)(jap6)" + "message": "" }, "refreshPromptText": { - "message": "請刷新頁面使更改生效。" + "message": "請重新載入頁面以使變更生效。" } } diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index aa7db8a..9df3e66 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -12,9 +12,9 @@ "message": "注入粵拼" }, "popupCheckboxText": { - "message": "(zi6)(dung6)(zyu3)(jap6)" + "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 e9dbdf6..0000000 --- a/background_scripts/main.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 轉換一個字串,取得字串中每個字及其讀音。 - * @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(); - - 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 */ - - browser.runtime.onConnect.addListener(port => { - const mm = new MessageManager(port); - mm.registerHandler('convert', s => convert(t, s)); - }); - - /* 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.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.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 74% rename from content_scripts/main.js rename to content_scripts/index.js index 25812b8..4d295fa 100644 --- a/content_scripts/main.js +++ b/content_scripts/index.js @@ -1,19 +1,17 @@ /** * 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 /[\p{Unified_Ideograph}\u3006\u3007]/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 +20,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) { @@ -48,22 +46,28 @@ function makeRuby(ch, pronunciation) { } 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 +76,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 () => { 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/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..4d264f0 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + + "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 + } +} diff --git a/lib/MessageManager.js b/lib/MessageManager.js index 3a8c458..fc6492f 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); +/** + * 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 你好你好你好 + * ``` + */ 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 => { @@ -50,3 +75,7 @@ class MessageManager { }); } } + +Object.assign(globalThis, { MessageManager }); + +/** @type {typeof MessageManager} MessageManager */ 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..4df8018 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "__MSG_extensionName__", - "version": "0.3.0", + "version": "0.4.0", "description": "__MSG_extensionDescription__", "icons": { "48": "icons/48.png", @@ -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", "lib/Trie.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 new file mode 100644 index 0000000..4f3a893 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,473 @@ +{ + "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", + "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", + "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..42eaf00 --- /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", + "scripts": { + "lint": "tsc -p jsconfig.json" + }, + "dependencies": { + "to-jyutping": "2.0.0", + "webextension-polyfill": "^0.12.0" + }, + "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 fa9884c..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,20 +19,22 @@ ruby.inject-jyutping > rt { text-transform: initial; } -#container { - display: flex; - flex-direction: column; - align-items: center; +#auto-inject-text { + white-space: nowrap; } -#checkboxText { - white-space: nowrap; - margin: 1rem; +#auto-inject-cantonese-text { + font-weight: 600; +} + +#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. @@ -31,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; @@ -39,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 2abce36..f178eef 100644 --- a/popup/index.html +++ b/popup/index.html @@ -3,17 +3,26 @@ - - +
-   - -

+
+
+
+ (zi6)(dung6)(zyu3)(jap6) +
+
+
+ +
+
diff --git a/popup/index.js b/popup/index.js index fb5017d..f26afeb 100644 --- a/popup/index.js +++ b/popup/index.js @@ -1,12 +1,20 @@ +import '/node_modules/webextension-polyfill/dist/browser-polyfill.min.js'; + +const i = browser.i18n.getMessage; + +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 = 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'); + autoInjectNativeText.textContent = i('popupCheckboxText'); + autoInjectCheckbox.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'); +autoInjectCheckbox.addEventListener('click', () => { + browser.storage.local.set({ enabled: autoInjectCheckbox.checked }); + refreshPromptText.innerHTML = i('refreshPromptText'); });