From 2f8ad720e0e2fd33975c66e583020721230eb583 Mon Sep 17 00:00:00 2001 From: Tony Hu Date: Fri, 20 Jun 2025 17:30:21 +0800 Subject: [PATCH 1/7] fix(translator): ensure translation menu updates only when document is visible and improve context menu handling --- .../content/composables/useTranslator.ts | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/entrypoints/content/composables/useTranslator.ts b/entrypoints/content/composables/useTranslator.ts index 966cff1d..6ad9a61d 100644 --- a/entrypoints/content/composables/useTranslator.ts +++ b/entrypoints/content/composables/useTranslator.ts @@ -61,35 +61,51 @@ async function _useTranslator() { }) watch(enabled, async (newVal) => { - await setTranslationMenuTargetLanguage(newVal, targetLocale.value) + if (document.visibilityState === 'visible') { + await setTranslationMenuTargetLanguage(newVal, targetLocale.value) + } }) watch(targetLocale, async (newVal) => { - await setTranslationMenuTargetLanguage(enabled.value, newVal) + if (document.visibilityState === 'visible') { + await setTranslationMenuTargetLanguage(enabled.value, newVal) + } }) + let isWaiting = false + registerContentScriptRpcEvent('contextMenuClicked', async (e) => { - if (!enabled.value && userConfig.llm.endpointType.get() === 'ollama') { - if (!(await ollamaStatusStore.updateConnectionStatus())) { - toast('Failed to connect to Ollama server, please check your Ollama connection', { duration: 2000 }) - showSettings(true, 'server-address-section') - return + if (isWaiting) return + isWaiting = true + try { + if (!enabled.value && userConfig.llm.endpointType.get() === 'ollama') { + if (!(await ollamaStatusStore.updateConnectionStatus())) { + toast('Failed to connect to Ollama server, please check your Ollama connection', { duration: 2000 }) + showSettings(true, 'server-address-section') + return + } + else if ((await ollamaStatusStore.updateModelList()).length === 0) { + toast('No model found, please download a model.', { duration: 2000 }) + showSettings(true, 'model-download-section') + return + } + } + if (e.menuItemId === 'native-mind-page-translate') { + await onInit() + enabled.value = toggleTranslation(!enabled.value) } - else if ((await ollamaStatusStore.updateModelList()).length === 0) { - toast('No model found, please download a model.', { duration: 2000 }) - showSettings(true, 'model-download-section') - return + else if (e.menuItemId === 'native-mind-selection-translate') { + await onInit() + const selection = window.getSelection() + const commonAncestor = getCommonAncestorElement(selection) + commonAncestor && translation.translateElement(commonAncestor) } } - if (e.menuItemId === 'native-mind-page-translate') { - await onInit() - enabled.value = toggleTranslation(!enabled.value) + catch (error) { + logger.error('Error handling context menu click', error) } - else if (e.menuItemId === 'native-mind-selection-translate') { - await onInit() - const selection = window.getSelection() - const commonAncestor = getCommonAncestorElement(selection) - commonAncestor && translation.translateElement(commonAncestor) + finally { + isWaiting = false } }) From fd81cbaf5fd51ece17811860146b5f4d0b2779d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Jun 2025 09:34:46 +0000 Subject: [PATCH 2/7] chore(release): v1.2.0-beta.14 --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c73978dc..c8707c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Changelog +## v1.2.0-beta.14 + +[compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.13...v1.2.0-beta.14) + +### 🩹 Fixes + +- **translator:** Ensure translation menu updates only when document is visible and improve context menu handling ([2f8ad72](https://github.com/NativeMindBrowser/NativeMindExtension/commit/2f8ad72)) + +### ❤️ Contributors + +- Tony Hu ([@tonyhu-012](http://github.com/tonyhu-012)) + ## v1.2.0-beta.13 [compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.12...v1.2.0-beta.13) diff --git a/package.json b/package.json index e93d03c7..abce0151 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nativemind-extension", - "version": "1.2.0-beta.13", + "version": "1.2.0-beta.14", "private": false, "author": "NativeMind", "keywords": [ From 93c5d030204e955e771264f6143d493a790ecf10 Mon Sep 17 00:00:00 2001 From: Tony Hu Date: Mon, 23 Jun 2025 09:15:17 +0800 Subject: [PATCH 3/7] feat(wxt): add module to expose web resources and update config --- package.json | 1 + pnpm-lock.yaml | 113 ++++++++++++++++++++ wxt-modules/expose-web-resources/index.d.ts | 22 ++++ wxt-modules/expose-web-resources/index.mjs | 34 ++++++ wxt.config.ts | 11 +- 5 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 wxt-modules/expose-web-resources/index.d.ts create mode 100644 wxt-modules/expose-web-resources/index.mjs diff --git a/package.json b/package.json index abce0151..340fc16f 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "eslint": "^9.26.0", "eslint-plugin-simple-import-sort": "^12.1.1", "fs-extra": "^11.3.0", + "glob": "^11.0.3", "globals": "^16.1.0", "husky": "^9.1.7", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f28e5a..3dc2d51a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: fs-extra: specifier: ^11.3.0 version: 11.3.0 + glob: + specifier: ^11.0.3 + version: 11.0.3 globals: specifier: ^16.1.0 version: 16.1.0 @@ -877,6 +880,18 @@ packages: resolution: {integrity: sha512-+I4vRzHm38VjLr/CAciEPJhGYFzWWW4HMTm+6H3WqknXLh0ozNX9oC8ogMUwTSXYR/wGUb1/lTpNziiCH5MybQ==} engines: {node: '>= 16'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -2137,6 +2152,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2149,6 +2167,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -2432,6 +2453,10 @@ packages: debug: optional: true + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.2: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} @@ -2535,6 +2560,11 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} @@ -2869,6 +2899,10 @@ packages: resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} engines: {node: ^18.17 || >=20.6.1} + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -3210,6 +3244,10 @@ packages: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -3466,6 +3504,9 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@10.0.1: resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} engines: {node: '>=18'} @@ -3528,6 +3569,10 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + path-to-regexp@8.2.0: resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} engines: {node: '>=16'} @@ -4169,6 +4214,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4606,6 +4655,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} @@ -5319,6 +5372,21 @@ snapshots: '@intlify/shared@11.1.5': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -6661,6 +6729,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} electron-to-chromium@1.5.151: {} @@ -6669,6 +6739,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} encoding-sniffer@0.2.0: @@ -7039,6 +7111,11 @@ snapshots: follow-redirects@1.15.9: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.2: dependencies: asynckit: 0.4.0 @@ -7158,6 +7235,15 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + global-agent@3.0.0: dependencies: boolean: 3.2.0 @@ -7425,6 +7511,10 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + jiti@1.21.7: {} jiti@2.4.2: {} @@ -7716,6 +7806,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -7998,6 +8092,8 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 + package-json-from-dist@1.0.1: {} + package-json@10.0.1: dependencies: ky: 1.8.1 @@ -8061,6 +8157,11 @@ snapshots: path-key@4.0.0: {} + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + path-to-regexp@8.2.0: {} pathe@1.1.2: {} @@ -8734,6 +8835,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.4.0 @@ -9183,6 +9290,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 diff --git a/wxt-modules/expose-web-resources/index.d.ts b/wxt-modules/expose-web-resources/index.d.ts new file mode 100644 index 00000000..84cd4ecc --- /dev/null +++ b/wxt-modules/expose-web-resources/index.d.ts @@ -0,0 +1,22 @@ +import * as wxt from 'wxt' + +declare const _default: wxt.WxtModule + +/** + * Options for the auto-icons module + */ +interface ExposeWebResourcesOptions { + /** + * Paths to expose as web resources. + * Paths are relative to the project's output directory. + * @default [] + */ + paths?: string[] +} +declare module 'wxt' { + interface InlineConfig { + exposeWebResources?: ExposeWebResourcesOptions + } +} + +export { _default as default, type ExposeWebResourcesOptions } diff --git a/wxt-modules/expose-web-resources/index.mjs b/wxt-modules/expose-web-resources/index.mjs new file mode 100644 index 00000000..56e59163 --- /dev/null +++ b/wxt-modules/expose-web-resources/index.mjs @@ -0,0 +1,34 @@ +// this module is forked from wxt-modules/auto-icons and fixed to work +// ref: https://github.com/wxt-dev/wxt/pull/1616 + +import 'wxt' + +import defu from 'defu' +import { globSync } from 'glob' +import { defineWxtModule } from 'wxt/modules' + +const index = defineWxtModule({ + name: '@wxt-dev/expose-web-resources', + configKey: 'exposeWebResources', + async setup(wxt, options) { + const parsedOptions = defu( + options, + { + paths: [], + }, + ) + wxt.hooks.hook('build:manifestGenerated', async (wxt2, manifest) => { + const outputDir = wxt2.config.outDir + const resources = globSync(parsedOptions.paths, { + root: outputDir, + }) + manifest.web_accessible_resources = manifest.web_accessible_resources || [] + manifest.web_accessible_resources.push({ + resources: resources.map((resource) => resource.replace(outputDir, '')), + matches: [''], + }) + }) + }, +}) + +export { index as default } diff --git a/wxt.config.ts b/wxt.config.ts index c88ae6f3..b412c5f0 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -39,13 +39,16 @@ svgLoaderPlugin.name = 'svg-loader' // See https://wxt.dev/api/config.html export default defineConfig({ imports: false, - modules: ['@wxt-dev/module-vue', './wxt-modules/auto-icons/index.mjs'], + modules: ['@wxt-dev/module-vue', './wxt-modules/auto-icons/index.mjs', './wxt-modules/expose-web-resources/index.mjs'], webExt: { chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'], }, zip: { artifactTemplate: '{{name}}-{{packageVersion}}-{{browser}}-{{mode}}.zip', }, + exposeWebResources: { + paths: ['/assets/*.woff2', '/content-scripts/*.css', '/main-world-injected.js'], + }, hooks: { // replace the default svg-loader plugin provided by wxt with our custom one 'vite:build:extendConfig': (_entrypoint, config) => { @@ -81,12 +84,6 @@ export default defineConfig({ content_security_policy: { extension_pages: 'script-src \'self\' \'wasm-unsafe-eval\'; object-src \'self\';', }, - web_accessible_resources: [ - { - resources: ['/main-world-injected.js'], - matches: [''], - }, - ], content_scripts: [ { matches: [''], From e9476970527f8dd76b43076770c414c7c45c46bc Mon Sep 17 00:00:00 2001 From: Tony Hu Date: Mon, 23 Jun 2025 09:15:50 +0800 Subject: [PATCH 4/7] fix(styles): enhance style injection and loading mechanism for shadow DOM --- composables/useInjectStyle.ts | 4 +-- entrypoints/content/index.tsx | 2 +- entrypoints/content/ui.ts | 21 +++++++++-- utils/constants.ts | 1 + utils/css.ts | 68 +++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 utils/css.ts diff --git a/composables/useInjectStyle.ts b/composables/useInjectStyle.ts index d6b34d47..c9629976 100644 --- a/composables/useInjectStyle.ts +++ b/composables/useInjectStyle.ts @@ -1,13 +1,13 @@ import { onScopeDispose } from 'vue' -export function useInjectStyle(inlineCss: string) { +export function useInjectStyle(inlineCss: string, attachedElement?: HTMLElement) { const styleElement = document.createElement('style') styleElement.textContent = inlineCss styleElement.setAttribute('data-nativemind-style', 'true') styleElement.setAttribute('data-nativemind-style-injected', 'true') // Append the style element to the head - document.head.appendChild(styleElement) + ;(attachedElement ?? document.head).appendChild(styleElement) onScopeDispose(() => { styleElement.remove() diff --git a/entrypoints/content/index.tsx b/entrypoints/content/index.tsx index 75a89f62..49a160e8 100644 --- a/entrypoints/content/index.tsx +++ b/entrypoints/content/index.tsx @@ -12,7 +12,7 @@ import { createShadowRootOverlay } from './ui' export default defineContentScript({ matches: ['*://*/*'], - cssInjectionMode: 'ui', + cssInjectionMode: 'manual', runAt: 'document_start', async main(ctx) { const ui = await createShadowRootOverlay(ctx, ({ rootElement }) => { diff --git a/entrypoints/content/ui.ts b/entrypoints/content/ui.ts index c8ff3544..c6083f8b 100644 --- a/entrypoints/content/ui.ts +++ b/entrypoints/content/ui.ts @@ -1,12 +1,26 @@ import { createPinia } from 'pinia' import type { Component } from 'vue' import { createApp } from 'vue' +import { browser } from 'wxt/browser' import { ContentScriptContext } from 'wxt/utils/content-script-context' import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root' +import { splitShadowRootCss } from 'wxt/utils/split-shadow-root-css' import { initToast } from '@/composables/useToast' +import { FONT_FACE_CSS } from '@/utils/constants' +import { convertPropertiesIntoSimpleVariables, createStyleSheetByCssText, loadContentScriptCss, replaceFontFaceUrl, scopeStyleIntoShadowRoot } from '@/utils/css' import { i18n } from '@/utils/i18n' +async function loadStyleSheet(shadowRoot: ShadowRoot) { + const contentScriptCss = await loadContentScriptCss(import.meta.env.ENTRYPOINT) + const fontFaceCss = await loadContentScriptCss(FONT_FACE_CSS) + const { shadowCss, documentCss } = splitShadowRootCss(contentScriptCss) + shadowRoot.adoptedStyleSheets.push(scopeStyleIntoShadowRoot(shadowCss)) + shadowRoot.adoptedStyleSheets.push(convertPropertiesIntoSimpleVariables(scopeStyleIntoShadowRoot(documentCss), true)) + // font-face can only be applied to the document, not the shadow root + document.adoptedStyleSheets.push(replaceFontFaceUrl(createStyleSheetByCssText(fontFaceCss), (url) => browser.runtime.getURL(url as Parameters[0]))) +} + export async function createShadowRootOverlay(ctx: ContentScriptContext, component: Component<{ rootElement: HTMLDivElement }>) { const ui = await createShadowRootUi(ctx, { name: 'nativemind-container', @@ -14,7 +28,8 @@ export async function createShadowRootOverlay(ctx: ContentScriptContext, compone isolateEvents: true, mode: 'open', anchor: 'html', - onMount(uiContainer, _shadow, shadowHost) { + async onMount(uiContainer, shadowRoot, shadowHost) { + await loadStyleSheet(shadowRoot) const rootElement = document.createElement('div') const toastRoot = document.createElement('div') uiContainer.appendChild(rootElement) @@ -31,8 +46,8 @@ export async function createShadowRootOverlay(ctx: ContentScriptContext, compone app.mount(rootElement) return app }, - onRemove(app) { - app?.unmount() + async onRemove(app) { + (await app)?.unmount() }, }) return ui diff --git a/utils/constants.ts b/utils/constants.ts index 5c58e98d..7e27e41b 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -6,6 +6,7 @@ export const OLLAMA_TUTORIAL_URL = 'https://nativemind.app/blog/tutorial/ollama- export const OLLAMA_DOWNLOAD_URL = 'https://ollama.com/download' export const OLLAMA_HOMEPAGE_URL = 'https://ollama.com' export const NATIVEMIND_HOMEPAGE_URL = 'https://nativemind.app' +export const FONT_FACE_CSS = 'content2' export const INVALID_URLS = [ /^https:\/\/chromewebstore.google.com/, diff --git a/utils/css.ts b/utils/css.ts new file mode 100644 index 00000000..b3e91b5f --- /dev/null +++ b/utils/css.ts @@ -0,0 +1,68 @@ +import { browser } from 'wxt/browser' + +import logger from './logger' + +export function convertPropertiesIntoSimpleVariables(sheet: CSSStyleSheet, scopeInShadowDom = true) { + const properties = [] + for (const rule of sheet.cssRules) { + if (rule instanceof CSSPropertyRule) { + if (rule.initialValue) { + properties.push(`${rule.name}: ${rule.initialValue}`) + } + } + } + const scope = scopeInShadowDom ? ':host' : ':root' + sheet.insertRule(`${scope} { ${properties.join('; ')} }`) + return sheet +} + +export function scopeStyleIntoShadowRoot(cssText: string) { + const sheet = createStyleSheetByCssText(cssText.replaceAll(':root', ':host')) + return sheet +} + +export async function loadContentScriptCss(name: string): Promise { + // @ts-expect-error - css output files is not defined in the types + const url = browser.runtime.getURL(`/content-scripts/${name}.css`) + try { + const res = await fetch(url) + return await res.text() + } + catch (err) { + logger.warn( + `Failed to load styles @ ${url}. Did you forget to import the stylesheet in your entrypoint?`, + err, + ) + return '' + } +} + +export function createStyleSheetByCssText(cssText: string) { + const sheet = new CSSStyleSheet() + try { + sheet.replaceSync(cssText) + } + catch (err) { + logger.error('Failed to create stylesheet from css text', err) + } + return sheet +} + +export function replaceFontFaceUrl(sheet: CSSStyleSheet, converter: (url: string) => string) { + for (const rule of sheet.cssRules) { + if (rule instanceof CSSFontFaceRule) { + const src = rule.style.getPropertyValue('src') + if (src) { + const newSrc = src.split(',').map((url) => { + const match = url.match(/url\(['"]?([^'"]+)['"]?\)/) + if (match && match[1]) { + return `url('${converter(match[1])}')` + } + return url + }).join(', ') + rule.style.setProperty('src', newSrc) + } + } + } + return sheet +} From cd9ea29aed7185c8157c6dbccff8f05cc1805c90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Jun 2025 01:22:01 +0000 Subject: [PATCH 5/7] chore(release): v1.2.0-beta.15 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8707c37..d29dfec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changelog +## v1.2.0-beta.15 + +[compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.14...v1.2.0-beta.15) + +### 🚀 Enhancements + +- **wxt:** Add module to expose web resources and update config ([93c5d03](https://github.com/NativeMindBrowser/NativeMindExtension/commit/93c5d03)) + +### 🩹 Fixes + +- **styles:** Enhance style injection and loading mechanism for shadow DOM ([e947697](https://github.com/NativeMindBrowser/NativeMindExtension/commit/e947697)) + +### ❤️ Contributors + +- Tony Hu ([@tonyhu-012](http://github.com/tonyhu-012)) + ## v1.2.0-beta.14 [compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.13...v1.2.0-beta.14) diff --git a/package.json b/package.json index 340fc16f..15b99ede 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nativemind-extension", - "version": "1.2.0-beta.14", + "version": "1.2.0-beta.15", "private": false, "author": "NativeMind", "keywords": [ From a9b410d97455a78f6ccd5946e5f938ca1d2cab49 Mon Sep 17 00:00:00 2001 From: Tony Hu Date: Mon, 23 Jun 2025 10:01:53 +0800 Subject: [PATCH 6/7] feat(background): inject content script on installation --- entrypoints/background.ts | 31 ++++++++++++++++++------------- utils/constants.ts | 1 + 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/entrypoints/background.ts b/entrypoints/background.ts index 7a43b638..ca477339 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -8,7 +8,6 @@ import { INVALID_URLS } from '@/utils/constants' import { CONTEXT_MENU, CONTEXT_MENU_ITEM_TRANSLATE_PAGE } from '@/utils/context-menu' import logger from '@/utils/logger' import { bgBroadcastRpc } from '@/utils/rpc' -import { registerBackgroundRpcEvent } from '@/utils/rpc/background-fns' import { isTabValid } from '@/utils/tab' import { registerDeclarativeNetRequestRule } from '@/utils/web-request' @@ -37,8 +36,7 @@ export default defineBackground(() => { }) const setPopupStatusBasedOnUrl = async (tabId: number, url: string) => { - const isValidUrl = /https?:\/\//.test(url ?? '') - if (!isValidUrl || unAttachedTabs.has(tabId) || INVALID_URLS.some((regex) => regex.test(url))) { + if (INVALID_URLS.some((regex) => regex.test(url))) { await browser.action.setPopup({ popup: 'popup.html' }) } else { @@ -84,14 +82,8 @@ export default defineBackground(() => { }) }) - const unAttachedTabs = new Set() - browser.runtime.onInstalled.addListener(async () => { logger.debug('Extension Installed') - const tabs = await browser.tabs.query({ currentWindow: true }) - for (const tab of tabs) { - tab.id && unAttachedTabs.add(tab.id) - } await browser.contextMenus.removeAll() for (const menu of CONTEXT_MENU) { browser.contextMenus.create({ @@ -100,6 +92,23 @@ export default defineBackground(() => { contexts: menu.contexts, }) } + // inject content script into all tabs which are opened before the extension is installed + const tabs = await browser.tabs.query({}) + for (const tab of tabs) { + if (tab.id && tab.url) { + const tabUrl = tab.url + if (INVALID_URLS.some((regex) => regex.test(tabUrl))) continue + await browser.scripting.executeScript({ + files: ['/content-scripts/content.js'], + target: { tabId: tab.id }, + world: 'ISOLATED', + }).then(() => { + logger.info('Content script injected', { tabId: tab.id }) + }).catch((error) => { + logger.error('Failed to inject content script', { tabId: tab.id, error }) + }) + } + } }) browser.contextMenus.onClicked.addListener(async (info, tab) => { @@ -112,9 +121,5 @@ export default defineBackground(() => { } }) - registerBackgroundRpcEvent('ready', (tabId) => { - unAttachedTabs.delete(tabId) - }) - logger.info('Hello background!', { id: browser.runtime.id }) }) diff --git a/utils/constants.ts b/utils/constants.ts index 7e27e41b..202f7735 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -11,4 +11,5 @@ export const FONT_FACE_CSS = 'content2' export const INVALID_URLS = [ /^https:\/\/chromewebstore.google.com/, /^https:\/\/chrome.google.com\/webstore\//, + /^(?!https?:\/\/).+/, ] From e8c9982190fa813c2ff01fcd354c517df0c192eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Jun 2025 02:04:52 +0000 Subject: [PATCH 7/7] chore(release): v1.2.0-beta.16 --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d29dfec7..392cf5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Changelog +## v1.2.0-beta.16 + +[compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.15...v1.2.0-beta.16) + +### 🚀 Enhancements + +- **background:** Inject content script on installation ([a9b410d](https://github.com/NativeMindBrowser/NativeMindExtension/commit/a9b410d)) + +### ❤️ Contributors + +- Tony Hu ([@tonyhu-012](http://github.com/tonyhu-012)) + ## v1.2.0-beta.15 [compare changes](https://github.com/NativeMindBrowser/NativeMindExtension/compare/v1.2.0-beta.14...v1.2.0-beta.15) diff --git a/package.json b/package.json index 15b99ede..cafbd92f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nativemind-extension", - "version": "1.2.0-beta.15", + "version": "1.2.0-beta.16", "private": false, "author": "NativeMind", "keywords": [