110 Commits

Author SHA1 Message Date
semantic-release-bot
576ab77fe6 chore(release): 1.12.1
## [1.12.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.12.0...v1.12.1) (2026-02-09)

### Bug Fixes

* permissions ([3e00090](3e00090ac9))
2026-02-09 16:51:01 +03:00
3e00090ac9 fix: permissions 2026-02-09 16:50:50 +03:00
semantic-release-bot
4d87f9d4a1 chore(release): 1.12.0
# [1.12.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.11.0...v1.12.0) (2026-02-09)

### Features

* list management ([5e3e2bb](5e3e2bbfe4))
2026-02-09 16:47:14 +03:00
5e3e2bbfe4 feat: list management 2026-02-09 16:46:56 +03:00
8a44ad8022 fixed remove from exceptions button 2026-02-09 16:13:28 +03:00
46976fc696 save popup state 2026-02-09 15:49:03 +03:00
16d4270442 bundle font with extension 2026-02-09 14:52:32 +03:00
fb5a7a02c3 updated translations 2026-02-09 14:43:37 +03:00
401f03f58b removed list manager window 2026-02-06 16:33:10 +03:00
24aca05575 open list manager in new tab 2026-02-06 15:54:24 +03:00
52ef7b51dc shortened hints 2026-02-06 14:16:03 +03:00
6dab60e2ea added pagination to popup 2026-02-06 13:57:33 +03:00
224745412a renamed import/export buttons in list manager 2026-02-06 13:10:06 +03:00
01135ff92e added move-to dropdown to word items 2026-02-06 13:06:24 +03:00
9294593975 fixed multiple selection actions in list manager 2026-02-06 12:40:37 +03:00
592cce580e styles refactor 2026-02-06 12:31:12 +03:00
b7f98912f2 restyled buttons 2026-02-06 12:01:15 +03:00
1bff2bd28d redesigned remove buttons in exception section 2026-02-06 11:50:11 +03:00
d0e3ab3e82 removed badges from "on page" section 2026-02-06 11:44:27 +03:00
f756c665fe updated styling and translations 2026-02-06 02:49:31 +03:00
01963218ab redesign of popup 2026-02-05 16:49:00 +03:00
caaeca6a88 fixed sizing 2026-02-05 16:48:53 +03:00
cdb2937e63 added import list option 2026-02-05 14:34:20 +03:00
22bc39cf6a fixed listmanager styling 2026-02-05 14:29:43 +03:00
90b9ea5134 updated list items ui 2026-02-04 23:45:25 +03:00
0f9babbb76 WIP: list manager 2026-02-04 23:30:19 +03:00
f8380e883c changed theme from ny 2026-02-04 22:46:12 +03:00
semantic-release-bot
11801bad88 chore(release): 1.11.0
# [1.11.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.2...v1.11.0) (2025-12-11)

### Features

* NY ([647f4f8](647f4f8ad4))
2025-12-11 17:00:53 +03:00
647f4f8ad4 feat: NY 2025-12-11 17:00:39 +03:00
7a50d24a9d style: new year theme 2025-12-11 16:56:55 +03:00
semantic-release-bot
3de4190ad7 chore(release): 1.10.2
## [1.10.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.1...v1.10.2) (2025-11-22)

### Bug Fixes

* do not highlight textareas. create badge with popup instead ([f800318](f800318e66))
* update badge when textarea text changes ([5d7766d](5d7766d5fd))
2025-11-22 05:41:02 +03:00
86b143f5a0 chore: refactor badge handling and highlight rendering for textareas 2025-11-22 05:29:06 +03:00
5d7766d5fd fix: update badge when textarea text changes 2025-11-22 05:19:46 +03:00
f800318e66 fix: do not highlight textareas. create badge with popup instead 2025-11-22 05:16:40 +03:00
semantic-release-bot
14ceafd04d chore(release): 1.10.1
## [1.10.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.0...v1.10.1) (2025-11-20)

### Bug Fixes

* add fake highlighting for textareas ([f3c999a](f3c999ad19))
* correct matches list buttons handling ([f895327](f895327eb0))
* go to textarea match ([fc4ef86](fc4ef8655d))
2025-11-20 22:47:21 +03:00
f895327eb0 fix: correct matches list buttons handling
fix: pressing on the match list item triggers jump to the current match
2025-11-20 22:46:58 +03:00
fc4ef8655d fix: go to textarea match 2025-11-20 22:35:21 +03:00
f3c999ad19 fix: add fake highlighting for textareas 2025-11-20 22:20:01 +03:00
semantic-release-bot
14f1b4b935 chore(release): 1.10.0
# [1.10.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.3...v1.10.0) (2025-11-20)

### Bug Fixes

* lighten debounce() usage, do not do full re-render on every change ([348d64f](348d64f356))
* made placeholder for textarea, added outline offset ([4f32be0](4f32be0b93))

### Features

* new tabbed layout ([18e167c](18e167cb7f))
* show all found words and allow jump to them (beta) ([1a4c91f](1a4c91fd5e))
2025-11-20 15:48:45 +03:00
1a4c91fd5e feat: show all found words and allow jump to them (beta) 2025-11-20 15:48:28 +03:00
1c58357418 chore(i18n): rephrased hint 2025-11-20 14:25:36 +03:00
521b3295e6 nit: show spinner while user settings are loaded 2025-11-20 14:13:49 +03:00
348d64f356 fix: lighten debounce() usage, do not do full re-render on every change 2025-11-20 14:09:43 +03:00
dfdc1742ec nit: add logo to header 2025-11-20 13:51:06 +03:00
18e167cb7f feat: new tabbed layout 2025-11-20 13:45:42 +03:00
0990543aa9 chore(i18n): update missing translations 2025-11-19 16:15:21 +03:00
4f32be0b93 fix: made placeholder for textarea, added outline offset 2025-11-19 16:12:55 +03:00
1ba701737e chore: add license 2025-11-19 10:02:59 +03:00
semantic-release-bot
d275a6fd0d chore(release): 1.9.3
## [1.9.3](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.2...v1.9.3) (2025-11-18)

### Bug Fixes

* use CSS Custom Highlight API to avoid dom modifications (fixes [#1](https://github.com/obsqrbtz/goose-highlighter/issues/1)) ([3f2bb60](3f2bb6080b))
2025-11-19 01:26:16 +03:00
3f2bb6080b fix: use CSS Custom Highlight API to avoid dom modifications (fixes #1) 2025-11-19 01:25:55 +03:00
3da28a2ad7 chore: update deps 2025-11-18 20:58:59 +03:00
semantic-release-bot
4f575d9534 chore(release): 1.9.2
## [1.9.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.1...v1.9.2) (2025-11-14)

### Bug Fixes

* **highlight:** prevent creating extra <span>'s ([#1](https://github.com/obsqrbtz/goose-highlighter/issues/1)) ([affddd3](affddd3dbc))
2025-11-14 13:40:56 +03:00
affddd3dbc fix(highlight): prevent creating extra <span>'s (#1) 2025-11-14 13:40:43 +03:00
semantic-release-bot
c8334f9e68 chore(release): 1.9.1
## [1.9.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.0...v1.9.1) (2025-11-05)

### Bug Fixes

* remove halowen styling ([172aa75](172aa7583b))
2025-11-05 14:59:42 +03:00
172aa7583b fix: remove halowen styling 2025-11-05 14:59:27 +03:00
semantic-release-bot
6d7d9ac151 chore(release): 1.9.0
# [1.9.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.5...v1.9.0) (2025-10-31)

### Features

* haloween styling ([5ef380e](5ef380e544))
2025-10-31 11:17:28 +03:00
5ef380e544 feat: haloween styling 2025-10-31 11:17:08 +03:00
semantic-release-bot
c634f6bc8b chore(release): 1.8.5
## [1.8.5](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.4...v1.8.5) (2025-10-29)

### Bug Fixes

* highlight colors when multiple list have different configurations ([67577c8](67577c89cf))
2025-10-29 12:30:37 +03:00
67577c89cf fix: highlight colors when multiple list have different configurations 2025-10-29 12:29:55 +03:00
semantic-release-bot
326e585021 chore(release): 1.8.4
## [1.8.4](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.3...v1.8.4) (2025-10-28)

### Bug Fixes

* do not re-highlight when already processing highlights ([8be53f3](8be53f3240))
2025-10-28 15:32:14 +03:00
8be53f3240 fix: do not re-highlight when already processing highlights 2025-10-28 15:31:46 +03:00
f07617fa55 Merge branch 'main' of https://github.com/obsqrbtz/goose-highlighter 2025-10-09 16:18:53 +03:00
e79874922a added logo 2025-10-09 16:18:36 +03:00
71216cbcd9 ci: update manifest in publish workflow 2025-10-08 17:54:06 +03:00
f292bd7149 nit: added footer with version and github. 2025-10-08 16:32:04 +03:00
584ced252f ci: publish on version tag push 2025-10-08 16:26:32 +03:00
semantic-release-bot
ff5752da84 chore(release): 1.8.3
## [1.8.3](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.2...v1.8.3) (2025-10-08)

### Bug Fixes

* stop observing when highlightting is disabled ([d7c8dbb](d7c8dbb5f0))
2025-10-08 16:11:52 +03:00
d7c8dbb5f0 fix: stop observing when highlightting is disabled 2025-10-08 16:11:25 +03:00
semantic-release-bot
bc02d0fb77 chore(release): 1.8.2
## [1.8.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.1...v1.8.2) (2025-10-08)

### Bug Fixes

* do not call save() on all keypresses in textboxes ([687d7c9](687d7c9e62))
* do not save anything in list settings section until presses the apply button ([0734bf3](0734bf3308))
2025-10-08 14:33:46 +03:00
0734bf3308 fix: do not save anything in list settings section until presses the apply button 2025-10-08 14:33:28 +03:00
687d7c9e62 fix: do not call save() on all keypresses in textboxes 2025-10-08 14:11:08 +03:00
58d48be6e4 chore: refactor 2025-10-08 13:53:47 +03:00
00d2cc592a ci: exclude scripts dir from release package 2025-10-08 11:42:37 +03:00
a6bc14ac76 docs: update readme 2025-10-08 11:34:33 +03:00
7d90f5d5bf ci: fix release script 2025-10-08 11:21:47 +03:00
a68f2ddbe8 ci: test release action 2025-10-08 11:18:54 +03:00
2a1034aef4 fix (ci): keep update-manifest-versions script in vanilla js 2025-10-08 11:16:31 +03:00
1ec17cd83e chore: migrated to typescript 2025-10-08 11:09:16 +03:00
b5386c706f fixed eslint config 2025-10-08 10:32:59 +03:00
5ca83fce0f fix npm install 2025-10-08 10:31:48 +03:00
semantic-release-bot
7e4f2b4ecf chore(release): 1.8.0
# [1.8.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.2...v1.8.0) (2025-10-07)

### Features

* add collapsible sections ([a158a30](a158a303b0))
* add websites to exception list ([915add3](915add3a4c))
2025-10-07 14:46:28 +03:00
a158a303b0 feat: add collapsible sections 2025-10-07 14:46:03 +03:00
915add3a4c feat: add websites to exception list 2025-10-07 14:18:23 +03:00
semantic-release-bot
a1701a3504 chore(release): 1.7.2
## [1.7.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.1...v1.7.2) (2025-10-06)

### Bug Fixes

* do not create <mark> elements, just wrap found words in <span> and add .css styling ([6ba0d2e](6ba0d2eb7c))
2025-10-06 14:53:36 +03:00
6ba0d2eb7c fix: do not create <mark> elements, just wrap found words in <span> and add .css styling 2025-10-06 14:53:24 +03:00
21a120e494 ci: corected auto commit message 2025-06-27 14:09:15 +03:00
semantic-release-bot
1ef21d0975 chore(release): 1.7.1 [skip ci]
## [1.7.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.0...v1.7.1) (2025-06-27)

### Bug Fixes

* unicode support in regex ([ae1cf48](ae1cf48c53))
2025-06-27 14:05:56 +03:00
ae1cf48c53 fix: unicode support in regex 2025-06-27 14:05:12 +03:00
bca37e690f Trigger Build 2025-06-27 00:51:41 +03:00
semantic-release-bot
50c3facfae chore(release): 1.7.0 [skip ci]
# [1.7.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.6.0...v1.7.0) (2025-06-26)

### Bug Fixes

* colorbox styling ([1e704b5](1e704b51a8))
* colorbox styling ([08ad7c4](08ad7c4325))
* moved import/export to options section ([fe15965](fe15965e89))
* wordlist scrollbar styling ([b30fac5](b30fac5ded))

### Features

* add word search ([80d4bff](80d4bff0b4))
* added matching flags ([759307f](759307f983))
2025-06-27 00:49:54 +03:00
fe15965e89 fix: moved import/export to options section 2025-06-27 00:46:09 +03:00
759307f983 feat: added matching flags 2025-06-27 00:43:31 +03:00
b766f61b0c ci: publish on push to master 2025-06-27 00:00:24 +03:00
1e704b51a8 fix: colorbox styling 2025-06-26 23:59:12 +03:00
b30fac5ded fix: wordlist scrollbar styling 2025-06-26 23:40:15 +03:00
08ad7c4325 fix: colorbox styling 2025-06-26 23:35:49 +03:00
80d4bff0b4 feat: add word search 2025-06-25 16:25:52 +03:00
dbb6806a78 corrected russian summary, chore(release): 1.6.0 2025-06-25 16:00:50 +03:00
e9c7d58273 corrected portuguese and korean locales, chore(release): 1.6.0 2025-06-25 15:57:40 +03:00
19ab6eec0b trigger release, chore(release): 1.6.0 2025-06-25 15:51:30 +03:00
semantic-release-bot
dff29d447d chore(release): 1.6.0 [skip ci]
# [1.6.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.5.0...v1.6.0) (2025-06-25)

### Features

* add more locales ([d97becf](d97becfaae))
2025-06-25 15:49:26 +03:00
d97becfaae feat: add more locales 2025-06-25 15:49:04 +03:00
9926e7978f trigger release, chore(release): 1.5.0 2025-06-24 15:18:05 +03:00
23e0c5f772 chore: upload placeholder privacy policy (enforced by google) 2025-06-24 15:15:20 +03:00
2aa60d5553 ci: updated workflow, chore(release): 1.5.0 2025-06-24 15:11:33 +03:00
5eca0b395b Update publish-extension.yml 2025-06-24 15:09:51 +03:00
51de84bcb7 ci: updated workflow, chore(release): 1.5.0 2025-06-24 15:09:28 +03:00
19f54f1c2f ci: updated workflow, chore(release): 1.5.0 2025-06-24 15:08:45 +03:00
semantic-release-bot
408080345b chore(release): 1.5.0 [skip ci]
# [1.5.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.4.0...v1.5.0) (2025-06-24)

### Features

* updated readme (wrong commit type is intentional to trigger ci) ([aac8749](aac87493f2))
2025-06-24 15:04:33 +03:00
aac87493f2 feat: updated readme (wrong commit type is intentional to trigger ci) 2025-06-24 15:04:22 +03:00
58 changed files with 11530 additions and 2084 deletions

View File

@@ -3,7 +3,7 @@ name: Publish Chrome Extension
on: on:
push: push:
tags: tags:
- 'v*.*.*' - 'v*'
jobs: jobs:
publish: publish:
@@ -17,13 +17,28 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build extension
run: npm run build
- name: Set version from tag - name: Set version from tag
id: version id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Update manifest version
run: node scripts/update-manifest-version.js ${{ steps.version.outputs.VERSION }}
- name: Create zip package - name: Create zip package
run: | run: |
zip -r goose-highlighter.zip . -x '*.git*' 'node_modules/*' 'versioning.md' '.releaserc.json' 'package.json' 'package-lock.json' 'README.md' zip -r goose-highlighter.zip . -x '*.git*' 'node_modules/*' 'src/*' 'scripts/*' 'versioning.md' '.releaserc.json' 'package.json' 'package-lock.json' 'README.md' 'tsconfig.json' 'eslint.config.mjs'
- name: Install webstore upload CLI - name: Install webstore upload CLI
run: npm install -g chrome-webstore-upload-cli run: npm install -g chrome-webstore-upload-cli

103
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Create GitHub Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Create Release with Extension Files
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build extension
run: npm run build
- name: Get version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Update manifest version
run: node scripts/update-manifest-version.js ${{ steps.version.outputs.VERSION }}
- name: Create extension zip
run: |
zip -r goose-highlighter-${{ steps.version.outputs.VERSION }}.zip . \
-x '*.git*' 'node_modules/*' 'src/*' 'scripts/*' 'versioning.md' '.releaserc.json' \
'package.json' 'package-lock.json' 'README.md' 'tsconfig.json' \
'eslint.config.mjs' '.github/*' 'CHANGELOG.md' 'PRIVACY.MD'
- name: Install Chrome extension packaging tool
run: npm install -g crx3
- name: Generate private key for CRX
run: |
openssl genrsa -out key.pem 2048
- name: Create CRX file
run: |
# Create a temporary directory for the extension
mkdir temp_extension
# Copy all files except excluded ones to temp directory
rsync -av --exclude='.git*' --exclude='node_modules' --exclude='src' \
--exclude='scripts' --exclude='versioning.md' --exclude='.releaserc.json' \
--exclude='package.json' --exclude='package-lock.json' \
--exclude='README.md' --exclude='tsconfig.json' \
--exclude='eslint.config.mjs' --exclude='.github' \
--exclude='CHANGELOG.md' --exclude='PRIVACY.MD' \
--exclude='key.pem' --exclude='temp_extension' \
./ temp_extension/
# Create CRX file
crx3 temp_extension -o goose-highlighter-${{ steps.version.outputs.VERSION }}.crx -p key.pem
# Clean up
rm -rf temp_extension key.pem
- name: Generate release notes
id: release_notes
run: |
if [ -f CHANGELOG.md ]; then
# Extract the latest version's changes from CHANGELOG.md
awk '/^## \[${{ steps.version.outputs.VERSION }}\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md > release_notes.txt
if [ -s release_notes.txt ]; then
echo "RELEASE_NOTES<<EOF" >> $GITHUB_OUTPUT
cat release_notes.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "RELEASE_NOTES=Release ${{ steps.version.outputs.VERSION }}" >> $GITHUB_OUTPUT
fi
else
echo "RELEASE_NOTES=Release ${{ steps.version.outputs.VERSION }}" >> $GITHUB_OUTPUT
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ steps.version.outputs.VERSION }}
body: ${{ steps.release_notes.outputs.RELEASE_NOTES }}
files: |
goose-highlighter-${{ steps.version.outputs.VERSION }}.zip
goose-highlighter-${{ steps.version.outputs.VERSION }}.crx
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

7
.gitignore vendored
View File

@@ -1 +1,6 @@
node_modules node_modules
dist
# Auto-generated files
src/content-standalone.ts

View File

@@ -19,7 +19,7 @@
"manifest.json", "manifest.json",
"CHANGELOG.md" "CHANGELOG.md"
], ],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
} }
] ]
] ]

2
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -1,3 +1,164 @@
## [1.12.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.12.0...v1.12.1) (2026-02-09)
### Bug Fixes
* permissions ([3e00090](https://github.com/obsqrbtz/goose-highlighter/commit/3e00090ac91ec58f08a46ac9dc826935a0e7085e))
# [1.12.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.11.0...v1.12.0) (2026-02-09)
### Features
* list management ([5e3e2bb](https://github.com/obsqrbtz/goose-highlighter/commit/5e3e2bbfe40bf3fcceeb4a5de88024ecc6987973))
# [1.11.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.2...v1.11.0) (2025-12-11)
### Features
* NY ([647f4f8](https://github.com/obsqrbtz/goose-highlighter/commit/647f4f8ad489b4fa573b182c7f7e730b591dd595))
## [1.10.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.1...v1.10.2) (2025-11-22)
### Bug Fixes
* do not highlight textareas. create badge with popup instead ([f800318](https://github.com/obsqrbtz/goose-highlighter/commit/f800318e66aba17eb95d60c3bfa44c5215989314))
* update badge when textarea text changes ([5d7766d](https://github.com/obsqrbtz/goose-highlighter/commit/5d7766d5fd3df0204f64eb15f938cb2a459897ad))
## [1.10.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.10.0...v1.10.1) (2025-11-20)
### Bug Fixes
* add fake highlighting for textareas ([f3c999a](https://github.com/obsqrbtz/goose-highlighter/commit/f3c999ad192d20ad4024261d8b3c8628de21382a))
* correct matches list buttons handling ([f895327](https://github.com/obsqrbtz/goose-highlighter/commit/f895327eb0efc325309fea02eb4030eb8301916a))
* go to textarea match ([fc4ef86](https://github.com/obsqrbtz/goose-highlighter/commit/fc4ef8655d0a4bce8ef6b67e45f5929e21c9b746))
# [1.10.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.3...v1.10.0) (2025-11-20)
### Bug Fixes
* lighten debounce() usage, do not do full re-render on every change ([348d64f](https://github.com/obsqrbtz/goose-highlighter/commit/348d64f35693c11e7b14edcbe59b910195974950))
* made placeholder for textarea, added outline offset ([4f32be0](https://github.com/obsqrbtz/goose-highlighter/commit/4f32be0b93b5a39dcb034b4a15bbeca05add0a1f))
### Features
* new tabbed layout ([18e167c](https://github.com/obsqrbtz/goose-highlighter/commit/18e167cb7f2e758e09b201f7eff4cdbad080774e))
* show all found words and allow jump to them (beta) ([1a4c91f](https://github.com/obsqrbtz/goose-highlighter/commit/1a4c91fd5e35cc2227a580465ba9078200200623))
## [1.9.3](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.2...v1.9.3) (2025-11-18)
### Bug Fixes
* use CSS Custom Highlight API to avoid dom modifications (fixes [#1](https://github.com/obsqrbtz/goose-highlighter/issues/1)) ([3f2bb60](https://github.com/obsqrbtz/goose-highlighter/commit/3f2bb6080ba3a9ac0599ad6594f0d877c12bb62f))
## [1.9.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.1...v1.9.2) (2025-11-14)
### Bug Fixes
* **highlight:** prevent creating extra <span>'s ([#1](https://github.com/obsqrbtz/goose-highlighter/issues/1)) ([affddd3](https://github.com/obsqrbtz/goose-highlighter/commit/affddd3dbc7de30100ca134ec65f4dc090275ca5))
## [1.9.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.9.0...v1.9.1) (2025-11-05)
### Bug Fixes
* remove halowen styling ([172aa75](https://github.com/obsqrbtz/goose-highlighter/commit/172aa7583b325761af43c780db4ac61dc4bda99b))
# [1.9.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.5...v1.9.0) (2025-10-31)
### Features
* haloween styling ([5ef380e](https://github.com/obsqrbtz/goose-highlighter/commit/5ef380e54447f45f7360dd4b7b84456aae55bfee))
## [1.8.5](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.4...v1.8.5) (2025-10-29)
### Bug Fixes
* highlight colors when multiple list have different configurations ([67577c8](https://github.com/obsqrbtz/goose-highlighter/commit/67577c89cffca1ab6d40a8913e51b7c3c6f91c85))
## [1.8.4](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.3...v1.8.4) (2025-10-28)
### Bug Fixes
* do not re-highlight when already processing highlights ([8be53f3](https://github.com/obsqrbtz/goose-highlighter/commit/8be53f32402c2f0f228ca003ef3805c5ff0b6e88))
## [1.8.3](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.2...v1.8.3) (2025-10-08)
### Bug Fixes
* stop observing when highlightting is disabled ([d7c8dbb](https://github.com/obsqrbtz/goose-highlighter/commit/d7c8dbb5f0011afe83739841218aa737794074e3))
## [1.8.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.8.1...v1.8.2) (2025-10-08)
### Bug Fixes
* do not call save() on all keypresses in textboxes ([687d7c9](https://github.com/obsqrbtz/goose-highlighter/commit/687d7c9e62f0f282ce73e86cdc62aaf275c9dafe))
* do not save anything in list settings section until presses the apply button ([0734bf3](https://github.com/obsqrbtz/goose-highlighter/commit/0734bf330824c60f0d5c4784e99660b9e652efd6))
# [1.8.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.2...v1.8.0) (2025-10-07)
### Features
* add collapsible sections ([a158a30](https://github.com/obsqrbtz/goose-highlighter/commit/a158a303b01416f81e69bb137b71d3369904b044))
* add websites to exception list ([915add3](https://github.com/obsqrbtz/goose-highlighter/commit/915add3a4cdbff390a4d0f7d227a4ece5fa31072))
## [1.7.2](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.1...v1.7.2) (2025-10-06)
### Bug Fixes
* do not create <mark> elements, just wrap found words in <span> and add .css styling ([6ba0d2e](https://github.com/obsqrbtz/goose-highlighter/commit/6ba0d2eb7c7346cdca3921a12d300a714439efa5))
## [1.7.1](https://github.com/obsqrbtz/goose-highlighter/compare/v1.7.0...v1.7.1) (2025-06-27)
### Bug Fixes
* unicode support in regex ([ae1cf48](https://github.com/obsqrbtz/goose-highlighter/commit/ae1cf48c53cd42e65279cf2acde1a2860d8a31ee))
# [1.7.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.6.0...v1.7.0) (2025-06-26)
### Bug Fixes
* colorbox styling ([1e704b5](https://github.com/obsqrbtz/goose-highlighter/commit/1e704b51a859845e539224aeb389a4e493d64520))
* colorbox styling ([08ad7c4](https://github.com/obsqrbtz/goose-highlighter/commit/08ad7c432541ea4240dec05a340ad0b3279ce82f))
* moved import/export to options section ([fe15965](https://github.com/obsqrbtz/goose-highlighter/commit/fe15965e89e8483f6b96eb779617053664c9d5b1))
* wordlist scrollbar styling ([b30fac5](https://github.com/obsqrbtz/goose-highlighter/commit/b30fac5deda7941035d8ae23001c998c2584c03e))
### Features
* add word search ([80d4bff](https://github.com/obsqrbtz/goose-highlighter/commit/80d4bff0b4ef7c9e97506d1fe43a827bcc4b28fd))
* added matching flags ([759307f](https://github.com/obsqrbtz/goose-highlighter/commit/759307f9834a2bbb23e963e2042b7d41d5cfda44))
# [1.6.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.5.0...v1.6.0) (2025-06-25)
### Features
* add more locales ([d97becf](https://github.com/obsqrbtz/goose-highlighter/commit/d97becfaae696e33247840090e8a752b5ed4ed72))
# [1.5.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.4.0...v1.5.0) (2025-06-24)
### Features
* updated readme (wrong commit type is intentional to trigger ci) ([aac8749](https://github.com/obsqrbtz/goose-highlighter/commit/aac87493f29293e3d3291ba899032cf62504c14c))
# [1.4.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.3.0...v1.4.0) (2025-06-24) # [1.4.0](https://github.com/obsqrbtz/goose-highlighter/compare/v1.3.0...v1.4.0) (2025-06-24)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Daniel Dada
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
PRIVACY.MD Normal file
View File

@@ -0,0 +1 @@
Goose Highlighter does not collect, store, or transmit any personal or sensitive user data.

View File

@@ -1,11 +1,59 @@
# Goose Highlighter # <img src="img/logo.png" alt="Goose Highlighter Logo" width="32" style="vertical-align: middle;"> Goose Highlighter
Goose Highlighter is a browser extension that allows you to highlight custom words and phrases on any webpage. Organize your highlights into lists, customize their appearance, and toggle highlighting or theme modes with ease. Goose Highlighter is a browser extension that allows you to highlight words on any webpage.
## Features ## Features
- **Multiple Highlight Lists:** Organize words into separate lists. - **Multiple Highlight Lists:** Organize words into separate lists.
- **Custom Colors:** Set background and foreground for each list and individual word. - **Custom Colors:** Set background and foreground for each list or individual word.
- **Bulk Add:** Paste multiple words at once. - **Bulk Add:** Paste multiple words at once.
- **Enable/Disable:** Toggle highlighting globally, per list, or per word. - **Enable/Disable:** Toggle highlighting globally, per list, or per word.
- **Import/Export:** Backup or share your highlight lists as JSON files. - **Page Navigation:** View all highlights on the current page and jump to any occurrence with a single click.
- **Site Exceptions:** Add specific websites to an exceptions list to disable highlighting there.
- **Import/Export:** Backup or share your highlight lists and exceptions as JSON files.
## Install
### From Chrome Web Store (Recommended)
- Go to [Chrome Web Store page](https://chromewebstore.google.com/detail/goose-highlighter/kdoehicejfnccbmecpkfjlbljpfogoep) and choose `Add to chrome`.
### Manual Installation
#### Option 1: Install from CRX File (Releases)
1. **Download:** Get the latest `.crx` file from the [Releases section](https://github.com/obsqrbtz/goose-highlighter/releases)
2. **Install in Chrome:**
- Open Chrome and go to `chrome://extensions/`
- Enable "Developer mode" (toggle in top-right corner)
- Drag and drop the `.crx` file onto the extensions page
- Click "Add extension" when prompted
#### Option 2: Install from ZIP File (Releases)
1. **Download:** Get the latest `.zip` file from the [Releases section](https://github.com/obsqrbtz/goose-highlighter/releases)
2. **Extract:** Unzip the downloaded file to a folder of your choice
3. **Load in Chrome:**
- Open Chrome and go to `chrome://extensions/`
- Enable "Developer mode" (toggle in top-right corner)
- Click "Load unpacked" button
- Select the extracted folder containing the extension files
#### Option 3: Build from Source
1. **Prerequisites:** Node.js 20+ and npm
2. **Clone the repository:**
```bash
git clone https://github.com/obsqrbtz/goose-highlighter.git
cd goose-highlighter
```
3. **Install dependencies:**
```bash
npm install
```
4. **Build the extension:**
```bash
npm run build
```
5. **Load in Chrome:**
- Open Chrome and go to `chrome://extensions/`
- Enable "Developer mode" (toggle in top-right corner)
- Click "Load unpacked" button
- Select the entire `goose-highlighter` folder (not the `dist` folder)

326
_locales/de/messages.json Normal file
View File

@@ -0,0 +1,326 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Markiere Wörter und Phrasen auf jeder Webseite. Erstelle benutzerdefinierte Listen, verwende Farben, importiere/exportiere und mehr."
},
"select_list": {
"message": "Liste auswählen:"
},
"new_list": {
"message": "Neue Liste"
},
"delete_list": {
"message": "Liste löschen"
},
"list_name": {
"message": "Listenname:"
},
"background": {
"message": "Hintergrund:"
},
"foreground": {
"message": "Vordergrund:"
},
"enable_highlight": {
"message": "Hervorhebung aktivieren"
},
"apply": {
"message": "Anwenden"
},
"paste_hint": {
"message": "Ein Wort oder Ausdruck pro Zeile"
},
"apply_paste": {
"message": "Wörter hinzufügen"
},
"select_all": {
"message": "Auswählen"
},
"delete_selected": {
"message": "Ausgewählte löschen"
},
"disable_selected": {
"message": "Ausgewählte deaktivieren"
},
"enable_selected": {
"message": "Ausgewählte aktivieren"
},
"import_list": {
"message": "Liste importieren"
},
"export_list": {
"message": "Liste exportieren"
},
"import_list_list_manager": {
"message": "Liste importieren"
},
"export_list_list_manager": {
"message": "Liste exportieren"
},
"default_list_name": {
"message": "Standardliste"
},
"new_list_name": {
"message": "Neue Liste"
},
"word_active_label": {
"message": "aktiv"
},
"invalid_json_error": {
"message": "Ungültige JSON-Datei"
},
"confirm_delete_list": {
"message": "Möchten Sie diese Liste wirklich löschen?"
},
"confirm_delete_words": {
"message": "Möchten Sie die ausgewählten Wörter wirklich löschen?"
},
"deselect_all": {
"message": "Abwählen"
},
"highlight_lists": {
"message": "Hervorhebungslisten"
},
"dark_mode": {
"message": "Dunkelmodus"
},
"list_settings": {
"message": "Listeneinstellungen"
},
"word_list": {
"message": "Wortliste"
},
"add_words": {
"message": "Wörter hinzufügen"
},
"global_highlight_toggle": {
"message": "Aktivieren"
},
"search_placeholder": {
"message": "Suchen..."
},
"options": { "message": "Optionen" },
"match_case": { "message": "Groß-/Kleinschreibung beachten" },
"match_whole": { "message": "Ganzes Wort übereinstimmen" },
"site_exceptions": {
"message": "Website-Ausnahmen"
},
"add_exception": {
"message": "Zu Ausnahmen hinzufügen"
},
"remove_exception": {
"message": "Aktuelle entfernen"
},
"manage_exceptions": {
"message": "Verwalten"
},
"exceptions_list": {
"message": "Ausnahme-Websites:"
},
"clear_all": {
"message": "Alle löschen"
},
"confirm_clear_exceptions": {
"message": "Möchten Sie wirklich alle Ausnahmen löschen?"
},
"remove": {
"message": "Entfernen"
},
"tab_lists": {
"message": "Listen"
},
"tab_words": {
"message": "Wörter"
},
"tab_exceptions": {
"message": "Ausnahmen"
},
"no_exceptions": {
"message": "Keine Ausnahmen"
},
"toggle_highlighting_title": {
"message": "Hervorhebung umschalten"
},
"toggle_dark_mode_title": {
"message": "Dunkelmodus umschalten"
},
"select_title": {
"message": "Auswählen"
},
"word_placeholder": {
"message": "Wort oder Phrase"
},
"background_color_title": {
"message": "Hintergrundfarbe"
},
"text_color_title": {
"message": "Textfarbe"
},
"tab_page_highlights": {
"message": "Auf Seite"
},
"highlights_on_page": {
"message": "Hervorhebungen auf dieser Seite"
},
"total_highlights": {
"message": "Gesamt"
},
"refresh": {
"message": "Aktualisieren"
},
"no_highlights_on_page": {
"message": "Keine Hervorhebungen auf dieser Seite gefunden"
},
"previous": {
"message": "Vorherige"
},
"next": {
"message": "Nächste"
},
"duplicate": {
"message": "Duplizieren"
},
"merge": {
"message": "Zusammenführen"
},
"move": {
"message": "Verschieben"
},
"copy": {
"message": "Kopieren"
},
"move_to_list": {
"message": "Zur Liste verschieben"
},
"copy_to_list": {
"message": "In Liste kopieren"
},
"move_selected": {
"message": "Ausgewählte verschieben"
},
"copy_selected": {
"message": "Ausgewählte kopieren"
},
"no_other_lists": {
"message": "Keine anderen Listen"
},
"word_actions": {
"message": "In eine andere Liste verschieben oder kopieren"
},
"merge_lists_min_two": {
"message": "Wählen Sie mindestens zwei Listen zum Zusammenführen aus."
},
"merge_lists_confirm": {
"message": "{count} Liste(n) in \"{target}\" zusammenführen? Quelllisten werden entfernt."
},
"delete_current_list": {
"message": "Aktuelle Liste löschen?"
},
"delete_lists_confirm": {
"message": "{count} ausgewählte Liste(n) löschen?"
},
"invalid_import_format": {
"message": "Ungültiges Dateiformat. Bitte wählen Sie eine gültige Goose Highlighter-Exportdatei."
},
"import_failed": {
"message": "Import der Datei fehlgeschlagen. Bitte stellen Sie sicher, dass es sich um eine gültige JSON-Datei handelt."
},
"import_confirm": {
"message": "{count} Liste(n) mit insgesamt {words} Wort/Wörtern importieren?"
},
"import_success": {
"message": "{count} Liste(n) mit {words} Wort/Wörtern erfolgreich importiert."
},
"preview_label": {
"message": "Vorschau"
},
"preview_text": {
"message": "So wird Ihr hervorgehobener Text angezeigt."
},
"preview_text_before": {
"message": "So wird Ihr"
},
"preview_text_highlight": {
"message": "hervorgehobener Text"
},
"preview_text_after": {
"message": "angezeigt."
},
"enable_highlighting_title": {
"message": "Hervorhebung aktivieren"
},
"enable_highlighting_subtitle": {
"message": "Hervorhebungen auf Seiten anzeigen"
},
"manage_lists": {
"message": "Listen verwalten"
},
"background_label": {
"message": "Hintergrund"
},
"foreground_label": {
"message": "Vordergrund"
},
"multi_select_hint": {
"message": "Klick - auswählen • Strg+Klick - mehrere"
},
"dark_mode_title": {
"message": "Dunkelmodus"
},
"dark_mode_subtitle": {
"message": "Zwischen hellem und dunklem Design wechseln"
},
"list_manager_title": {
"message": "Listenverwaltung"
},
"words_label": {
"message": "Wörter"
},
"active_label": {
"message": "aktiv"
},
"inactive_label": {
"message": "inaktiv"
},
"drag_words_hint": {
"message": "Klick - auswählen • Strg+Klick - mehrere • Ziehen in Liste - verschieben"
},
"drag_lists_hint": {
"message": "Ziehen - neu ordnen • Strg+Klick - mehrere"
},
"showing_items": {
"message": "Zeige {start}-{end} von {total} Wörtern"
},
"items_per_page": {
"message": "Elemente pro Seite:"
},
"page_info": {
"message": "Seite {current} von {total}"
},
"toggle_active": {
"message": "Aktiv umschalten"
},
"rename_list_title": {
"message": "Liste umbenennen"
},
"new_list_title": {
"message": "Neue Liste"
},
"delete_list_title": {
"message": "Liste löschen"
},
"list_active_badge": {
"message": "Aktiv"
},
"list_paused_badge": {
"message": "Pausiert"
},
"words_stats": {
"message": "{total} Wörter • {active} aktiv • {inactive} inaktiv"
},
"rename_list": {
"message": "Liste umbenennen"
}
}

View File

@@ -2,6 +2,9 @@
"extension_name": { "extension_name": {
"message": "Goose Highlighter" "message": "Goose Highlighter"
}, },
"extension_description": {
"message": "Highlight words and phrases on any website. Create custom lists, use colors, import/export, and more."
},
"select_list": { "select_list": {
"message": "Select List:" "message": "Select List:"
}, },
@@ -23,8 +26,11 @@
"enable_highlight": { "enable_highlight": {
"message": "Enable Highlight" "message": "Enable Highlight"
}, },
"apply": {
"message": "Apply"
},
"paste_hint": { "paste_hint": {
"message": "Paste words here" "message": "One word or phrase per line"
}, },
"apply_paste": { "apply_paste": {
"message": "Add Words" "message": "Add Words"
@@ -33,19 +39,25 @@
"message": "Select" "message": "Select"
}, },
"delete_selected": { "delete_selected": {
"message": "Delete" "message": "Delete selected"
}, },
"disable_selected": { "disable_selected": {
"message": "Disable" "message": "Disable selected"
}, },
"enable_selected": { "enable_selected": {
"message": "Enable" "message": "Enable selected"
}, },
"import_list": { "import_list": {
"message": "Import JSON" "message": "Import list"
}, },
"export_list": { "export_list": {
"message": "Export JSON" "message": "Export list"
},
"import_list_list_manager": {
"message": "Import list"
},
"export_list_list_manager": {
"message": "Export list"
}, },
"default_list_name": { "default_list_name": {
"message": "Default List" "message": "Default List"
@@ -85,5 +97,266 @@
}, },
"global_highlight_toggle": { "global_highlight_toggle": {
"message": "Enable" "message": "Enable"
},
"search_placeholder": {
"message": "Search..."
},
"options": {
"message": "Options"
},
"match_case": {
"message": "Match Case"
},
"match_whole": {
"message": "Match Whole Word"
},
"site_exceptions": {
"message": "Site Exceptions"
},
"add_exception": {
"message": "Add to Exceptions"
},
"remove_exception": {
"message": "Remove current"
},
"manage_exceptions": {
"message": "Manage"
},
"exceptions_list": {
"message": "Exception Sites:"
},
"clear_all": {
"message": "Clear All"
},
"confirm_clear_exceptions": {
"message": "Are you sure you want to clear all exceptions?"
},
"remove": {
"message": "Remove"
},
"tab_lists": {
"message": "Lists"
},
"tab_words": {
"message": "Words"
},
"tab_exceptions": {
"message": "Exceptions"
},
"no_exceptions": {
"message": "No exceptions"
},
"toggle_highlighting_title": {
"message": "Toggle highlighting"
},
"toggle_dark_mode_title": {
"message": "Toggle dark mode"
},
"select_title": {
"message": "Select"
},
"word_placeholder": {
"message": "Word or phrase"
},
"background_color_title": {
"message": "Background color"
},
"text_color_title": {
"message": "Text color"
},
"tab_page_highlights": {
"message": "On Page"
},
"highlights_on_page": {
"message": "Highlights on This Page"
},
"total_highlights": {
"message": "Total"
},
"refresh": {
"message": "Refresh"
},
"no_highlights_on_page": {
"message": "No highlights found on this page"
},
"previous": {
"message": "Previous"
},
"next": {
"message": "Next"
},
"duplicate": {
"message": "Duplicate"
},
"merge": {
"message": "Merge"
},
"move": {
"message": "Move"
},
"copy": {
"message": "Copy"
},
"move_to_list": {
"message": "Move to list"
},
"copy_to_list": {
"message": "Copy to list"
},
"move_selected": {
"message": "Move selected"
},
"copy_selected": {
"message": "Copy selected"
},
"no_other_lists": {
"message": "No other lists"
},
"word_actions": {
"message": "Move or copy to another list"
},
"merge_lists_min_two": {
"message": "Select at least two lists to merge."
},
"merge_lists_confirm": {
"message": "Merge {count} list(s) into \"{target}\"? Source lists will be removed."
},
"delete_current_list": {
"message": "Delete current list?"
},
"delete_lists_confirm": {
"message": "Delete {count} selected list(s)?"
},
"invalid_import_format": {
"message": "Invalid file format. Please select a valid Goose Highlighter export file."
},
"import_failed": {
"message": "Failed to import file. Please ensure it is a valid JSON file."
},
"import_confirm": {
"message": "Import {count} list(s) with {words} total word(s)?"
},
"import_success": {
"message": "Successfully imported {count} list(s) with {words} word(s)."
},
"preview_label": {
"message": "Preview"
},
"preview_text": {
"message": "This is how your highlighted text will appear."
},
"preview_text_before": {
"message": "This is how your"
},
"preview_text_highlight": {
"message": "highlighted text"
},
"preview_text_after": {
"message": "will appear."
},
"enable_highlighting_title": {
"message": "Enable Highlighting"
},
"enable_highlighting_subtitle": {
"message": "Show highlights on pages"
},
"manage_lists": {
"message": "Manage Lists"
},
"background_label": {
"message": "Background"
},
"foreground_label": {
"message": "Foreground"
},
"multi_select_hint": {
"message": "Click - select • Ctrl+click - select multiple"
},
"dark_mode_title": {
"message": "Dark Mode"
},
"dark_mode_subtitle": {
"message": "Toggle dark/light theme"
},
"list_manager_title": {
"message": "List Manager"
},
"words_label": {
"message": "words"
},
"active_label": {
"message": "active"
},
"inactive_label": {
"message": "inactive"
},
"drag_words_hint": {
"message": "Click - select • Ctrl+click - multiple • Drag to list - move"
},
"drag_lists_hint": {
"message": "Drag - reorder • Ctrl+click - select multiple"
},
"showing_items": {
"message": "Showing {start}-{end} of {total} words"
},
"items_per_page": {
"message": "Items per page:"
},
"page_info": {
"message": "Page {current} of {total}"
},
"toggle_active": {
"message": "toggle active"
},
"rename_list_title": {
"message": "Rename list"
},
"new_list_title": {
"message": "New list"
},
"delete_list_title": {
"message": "Delete list"
},
"list_active_badge": {
"message": "Active"
},
"list_paused_badge": {
"message": "Paused"
},
"words_stats": {
"message": "{total} words • {active} active • {inactive} inactive"
},
"rename_list": {
"message": "Rename List"
},
"no_words_in_list": {
"message": "No words in this list."
},
"edit_word": {
"message": "Edit word"
},
"first_page": {
"message": "First page"
},
"previous_page": {
"message": "Previous page"
},
"next_page": {
"message": "Next page"
},
"last_page": {
"message": "Last page"
},
"enter_list_name": {
"message": "Enter list name:"
},
"cannot_delete_last_list": {
"message": "Cannot delete the last list"
},
"edit_list_name_and_colors_title": {
"message": "Edit list name and colors"
},
"close": {
"message": "Close"
} }
} }

332
_locales/es/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Resalta palabras y frases en cualquier sitio web. Crea listas personalizadas, usa colores, importa/exporta y más."
},
"select_list": {
"message": "Seleccionar lista:"
},
"new_list": {
"message": "Nueva lista"
},
"delete_list": {
"message": "Eliminar lista"
},
"list_name": {
"message": "Nombre de la lista:"
},
"background": {
"message": "Fondo:"
},
"foreground": {
"message": "Texto:"
},
"enable_highlight": {
"message": "Activar resaltado"
},
"apply": {
"message": "Aplicar"
},
"paste_hint": {
"message": "Una palabra o frase por línea"
},
"apply_paste": {
"message": "Agregar palabras"
},
"select_all": {
"message": "Seleccionar"
},
"delete_selected": {
"message": "Eliminar seleccionados"
},
"disable_selected": {
"message": "Desactivar seleccionados"
},
"enable_selected": {
"message": "Activar seleccionados"
},
"import_list": {
"message": "Importar lista"
},
"export_list": {
"message": "Exportar lista"
},
"import_list_list_manager": {
"message": "Importar lista"
},
"export_list_list_manager": {
"message": "Exportar lista"
},
"default_list_name": {
"message": "Lista predeterminada"
},
"new_list_name": {
"message": "Nueva lista"
},
"word_active_label": {
"message": "activo"
},
"invalid_json_error": {
"message": "Archivo JSON no válido"
},
"confirm_delete_list": {
"message": "¿Estás seguro de que deseas eliminar esta lista?"
},
"confirm_delete_words": {
"message": "¿Estás seguro de que deseas eliminar las palabras seleccionadas?"
},
"deselect_all": {
"message": "Deseleccionar"
},
"highlight_lists": {
"message": "Listas de resaltado"
},
"dark_mode": {
"message": "Modo oscuro"
},
"list_settings": {
"message": "Configuración de la lista"
},
"word_list": {
"message": "Lista de palabras"
},
"add_words": {
"message": "Agregar palabras"
},
"global_highlight_toggle": {
"message": "Activar"
},
"search_placeholder": {
"message": "Buscar..."
},
"options": {
"message": "Opciones"
},
"match_case": {
"message": "Coincidir mayúsculas/minúsculas"
},
"match_whole": {
"message": "Coincidir palabra completa"
},
"site_exceptions": {
"message": "Excepciones de sitios"
},
"add_exception": {
"message": "Agregar a excepciones"
},
"remove_exception": {
"message": "Eliminar actual"
},
"manage_exceptions": {
"message": "Gestionar"
},
"exceptions_list": {
"message": "Sitios de excepción:"
},
"clear_all": {
"message": "Limpiar todo"
},
"confirm_clear_exceptions": {
"message": "¿Estás seguro de que deseas limpiar todas las excepciones?"
},
"remove": {
"message": "Eliminar"
},
"tab_lists": {
"message": "Listas"
},
"tab_words": {
"message": "Palabras"
},
"tab_exceptions": {
"message": "Excepciones"
},
"no_exceptions": {
"message": "Sin excepciones"
},
"toggle_highlighting_title": {
"message": "Alternar resaltado"
},
"toggle_dark_mode_title": {
"message": "Alternar modo oscuro"
},
"select_title": {
"message": "Seleccionar"
},
"word_placeholder": {
"message": "Palabra o frase"
},
"background_color_title": {
"message": "Color de fondo"
},
"text_color_title": {
"message": "Color de texto"
},
"tab_page_highlights": {
"message": "En página"
},
"highlights_on_page": {
"message": "Resaltados en esta página"
},
"total_highlights": {
"message": "Total"
},
"refresh": {
"message": "Actualizar"
},
"no_highlights_on_page": {
"message": "No se encontraron resaltados en esta página"
},
"previous": {
"message": "Anterior"
},
"next": {
"message": "Siguiente"
},
"duplicate": {
"message": "Duplicar"
},
"merge": {
"message": "Combinar"
},
"move": {
"message": "Mover"
},
"copy": {
"message": "Copiar"
},
"move_to_list": {
"message": "Mover a lista"
},
"copy_to_list": {
"message": "Copiar a lista"
},
"move_selected": {
"message": "Mover seleccionados"
},
"copy_selected": {
"message": "Copiar seleccionados"
},
"no_other_lists": {
"message": "No hay otras listas"
},
"word_actions": {
"message": "Mover o copiar a otra lista"
},
"merge_lists_min_two": {
"message": "Seleccione al menos dos listas para combinar."
},
"merge_lists_confirm": {
"message": "¿Combinar {count} lista(s) en \"{target}\"? Las listas de origen se eliminarán."
},
"delete_current_list": {
"message": "¿Eliminar la lista actual?"
},
"delete_lists_confirm": {
"message": "¿Eliminar {count} lista(s) seleccionada(s)?"
},
"invalid_import_format": {
"message": "Formato de archivo no válido. Seleccione un archivo de exportación válido de Goose Highlighter."
},
"import_failed": {
"message": "Error al importar el archivo. Asegúrese de que sea un archivo JSON válido."
},
"import_confirm": {
"message": "¿Importar {count} lista(s) con {words} palabra(s) en total?"
},
"import_success": {
"message": "Se importaron correctamente {count} lista(s) con {words} palabra(s)."
},
"preview_label": {
"message": "Vista previa"
},
"preview_text": {
"message": "Así es como aparecerá su texto resaltado."
},
"preview_text_before": {
"message": "Así es como aparecerá su"
},
"preview_text_highlight": {
"message": "texto resaltado"
},
"preview_text_after": {
"message": "."
},
"enable_highlighting_title": {
"message": "Activar resaltado"
},
"enable_highlighting_subtitle": {
"message": "Mostrar resaltados en páginas"
},
"manage_lists": {
"message": "Gestionar listas"
},
"background_label": {
"message": "Fondo"
},
"foreground_label": {
"message": "Texto"
},
"multi_select_hint": {
"message": "Clic - seleccionar • Ctrl+clic - varios"
},
"dark_mode_title": {
"message": "Modo oscuro"
},
"dark_mode_subtitle": {
"message": "Alternar tema claro/oscuro"
},
"list_manager_title": {
"message": "Gestor de listas"
},
"words_label": {
"message": "palabras"
},
"active_label": {
"message": "activo"
},
"inactive_label": {
"message": "inactivo"
},
"drag_words_hint": {
"message": "Clic - seleccionar • Ctrl+clic - varios • Arrastrar a lista - mover"
},
"drag_lists_hint": {
"message": "Arrastrar - reordenar • Ctrl+clic - varios"
},
"showing_items": {
"message": "Mostrando {start}-{end} de {total} palabras"
},
"items_per_page": {
"message": "Elementos por página:"
},
"page_info": {
"message": "Página {current} de {total}"
},
"toggle_active": {
"message": "alternar activo"
},
"rename_list_title": {
"message": "Renombrar lista"
},
"new_list_title": {
"message": "Nueva lista"
},
"delete_list_title": {
"message": "Eliminar lista"
},
"list_active_badge": {
"message": "Activo"
},
"list_paused_badge": {
"message": "Pausado"
},
"words_stats": {
"message": "{total} palabras • {active} activas • {inactive} inactivas"
},
"rename_list": {
"message": "Renombrar lista"
}
}

332
_locales/fr/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Surlignez des mots et des phrases sur n'importe quel site web. Créez des listes personnalisées, utilisez des couleurs, importez/exportez, et plus encore."
},
"select_list": {
"message": "Sélectionner une liste :"
},
"new_list": {
"message": "Nouvelle liste"
},
"delete_list": {
"message": "Supprimer la liste"
},
"list_name": {
"message": "Nom de la liste :"
},
"background": {
"message": "Arrière-plan :"
},
"foreground": {
"message": "Texte :"
},
"enable_highlight": {
"message": "Activer la surbrillance"
},
"apply": {
"message": "Appliquer"
},
"paste_hint": {
"message": "Un mot ou une phrase par ligne"
},
"apply_paste": {
"message": "Ajouter des mots"
},
"select_all": {
"message": "Sélectionner"
},
"delete_selected": {
"message": "Supprimer la sélection"
},
"disable_selected": {
"message": "Désactiver la sélection"
},
"enable_selected": {
"message": "Activer la sélection"
},
"import_list": {
"message": "Importer la liste"
},
"export_list": {
"message": "Exporter la liste"
},
"import_list_list_manager": {
"message": "Importer la liste"
},
"export_list_list_manager": {
"message": "Exporter la liste"
},
"default_list_name": {
"message": "Liste par défaut"
},
"new_list_name": {
"message": "Nouvelle liste"
},
"word_active_label": {
"message": "actif"
},
"invalid_json_error": {
"message": "Fichier JSON invalide"
},
"confirm_delete_list": {
"message": "Êtes-vous sûr de vouloir supprimer cette liste ?"
},
"confirm_delete_words": {
"message": "Êtes-vous sûr de vouloir supprimer les mots sélectionnés ?"
},
"deselect_all": {
"message": "Désélectionner"
},
"highlight_lists": {
"message": "Listes de surbrillance"
},
"dark_mode": {
"message": "Mode sombre"
},
"list_settings": {
"message": "Paramètres de la liste"
},
"word_list": {
"message": "Liste de mots"
},
"add_words": {
"message": "Ajouter des mots"
},
"global_highlight_toggle": {
"message": "Activer"
},
"search_placeholder": {
"message": "Rechercher..."
},
"options": {
"message": "Options"
},
"match_case": {
"message": "Respecter la casse"
},
"match_whole": {
"message": "Mot entier seulement"
},
"site_exceptions": {
"message": "Exceptions de sites"
},
"add_exception": {
"message": "Ajouter aux exceptions"
},
"remove_exception": {
"message": "Supprimer l'actuel"
},
"manage_exceptions": {
"message": "Gérer"
},
"exceptions_list": {
"message": "Sites d'exception :"
},
"clear_all": {
"message": "Tout effacer"
},
"confirm_clear_exceptions": {
"message": "Êtes-vous sûr de vouloir effacer toutes les exceptions ?"
},
"remove": {
"message": "Supprimer"
},
"tab_lists": {
"message": "Listes"
},
"tab_words": {
"message": "Mots"
},
"tab_exceptions": {
"message": "Exceptions"
},
"no_exceptions": {
"message": "Aucune exception"
},
"toggle_highlighting_title": {
"message": "Activer/désactiver la surbrillance"
},
"toggle_dark_mode_title": {
"message": "Activer/désactiver le mode sombre"
},
"select_title": {
"message": "Sélectionner"
},
"word_placeholder": {
"message": "Mot ou phrase"
},
"background_color_title": {
"message": "Couleur d'arrière-plan"
},
"text_color_title": {
"message": "Couleur du texte"
},
"tab_page_highlights": {
"message": "Sur la page"
},
"highlights_on_page": {
"message": "Surlignages sur cette page"
},
"total_highlights": {
"message": "Total"
},
"refresh": {
"message": "Actualiser"
},
"no_highlights_on_page": {
"message": "Aucun surlignage trouvé sur cette page"
},
"previous": {
"message": "Précédent"
},
"next": {
"message": "Suivant"
},
"duplicate": {
"message": "Dupliquer"
},
"merge": {
"message": "Fusionner"
},
"move": {
"message": "Déplacer"
},
"copy": {
"message": "Copier"
},
"move_to_list": {
"message": "Déplacer vers la liste"
},
"copy_to_list": {
"message": "Copier vers la liste"
},
"move_selected": {
"message": "Déplacer la sélection"
},
"copy_selected": {
"message": "Copier la sélection"
},
"no_other_lists": {
"message": "Aucune autre liste"
},
"word_actions": {
"message": "Déplacer ou copier vers une autre liste"
},
"merge_lists_min_two": {
"message": "Sélectionnez au moins deux listes à fusionner."
},
"merge_lists_confirm": {
"message": "Fusionner {count} liste(s) dans \"{target}\" ? Les listes sources seront supprimées."
},
"delete_current_list": {
"message": "Supprimer la liste actuelle ?"
},
"delete_lists_confirm": {
"message": "Supprimer {count} liste(s) sélectionnée(s) ?"
},
"invalid_import_format": {
"message": "Format de fichier invalide. Veuillez sélectionner un fichier d'exportation Goose Highlighter valide."
},
"import_failed": {
"message": "Échec de l'importation du fichier. Veuillez vous assurer qu'il s'agit d'un fichier JSON valide."
},
"import_confirm": {
"message": "Importer {count} liste(s) avec {words} mot(s) au total ?"
},
"import_success": {
"message": "{count} liste(s) avec {words} mot(s) importée(s) avec succès."
},
"preview_label": {
"message": "Aperçu"
},
"preview_text": {
"message": "Voici comment votre texte surligné apparaîtra."
},
"preview_text_before": {
"message": "Voici comment votre"
},
"preview_text_highlight": {
"message": "texte surligné"
},
"preview_text_after": {
"message": "apparaîtra."
},
"enable_highlighting_title": {
"message": "Activer la surbrillance"
},
"enable_highlighting_subtitle": {
"message": "Afficher les surlignages sur les pages"
},
"manage_lists": {
"message": "Gérer les listes"
},
"background_label": {
"message": "Arrière-plan"
},
"foreground_label": {
"message": "Texte"
},
"multi_select_hint": {
"message": "Clic - sélectionner • Ctrl+clic - plusieurs"
},
"dark_mode_title": {
"message": "Mode sombre"
},
"dark_mode_subtitle": {
"message": "Basculer entre thème clair/sombre"
},
"list_manager_title": {
"message": "Gestionnaire de listes"
},
"words_label": {
"message": "mots"
},
"active_label": {
"message": "actif"
},
"inactive_label": {
"message": "inactif"
},
"drag_words_hint": {
"message": "Clic - sélectionner • Ctrl+clic - plusieurs • Glisser vers liste - déplacer"
},
"drag_lists_hint": {
"message": "Glisser - réordonner • Ctrl+clic - plusieurs"
},
"showing_items": {
"message": "Affichage de {start}-{end} sur {total} mots"
},
"items_per_page": {
"message": "Éléments par page :"
},
"page_info": {
"message": "Page {current} sur {total}"
},
"toggle_active": {
"message": "basculer actif"
},
"rename_list_title": {
"message": "Renommer la liste"
},
"new_list_title": {
"message": "Nouvelle liste"
},
"delete_list_title": {
"message": "Supprimer la liste"
},
"list_active_badge": {
"message": "Actif"
},
"list_paused_badge": {
"message": "En pause"
},
"words_stats": {
"message": "{total} mots • {active} actifs • {inactive} inactifs"
},
"rename_list": {
"message": "Renommer la liste"
}
}

332
_locales/hi/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "किसी भी वेबसाइट पर शब्द और वाक्यांश हाइलाइट करें। कस्टम सूचियाँ बनाएं, रंगों का उपयोग करें, आयात/निर्यात करें और बहुत कुछ।"
},
"select_list": {
"message": "सूची चुनें:"
},
"new_list": {
"message": "नई सूची"
},
"delete_list": {
"message": "सूची हटाएं"
},
"list_name": {
"message": "सूची का नाम:"
},
"background": {
"message": "पृष्ठभूमि:"
},
"foreground": {
"message": "फॉरग्राउंड:"
},
"enable_highlight": {
"message": "हाइलाइट सक्षम करें"
},
"apply": {
"message": "लागू करें"
},
"paste_hint": {
"message": "प्रति पंक्ति एक शब्द या वाक्यांश"
},
"apply_paste": {
"message": "शब्द जोड़ें"
},
"select_all": {
"message": "सभी चुनें"
},
"delete_selected": {
"message": "चयनित हटाएं"
},
"disable_selected": {
"message": "चयनित अक्षम करें"
},
"enable_selected": {
"message": "चयनित सक्षम करें"
},
"import_list": {
"message": "सूची आयात करें"
},
"export_list": {
"message": "सूची निर्यात करें"
},
"import_list_list_manager": {
"message": "सूची आयात करें"
},
"export_list_list_manager": {
"message": "सूची निर्यात करें"
},
"default_list_name": {
"message": "डिफ़ॉल्ट सूची"
},
"new_list_name": {
"message": "नई सूची"
},
"word_active_label": {
"message": "सक्रिय"
},
"invalid_json_error": {
"message": "अमान्य JSON फ़ाइल"
},
"confirm_delete_list": {
"message": "क्या आप वाकई इस सूची को हटाना चाहते हैं?"
},
"confirm_delete_words": {
"message": "क्या आप वाकई चयनित शब्दों को हटाना चाहते हैं?"
},
"deselect_all": {
"message": "चयन हटाएँ"
},
"highlight_lists": {
"message": "हाइलाइट सूचियाँ"
},
"dark_mode": {
"message": "डार्क मोड"
},
"list_settings": {
"message": "सूची सेटिंग्स"
},
"word_list": {
"message": "शब्द सूची"
},
"add_words": {
"message": "शब्द जोड़ें"
},
"global_highlight_toggle": {
"message": "सक्षम करें"
},
"search_placeholder": {
"message": "खोजें..."
},
"options": {
"message": "विकल्प"
},
"match_case": {
"message": "केस मिलाएं"
},
"match_whole": {
"message": "पूरा शब्द मिलाएं"
},
"site_exceptions": {
"message": "साइट अपवाद"
},
"add_exception": {
"message": "अपवादों में जोड़ें"
},
"remove_exception": {
"message": "वर्तमान हटाएं"
},
"manage_exceptions": {
"message": "प्रबंधित करें"
},
"exceptions_list": {
"message": "अपवाद साइटें:"
},
"clear_all": {
"message": "सभी साफ करें"
},
"confirm_clear_exceptions": {
"message": "क्या आप वाकई सभी अपवादों को साफ करना चाहते हैं?"
},
"remove": {
"message": "हटाएं"
},
"tab_lists": {
"message": "सूचियाँ"
},
"tab_words": {
"message": "शब्द"
},
"tab_exceptions": {
"message": "अपवाद"
},
"no_exceptions": {
"message": "कोई अपवाद नहीं"
},
"toggle_highlighting_title": {
"message": "हाइलाइटिंग टॉगल करें"
},
"toggle_dark_mode_title": {
"message": "डार्क मोड टॉगल करें"
},
"select_title": {
"message": "चुनें"
},
"word_placeholder": {
"message": "शब्द या वाक्यांश"
},
"background_color_title": {
"message": "पृष्ठभूमि रंग"
},
"text_color_title": {
"message": "पाठ रंग"
},
"tab_page_highlights": {
"message": "पृष्ठ पर"
},
"highlights_on_page": {
"message": "इस पृष्ठ पर हाइलाइट"
},
"total_highlights": {
"message": "कुल"
},
"refresh": {
"message": "रीफ्रेश करें"
},
"no_highlights_on_page": {
"message": "इस पृष्ठ पर कोई हाइलाइट नहीं मिला"
},
"previous": {
"message": "पिछला"
},
"next": {
"message": "अगला"
},
"duplicate": {
"message": "डुप्लिकेट"
},
"merge": {
"message": "मर्ज करें"
},
"move": {
"message": "स्थानांतरित करें"
},
"copy": {
"message": "कॉपी करें"
},
"move_to_list": {
"message": "सूची में ले जाएं"
},
"copy_to_list": {
"message": "सूची में कॉपी करें"
},
"move_selected": {
"message": "चयनित स्थानांतरित करें"
},
"copy_selected": {
"message": "चयनित कॉपी करें"
},
"no_other_lists": {
"message": "कोई अन्य सूची नहीं"
},
"word_actions": {
"message": "किसी अन्य सूची में ले जाएं या कॉपी करें"
},
"merge_lists_min_two": {
"message": "मर्ज करने के लिए कम से कम दो सूचियाँ चुनें।"
},
"merge_lists_confirm": {
"message": "{count} सूची(याँ) को \"{target}\" में मर्ज करें? स्रोत सूचियाँ हटा दी जाएंगी।"
},
"delete_current_list": {
"message": "वर्तमान सूची हटाएं?"
},
"delete_lists_confirm": {
"message": "{count} चयनित सूची(याँ) हटाएं?"
},
"invalid_import_format": {
"message": "अमान्य फ़ाइल प्रारूप। कृपया एक मान्य Goose Highlighter निर्यात फ़ाइल चुनें।"
},
"import_failed": {
"message": "फ़ाइल आयात करने में विफल। कृपया सुनिश्चित करें कि यह एक मान्य JSON फ़ाइल है।"
},
"import_confirm": {
"message": "कुल {words} शब्द(ों) के साथ {count} सूची(याँ) आयात करें?"
},
"import_success": {
"message": "{words} शब्द(ों) के साथ {count} सूची(याँ) सफलतापूर्वक आयात की गईं।"
},
"preview_label": {
"message": "पूर्वावलोकन"
},
"preview_text": {
"message": "आपका हाइलाइट किया गया टेक्स्ट इस तरह दिखाई देगा।"
},
"preview_text_before": {
"message": "आपका"
},
"preview_text_highlight": {
"message": "हाइलाइट किया गया टेक्स्ट"
},
"preview_text_after": {
"message": "इस तरह दिखाई देगा।"
},
"enable_highlighting_title": {
"message": "हाइलाइटिंग सक्षम करें"
},
"enable_highlighting_subtitle": {
"message": "पृष्ठों पर हाइलाइट दिखाएं"
},
"manage_lists": {
"message": "सूचियाँ प्रबंधित करें"
},
"background_label": {
"message": "पृष्ठभूमि"
},
"foreground_label": {
"message": "अग्रभूमि"
},
"multi_select_hint": {
"message": "क्लिक - चुनें • Ctrl+क्लिक - कई"
},
"dark_mode_title": {
"message": "डार्क मोड"
},
"dark_mode_subtitle": {
"message": "हल्की/गहरी थीम टॉगल करें"
},
"list_manager_title": {
"message": "सूची प्रबंधक"
},
"words_label": {
"message": "शब्द"
},
"active_label": {
"message": "सक्रिय"
},
"inactive_label": {
"message": "निष्क्रिय"
},
"drag_words_hint": {
"message": "क्लिक - चुनें • Ctrl+क्लिक - कई • सूची में खींचें - स्थानांतरित"
},
"drag_lists_hint": {
"message": "खींचें - क्रम बदलें • Ctrl+क्लिक - कई"
},
"showing_items": {
"message": "{total} शब्दों में से {start}-{end} दिखा रहे हैं"
},
"items_per_page": {
"message": "प्रति पृष्ठ आइटम:"
},
"page_info": {
"message": "पृष्ठ {current} का {total}"
},
"toggle_active": {
"message": "सक्रिय टॉगल करें"
},
"rename_list_title": {
"message": "सूची का नाम बदलें"
},
"new_list_title": {
"message": "नई सूची"
},
"delete_list_title": {
"message": "सूची हटाएं"
},
"list_active_badge": {
"message": "सक्रिय"
},
"list_paused_badge": {
"message": "रोका गया"
},
"words_stats": {
"message": "{total} शब्द • {active} सक्रिय • {inactive} निष्क्रिय"
},
"rename_list": {
"message": "सूची का नाम बदलें"
}
}

332
_locales/it/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Evidenzia parole e frasi su qualsiasi sito web. Crea liste personalizzate, usa colori, importa/esporta e altro ancora."
},
"select_list": {
"message": "Seleziona elenco:"
},
"new_list": {
"message": "Nuovo elenco"
},
"delete_list": {
"message": "Elimina elenco"
},
"list_name": {
"message": "Nome elenco:"
},
"background": {
"message": "Sfondo:"
},
"foreground": {
"message": "Colore del testo:"
},
"enable_highlight": {
"message": "Abilita evidenziazione"
},
"apply": {
"message": "Applica"
},
"paste_hint": {
"message": "Una parola o frase per riga"
},
"apply_paste": {
"message": "Aggiungi parole"
},
"select_all": {
"message": "Seleziona"
},
"delete_selected": {
"message": "Elimina selezionati"
},
"disable_selected": {
"message": "Disattiva selezionati"
},
"enable_selected": {
"message": "Attiva selezionati"
},
"import_list": {
"message": "Importa lista"
},
"export_list": {
"message": "Esporta lista"
},
"import_list_list_manager": {
"message": "Importa lista"
},
"export_list_list_manager": {
"message": "Esporta lista"
},
"default_list_name": {
"message": "Elenco predefinito"
},
"new_list_name": {
"message": "Nuovo elenco"
},
"word_active_label": {
"message": "attivo"
},
"invalid_json_error": {
"message": "File JSON non valido"
},
"confirm_delete_list": {
"message": "Sei sicuro di voler eliminare questo elenco?"
},
"confirm_delete_words": {
"message": "Sei sicuro di voler eliminare le parole selezionate?"
},
"deselect_all": {
"message": "Deseleziona"
},
"highlight_lists": {
"message": "Elenchi di evidenziazione"
},
"dark_mode": {
"message": "Modalità scura"
},
"list_settings": {
"message": "Impostazioni elenco"
},
"word_list": {
"message": "Elenco parole"
},
"add_words": {
"message": "Aggiungi parole"
},
"global_highlight_toggle": {
"message": "Attiva"
},
"search_placeholder": {
"message": "Cerca..."
},
"options": {
"message": "Opzioni"
},
"match_case": {
"message": "Maiuscole/minuscole"
},
"match_whole": {
"message": "Solo parola intera"
},
"site_exceptions": {
"message": "Eccezioni siti"
},
"add_exception": {
"message": "Aggiungi alle eccezioni"
},
"remove_exception": {
"message": "Rimuovi corrente"
},
"manage_exceptions": {
"message": "Gestisci"
},
"exceptions_list": {
"message": "Siti di eccezione:"
},
"clear_all": {
"message": "Cancella tutto"
},
"confirm_clear_exceptions": {
"message": "Sei sicuro di voler cancellare tutte le eccezioni?"
},
"remove": {
"message": "Rimuovi"
},
"tab_lists": {
"message": "Elenchi"
},
"tab_words": {
"message": "Parole"
},
"tab_exceptions": {
"message": "Eccezioni"
},
"no_exceptions": {
"message": "Nessuna eccezione"
},
"toggle_highlighting_title": {
"message": "Attiva/disattiva evidenziazione"
},
"toggle_dark_mode_title": {
"message": "Attiva/disattiva modalità scura"
},
"select_title": {
"message": "Seleziona"
},
"word_placeholder": {
"message": "Parola o frase"
},
"background_color_title": {
"message": "Colore di sfondo"
},
"text_color_title": {
"message": "Colore del testo"
},
"tab_page_highlights": {
"message": "Sulla pagina"
},
"highlights_on_page": {
"message": "Evidenziazioni su questa pagina"
},
"total_highlights": {
"message": "Totale"
},
"refresh": {
"message": "Aggiorna"
},
"no_highlights_on_page": {
"message": "Nessuna evidenziazione trovata su questa pagina"
},
"previous": {
"message": "Precedente"
},
"next": {
"message": "Successivo"
},
"duplicate": {
"message": "Duplica"
},
"merge": {
"message": "Unisci"
},
"move": {
"message": "Sposta"
},
"copy": {
"message": "Copia"
},
"move_to_list": {
"message": "Sposta in elenco"
},
"copy_to_list": {
"message": "Copia in elenco"
},
"move_selected": {
"message": "Sposta selezionati"
},
"copy_selected": {
"message": "Copia selezionati"
},
"no_other_lists": {
"message": "Nessun altro elenco"
},
"word_actions": {
"message": "Sposta o copia in un altro elenco"
},
"merge_lists_min_two": {
"message": "Seleziona almeno due elenchi da unire."
},
"merge_lists_confirm": {
"message": "Unire {count} elenco/i in \"{target}\"? Gli elenchi di origine verranno rimossi."
},
"delete_current_list": {
"message": "Eliminare l'elenco corrente?"
},
"delete_lists_confirm": {
"message": "Eliminare {count} elenco/i selezionato/i?"
},
"invalid_import_format": {
"message": "Formato file non valido. Seleziona un file di esportazione Goose Highlighter valido."
},
"import_failed": {
"message": "Importazione del file non riuscita. Assicurati che sia un file JSON valido."
},
"import_confirm": {
"message": "Importare {count} elenco/i con {words} parola/e totali?"
},
"import_success": {
"message": "Importati con successo {count} elenco/i con {words} parola/e."
},
"preview_label": {
"message": "Anteprima"
},
"preview_text": {
"message": "Ecco come apparirà il tuo testo evidenziato."
},
"preview_text_before": {
"message": "Ecco come apparirà il tuo"
},
"preview_text_highlight": {
"message": "testo evidenziato"
},
"preview_text_after": {
"message": "."
},
"enable_highlighting_title": {
"message": "Abilita evidenziazione"
},
"enable_highlighting_subtitle": {
"message": "Mostra evidenziazioni sulle pagine"
},
"manage_lists": {
"message": "Gestisci elenchi"
},
"background_label": {
"message": "Sfondo"
},
"foreground_label": {
"message": "Testo"
},
"multi_select_hint": {
"message": "Clic - seleziona • Ctrl+clic - più"
},
"dark_mode_title": {
"message": "Modalità scura"
},
"dark_mode_subtitle": {
"message": "Passa tra tema chiaro/scuro"
},
"list_manager_title": {
"message": "Gestore elenchi"
},
"words_label": {
"message": "parole"
},
"active_label": {
"message": "attivo"
},
"inactive_label": {
"message": "inattivo"
},
"drag_words_hint": {
"message": "Clic - seleziona • Ctrl+clic - più • Trascina in lista - sposta"
},
"drag_lists_hint": {
"message": "Trascina - riordina • Ctrl+clic - più"
},
"showing_items": {
"message": "Visualizzazione di {start}-{end} su {total} parole"
},
"items_per_page": {
"message": "Elementi per pagina:"
},
"page_info": {
"message": "Pagina {current} di {total}"
},
"toggle_active": {
"message": "attiva/disattiva"
},
"rename_list_title": {
"message": "Rinomina elenco"
},
"new_list_title": {
"message": "Nuovo elenco"
},
"delete_list_title": {
"message": "Elimina elenco"
},
"list_active_badge": {
"message": "Attivo"
},
"list_paused_badge": {
"message": "In pausa"
},
"words_stats": {
"message": "{total} parole • {active} attive • {inactive} inattive"
},
"rename_list": {
"message": "Rinomina elenco"
}
}

332
_locales/ja/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "任意のウェブサイトで単語やフレーズをハイライトします。カスタムリストの作成、色の使用、インポート/エクスポートなどが可能です。"
},
"select_list": {
"message": "リストを選択:"
},
"new_list": {
"message": "新しいリスト"
},
"delete_list": {
"message": "リストを削除"
},
"list_name": {
"message": "リスト名:"
},
"background": {
"message": "背景色:"
},
"foreground": {
"message": "文字色:"
},
"enable_highlight": {
"message": "ハイライトを有効にする"
},
"apply": {
"message": "適用"
},
"paste_hint": {
"message": "1行に1単語または1フレーズ"
},
"apply_paste": {
"message": "単語を追加"
},
"select_all": {
"message": "すべて選択"
},
"delete_selected": {
"message": "選択項目を削除"
},
"disable_selected": {
"message": "選択項目を無効にする"
},
"enable_selected": {
"message": "選択項目を有効にする"
},
"import_list": {
"message": "リストをインポート"
},
"export_list": {
"message": "リストをエクスポート"
},
"import_list_list_manager": {
"message": "リストをインポート"
},
"export_list_list_manager": {
"message": "リストをエクスポート"
},
"default_list_name": {
"message": "デフォルトリスト"
},
"new_list_name": {
"message": "新しいリスト"
},
"word_active_label": {
"message": "有効"
},
"invalid_json_error": {
"message": "無効なJSONファイル"
},
"confirm_delete_list": {
"message": "このリストを削除してもよろしいですか?"
},
"confirm_delete_words": {
"message": "選択した単語を削除してもよろしいですか?"
},
"deselect_all": {
"message": "すべて解除"
},
"highlight_lists": {
"message": "ハイライトリスト"
},
"dark_mode": {
"message": "ダークモード"
},
"list_settings": {
"message": "リスト設定"
},
"word_list": {
"message": "単語リスト"
},
"add_words": {
"message": "単語を追加"
},
"global_highlight_toggle": {
"message": "有効にする"
},
"search_placeholder": {
"message": "検索..."
},
"options": {
"message": "オプション"
},
"match_case": {
"message": "大文字と小文字を区別"
},
"match_whole": {
"message": "完全一致"
},
"site_exceptions": {
"message": "サイト例外"
},
"add_exception": {
"message": "例外に追加"
},
"remove_exception": {
"message": "現在を削除"
},
"manage_exceptions": {
"message": "管理"
},
"exceptions_list": {
"message": "例外サイト:"
},
"clear_all": {
"message": "すべてクリア"
},
"confirm_clear_exceptions": {
"message": "すべての例外をクリアしてもよろしいですか?"
},
"remove": {
"message": "削除"
},
"tab_lists": {
"message": "リスト"
},
"tab_words": {
"message": "単語"
},
"tab_exceptions": {
"message": "例外"
},
"no_exceptions": {
"message": "例外なし"
},
"toggle_highlighting_title": {
"message": "ハイライトの切り替え"
},
"toggle_dark_mode_title": {
"message": "ダークモードの切り替え"
},
"select_title": {
"message": "選択"
},
"word_placeholder": {
"message": "単語またはフレーズ"
},
"background_color_title": {
"message": "背景色"
},
"text_color_title": {
"message": "文字色"
},
"tab_page_highlights": {
"message": "ページ上"
},
"highlights_on_page": {
"message": "このページのハイライト"
},
"total_highlights": {
"message": "合計"
},
"refresh": {
"message": "更新"
},
"no_highlights_on_page": {
"message": "このページにハイライトが見つかりません"
},
"previous": {
"message": "前へ"
},
"next": {
"message": "次へ"
},
"duplicate": {
"message": "複製"
},
"merge": {
"message": "統合"
},
"move": {
"message": "移動"
},
"copy": {
"message": "コピー"
},
"move_to_list": {
"message": "リストに移動"
},
"copy_to_list": {
"message": "リストにコピー"
},
"move_selected": {
"message": "選択項目を移動"
},
"copy_selected": {
"message": "選択項目をコピー"
},
"no_other_lists": {
"message": "他のリストはありません"
},
"word_actions": {
"message": "別のリストに移動またはコピー"
},
"merge_lists_min_two": {
"message": "統合するには少なくとも2つのリストを選択してください。"
},
"merge_lists_confirm": {
"message": "{count}個のリストを\"{target}\"に統合しますか?ソースリストは削除されます。"
},
"delete_current_list": {
"message": "現在のリストを削除しますか?"
},
"delete_lists_confirm": {
"message": "選択した{count}個のリストを削除しますか?"
},
"invalid_import_format": {
"message": "無効なファイル形式です。有効なGoose Highlighterエクスポートファイルを選択してください。"
},
"import_failed": {
"message": "ファイルのインポートに失敗しました。有効なJSONファイルであることを確認してください。"
},
"import_confirm": {
"message": "合計{words}個の単語を含む{count}個のリストをインポートしますか?"
},
"import_success": {
"message": "{words}個の単語を含む{count}個のリストを正常にインポートしました。"
},
"preview_label": {
"message": "プレビュー"
},
"preview_text": {
"message": "ハイライトされたテキストはこのように表示されます。"
},
"preview_text_before": {
"message": ""
},
"preview_text_highlight": {
"message": "ハイライトされたテキスト"
},
"preview_text_after": {
"message": "はこのように表示されます。"
},
"enable_highlighting_title": {
"message": "ハイライトを有効にする"
},
"enable_highlighting_subtitle": {
"message": "ページにハイライトを表示"
},
"manage_lists": {
"message": "リストを管理"
},
"background_label": {
"message": "背景"
},
"foreground_label": {
"message": "前景"
},
"multi_select_hint": {
"message": "クリック - 選択 • Ctrl+クリック - 複数"
},
"dark_mode_title": {
"message": "ダークモード"
},
"dark_mode_subtitle": {
"message": "ライト/ダークテーマを切り替え"
},
"list_manager_title": {
"message": "リストマネージャー"
},
"words_label": {
"message": "単語"
},
"active_label": {
"message": "有効"
},
"inactive_label": {
"message": "無効"
},
"drag_words_hint": {
"message": "クリック - 選択 • Ctrl+クリック - 複数 • リストへドラッグ - 移動"
},
"drag_lists_hint": {
"message": "ドラッグ - 並べ替え • Ctrl+クリック - 複数"
},
"showing_items": {
"message": "{total}単語中{start}-{end}を表示"
},
"items_per_page": {
"message": "ページあたりのアイテム数:"
},
"page_info": {
"message": "ページ{current}/{total}"
},
"toggle_active": {
"message": "有効/無効を切り替え"
},
"rename_list_title": {
"message": "リスト名を変更"
},
"new_list_title": {
"message": "新しいリスト"
},
"delete_list_title": {
"message": "リストを削除"
},
"list_active_badge": {
"message": "有効"
},
"list_paused_badge": {
"message": "一時停止"
},
"words_stats": {
"message": "{total}単語 • {active}有効 • {inactive}無効"
},
"rename_list": {
"message": "リスト名を変更"
}
}

332
_locales/ko/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "어떤 웹사이트에서든 단어와 구문을 하이라이트하세요. 맞춤 목록 생성, 색상 사용, 가져오기/내보내기 등 다양한 기능을 제공합니다."
},
"select_list": {
"message": "리스트 선택:"
},
"new_list": {
"message": "새 리스트"
},
"delete_list": {
"message": "리스트 삭제"
},
"list_name": {
"message": "리스트 이름:"
},
"background": {
"message": "배경색:"
},
"foreground": {
"message": "글자색:"
},
"enable_highlight": {
"message": "하이라이트 활성화"
},
"apply": {
"message": "적용"
},
"paste_hint": {
"message": "한 줄에 단어 또는 구문 하나"
},
"apply_paste": {
"message": "단어 추가"
},
"select_all": {
"message": "선택"
},
"delete_selected": {
"message": "선택 항목 삭제"
},
"disable_selected": {
"message": "선택 항목 비활성화"
},
"enable_selected": {
"message": "선택 항목 활성화"
},
"import_list": {
"message": "목록 가져오기"
},
"export_list": {
"message": "목록 내보내기"
},
"import_list_list_manager": {
"message": "목록 가져오기"
},
"export_list_list_manager": {
"message": "목록 내보내기"
},
"default_list_name": {
"message": "기본 리스트"
},
"new_list_name": {
"message": "새 리스트"
},
"word_active_label": {
"message": "활성"
},
"invalid_json_error": {
"message": "잘못된 JSON 파일"
},
"confirm_delete_list": {
"message": "이 리스트를 삭제하시겠습니까?"
},
"confirm_delete_words": {
"message": "선택한 단어를 삭제하시겠습니까?"
},
"deselect_all": {
"message": "선택 해제"
},
"highlight_lists": {
"message": "하이라이트 리스트"
},
"dark_mode": {
"message": "다크 모드"
},
"list_settings": {
"message": "리스트 설정"
},
"word_list": {
"message": "단어 리스트"
},
"add_words": {
"message": "단어 추가"
},
"global_highlight_toggle": {
"message": "활성화"
},
"search_placeholder": {
"message": "검색..."
},
"options": {
"message": "옵션"
},
"match_case": {
"message": "대소문자 구분"
},
"match_whole": {
"message": "전체 단어 일치"
},
"site_exceptions": {
"message": "사이트 예외"
},
"add_exception": {
"message": "예외에 추가"
},
"remove_exception": {
"message": "현재 제거"
},
"manage_exceptions": {
"message": "관리"
},
"exceptions_list": {
"message": "예외 사이트:"
},
"clear_all": {
"message": "모두 지우기"
},
"confirm_clear_exceptions": {
"message": "모든 예외를 지우시겠습니까?"
},
"remove": {
"message": "제거"
},
"tab_lists": {
"message": "리스트"
},
"tab_words": {
"message": "단어"
},
"tab_exceptions": {
"message": "예외"
},
"no_exceptions": {
"message": "예외 없음"
},
"toggle_highlighting_title": {
"message": "하이라이트 전환"
},
"toggle_dark_mode_title": {
"message": "다크 모드 전환"
},
"select_title": {
"message": "선택"
},
"word_placeholder": {
"message": "단어 또는 구문"
},
"background_color_title": {
"message": "배경색"
},
"text_color_title": {
"message": "글자색"
},
"tab_page_highlights": {
"message": "페이지에서"
},
"highlights_on_page": {
"message": "이 페이지의 하이라이트"
},
"total_highlights": {
"message": "전체"
},
"refresh": {
"message": "새로고침"
},
"no_highlights_on_page": {
"message": "이 페이지에서 하이라이트를 찾을 수 없습니다"
},
"previous": {
"message": "이전"
},
"next": {
"message": "다음"
},
"duplicate": {
"message": "복제"
},
"merge": {
"message": "병합"
},
"move": {
"message": "이동"
},
"copy": {
"message": "복사"
},
"move_to_list": {
"message": "목록으로 이동"
},
"copy_to_list": {
"message": "목록에 복사"
},
"move_selected": {
"message": "선택 항목 이동"
},
"copy_selected": {
"message": "선택 항목 복사"
},
"no_other_lists": {
"message": "다른 목록 없음"
},
"word_actions": {
"message": "다른 목록으로 이동 또는 복사"
},
"merge_lists_min_two": {
"message": "병합하려면 최소 두 개의 리스트를 선택하세요."
},
"merge_lists_confirm": {
"message": "{count}개의 리스트를 \"{target}\"에 병합하시겠습니까? 소스 리스트는 제거됩니다."
},
"delete_current_list": {
"message": "현재 리스트를 삭제하시겠습니까?"
},
"delete_lists_confirm": {
"message": "선택한 {count}개의 리스트를 삭제하시겠습니까?"
},
"invalid_import_format": {
"message": "잘못된 파일 형식입니다. 유효한 Goose Highlighter 내보내기 파일을 선택하세요."
},
"import_failed": {
"message": "파일 가져오기에 실패했습니다. 유효한 JSON 파일인지 확인하세요."
},
"import_confirm": {
"message": "총 {words}개의 단어가 포함된 {count}개의 리스트를 가져오시겠습니까?"
},
"import_success": {
"message": "{words}개의 단어가 포함된 {count}개의 리스트를 성공적으로 가져왔습니다."
},
"preview_label": {
"message": "미리보기"
},
"preview_text": {
"message": "하이라이트된 텍스트가 이렇게 표시됩니다."
},
"preview_text_before": {
"message": ""
},
"preview_text_highlight": {
"message": "하이라이트된 텍스트"
},
"preview_text_after": {
"message": "가 이렇게 표시됩니다."
},
"enable_highlighting_title": {
"message": "하이라이트 활성화"
},
"enable_highlighting_subtitle": {
"message": "페이지에 하이라이트 표시"
},
"manage_lists": {
"message": "리스트 관리"
},
"background_label": {
"message": "배경"
},
"foreground_label": {
"message": "전경"
},
"multi_select_hint": {
"message": "클릭 - 선택 • Ctrl+클릭 - 여러 개"
},
"dark_mode_title": {
"message": "다크 모드"
},
"dark_mode_subtitle": {
"message": "밝은/어두운 테마 전환"
},
"list_manager_title": {
"message": "리스트 관리자"
},
"words_label": {
"message": "단어"
},
"active_label": {
"message": "활성"
},
"inactive_label": {
"message": "비활성"
},
"drag_words_hint": {
"message": "클릭 - 선택 • Ctrl+클릭 - 여러 개 • 리스트로 드래그 - 이동"
},
"drag_lists_hint": {
"message": "드래그 - 순서 변경 • Ctrl+클릭 - 여러 개"
},
"showing_items": {
"message": "{total}개 단어 중 {start}-{end} 표시"
},
"items_per_page": {
"message": "페이지당 항목 수:"
},
"page_info": {
"message": "페이지 {current}/{total}"
},
"toggle_active": {
"message": "활성 전환"
},
"rename_list_title": {
"message": "리스트 이름 변경"
},
"new_list_title": {
"message": "새 리스트"
},
"delete_list_title": {
"message": "리스트 삭제"
},
"list_active_badge": {
"message": "활성"
},
"list_paused_badge": {
"message": "일시정지"
},
"words_stats": {
"message": "{total}개 단어 • {active}개 활성 • {inactive}개 비활성"
},
"rename_list": {
"message": "리스트 이름 변경"
}
}

332
_locales/nl/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Markeer woorden en zinnen op elke website. Maak aangepaste lijsten, gebruik kleuren, importeer/exporteer en meer."
},
"select_list": {
"message": "Selecteer lijst:"
},
"new_list": {
"message": "Nieuwe lijst"
},
"delete_list": {
"message": "Lijst verwijderen"
},
"list_name": {
"message": "Lijstnaam:"
},
"background": {
"message": "Achtergrond:"
},
"foreground": {
"message": "Tekstkleur:"
},
"enable_highlight": {
"message": "Markeren inschakelen"
},
"apply": {
"message": "Toepassen"
},
"paste_hint": {
"message": "Eén woord of zin per regel"
},
"apply_paste": {
"message": "Woorden toevoegen"
},
"select_all": {
"message": "Selecteren"
},
"delete_selected": {
"message": "Geselecteerde verwijderen"
},
"disable_selected": {
"message": "Geselecteerde uitschakelen"
},
"enable_selected": {
"message": "Geselecteerde inschakelen"
},
"import_list": {
"message": "Lijst importeren"
},
"export_list": {
"message": "Lijst exporteren"
},
"import_list_list_manager": {
"message": "Lijst importeren"
},
"export_list_list_manager": {
"message": "Lijst exporteren"
},
"default_list_name": {
"message": "Standaardlijst"
},
"new_list_name": {
"message": "Nieuwe lijst"
},
"word_active_label": {
"message": "actief"
},
"invalid_json_error": {
"message": "Ongeldig JSON-bestand"
},
"confirm_delete_list": {
"message": "Weet je zeker dat je deze lijst wilt verwijderen?"
},
"confirm_delete_words": {
"message": "Weet je zeker dat je de geselecteerde woorden wilt verwijderen?"
},
"deselect_all": {
"message": "Deselecteren"
},
"highlight_lists": {
"message": "Markeer lijsten"
},
"dark_mode": {
"message": "Donkere modus"
},
"list_settings": {
"message": "Lijst instellingen"
},
"word_list": {
"message": "Woordenlijst"
},
"add_words": {
"message": "Woorden toevoegen"
},
"global_highlight_toggle": {
"message": "Inschakelen"
},
"search_placeholder": {
"message": "Zoeken..."
},
"options": {
"message": "Opties"
},
"match_case": {
"message": "Hoofdlettergevoelig"
},
"match_whole": {
"message": "Alleen volledig woord"
},
"site_exceptions": {
"message": "Site-uitzonderingen"
},
"add_exception": {
"message": "Toevoegen aan uitzonderingen"
},
"remove_exception": {
"message": "Huidige verwijderen"
},
"manage_exceptions": {
"message": "Beheren"
},
"exceptions_list": {
"message": "Uitzondering sites:"
},
"clear_all": {
"message": "Alles wissen"
},
"confirm_clear_exceptions": {
"message": "Weet je zeker dat je alle uitzonderingen wilt wissen?"
},
"remove": {
"message": "Verwijderen"
},
"tab_lists": {
"message": "Lijsten"
},
"tab_words": {
"message": "Woorden"
},
"tab_exceptions": {
"message": "Uitzonderingen"
},
"no_exceptions": {
"message": "Geen uitzonderingen"
},
"toggle_highlighting_title": {
"message": "Markeren in-/uitschakelen"
},
"toggle_dark_mode_title": {
"message": "Donkere modus in-/uitschakelen"
},
"select_title": {
"message": "Selecteren"
},
"word_placeholder": {
"message": "Woord of zin"
},
"background_color_title": {
"message": "Achtergrondkleur"
},
"text_color_title": {
"message": "Tekstkleur"
},
"tab_page_highlights": {
"message": "Op pagina"
},
"highlights_on_page": {
"message": "Markeringen op deze pagina"
},
"total_highlights": {
"message": "Totaal"
},
"refresh": {
"message": "Vernieuwen"
},
"no_highlights_on_page": {
"message": "Geen markeringen gevonden op deze pagina"
},
"previous": {
"message": "Vorige"
},
"next": {
"message": "Volgende"
},
"duplicate": {
"message": "Dupliceren"
},
"merge": {
"message": "Samenvoegen"
},
"move": {
"message": "Verplaatsen"
},
"copy": {
"message": "Kopiëren"
},
"move_to_list": {
"message": "Verplaats naar lijst"
},
"copy_to_list": {
"message": "Kopieer naar lijst"
},
"move_selected": {
"message": "Geselecteerde verplaatsen"
},
"copy_selected": {
"message": "Geselecteerde kopiëren"
},
"no_other_lists": {
"message": "Geen andere lijsten"
},
"word_actions": {
"message": "Verplaats of kopieer naar een andere lijst"
},
"merge_lists_min_two": {
"message": "Selecteer minimaal twee lijsten om samen te voegen."
},
"merge_lists_confirm": {
"message": "{count} lijst(en) samenvoegen in \"{target}\"? Bronlijsten worden verwijderd."
},
"delete_current_list": {
"message": "Huidige lijst verwijderen?"
},
"delete_lists_confirm": {
"message": "{count} geselecteerde lijst(en) verwijderen?"
},
"invalid_import_format": {
"message": "Ongeldig bestandsformaat. Selecteer een geldig Goose Highlighter exportbestand."
},
"import_failed": {
"message": "Importeren van bestand mislukt. Zorg ervoor dat het een geldig JSON-bestand is."
},
"import_confirm": {
"message": "{count} lijst(en) met in totaal {words} woord(en) importeren?"
},
"import_success": {
"message": "{count} lijst(en) met {words} woord(en) succesvol geïmporteerd."
},
"preview_label": {
"message": "Voorbeeld"
},
"preview_text": {
"message": "Zo zal uw gemarkeerde tekst verschijnen."
},
"preview_text_before": {
"message": "Zo zal uw"
},
"preview_text_highlight": {
"message": "gemarkeerde tekst"
},
"preview_text_after": {
"message": "verschijnen."
},
"enable_highlighting_title": {
"message": "Markeren inschakelen"
},
"enable_highlighting_subtitle": {
"message": "Markeringen op pagina's weergeven"
},
"manage_lists": {
"message": "Lijsten beheren"
},
"background_label": {
"message": "Achtergrond"
},
"foreground_label": {
"message": "Voorgrond"
},
"multi_select_hint": {
"message": "Klik - selecteren • Ctrl+klik - meerdere"
},
"dark_mode_title": {
"message": "Donkere modus"
},
"dark_mode_subtitle": {
"message": "Schakel tussen licht/donker thema"
},
"list_manager_title": {
"message": "Lijstbeheerder"
},
"words_label": {
"message": "woorden"
},
"active_label": {
"message": "actief"
},
"inactive_label": {
"message": "inactief"
},
"drag_words_hint": {
"message": "Klik - selecteren • Ctrl+klik - meerdere • Sleep naar lijst - verplaatsen"
},
"drag_lists_hint": {
"message": "Sleep - herschikken • Ctrl+klik - meerdere"
},
"showing_items": {
"message": "{start}-{end} van {total} woorden weergegeven"
},
"items_per_page": {
"message": "Items per pagina:"
},
"page_info": {
"message": "Pagina {current} van {total}"
},
"toggle_active": {
"message": "actief schakelen"
},
"rename_list_title": {
"message": "Lijst hernoemen"
},
"new_list_title": {
"message": "Nieuwe lijst"
},
"delete_list_title": {
"message": "Lijst verwijderen"
},
"list_active_badge": {
"message": "Actief"
},
"list_paused_badge": {
"message": "Gepauzeerd"
},
"words_stats": {
"message": "{total} woorden • {active} actief • {inactive} inactief"
},
"rename_list": {
"message": "Lijst hernoemen"
}
}

332
_locales/pl/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Podświetlaj słowa i frazy na dowolnej stronie internetowej. Twórz niestandardowe listy, używaj kolorów, importuj/eksportuj i więcej."
},
"select_list": {
"message": "Wybierz listę:"
},
"new_list": {
"message": "Nowa lista"
},
"delete_list": {
"message": "Usuń listę"
},
"list_name": {
"message": "Nazwa listy:"
},
"background": {
"message": "Tło:"
},
"foreground": {
"message": "Kolor tekstu:"
},
"enable_highlight": {
"message": "Włącz podświetlanie"
},
"apply": {
"message": "Zastosuj"
},
"paste_hint": {
"message": "Jedno słowo lub fraza na linię"
},
"apply_paste": {
"message": "Dodaj słowa"
},
"select_all": {
"message": "Zaznacz"
},
"delete_selected": {
"message": "Usuń zaznaczone"
},
"disable_selected": {
"message": "Wyłącz zaznaczone"
},
"enable_selected": {
"message": "Włącz zaznaczone"
},
"import_list": {
"message": "Importuj listę"
},
"export_list": {
"message": "Eksportuj listę"
},
"import_list_list_manager": {
"message": "Importuj listę"
},
"export_list_list_manager": {
"message": "Eksportuj listę"
},
"default_list_name": {
"message": "Lista domyślna"
},
"new_list_name": {
"message": "Nowa lista"
},
"word_active_label": {
"message": "aktywny"
},
"invalid_json_error": {
"message": "Nieprawidłowy plik JSON"
},
"confirm_delete_list": {
"message": "Czy na pewno chcesz usunąć tę listę?"
},
"confirm_delete_words": {
"message": "Czy na pewno chcesz usunąć wybrane słowa?"
},
"deselect_all": {
"message": "Odznacz"
},
"highlight_lists": {
"message": "Listy podświetleń"
},
"dark_mode": {
"message": "Tryb ciemny"
},
"list_settings": {
"message": "Ustawienia listy"
},
"word_list": {
"message": "Lista słów"
},
"add_words": {
"message": "Dodaj słowa"
},
"global_highlight_toggle": {
"message": "Włącz"
},
"search_placeholder": {
"message": "Szukaj..."
},
"options": {
"message": "Opcje"
},
"match_case": {
"message": "Uwzględnij wielkość liter"
},
"match_whole": {
"message": "Tylko całe słowo"
},
"site_exceptions": {
"message": "Wyjątki stron"
},
"add_exception": {
"message": "Dodaj do wyjątków"
},
"remove_exception": {
"message": "Usuń bieżący"
},
"manage_exceptions": {
"message": "Zarządzaj"
},
"exceptions_list": {
"message": "Strony wyjątków:"
},
"clear_all": {
"message": "Wyczyść wszystko"
},
"confirm_clear_exceptions": {
"message": "Czy na pewno chcesz wyczyścić wszystkie wyjątki?"
},
"remove": {
"message": "Usuń"
},
"tab_lists": {
"message": "Listy"
},
"tab_words": {
"message": "Słowa"
},
"tab_exceptions": {
"message": "Wyjątki"
},
"no_exceptions": {
"message": "Brak wyjątków"
},
"toggle_highlighting_title": {
"message": "Przełącz podświetlanie"
},
"toggle_dark_mode_title": {
"message": "Przełącz tryb ciemny"
},
"select_title": {
"message": "Zaznacz"
},
"word_placeholder": {
"message": "Słowo lub fraza"
},
"background_color_title": {
"message": "Kolor tła"
},
"text_color_title": {
"message": "Kolor tekstu"
},
"tab_page_highlights": {
"message": "Na stronie"
},
"highlights_on_page": {
"message": "Podświetlenia na tej stronie"
},
"total_highlights": {
"message": "Łącznie"
},
"refresh": {
"message": "Odśwież"
},
"no_highlights_on_page": {
"message": "Nie znaleziono podświetleń na tej stronie"
},
"previous": {
"message": "Poprzedni"
},
"next": {
"message": "Następny"
},
"duplicate": {
"message": "Duplikuj"
},
"merge": {
"message": "Scal"
},
"move": {
"message": "Przenieś"
},
"copy": {
"message": "Kopiuj"
},
"move_to_list": {
"message": "Przenieś do listy"
},
"copy_to_list": {
"message": "Kopiuj do listy"
},
"move_selected": {
"message": "Przenieś zaznaczone"
},
"copy_selected": {
"message": "Kopiuj zaznaczone"
},
"no_other_lists": {
"message": "Brak innych list"
},
"word_actions": {
"message": "Przenieś lub skopiuj do innej listy"
},
"merge_lists_min_two": {
"message": "Wybierz co najmniej dwie listy do scalenia."
},
"merge_lists_confirm": {
"message": "Scalić {count} list(y) w \"{target}\"? Listy źródłowe zostaną usunięte."
},
"delete_current_list": {
"message": "Usunąć bieżącą listę?"
},
"delete_lists_confirm": {
"message": "Usunąć {count} wybraną/e listę/y?"
},
"invalid_import_format": {
"message": "Nieprawidłowy format pliku. Wybierz prawidłowy plik eksportu Goose Highlighter."
},
"import_failed": {
"message": "Nie udało się zaimportować pliku. Upewnij się, że jest to prawidłowy plik JSON."
},
"import_confirm": {
"message": "Zaimportować {count} list(y) z łącznie {words} słowami?"
},
"import_success": {
"message": "Pomyślnie zaimportowano {count} list(y) z {words} słowami."
},
"preview_label": {
"message": "Podgląd"
},
"preview_text": {
"message": "Tak będzie wyglądał Twój podświetlony tekst."
},
"preview_text_before": {
"message": "Tak będzie wyglądał Twój"
},
"preview_text_highlight": {
"message": "podświetlony tekst"
},
"preview_text_after": {
"message": "."
},
"enable_highlighting_title": {
"message": "Włącz podświetlanie"
},
"enable_highlighting_subtitle": {
"message": "Pokaż podświetlenia na stronach"
},
"manage_lists": {
"message": "Zarządzaj listami"
},
"background_label": {
"message": "Tło"
},
"foreground_label": {
"message": "Pierwszy plan"
},
"multi_select_hint": {
"message": "Klik - zaznacz • Ctrl+klik - kilka"
},
"dark_mode_title": {
"message": "Tryb ciemny"
},
"dark_mode_subtitle": {
"message": "Przełącz między jasnym/ciemnym motywem"
},
"list_manager_title": {
"message": "Menedżer list"
},
"words_label": {
"message": "słowa"
},
"active_label": {
"message": "aktywny"
},
"inactive_label": {
"message": "nieaktywny"
},
"drag_words_hint": {
"message": "Klik - zaznacz • Ctrl+klik - kilka • Przeciągnij do listy - przenieś"
},
"drag_lists_hint": {
"message": "Przeciągnij - zmień kolejność • Ctrl+klik - kilka"
},
"showing_items": {
"message": "Wyświetlanie {start}-{end} z {total} słów"
},
"items_per_page": {
"message": "Elementów na stronę:"
},
"page_info": {
"message": "Strona {current} z {total}"
},
"toggle_active": {
"message": "przełącz aktywny"
},
"rename_list_title": {
"message": "Zmień nazwę listy"
},
"new_list_title": {
"message": "Nowa lista"
},
"delete_list_title": {
"message": "Usuń listę"
},
"list_active_badge": {
"message": "Aktywny"
},
"list_paused_badge": {
"message": "Wstrzymany"
},
"words_stats": {
"message": "{total} słów • {active} aktywnych • {inactive} nieaktywnych"
},
"rename_list": {
"message": "Zmień nazwę listy"
}
}

View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Destaque palavras e frases em qualquer site. Crie listas personalizadas, use cores, importe/exporte e muito mais."
},
"select_list": {
"message": "Selecionar lista:"
},
"new_list": {
"message": "Nova lista"
},
"delete_list": {
"message": "Excluir lista"
},
"list_name": {
"message": "Nome da lista:"
},
"background": {
"message": "Fundo:"
},
"foreground": {
"message": "Texto:"
},
"enable_highlight": {
"message": "Ativar destaque"
},
"apply": {
"message": "Aplicar"
},
"paste_hint": {
"message": "Uma palavra ou frase por linha"
},
"apply_paste": {
"message": "Adicionar palavras"
},
"select_all": {
"message": "Selecionar"
},
"delete_selected": {
"message": "Excluir selecionados"
},
"disable_selected": {
"message": "Desativar selecionados"
},
"enable_selected": {
"message": "Ativar selecionados"
},
"import_list": {
"message": "Importar lista"
},
"export_list": {
"message": "Exportar lista"
},
"import_list_list_manager": {
"message": "Importar lista"
},
"export_list_list_manager": {
"message": "Exportar lista"
},
"default_list_name": {
"message": "Lista padrão"
},
"new_list_name": {
"message": "Nova lista"
},
"word_active_label": {
"message": "ativo"
},
"invalid_json_error": {
"message": "Arquivo JSON inválido"
},
"confirm_delete_list": {
"message": "Tem certeza de que deseja excluir esta lista?"
},
"confirm_delete_words": {
"message": "Tem certeza de que deseja excluir as palavras selecionadas?"
},
"deselect_all": {
"message": "Desmarcar"
},
"highlight_lists": {
"message": "Listas de destaque"
},
"dark_mode": {
"message": "Modo escuro"
},
"list_settings": {
"message": "Configurações da lista"
},
"word_list": {
"message": "Lista de palavras"
},
"add_words": {
"message": "Adicionar palavras"
},
"global_highlight_toggle": {
"message": "Ativar"
},
"search_placeholder": {
"message": "Pesquisar..."
},
"options": {
"message": "Opções"
},
"match_case": {
"message": "Diferenciar maiúsculas/minúsculas"
},
"match_whole": {
"message": "Palavra inteira"
},
"site_exceptions": {
"message": "Exceções de sites"
},
"add_exception": {
"message": "Adicionar às exceções"
},
"remove_exception": {
"message": "Remover atual"
},
"manage_exceptions": {
"message": "Gerenciar"
},
"exceptions_list": {
"message": "Sites de exceção:"
},
"clear_all": {
"message": "Limpar tudo"
},
"confirm_clear_exceptions": {
"message": "Tem certeza de que deseja limpar todas as exceções?"
},
"remove": {
"message": "Remover"
},
"tab_lists": {
"message": "Listas"
},
"tab_words": {
"message": "Palavras"
},
"tab_exceptions": {
"message": "Exceções"
},
"no_exceptions": {
"message": "Sem exceções"
},
"toggle_highlighting_title": {
"message": "Alternar destaque"
},
"toggle_dark_mode_title": {
"message": "Alternar modo escuro"
},
"select_title": {
"message": "Selecionar"
},
"word_placeholder": {
"message": "Palavra ou frase"
},
"background_color_title": {
"message": "Cor de fundo"
},
"text_color_title": {
"message": "Cor do texto"
},
"tab_page_highlights": {
"message": "Na página"
},
"highlights_on_page": {
"message": "Destaques nesta página"
},
"total_highlights": {
"message": "Total"
},
"refresh": {
"message": "Atualizar"
},
"no_highlights_on_page": {
"message": "Nenhum destaque encontrado nesta página"
},
"previous": {
"message": "Anterior"
},
"next": {
"message": "Próximo"
},
"duplicate": {
"message": "Duplicar"
},
"merge": {
"message": "Mesclar"
},
"move": {
"message": "Mover"
},
"copy": {
"message": "Copiar"
},
"move_to_list": {
"message": "Mover para lista"
},
"copy_to_list": {
"message": "Copiar para lista"
},
"move_selected": {
"message": "Mover selecionados"
},
"copy_selected": {
"message": "Copiar selecionados"
},
"no_other_lists": {
"message": "Nenhuma outra lista"
},
"word_actions": {
"message": "Mover ou copiar para outra lista"
},
"merge_lists_min_two": {
"message": "Selecione pelo menos duas listas para mesclar."
},
"merge_lists_confirm": {
"message": "Mesclar {count} lista(s) em \"{target}\"? As listas de origem serão removidas."
},
"delete_current_list": {
"message": "Excluir lista atual?"
},
"delete_lists_confirm": {
"message": "Excluir {count} lista(s) selecionada(s)?"
},
"invalid_import_format": {
"message": "Formato de arquivo inválido. Selecione um arquivo de exportação válido do Goose Highlighter."
},
"import_failed": {
"message": "Falha ao importar arquivo. Certifique-se de que é um arquivo JSON válido."
},
"import_confirm": {
"message": "Importar {count} lista(s) com {words} palavra(s) no total?"
},
"import_success": {
"message": "{count} lista(s) com {words} palavra(s) importada(s) com sucesso."
},
"preview_label": {
"message": "Visualização"
},
"preview_text": {
"message": "É assim que seu texto destacado aparecerá."
},
"preview_text_before": {
"message": "É assim que seu"
},
"preview_text_highlight": {
"message": "texto destacado"
},
"preview_text_after": {
"message": "aparecerá."
},
"enable_highlighting_title": {
"message": "Ativar destaque"
},
"enable_highlighting_subtitle": {
"message": "Mostrar destaques nas páginas"
},
"manage_lists": {
"message": "Gerenciar listas"
},
"background_label": {
"message": "Fundo"
},
"foreground_label": {
"message": "Texto"
},
"multi_select_hint": {
"message": "Clique - selecionar • Ctrl+clique - vários"
},
"dark_mode_title": {
"message": "Modo escuro"
},
"dark_mode_subtitle": {
"message": "Alternar entre tema claro/escuro"
},
"list_manager_title": {
"message": "Gerenciador de listas"
},
"words_label": {
"message": "palavras"
},
"active_label": {
"message": "ativo"
},
"inactive_label": {
"message": "inativo"
},
"drag_words_hint": {
"message": "Clique - selecionar • Ctrl+clique - vários • Arraste para lista - mover"
},
"drag_lists_hint": {
"message": "Arraste - reordenar • Ctrl+clique - vários"
},
"showing_items": {
"message": "Mostrando {start}-{end} de {total} palavras"
},
"items_per_page": {
"message": "Itens por página:"
},
"page_info": {
"message": "Página {current} de {total}"
},
"toggle_active": {
"message": "alternar ativo"
},
"rename_list_title": {
"message": "Renomear lista"
},
"new_list_title": {
"message": "Nova lista"
},
"delete_list_title": {
"message": "Excluir lista"
},
"list_active_badge": {
"message": "Ativo"
},
"list_paused_badge": {
"message": "Pausado"
},
"words_stats": {
"message": "{total} palavras • {active} ativas • {inactive} inativas"
},
"rename_list": {
"message": "Renomear lista"
}
}

View File

@@ -2,6 +2,9 @@
"extension_name": { "extension_name": {
"message": "Goose Highlighter" "message": "Goose Highlighter"
}, },
"extension_description": {
"message": "Выделяйте слова и фразы на любом сайте. Создавайте собственные списки, используйте цвета, импортируйте и экспортируйте данные."
},
"select_list": { "select_list": {
"message": "Выберите список:" "message": "Выберите список:"
}, },
@@ -23,8 +26,11 @@
"enable_highlight": { "enable_highlight": {
"message": "Включить выделение" "message": "Включить выделение"
}, },
"apply": {
"message": "Применить"
},
"paste_hint": { "paste_hint": {
"message": "Вставьте список слов здесь" "message": "Одно слово или фраза на строку"
}, },
"apply_paste": { "apply_paste": {
"message": "Добавить слова" "message": "Добавить слова"
@@ -33,19 +39,25 @@
"message": "Выбрать все" "message": "Выбрать все"
}, },
"delete_selected": { "delete_selected": {
"message": "Удалить" "message": "Удалить выбранные"
}, },
"disable_selected": { "disable_selected": {
"message": "Отключить" "message": "Отключить выбранные"
}, },
"enable_selected": { "enable_selected": {
"message": "Включить" "message": "Включить выбранные"
}, },
"import_list": { "import_list": {
"message": "Импорт JSON" "message": "Импорт списка"
}, },
"export_list": { "export_list": {
"message": "Экспорт JSON" "message": "Экспорт списка"
},
"import_list_list_manager": {
"message": "Импорт списка"
},
"export_list_list_manager": {
"message": "Экспорт списка"
}, },
"default_list_name": { "default_list_name": {
"message": "Список по умолчанию" "message": "Список по умолчанию"
@@ -85,5 +97,237 @@
}, },
"global_highlight_toggle": { "global_highlight_toggle": {
"message": "Вкл" "message": "Вкл"
},
"search_placeholder": {
"message": "Поиск..."
},
"options": {
"message": "Опции"
},
"match_case": {
"message": "С учетом регистра"
},
"match_whole": {
"message": "Слово целиком"
},
"site_exceptions": {
"message": "Сайты-исключения"
},
"add_exception": {
"message": "Добавить в исключения"
},
"remove_exception": {
"message": "Удалить из исключений"
},
"manage_exceptions": {
"message": "Управление"
},
"exceptions_list": {
"message": "Сайты-исключения:"
},
"clear_all": {
"message": "Очистить все"
},
"confirm_clear_exceptions": {
"message": "Вы уверены, что хотите очистить все исключения?"
},
"remove": {
"message": "Удалить"
},
"tab_lists": {
"message": "Списки"
},
"tab_words": {
"message": "Слова"
},
"tab_exceptions": {
"message": "Исключения"
},
"no_exceptions": {
"message": "Нет исключений"
},
"toggle_highlighting_title": {
"message": "Выделение(вкл/выкл)"
},
"toggle_dark_mode_title": {
"message": "Темная/светлая тема"
},
"select_title": {
"message": "Выбрать"
},
"word_placeholder": {
"message": "Слово или фраза"
},
"background_color_title": {
"message": "Цвет фона"
},
"text_color_title": {
"message": "Цвет текста"
},
"tab_page_highlights": {
"message": "На странице"
},
"highlights_on_page": {
"message": "Найдено на этой странице"
},
"total_highlights": {
"message": "Всего"
},
"refresh": {
"message": "Обновить"
},
"no_highlights_on_page": {
"message": "На этой странице не найдены слова из списка"
},
"previous": {
"message": "Предыдущее"
},
"next": {
"message": "Следующее"
},
"duplicate": {
"message": "Дублировать"
},
"merge": {
"message": "Объединить"
},
"move": {
"message": "Переместить"
},
"copy": {
"message": "Копировать"
},
"move_to_list": {
"message": "Переместить в список"
},
"copy_to_list": {
"message": "Копировать в список"
},
"move_selected": {
"message": "Переместить выбранные"
},
"copy_selected": {
"message": "Копировать выбранные"
},
"no_other_lists": {
"message": "Нет других списков"
},
"word_actions": {
"message": "Переместить или скопировать в другой список"
},
"merge_lists_min_two": {
"message": "Выберите как минимум два списка для объединения."
},
"merge_lists_confirm": {
"message": "Объединить {count} список(ов) в \"{target}\"? Исходные списки будут удалены."
},
"delete_current_list": {
"message": "Удалить текущий список?"
},
"delete_lists_confirm": {
"message": "Удалить {count} выбранный(ых) список(ов)?"
},
"invalid_import_format": {
"message": "Неверный формат файла. Выберите действительный файл экспорта Goose Highlighter."
},
"import_failed": {
"message": "Не удалось импортировать файл. Убедитесь, что это действительный файл JSON."
},
"import_confirm": {
"message": "Импортировать {count} список(ов) с {words} словами всего?"
},
"import_success": {
"message": "Успешно импортировано {count} список(ов) с {words} словами."
}
,
"preview_label": {
"message": "Предпросмотр"
},
"preview_text": {
"message": "Так будет выглядеть выделенный текст."
},
"preview_text_before": {
"message": "Так будет выглядеть"
},
"preview_text_highlight": {
"message": "выделенный текст"
},
"preview_text_after": {
"message": "."
},
"enable_highlighting_title": {
"message": "Включить выделение"
},
"enable_highlighting_subtitle": {
"message": "Выделять слова из выбранного списка"
},
"manage_lists": {
"message": "Управление списками"
},
"background_label": {
"message": "Фон"
},
"foreground_label": {
"message": "Текст"
},
"multi_select_hint": {
"message": "Клик - выбрать • Ctrl+клик - выбрать несколько"
},
"dark_mode_title": {
"message": "Темная тема"
},
"dark_mode_subtitle": {
"message": "Переключить светлую/темную тему"
},
"list_manager_title": {
"message": "Управление списками"
},
"words_label": {
"message": "слова"
},
"active_label": {
"message": "активно"
},
"inactive_label": {
"message": "неактивно"
},
"drag_words_hint": {
"message": "Клик - выбрать • Ctrl+клик - несколько • Перетащить в список - переместить"
},
"drag_lists_hint": {
"message": "Перетащить - изменить порядок • Ctrl+клик - выбрать несколько"
},
"showing_items": {
"message": "Показано {start}-{end} из {total} слов"
},
"items_per_page": {
"message": "Элементов на странице:"
},
"page_info": {
"message": "Страница {current} из {total}"
},
"toggle_active": {
"message": "переключить активность"
},
"rename_list_title": {
"message": "Переименовать список"
},
"new_list_title": {
"message": "Новый список"
},
"delete_list_title": {
"message": "Удалить список"
},
"list_active_badge": {
"message": "Активен"
},
"list_paused_badge": {
"message": "Приостановлен"
},
"words_stats": {
"message": "{total} слов • {active} активных • {inactive} неактивных"
},
"rename_list": {
"message": "Переименовать список"
} }
} }

332
_locales/tr/messages.json Normal file
View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "Herhangi bir web sitesinde kelimeleri ve ifadeleri vurgulayın. Özel listeler oluşturun, renkler kullanın, içe/dışa aktarın ve daha fazlası."
},
"select_list": {
"message": "Liste Seç:"
},
"new_list": {
"message": "Yeni Liste"
},
"delete_list": {
"message": "Listeyi Sil"
},
"list_name": {
"message": "Liste Adı:"
},
"background": {
"message": "Arka Plan:"
},
"foreground": {
"message": "Metin Rengi:"
},
"enable_highlight": {
"message": "Vurgulamayı Etkinleştir"
},
"apply": {
"message": "Uygula"
},
"paste_hint": {
"message": "Satır başına bir kelime veya ifade"
},
"apply_paste": {
"message": "Kelimeleri Ekle"
},
"select_all": {
"message": "Seç"
},
"delete_selected": {
"message": "Seçilenleri sil"
},
"disable_selected": {
"message": "Seçilenleri devre dışı bırak"
},
"enable_selected": {
"message": "Seçilenleri etkinleştir"
},
"import_list": {
"message": "Listeyi içe aktar"
},
"export_list": {
"message": "Listeyi dışa aktar"
},
"import_list_list_manager": {
"message": "Listeyi içe aktar"
},
"export_list_list_manager": {
"message": "Listeyi dışa aktar"
},
"default_list_name": {
"message": "Varsayılan Liste"
},
"new_list_name": {
"message": "Yeni Liste"
},
"word_active_label": {
"message": "aktif"
},
"invalid_json_error": {
"message": "Geçersiz JSON dosyası"
},
"confirm_delete_list": {
"message": "Bu listeyi silmek istediğinizden emin misiniz?"
},
"confirm_delete_words": {
"message": "Seçili kelimeleri silmek istediğinizden emin misiniz?"
},
"deselect_all": {
"message": "Seçimi Kaldır"
},
"highlight_lists": {
"message": "Vurgulama Listeleri"
},
"dark_mode": {
"message": "Karanlık Mod"
},
"list_settings": {
"message": "Liste Ayarları"
},
"word_list": {
"message": "Kelime Listesi"
},
"add_words": {
"message": "Kelimeleri Ekle"
},
"global_highlight_toggle": {
"message": "Etkinleştir"
},
"search_placeholder": {
"message": "Ara..."
},
"options": {
"message": "Seçenekler"
},
"match_case": {
"message": "Büyük/küçük harf duyarlı"
},
"match_whole": {
"message": "Tüm kelimeyle eşleş"
},
"site_exceptions": {
"message": "Site İstisnaları"
},
"add_exception": {
"message": "İstisnalara Ekle"
},
"remove_exception": {
"message": "Mevcut olanı kaldır"
},
"manage_exceptions": {
"message": "Yönet"
},
"exceptions_list": {
"message": "İstisna Siteleri:"
},
"clear_all": {
"message": "Hepsini Temizle"
},
"confirm_clear_exceptions": {
"message": "Tüm istisnaları temizlemek istediğinizden emin misiniz?"
},
"remove": {
"message": "Kaldır"
},
"tab_lists": {
"message": "Listeler"
},
"tab_words": {
"message": "Kelimeler"
},
"tab_exceptions": {
"message": "İstisnalar"
},
"no_exceptions": {
"message": "İstisna yok"
},
"toggle_highlighting_title": {
"message": "Vurgulamayı aç/kapat"
},
"toggle_dark_mode_title": {
"message": "Karanlık modu aç/kapat"
},
"select_title": {
"message": "Seç"
},
"word_placeholder": {
"message": "Kelime veya ifade"
},
"background_color_title": {
"message": "Arka plan rengi"
},
"text_color_title": {
"message": "Metin rengi"
},
"tab_page_highlights": {
"message": "Sayfada"
},
"highlights_on_page": {
"message": "Bu sayfadaki vurgular"
},
"total_highlights": {
"message": "Toplam"
},
"refresh": {
"message": "Yenile"
},
"no_highlights_on_page": {
"message": "Bu sayfada vurgu bulunamadı"
},
"previous": {
"message": "Önceki"
},
"next": {
"message": "Sonraki"
},
"duplicate": {
"message": "Çoğalt"
},
"merge": {
"message": "Birleştir"
},
"move": {
"message": "Taşı"
},
"copy": {
"message": "Kopyala"
},
"move_to_list": {
"message": "Listeye taşı"
},
"copy_to_list": {
"message": "Listeye kopyala"
},
"move_selected": {
"message": "Seçilenleri taşı"
},
"copy_selected": {
"message": "Seçilenleri kopyala"
},
"no_other_lists": {
"message": "Başka liste yok"
},
"word_actions": {
"message": "Başka bir listeye taşı veya kopyala"
},
"merge_lists_min_two": {
"message": "Birleştirmek için en az iki liste seçin."
},
"merge_lists_confirm": {
"message": "{count} listeyi \"{target}\" ile birleştir? Kaynak listeler kaldırılacak."
},
"delete_current_list": {
"message": "Mevcut listeyi sil?"
},
"delete_lists_confirm": {
"message": "Seçili {count} listeyi sil?"
},
"invalid_import_format": {
"message": "Geçersiz dosya formatı. Lütfen geçerli bir Goose Highlighter dışa aktarma dosyası seçin."
},
"import_failed": {
"message": "Dosya içe aktarılamadı. Lütfen geçerli bir JSON dosyası olduğundan emin olun."
},
"import_confirm": {
"message": "Toplam {words} kelime içeren {count} liste içe aktarılsın mı?"
},
"import_success": {
"message": "{words} kelime içeren {count} liste başarıyla içe aktarıldı."
},
"preview_label": {
"message": "Önizleme"
},
"preview_text": {
"message": "Vurgulanan metniniz böyle görünecek."
},
"preview_text_before": {
"message": "Vurgulanan metniniz"
},
"preview_text_highlight": {
"message": "böyle"
},
"preview_text_after": {
"message": "görünecek."
},
"enable_highlighting_title": {
"message": "Vurgulamayı Etkinleştir"
},
"enable_highlighting_subtitle": {
"message": "Sayfalarda vurguları göster"
},
"manage_lists": {
"message": "Listeleri Yönet"
},
"background_label": {
"message": "Arka Plan"
},
"foreground_label": {
"message": "Ön Plan"
},
"multi_select_hint": {
"message": "Tıklama - seç • Ctrl+tıklama - birden fazla"
},
"dark_mode_title": {
"message": "Karanlık Mod"
},
"dark_mode_subtitle": {
"message": "Açık/koyu tema arasında geçiş yap"
},
"list_manager_title": {
"message": "Liste Yöneticisi"
},
"words_label": {
"message": "kelime"
},
"active_label": {
"message": "aktif"
},
"inactive_label": {
"message": "pasif"
},
"drag_words_hint": {
"message": "Tıklama - seç • Ctrl+tıklama - birden fazla • Listeye sürükle - taşı"
},
"drag_lists_hint": {
"message": "Sürükle - sırala • Ctrl+tıklama - birden fazla"
},
"showing_items": {
"message": "{total} kelimeden {start}-{end} gösteriliyor"
},
"items_per_page": {
"message": "Sayfa başına öğe:"
},
"page_info": {
"message": "Sayfa {current} / {total}"
},
"toggle_active": {
"message": "aktif/pasif değiştir"
},
"rename_list_title": {
"message": "Listeyi yeniden adlandır"
},
"new_list_title": {
"message": "Yeni liste"
},
"delete_list_title": {
"message": "Listeyi sil"
},
"list_active_badge": {
"message": "Aktif"
},
"list_paused_badge": {
"message": "Duraklatıldı"
},
"words_stats": {
"message": "{total} kelime • {active} aktif • {inactive} pasif"
},
"rename_list": {
"message": "Listeyi Yeniden Adlandır"
}
}

View File

@@ -0,0 +1,332 @@
{
"extension_name": {
"message": "Goose Highlighter"
},
"extension_description": {
"message": "在任何网站上高亮显示单词和短语。创建自定义列表,使用颜色,导入/导出等功能。"
},
"select_list": {
"message": "选择列表:"
},
"new_list": {
"message": "新建列表"
},
"delete_list": {
"message": "删除列表"
},
"list_name": {
"message": "列表名称:"
},
"background": {
"message": "背景:"
},
"foreground": {
"message": "文字颜色:"
},
"enable_highlight": {
"message": "启用高亮"
},
"apply": {
"message": "应用"
},
"paste_hint": {
"message": "每行一个词或短语"
},
"apply_paste": {
"message": "添加单词"
},
"select_all": {
"message": "选择"
},
"delete_selected": {
"message": "删除所选"
},
"disable_selected": {
"message": "禁用所选"
},
"enable_selected": {
"message": "启用所选"
},
"import_list": {
"message": "导入列表"
},
"export_list": {
"message": "导出列表"
},
"import_list_list_manager": {
"message": "导入列表"
},
"export_list_list_manager": {
"message": "导出列表"
},
"default_list_name": {
"message": "默认列表"
},
"new_list_name": {
"message": "新建列表"
},
"word_active_label": {
"message": "激活"
},
"invalid_json_error": {
"message": "无效的 JSON 文件"
},
"confirm_delete_list": {
"message": "您确定要删除此列表吗?"
},
"confirm_delete_words": {
"message": "您确定要删除选中的单词吗?"
},
"deselect_all": {
"message": "取消选择"
},
"highlight_lists": {
"message": "高亮列表"
},
"dark_mode": {
"message": "暗黑模式"
},
"list_settings": {
"message": "列表设置"
},
"word_list": {
"message": "单词列表"
},
"add_words": {
"message": "添加单词"
},
"global_highlight_toggle": {
"message": "启用"
},
"search_placeholder": {
"message": "搜索..."
},
"options": {
"message": "选项"
},
"match_case": {
"message": "区分大小写"
},
"match_whole": {
"message": "全词匹配"
},
"site_exceptions": {
"message": "网站例外"
},
"add_exception": {
"message": "添加到例外"
},
"remove_exception": {
"message": "移除当前"
},
"manage_exceptions": {
"message": "管理"
},
"exceptions_list": {
"message": "例外网站:"
},
"clear_all": {
"message": "清除全部"
},
"confirm_clear_exceptions": {
"message": "您确定要清除所有例外吗?"
},
"remove": {
"message": "移除"
},
"tab_lists": {
"message": "列表"
},
"tab_words": {
"message": "单词"
},
"tab_exceptions": {
"message": "例外"
},
"no_exceptions": {
"message": "无例外"
},
"toggle_highlighting_title": {
"message": "切换高亮"
},
"toggle_dark_mode_title": {
"message": "切换暗黑模式"
},
"select_title": {
"message": "选择"
},
"word_placeholder": {
"message": "单词或短语"
},
"background_color_title": {
"message": "背景颜色"
},
"text_color_title": {
"message": "文字颜色"
},
"tab_page_highlights": {
"message": "页面上"
},
"highlights_on_page": {
"message": "此页面上的高亮"
},
"total_highlights": {
"message": "总计"
},
"refresh": {
"message": "刷新"
},
"no_highlights_on_page": {
"message": "此页面上未找到高亮"
},
"previous": {
"message": "上一个"
},
"next": {
"message": "下一个"
},
"duplicate": {
"message": "复制"
},
"merge": {
"message": "合并"
},
"move": {
"message": "移动"
},
"copy": {
"message": "复制"
},
"move_to_list": {
"message": "移动到列表"
},
"copy_to_list": {
"message": "复制到列表"
},
"move_selected": {
"message": "移动所选"
},
"copy_selected": {
"message": "复制所选"
},
"no_other_lists": {
"message": "没有其他列表"
},
"word_actions": {
"message": "移动或复制到另一个列表"
},
"merge_lists_min_two": {
"message": "选择至少两个列表进行合并。"
},
"merge_lists_confirm": {
"message": "将 {count} 个列表合并到 \"{target}\"?源列表将被删除。"
},
"delete_current_list": {
"message": "删除当前列表?"
},
"delete_lists_confirm": {
"message": "删除 {count} 个选定的列表?"
},
"invalid_import_format": {
"message": "无效的文件格式。请选择有效的 Goose Highlighter 导出文件。"
},
"import_failed": {
"message": "导入文件失败。请确保它是有效的 JSON 文件。"
},
"import_confirm": {
"message": "导入 {count} 个列表,共 {words} 个单词?"
},
"import_success": {
"message": "成功导入 {count} 个列表,共 {words} 个单词。"
},
"preview_label": {
"message": "预览"
},
"preview_text": {
"message": "您的高亮文本将如此显示。"
},
"preview_text_before": {
"message": "您的"
},
"preview_text_highlight": {
"message": "高亮文本"
},
"preview_text_after": {
"message": "将如此显示。"
},
"enable_highlighting_title": {
"message": "启用高亮"
},
"enable_highlighting_subtitle": {
"message": "在页面上显示高亮"
},
"manage_lists": {
"message": "管理列表"
},
"background_label": {
"message": "背景"
},
"foreground_label": {
"message": "前景"
},
"multi_select_hint": {
"message": "点击 - 选择 • Ctrl+点击 - 多选"
},
"dark_mode_title": {
"message": "暗黑模式"
},
"dark_mode_subtitle": {
"message": "切换亮色/暗色主题"
},
"list_manager_title": {
"message": "列表管理器"
},
"words_label": {
"message": "单词"
},
"active_label": {
"message": "激活"
},
"inactive_label": {
"message": "未激活"
},
"drag_words_hint": {
"message": "点击 - 选择 • Ctrl+点击 - 多选 • 拖到列表 - 移动"
},
"drag_lists_hint": {
"message": "拖动 - 排序 • Ctrl+点击 - 多选"
},
"showing_items": {
"message": "显示 {start}-{end} / 共 {total} 个单词"
},
"items_per_page": {
"message": "每页项目数:"
},
"page_info": {
"message": "第 {current} 页,共 {total} 页"
},
"toggle_active": {
"message": "切换激活状态"
},
"rename_list_title": {
"message": "重命名列表"
},
"new_list_title": {
"message": "新建列表"
},
"delete_list_title": {
"message": "删除列表"
},
"list_active_badge": {
"message": "激活"
},
"list_paused_badge": {
"message": "已暂停"
},
"words_stats": {
"message": "{total} 个单词 • {active} 个激活 • {inactive} 个未激活"
},
"rename_list": {
"message": "重命名列表"
}
}

View File

@@ -1,10 +0,0 @@
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete" && /^https?:/.test(tab.url)) {
chrome.scripting.executeScript({
target: { tabId },
files: ["main.js"]
}).catch(err => {
console.warn("Injection failed:", err);
});
}
});

40
eslint.config.mjs Normal file
View File

@@ -0,0 +1,40 @@
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [...compat.extends('eslint:recommended'), {
languageOptions: {
globals: {
...globals.browser,
...globals.webextensions,
chrome: 'readonly',
},
ecmaVersion: 12,
sourceType: 'module',
},
rules: {
semi: ['error', 'always'],
quotes: ['error', 'single'],
},
}, {
files: ['scripts/**/*.js'],
languageOptions: {
globals: {
...globals.node,
},
ecmaVersion: 12,
sourceType: 'module',
},
}];

BIN
img/logo-outlined.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

BIN
img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

133
main.js
View File

@@ -1,133 +0,0 @@
let currentLists = [];
let isGlobalHighlightEnabled = true;
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function clearHighlights() {
// Remove all <mark> elements added by the highlighter
const marks = document.querySelectorAll('mark[data-gh]');
for (const mark of marks) {
// Replace the <mark> with its text content
const parent = mark.parentNode;
if (parent) {
parent.replaceChild(document.createTextNode(mark.textContent), mark);
parent.normalize(); // Merge adjacent text nodes
}
}
}
function processNodes() {
observer.disconnect();
clearHighlights();
// If global highlighting is disabled, skip processing
if (!isGlobalHighlightEnabled) {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
return;
}
const textNodes = [];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: node => {
if (node.parentNode && node.parentNode.nodeName === 'MARK') return NodeFilter.FILTER_REJECT;
if (node.parentNode && ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME'].includes(node.parentNode.nodeName)) return NodeFilter.FILTER_REJECT;
if (!node.nodeValue.trim()) return NodeFilter.FILTER_SKIP;
return NodeFilter.FILTER_ACCEPT;
}
});
while (walker.nextNode()) textNodes.push(walker.currentNode);
const activeWords = [];
for (const list of currentLists) {
if (!list.active) continue;
for (const word of list.words) {
if (!word.active) continue;
activeWords.push({
text: word.wordStr,
background: word.background || list.background,
foreground: word.foreground || list.foreground
});
}
}
if (activeWords.length > 0) {
const wordMap = new Map();
for (const word of activeWords) wordMap.set(word.text.toLowerCase(), word);
const pattern = new RegExp(`(${Array.from(wordMap.keys()).map(escapeRegex).join('|')})`, 'gi');
for (const node of textNodes) {
if (!pattern.test(node.nodeValue)) continue;
const span = document.createElement('span');
span.innerHTML = node.nodeValue.replace(pattern, match => {
const word = wordMap.get(match.toLowerCase()) || { background: '#ffff00', foreground: '#000000' };
return `<mark data-gh style="background:${word.background};color:${word.foreground};padding:0 2px;">${match}</mark>`;
});
node.parentNode.replaceChild(span, node);
}
}
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
const debouncedProcessNodes = debounce(processNodes, 300);
function setListsAndUpdate(lists) {
currentLists = lists;
debouncedProcessNodes();
}
// Debounce helper function
function debounce(func, wait) {
let timeout;
return function () {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// Initial highlight on load
chrome.storage.local.get(["lists", "globalHighlightEnabled"], ({ lists, globalHighlightEnabled }) => {
if (Array.isArray(lists)) setListsAndUpdate(lists);
if (globalHighlightEnabled !== undefined) {
isGlobalHighlightEnabled = globalHighlightEnabled;
}
processNodes(); // Initial processing
});
// Listen for updates from the popup and re-apply highlights
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "WORD_LIST_UPDATED") {
chrome.storage.local.get("lists", ({ lists }) => {
if (Array.isArray(lists)) setListsAndUpdate(lists);
});
} else if (message.type === "GLOBAL_TOGGLE_UPDATED") {
isGlobalHighlightEnabled = message.enabled;
processNodes();
}
});
// Set up observer and scroll handler
const observer = new MutationObserver(debouncedProcessNodes);
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
window.addEventListener('scroll', debouncedProcessNodes);

View File

@@ -1,12 +1,13 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Goose Highlighter", "name": "__MSG_extension_name__",
"version": "1.4.0", "description": "__MSG_extension_description__",
"description": "Highlight text on web pages", "version": "1.12.1",
"default_locale": "en", "default_locale": "en",
"permissions": [ "permissions": [
"scripting", "scripting",
"storage" "storage",
"tabs"
], ],
"host_permissions": [ "host_permissions": [
"<all_urls>" "<all_urls>"
@@ -16,7 +17,8 @@
"default_icon": "icons/icon128.png" "default_icon": "icons/icon128.png"
}, },
"background": { "background": {
"service_worker": "background.js" "service_worker": "dist/background.js",
"type": "module"
}, },
"icons": { "icons": {
"48": "icons/icon48.png", "48": "icons/icon48.png",

2720
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,21 @@
{ {
"devDependencies": { "devDependencies": {
"@eslint/css": "^0.9.0",
"@eslint/js": "^9.30.0",
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^7.1.0", "@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"semantic-release": "^24.2.5" "@types/chrome": "^0.0.270",
"eslint": "^9.30.0",
"globals": "^16.2.0",
"semantic-release": "^24.2.9",
"typescript": "^5.6.0"
}, },
"scripts": { "scripts": {
"prepare": "node scripts/update-manifest-version.js" "build": "node scripts/build-content-standalone.js && tsc",
"watch": "tsc --watch",
"clean": "rimraf dist",
"rebuild": "npm run clean && npm run build",
"prepare": "npm run build && node scripts/update-manifest-version.js"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,90 +7,290 @@
<title data-i18n="extension_name">Goose Highlighter</title> <title data-i18n="extension_name">Goose Highlighter</title>
<link rel="stylesheet" href="popup.css" /> <link rel="stylesheet" href="popup.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
</head> </head>
<body class="dark"> <body>
<div class="container"> <div class="loading-overlay">
<div class="loading-spinner"></div>
</div>
<div class="popup-container">
<div class="header-bar"> <!-- Header -->
<span class="title"> <div class="header app-header">
<i class="fa-solid fa-highlighter"></i> Goose Highlighter <div class="header-content app-header-content">
</span> <div class="header-logo app-logo">
<div class="icon-toggles"> <img src="../img/logo-outlined.png" alt="Goose" style="width: 18px; height: 18px;">
<label class="icon-toggle" title="Toggle highlighting"> </div>
<input type="checkbox" class="hidden-toggle" id="globalHighlightToggle" /> <span class="header-title app-title" data-i18n="extension_name">Goose Highlighter</span>
<i class="toggle-icon global-icon fa-solid"></i> </div>
</label> <div class="header-actions app-header-actions">
<label class="icon-toggle" title="Toggle dark mode"> <button class="icon-button header-settings-btn" id="settingsBtn" data-i18n-title="options" title="Options" aria-label="Settings">
<input type="checkbox" class="hidden-toggle" id="themeToggle" /> <i class="fa-solid fa-gear"></i>
<i class="toggle-icon theme-icon fa-solid"></i> </button>
<label class="header-power-toggle" title="Toggle highlighting" aria-label="Toggle highlighting">
<input type="checkbox" id="globalHighlightToggle" class="header-power-input" />
<span class="power-icon-wrapper">
<i class="fa-solid fa-power-off"></i>
</span>
</label> </label>
</div> </div>
</div> </div>
<div class="section"> <!-- Tabs -->
<div class="section-header"> <div class="tabs">
<h2><i class="fa-solid fa-list"></i> <span data-i18n="highlight_lists">Highlight Lists</span></h2> <button class="tab-button active" data-tab="lists">
</div> <i class="fa-solid fa-list"></i>
<label for="listSelect" data-i18n="select_list">Select List:</label> <span data-i18n="tab_lists">Lists</span>
<select id="listSelect"></select> </button>
<div class="button-row"> <button class="tab-button" data-tab="words">
<button id="newListBtn"><i class="fa-solid fa-plus"></i> <span data-i18n="new_list">New</span></button> <i class="fa-solid fa-tags"></i>
<button id="deleteListBtn" class="danger"><i class="fa-solid fa-trash"></i> <span <span data-i18n="tab_words">Words</span>
data-i18n="delete_list">Delete</span></button> </button>
<button class="tab-button" data-tab="page-highlights">
<i class="fa-solid fa-location-dot"></i>
<span data-i18n="tab_page_highlights">On Page</span>
</button>
<button class="tab-button" data-tab="exceptions">
<i class="fa-solid fa-ban"></i>
<span data-i18n="tab_exceptions">Exceptions</span>
</button>
</div>
<!-- Lists Tab -->
<div class="tab-content active" data-tab-content="lists">
<div class="tab-inner">
<div class="list-selector-section">
<!-- List Selector with Actions -->
<div class="list-selector-row">
<div class="list-dropdown-wrapper">
<button class="list-dropdown-button" id="listDropdownBtn">
<div class="list-dropdown-content">
<div class="list-color-indicator" id="currentListColor"></div>
<span class="list-dropdown-text" id="currentListName">Default List</span>
</div>
<i class="fa-solid fa-chevron-down"></i>
</button>
<div class="list-dropdown-menu" id="listDropdownMenu">
<!-- Populated dynamically -->
</div>
</div>
<button class="icon-button" id="renameListBtn" data-i18n-title="rename_list_title" title="Rename list">
<i class="fa-solid fa-pen"></i>
</button>
<button class="icon-button" id="newListBtn" data-i18n-title="new_list_title" title="New list">
<i class="fa-solid fa-plus"></i>
</button>
<button class="icon-button danger" id="deleteListBtn" data-i18n-title="delete_list_title" title="Delete list">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<!-- Export / Import (Lists tab) -->
<div class="list-export-import-row">
<button class="list-export-import-btn" id="exportListBtn">
<i class="fa-solid fa-download"></i>
<span data-i18n="export_list">Export</span>
</button>
<button class="list-export-import-btn" id="importListBtn">
<i class="fa-solid fa-upload"></i>
<span data-i18n="import_list">Import</span>
</button>
<input type="file" id="importListInput" accept="application/json" hidden />
</div>
<!-- Color Pickers -->
<div class="color-pickers-row">
<div class="color-picker-group">
<label class="color-picker-label" data-i18n="background_label">Background</label>
<div class="color-picker-input-group">
<input type="color" id="listBg" class="color-picker-swatch" />
<input type="text" id="listBgText" class="color-picker-text" maxlength="7" />
</div>
</div>
<div class="color-picker-group">
<label class="color-picker-label" data-i18n="foreground_label">Foreground</label>
<div class="color-picker-input-group">
<input type="color" id="listFg" class="color-picker-swatch" />
<input type="text" id="listFgText" class="color-picker-text" maxlength="7" />
</div>
</div>
</div>
<!-- Preview -->
<div class="preview-section">
<label class="preview-label" data-i18n="preview_label">Preview</label>
<div class="preview-box">
<p class="preview-text">
<span data-i18n="preview_text_before">This is how your</span>
<span class="preview-highlight" id="previewHighlight" data-i18n="preview_text_highlight">highlighted text</span>
<span data-i18n="preview_text_after">will appear.</span>
</p>
</div>
</div>
<!-- Enable Toggle -->
<div class="enable-toggle-section">
<div class="enable-toggle-content">
<div class="enable-toggle-text">
<p class="enable-toggle-title" data-i18n="enable_highlighting_title">Enable Highlighting</p>
<p class="enable-toggle-subtitle" data-i18n="enable_highlighting_subtitle">Show highlights on pages</p>
</div>
<label class="switch-wrapper">
<input type="checkbox" id="listActive" class="switch-input" />
<span class="switch-slider"></span>
</label>
</div>
</div>
</div>
<!-- Apply Button -->
<button class="apply-button btn-primary-subdued" id="applyListSettingsBtn">
<i class="fa-solid fa-check"></i>
<span data-i18n="apply">Apply Changes</span>
</button>
</div> </div>
</div> </div>
<div class="section"> <!-- Words Tab -->
<h2><i class="fa-solid fa-gear"></i> <span data-i18n="list_settings">List Settings</span></h2> <div class="tab-content" data-tab-content="words">
<label><span data-i18n="list_name">List Name:</span> <input type="text" id="listName" /></label> <div class="tab-inner">
<div class="color-row"> <!-- Add Words Section -->
<div class="color-label"> <div class="add-words-section">
<span data-i18n="background">Background:</span> <label class="section-label">
<input type="color" id="listBg" /> <i class="fa-solid fa-pen"></i>
<span data-i18n="add_words">Add Words</span>
</label>
<textarea id="bulkPaste" class="add-words-textarea" data-i18n="paste_hint" placeholder="Paste words or phrases here. Each new word/phrase should start from next line."></textarea>
<button class="add-words-button" id="addWordsBtn">
<span data-i18n="apply_paste">Add Words</span>
</button>
</div> </div>
<div class="color-label">
<span data-i18n="foreground">Foreground:</span> <!-- Word List Section -->
<input type="color" id="listFg" /> <div class="word-list-section">
<div class="word-list-header">
<label class="section-label">
<i class="fa-solid fa-tags"></i>
<span data-i18n="word_list">Word List</span>
<span class="word-count-badge" id="wordCount">0</span>
</label>
</div>
<!-- Search -->
<div class="word-search-wrapper">
<input type="text" id="wordSearch" class="word-search-input" data-i18n="search_placeholder" placeholder="Search..." />
</div>
<!-- Word List -->
<div id="wordList" class="word-list-container"></div>
<div id="paginationControls" class="pagination-container"></div>
<!-- Word item 3-dot menu dropdown (positioned by JS) -->
<div id="wordItemMenuDropdown" class="word-item-menu-dropdown" role="menu" aria-hidden="true"></div>
<!-- Help text -->
<p class="word-list-hint" data-i18n="multi_select_hint">Click to select • Ctrl/Cmd+Click for multi-select</p>
</div> </div>
</div> </div>
<label>
<span data-i18n="enable_highlight">Enable Highlighting</span>
<input type="checkbox" class="switch" id="listActive" />
</label>
</div> </div>
<div class="section"> <!-- Page Highlights Tab -->
<h2><i class="fa-solid fa-pen"></i> <span data-i18n="add_words">Add Words</span></h2> <div class="tab-content" data-tab-content="page-highlights">
<textarea id="bulkPaste" data-i18n="paste_hint" placeholder="Paste words here..."></textarea> <div class="tab-inner">
<button id="addWordsBtn"><span data-i18n="apply_paste">Add Words</span></button> <div class="page-highlights-section">
</div> <div class="page-highlights-info-card">
<span data-i18n="total_highlights">Total:</span>
<div class="section"> <strong id="totalHighlightsCount">0</strong>
<h2><i class="fa-solid fa-tags"></i> <span data-i18n="word_list">Word List</span>(<span id="wordCount">0</span>) </div>
</h2>
<div class="button-row wrap"> <button class="refresh-button" id="refreshHighlightsBtn">
<button id="selectAllBtn"><span data-i18n="select_all">Select All</span></button> <i class="fa-solid fa-rotate"></i>
<button id="deselectAllBtn"><span data-i18n="deselect_all">Clear</span></button> <span data-i18n="refresh">Refresh</span>
<button id="enableSelectedBtn"><span data-i18n="enable_selected">Enable</span></button> </button>
<button id="disableSelectedBtn"><span data-i18n="disable_selected">Disable</span></button>
<button id="deleteSelectedBtn" class="danger"><span data-i18n="delete_selected">Delete</span></button> <div id="pageHighlightsList" class="page-highlights-list"></div>
</div>
</div> </div>
<div id="wordList"></div>
</div> </div>
<div class="section"> <!-- Exceptions Tab -->
<div class="button-row"> <div class="tab-content" data-tab-content="exceptions">
<button id="importBtn"><i class="fa-solid fa-upload"></i> <span data-i18n="import_list">Import</span></button> <div class="tab-inner">
<input type="file" id="importInput" accept="application/json" hidden /> <div class="exceptions-section">
<button id="exportBtn"><i class="fa-solid fa-download"></i> <span data-i18n="export_list">Export</span></button> <label class="section-label">
<i class="fa-solid fa-ban"></i>
<span data-i18n="site_exceptions">Site Exceptions</span>
</label>
<button class="exception-toggle-btn" id="toggleExceptionBtn">
<i class="fa-solid fa-plus"></i>
<span id="exceptionBtnText" data-i18n="add_exception">Add to Exceptions</span>
</button>
<div class="exceptions-list-wrapper">
<label class="section-label">
<span data-i18n="exceptions_list">Exception Sites:</span>
</label>
<div id="exceptionsList" class="exceptions-list"></div>
</div>
<button class="clear-exceptions-btn" id="clearExceptionsBtn">
<i class="fa-solid fa-trash"></i>
<span data-i18n="clear_all">Clear All</span>
</button>
</div>
</div> </div>
</div> </div>
<!-- Settings Overlay -->
<div class="settings-overlay" id="settingsOverlay">
<div class="settings-overlay-content">
<div class="settings-overlay-header">
<h3 class="settings-overlay-title" data-i18n="options">Options</h3>
<button class="icon-button settings-close-btn" id="settingsCloseBtn" aria-label="Close">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<div class="settings-overlay-body">
<div class="options-checkboxes">
<label class="option-checkbox-label">
<input type="checkbox" id="matchCase" class="option-checkbox" />
<span data-i18n="match_case">Match Case</span>
</label>
<label class="option-checkbox-label">
<input type="checkbox" id="matchWhole" class="option-checkbox" />
<span data-i18n="match_whole">Match Whole Word</span>
</label>
</div>
<div class="theme-toggle-section">
<div class="theme-toggle-content">
<div class="theme-toggle-text">
<p class="theme-toggle-title" data-i18n="dark_mode_title">Dark Mode</p>
<p class="theme-toggle-subtitle" data-i18n="dark_mode_subtitle">Toggle dark/light theme</p>
</div>
<label class="switch-wrapper">
<input type="checkbox" id="themeToggle" class="switch-input" />
<span class="switch-slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="footer">
<span class="footer-version">v<span id="version-number">...</span></span>
<a href="https://github.com/obsqrbtz/goose-highlighter" target="_blank" class="footer-link">
<i class="fa-brands fa-github"></i>
<span>GitHub</span>
</a>
</div>
</div> </div>
<script src="../storage.js"></script> <script type="module" src="../dist/popup/popup.js"></script>
<script src="popup.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,419 +0,0 @@
const listSelect = document.getElementById("listSelect");
const listName = document.getElementById("listName");
const listBg = document.getElementById("listBg");
const listFg = document.getElementById("listFg");
const listActive = document.getElementById("listActive");
const bulkPaste = document.getElementById("bulkPaste");
const wordList = document.getElementById("wordList");
const importInput = document.getElementById("importInput");
let lists = [];
let currentListIndex = 0;
let saveTimeout;
let selectedCheckboxes = new Set();
let globalHighlightEnabled = true;
function escapeHtml(str) {
return str.replace(/[&<>"']/g, function (m) {
return ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
})[m];
});
}
async function debouncedSave() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(async () => {
await chrome.storage.local.set({ lists });
}, 500);
}
async function save() {
await chrome.storage.local.set({
lists: lists,
globalHighlightEnabled: globalHighlightEnabled
});
renderLists();
renderWords();
chrome.tabs.query({}, function (tabs) {
for (let tab of tabs) {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, { type: "WORD_LIST_UPDATED" });
chrome.tabs.sendMessage(tab.id, {
type: "GLOBAL_TOGGLE_UPDATED",
enabled: globalHighlightEnabled
});
}
}
});
}
async function updateGlobalToggleState() {
await chrome.storage.local.set({ globalHighlightEnabled: globalHighlightEnabled });
chrome.tabs.query({}, function (tabs) {
for (let tab of tabs) {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, {
type: "GLOBAL_TOGGLE_UPDATED",
enabled: globalHighlightEnabled
});
}
}
});
}
async function load() {
const res = await chrome.storage.local.get({
lists: [],
globalHighlightEnabled: true
});
lists = res.lists;
globalHighlightEnabled = res.globalHighlightEnabled !== false; // Default to true if undefined
if (!lists.length) {
lists.push({
id: Date.now(),
name: chrome.i18n.getMessage("default_list_name"),
background: "#ffff00",
foreground: "#000000",
active: true,
words: []
});
}
renderLists();
renderWords();
document.getElementById("globalHighlightToggle").checked = globalHighlightEnabled;
}
function renderLists() {
listSelect.innerHTML = lists.map((list, index) =>
`<option value="${index}">${escapeHtml(list.name)}</option>`
).join("");
listSelect.value = currentListIndex;
updateListForm();
}
function updateListForm() {
const list = lists[currentListIndex];
listName.value = list.name;
listBg.value = list.background;
listFg.value = list.foreground;
listActive.checked = list.active;
}
function renderWords() {
const list = lists[currentListIndex];
const fragment = document.createDocumentFragment();
const itemHeight = 32;
const containerHeight = wordList.clientHeight;
const scrollTop = wordList.scrollTop;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 2,
list.words.length
);
wordList.innerHTML = '';
const spacer = document.createElement('div');
spacer.style.position = 'relative';
spacer.style.height = `${list.words.length * itemHeight}px`;
spacer.style.width = '100%';
for (let i = startIndex; i < endIndex; i++) {
const w = list.words[i];
const container = document.createElement("div");
container.style.height = `${itemHeight}px`;
container.style.position = 'absolute';
container.style.top = `${i * itemHeight}px`;
container.style.width = 'calc(100% - 8px)';
container.style.left = '4px';
container.style.right = '4px';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.gap = '6px';
container.style.padding = '0 4px';
container.style.boxSizing = 'border-box';
container.style.background = 'var(--highlight-tag)';
container.style.border = '1px solid var(--highlight-tag-border)';
const cbSelect = document.createElement("input");
cbSelect.type = "checkbox";
cbSelect.className = "word-checkbox";
cbSelect.dataset.index = i;
if (selectedCheckboxes.has(i)) {
cbSelect.checked = true;
}
const inputWord = document.createElement("input");
inputWord.type = "text";
inputWord.value = w.wordStr;
inputWord.dataset.wordEdit = i;
inputWord.style.flexGrow = '1';
inputWord.style.minWidth = '0';
inputWord.style.padding = '4px 8px';
inputWord.style.borderRadius = '4px';
inputWord.style.border = '1px solid var(--input-border)';
inputWord.style.backgroundColor = 'var(--input-bg)';
inputWord.style.color = 'var(--text-color)';
const inputBg = document.createElement("input");
inputBg.type = "color";
inputBg.value = w.background || list.background;
inputBg.dataset.bgEdit = i;
inputBg.style.width = '24px';
inputBg.style.height = '24px';
inputBg.style.flexShrink = '0';
const inputFg = document.createElement("input");
inputFg.type = "color";
inputFg.value = w.foreground || list.foreground;
inputFg.dataset.fgEdit = i;
inputFg.style.width = '24px';
inputFg.style.height = '24px';
inputFg.style.flexShrink = '0';
const activeContainer = document.createElement("label");
activeContainer.className = "word-active";
activeContainer.style.display = 'flex';
activeContainer.style.alignItems = 'center';
activeContainer.style.gap = '4px';
activeContainer.style.flexShrink = '0';
const cbActive = document.createElement("input");
cbActive.type = "checkbox";
cbActive.checked = w.active !== false;
cbActive.dataset.activeEdit = i;
cbActive.className = "switch";
activeContainer.appendChild(cbActive);
container.appendChild(cbSelect);
container.appendChild(inputWord);
container.appendChild(inputBg);
container.appendChild(inputFg);
container.appendChild(activeContainer);
spacer.appendChild(container);
}
wordList.appendChild(spacer);
const wordCount = document.getElementById('wordCount');
if (wordCount) {
wordCount.textContent = list.words.length;
}
}
document.addEventListener('DOMContentLoaded', () => {
localizePage();
document.getElementById("selectAllBtn").onclick = () => {
const list = lists[currentListIndex];
list.words.forEach((_, index) => {
selectedCheckboxes.add(index);
});
renderWords();
};
// Add event listener for the global toggle
document.getElementById("globalHighlightToggle").addEventListener('change', function () {
globalHighlightEnabled = this.checked;
updateGlobalToggleState();
});
wordList.addEventListener("change", e => {
if (e.target.type === "checkbox") {
if (e.target.dataset.index != null) {
if (e.target.checked) {
selectedCheckboxes.add(+e.target.dataset.index);
} else {
selectedCheckboxes.delete(+e.target.dataset.index);
}
renderWords();
} else if (e.target.dataset.activeEdit != null) {
lists[currentListIndex].words[e.target.dataset.activeEdit].active = e.target.checked;
save();
}
}
});
let scrollTimeout;
wordList.addEventListener('scroll', () => {
if (scrollTimeout) {
return;
}
scrollTimeout = setTimeout(() => {
requestAnimationFrame(renderWords);
scrollTimeout = null;
}, 16); // ~60fps
});
listSelect.onchange = () => {
selectedCheckboxes.clear();
currentListIndex = +listSelect.value;
renderWords();
updateListForm();
};
document.getElementById("newListBtn").onclick = () => {
lists.push({
id: Date.now(),
name: chrome.i18n.getMessage("new_list_name"),
background: "#ffff00",
foreground: "#000000",
active: true,
words: []
});
currentListIndex = lists.length - 1;
save();
};
document.getElementById("deleteListBtn").onclick = () => {
if (confirm(chrome.i18n.getMessage("confirm_delete_list"))) {
lists.splice(currentListIndex, 1);
currentListIndex = Math.max(0, currentListIndex - 1);
save();
}
};
listName.oninput = () => { lists[currentListIndex].name = listName.value; save(); };
listBg.oninput = () => { lists[currentListIndex].background = listBg.value; save(); };
listFg.oninput = () => { lists[currentListIndex].foreground = listFg.value; save(); };
listActive.onchange = () => { lists[currentListIndex].active = listActive.checked; save(); };
document.getElementById("addWordsBtn").onclick = () => {
const words = bulkPaste.value.split(/\n+/).map(w => w.trim()).filter(Boolean);
const list = lists[currentListIndex];
for (const w of words) list.words.push({ wordStr: w, background: "", foreground: "", active: true });
bulkPaste.value = "";
save();
};
document.getElementById("deleteSelectedBtn").onclick = () => {
if (confirm(chrome.i18n.getMessage("confirm_delete_words"))) {
const list = lists[currentListIndex];
const toDelete = Array.from(selectedCheckboxes);
lists[currentListIndex].words = list.words.filter((_, i) => !toDelete.includes(i));
selectedCheckboxes.clear();
save();
renderWords();
}
};
document.getElementById("disableSelectedBtn").onclick = () => {
const list = lists[currentListIndex];
selectedCheckboxes.forEach(index => {
list.words[index].active = false;
});
save();
renderWords();
};
document.getElementById("enableSelectedBtn").onclick = () => {
const list = lists[currentListIndex];
selectedCheckboxes.forEach(index => {
list.words[index].active = true;
});
save();
renderWords();
};
wordList.addEventListener("input", e => {
const index = e.target.dataset.wordEdit ?? e.target.dataset.bgEdit ?? e.target.dataset.fgEdit;
if (index == null) return;
const word = lists[currentListIndex].words[index];
if (e.target.dataset.wordEdit != null) word.wordStr = e.target.value;
if (e.target.dataset.bgEdit != null) word.background = e.target.value;
if (e.target.dataset.fgEdit != null) word.foreground = e.target.value;
save();
});
const exportBtn = document.getElementById("exportBtn");
exportBtn.onclick = () => {
const blob = new Blob([JSON.stringify(lists, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "highlight-lists.json";
a.click();
URL.revokeObjectURL(url);
};
const importBtn = document.getElementById("importBtn");
importBtn.onclick = () => importInput.click();
importInput.onchange = e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
try {
const data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
lists = data;
currentListIndex = 0;
save();
}
} catch (err) {
alert(chrome.i18n.getMessage("invalid_json_error"));
}
};
reader.readAsText(file);
};
function localizePage() {
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const message = element.dataset.i18n;
const localizedText = chrome.i18n.getMessage(message);
if (localizedText) {
if (element.tagName === 'INPUT' && element.hasAttribute('placeholder')) {
element.placeholder = localizedText;
} else {
element.textContent = localizedText;
}
}
});
}
const toggle = document.getElementById('themeToggle');
const body = document.body;
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
body.classList.remove('dark');
body.classList.add('light');
toggle.checked = false;
} else {
body.classList.add('dark');
body.classList.remove('light');
toggle.checked = true;
}
toggle.addEventListener('change', () => {
if (toggle.checked) {
body.classList.add('dark');
body.classList.remove('light');
localStorage.setItem('theme', 'dark');
} else {
body.classList.remove('dark');
body.classList.add('light');
localStorage.setItem('theme', 'light');
}
});
document.getElementById("deselectAllBtn").onclick = () => {
selectedCheckboxes.clear();
renderWords();
};
load();
});

View File

@@ -0,0 +1,39 @@
const fs = require('fs');
const typesContent = fs.readFileSync('src/types.ts', 'utf8');
const domUtilsContent = fs.readFileSync('src/utils/DOMUtils.ts', 'utf8');
const storageServiceContent = fs.readFileSync('src/services/StorageService.ts', 'utf8');
const messageServiceContent = fs.readFileSync('src/services/MessageService.ts', 'utf8');
const highlightEngineContent = fs.readFileSync('src/content/HighlightEngine.ts', 'utf8');
const contentScriptContent = fs.readFileSync('src/content/ContentScript.ts', 'utf8');
const mainContent = fs.readFileSync('src/main.ts', 'utf8');
function extractDefinitions(content, filename) {
// Remove import statements
let cleaned = content.replace(/^import\s+.*?;?\s*$/gm, '');
// Remove export keywords but keep the definitions
cleaned = cleaned.replace(/^export\s+/gm, '');
// Add a comment header
cleaned = `// === ${filename} ===\n${cleaned}\n`;
return cleaned;
}
// Extract and combine all definitions
const combinedContent = `// Auto-generated standalone content script
// Do not edit this file directly - edit the source files and rebuild
${extractDefinitions(typesContent, 'types.ts')}
${extractDefinitions(domUtilsContent, 'utils/DOMUtils.ts')}
${extractDefinitions(storageServiceContent, 'services/StorageService.ts')}
${extractDefinitions(messageServiceContent, 'services/MessageService.ts')}
${extractDefinitions(highlightEngineContent, 'content/HighlightEngine.ts')}
${extractDefinitions(contentScriptContent, 'content/ContentScript.ts')}
${extractDefinitions(mainContent, 'main.ts')}
`;
fs.writeFileSync('src/content-standalone.ts', combinedContent);
console.log('Generated standalone content script');

View File

@@ -1,13 +1,13 @@
const fs = require("fs"); const fs = require('fs');
const version = process.argv[2]; const version = process.argv[2];
if (!version) { if (!version) {
console.error("❌ No version passed"); console.log('No version passed, skipping manifest update');
process.exit(1); process.exit(0);
} }
const manifest = JSON.parse(fs.readFileSync("manifest.json", "utf-8")); const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf-8'));
manifest.version = version; manifest.version = version;
fs.writeFileSync("manifest.json", JSON.stringify(manifest, null, 2)); fs.writeFileSync('manifest.json', JSON.stringify(manifest, null, 2));
console.log(`Updated manifest.json to version ${version}`); console.log(`Updated manifest.json to version ${version}`);

18
shared/base.css Normal file
View File

@@ -0,0 +1,18 @@
/* Shared base reset and typography used by list-manager and popup */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-normal);
letter-spacing: var(--tracking-tight);
background: var(--bg-color);
color: var(--text-color);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

73
shared/colors.css Normal file
View File

@@ -0,0 +1,73 @@
/* Shared Color Scheme */
/* Typography + Light theme */
:root {
--font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'DM Sans', ui-monospace, monospace;
--text-xs: 11px;
--text-sm: 12px;
--text-base: 13px;
--text-md: 14px;
--text-lg: 16px;
--leading-tight: 1.25;
--leading-normal: 1.4;
--leading-relaxed: 1.5;
--tracking-tight: -0.01em;
--tracking-wide: 0.05em;
--bg-color: #f5f5f5;
--text-color: #2d2d2d;
--input-bg: #ffffff;
--input-border: #e0e0e0;
--button-bg: #ffffff;
--button-hover: #eeeeee;
--button-text: #2d2d2d;
--accent: #ff8c00;
--accent-text: #ffffff;
--accent-hover: #ff9f1a;
--highlight-tag: #fff3e6;
--highlight-tag-border: #ffe4cc;
--danger: #ef4444;
--success: #22c55e;
--shadow: 0 10px 22px rgba(0, 0, 0, 0.08);
--shadow-sm: 0 4px 10px rgba(0, 0, 0, 0.06);
--border-radius: 12px;
--section-bg: #ffffff;
--panel-bg: #ffffff;
--switch-bg: #e0e0e0;
--checkbox-accent: #ff8c00;
--checkbox-border: #d0d0d0;
--focus-ring: 0 0 0 3px rgba(255, 140, 0, 0.25);
--footer-text: #6b6b6b;
--tab-active-bg: #e8e8e8;
--tab-inactive-color: #6b6b6b;
}
/* Dark theme */
html.dark,
body.dark {
--bg-color: #0d0d0d;
--text-color: #f0f0f0;
--input-bg: #171717;
--input-border: #2a2a2a;
--button-bg: #171717;
--button-hover: #222222;
--button-text: #f0f0f0;
--accent: #ff8c00;
--accent-text: #ffffff;
--accent-hover: #ff9f1a;
--highlight-tag: #171717;
--highlight-tag-border: #2a2a2a;
--danger: #f87171;
--success: #22c55e;
--shadow: 0 10px 22px rgba(0, 0, 0, 0.6);
--shadow-sm: 0 4px 10px rgba(0, 0, 0, 0.5);
--section-bg: #121212;
--panel-bg: #121212;
--switch-bg: #2a2a2a;
--checkbox-accent: #ff8c00;
--checkbox-border: #333333;
--focus-ring: 0 0 0 3px rgba(255, 140, 0, 0.3);
--footer-text: #8a8a8a;
--tab-active-bg: #2b2b2b;
--tab-inactive-color: #adadad;
}

23
shared/fonts.css Normal file
View File

@@ -0,0 +1,23 @@
@font-face {
font-family: 'DM Sans';
font-style: normal;
font-display: swap;
font-weight: 400;
src: url('fonts/dm-sans-latin-400-normal.woff2') format('woff2');
}
@font-face {
font-family: 'DM Sans';
font-style: normal;
font-display: swap;
font-weight: 500;
src: url('fonts/dm-sans-latin-500-normal.woff2') format('woff2');
}
@font-face {
font-family: 'DM Sans';
font-style: normal;
font-display: swap;
font-weight: 600;
src: url('fonts/dm-sans-latin-600-normal.woff2') format('woff2');
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

117
shared/layout.css Normal file
View File

@@ -0,0 +1,117 @@
/* Shared app chrome header/topbar used by list-manager and popup */
/* App header bar (use with .topbar, .header, or standalone .app-header) */
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: var(--bg-color);
border-bottom: 1px solid var(--input-border);
flex-shrink: 0;
}
.app-header-content {
display: flex;
align-items: center;
gap: 10px;
}
.app-logo {
height: 28px;
width: 28px;
border-radius: 8px;
background: var(--text-color);
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
html.dark .app-logo,
body.dark .app-logo {
background: var(--highlight-tag);
}
.app-logo img {
height: 100%;
width: 100%;
object-fit: contain;
}
.app-title {
font-weight: 600;
font-size: 13px;
color: var(--text-color);
}
.app-subtitle {
font-size: 11px;
opacity: 0.6;
}
.app-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.app-header-actions button {
min-height: 28px;
padding: 4px 10px;
font-size: 12px;
border-radius: 6px;
display: inline-flex;
align-items: center;
gap: 6px;
}
/* Subdued primary button shared orange outline style */
.btn-primary-subdued {
background: rgba(255, 140, 0, 0.1);
color: var(--accent);
border: 1px solid rgba(255, 140, 0, 0.35);
}
.btn-primary-subdued:hover {
background: rgba(255, 140, 0, 0.18);
border-color: rgba(255, 140, 0, 0.5);
}
html.dark .btn-primary-subdued,
body.dark .btn-primary-subdued {
background: rgba(255, 140, 0, 0.12);
border-color: rgba(255, 140, 0, 0.4);
}
html.dark .btn-primary-subdued:hover,
body.dark .btn-primary-subdued:hover {
background: rgba(255, 140, 0, 0.2);
border-color: rgba(255, 140, 0, 0.55);
}
/* Ghost button in header/panels */
.app-header-actions button.ghost,
.panel-actions button.ghost {
background: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--text-color);
}
.app-header-actions button.ghost:hover,
.panel-actions button.ghost:hover {
background: var(--section-bg);
}
/* Danger button in header/panels */
.panel-actions button.danger {
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.3);
color: var(--danger);
}
.panel-actions button.danger:hover {
background: rgba(239, 68, 68, 0.15);
border-color: rgba(239, 68, 68, 0.45);
}

445
shared/ui-components.css Normal file
View File

@@ -0,0 +1,445 @@
/* Buttons */
button {
border: none;
border-radius: 10px;
padding: 8px 10px;
background: var(--button-bg);
color: var(--button-text);
cursor: pointer;
transition: all 0.2s ease;
font-size: var(--text-base, 13px);
font-family: inherit;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
white-space: nowrap;
line-height: var(--leading-tight, 1.25);
}
button:hover {
background: var(--button-hover);
}
button.primary {
background: var(--accent);
color: var(--accent-text);
}
button.primary:hover {
background: var(--accent-hover);
}
button.danger {
background: rgba(248, 113, 113, 0.15);
color: var(--danger);
border: 1px solid rgba(248, 113, 113, 0.4);
}
button.danger:hover {
background: rgba(248, 113, 113, 0.25);
}
button.ghost {
background: transparent;
border: 1px solid var(--input-border);
}
button.ghost:hover {
background: var(--button-hover);
}
button i {
display: flex;
align-items: center;
font-size: 0.95em;
}
button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Icon Buttons */
.icon-btn {
background: transparent;
border: none;
color: var(--text-color);
opacity: 0.6;
cursor: pointer;
padding: 4px 6px;
border-radius: 6px;
transition: all 0.2s ease;
font-size: 0.85rem;
display: flex;
align-items: center;
justify-content: center;
min-width: 28px;
min-height: 28px;
}
.icon-btn:hover {
opacity: 1;
background: rgba(255, 140, 0, 0.15);
color: var(--accent);
}
.icon-btn i {
pointer-events: none;
}
/* Shared Switch (label + input + span) used in popup and list manager */
.switch-wrapper {
position: relative;
display: inline-block;
width: 40px;
height: 22px;
cursor: pointer;
flex-shrink: 0;
}
.switch-input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.switch-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--input-border);
transition: 0.3s;
border-radius: 11px;
}
.switch-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
.switch-input:checked + .switch-slider {
background-color: var(--accent);
}
.switch-input:checked + .switch-slider:before {
transform: translateX(18px);
}
/* Switch Toggle (Checkbox Style) */
input[type="checkbox"].switch {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 36px;
height: 20px;
background: var(--switch-bg);
border: none;
border-radius: 20px;
position: relative;
outline: none;
cursor: pointer;
transition: background 0.2s;
}
input[type="checkbox"].switch::before {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
transition: left 0.2s;
}
input[type="checkbox"].switch:checked {
background: var(--accent);
}
input[type="checkbox"].switch:checked::before {
left: 18px;
}
input[type="checkbox"].switch:hover {
opacity: 0.9;
}
input[type="checkbox"].switch::after {
content: none !important;
}
label:has(input.switch) {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
}
/* Checkboxes */
input[type="checkbox"]:not(.switch) {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: var(--input-bg);
border: 1.5px solid var(--checkbox-border);
border-radius: 4px;
width: 16px;
height: 16px;
cursor: pointer;
position: relative;
flex-shrink: 0;
}
input[type="checkbox"]:not(.switch):hover {
border-color: var(--accent);
}
input[type="checkbox"]:not(.switch):checked {
background-color: var(--accent);
border-color: var(--accent);
}
input[type="checkbox"]:not(.switch):checked::before {
content: "✓";
position: absolute;
top: -3px;
left: 2px;
color: white;
font-size: 12px;
font-weight: bold;
}
/* Scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--section-bg);
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
background: var(--accent);
border-radius: 8px;
min-height: 24px;
border: 2px solid var(--section-bg);
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-hover);
}
::-webkit-scrollbar-corner {
background: var(--section-bg);
}
/* Firefox scrollbar */
* {
scrollbar-width: thin;
scrollbar-color: var(--accent) var(--section-bg);
}
/* Form Inputs */
input[type="text"],
textarea,
select {
width: 100%;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--input-border);
background-color: var(--input-bg);
color: var(--text-color);
font-size: var(--text-base, 13px);
box-sizing: border-box;
font-family: inherit;
line-height: var(--leading-normal, 1.4);
}
input[type="text"]:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--focus-ring);
}
textarea {
resize: vertical;
line-height: 1.4;
}
/* Color Inputs */
input[type="color"] {
background: none;
border: 1px solid var(--input-border);
border-radius: 8px;
cursor: pointer;
padding: 0;
appearance: none;
-webkit-appearance: none;
overflow: hidden;
}
input[type="color"]:hover {
border-color: var(--accent);
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
border-radius: 0;
}
input[type="color"]::-webkit-color-swatch {
border-radius: 0;
border: none;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
input[type="color"]::-moz-color-swatch {
border-radius: 0;
border: none;
padding: 0;
width: 100%;
height: 100%;
}
/* Theme Toggle Icon */
.icon-toggle {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: background-color 0.2s ease;
color: var(--accent);
font-size: 16px;
border: 1px solid transparent;
}
.icon-toggle:hover {
background: var(--highlight-tag);
border-color: var(--highlight-tag-border);
color: var(--accent-hover);
}
.hidden-toggle {
position: absolute;
opacity: 0;
pointer-events: none;
display: none;
}
.toggle-icon {
font-family: "Font Awesome 6 Free";
font-weight: 900;
color: inherit;
transition: all 0.2s ease;
}
.theme-icon::before {
content: "\f185"; /* sun icon */
}
#themeToggle:checked + .theme-icon::before {
content: "\f186"; /* moon icon */
}
/* Word list eye toggle (active/disabled) replaces switch in word items */
.word-item-eye-toggle {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
min-width: 28px;
margin-right: 6px;
border: none;
border-radius: 4px;
cursor: pointer;
background: transparent;
color: var(--text-color);
opacity: 0.7;
flex-shrink: 0;
transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}
.word-item-eye-toggle:hover {
opacity: 1;
background: var(--highlight-tag);
color: var(--accent);
}
.word-item-eye-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
.word-item-eye-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
pointer-events: none;
}
.word-item-eye-icon i {
position: absolute;
font-size: 14px;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease;
}
/* Active state: show eye, hide eye-slash */
.word-item-eye-input:checked ~ .word-item-eye-icon .eye-active {
opacity: 1;
color: var(--accent);
}
.word-item-eye-input:checked ~ .word-item-eye-icon .eye-disabled {
opacity: 0;
pointer-events: none;
}
/* Disabled state: show eye-slash, hide eye */
.word-item-eye-input:not(:checked) ~ .word-item-eye-icon .eye-active {
opacity: 0;
pointer-events: none;
}
.word-item-eye-input:not(:checked) ~ .word-item-eye-icon .eye-disabled {
opacity: 1;
color: var(--text-color);
}

272
shared/word-list.css Normal file
View File

@@ -0,0 +1,272 @@
/* Shared word list UI used by list-manager and popup (class names aliased for both) */
/* Word list container */
.word-list,
.word-list-container {
flex: 1;
overflow-y: auto;
background: var(--input-bg);
border: 1px solid var(--input-border);
border-radius: 8px;
min-height: 0;
display: flex;
flex-direction: column;
}
.word-list .empty,
.word-list-empty {
padding: 16px;
text-align: center;
font-size: 14px;
color: var(--text-color);
opacity: 0.6;
}
/* Word item row */
.word-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
cursor: pointer;
transition: all 0.2s;
border-bottom: 1px solid var(--input-border);
min-width: 0;
}
.word-item:last-child {
border-bottom: none;
}
.word-item:hover {
background: var(--highlight-tag);
}
.word-item.selected {
background: var(--section-bg);
border-left: 3px solid var(--accent);
padding-left: 9px;
}
.word-item.disabled {
opacity: 0.5;
}
/* Text label (list-manager: .word-text, popup: .word-item-text) */
.word-text,
.word-item-text {
flex: 1;
font-size: 14px;
color: var(--text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
user-select: none;
}
.word-text.editing,
.word-item-text.editing {
display: none;
}
/* Inline edit input */
.word-edit-input,
.word-item-edit-input {
display: none;
flex: 1;
height: 28px;
padding: 0 8px;
background: var(--bg-color);
border: 1px solid var(--accent);
border-radius: 4px;
font-size: 14px;
color: var(--text-color);
min-width: 0;
}
.word-edit-input:focus,
.word-item-edit-input:focus {
outline: none;
}
.word-edit-input.active,
.word-item-edit-input.active {
display: block;
}
/* Actions container */
.word-actions,
.word-item-actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
pointer-events: auto;
}
.word-actions > *,
.word-item-actions > * {
pointer-events: auto;
}
/* Icon button inside word item (list-manager: .word-item .icon-btn, popup: .word-item-icon-btn) */
.word-item .icon-btn,
.word-item-icon-btn {
width: 28px;
height: 28px;
min-width: 28px;
min-height: 28px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
color: var(--text-color);
opacity: 0.6;
}
.word-item .icon-btn:hover,
.word-item-icon-btn:hover {
opacity: 1;
background: var(--highlight-tag);
color: var(--accent);
}
.word-item .icon-btn i,
.word-item-icon-btn i {
font-size: 14px;
}
/* Color picker inside word item */
.word-item input[type="color"],
.word-item-color-picker {
width: 28px;
height: 28px;
border-radius: 4px;
border: 1px solid var(--input-border);
cursor: pointer;
padding: 0;
flex-shrink: 0;
}
.word-item input[type="color"]::-webkit-color-swatch-wrapper,
.word-item-color-picker::-webkit-color-swatch-wrapper {
padding: 0;
border-radius: 3px;
}
.word-item input[type="color"]::-webkit-color-swatch,
.word-item-color-picker::-webkit-color-swatch {
border: none;
border-radius: 3px;
}
.word-item input[type="color"]:hover,
.word-item-color-picker:hover {
border-color: var(--accent);
}
/* Word action row (Select All, Delete, etc.) list-manager: .word-controls .row, popup: .word-actions-row */
.word-controls .row,
.word-actions-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.word-controls .row button,
.word-action-btn {
height: 26px;
padding: 0 8px;
font-size: 12px;
border-radius: 6px;
background: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--text-color);
}
.word-controls .row button:hover,
.word-action-btn:hover {
background: var(--section-bg);
}
.word-controls .row button.danger,
.word-action-btn.danger {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: var(--danger);
}
.word-controls .row button.danger:hover,
.word-action-btn.danger:hover {
background: rgba(239, 68, 68, 0.2);
}
/* Add-words textarea (shared look) */
.add-words textarea,
.add-words-textarea {
flex: 1;
min-height: 56px;
padding: 6px 10px;
background: var(--input-bg);
color: var(--text-color);
border: 1px solid var(--input-border);
border-radius: 8px;
font-size: 13px;
resize: vertical;
}
.add-words textarea:focus,
.add-words-textarea:focus {
outline: none;
border-color: var(--accent);
}
/* Add-words primary button (subdued) */
.add-words button.primary,
.add-words-button {
background: rgba(255, 140, 0, 0.1);
color: var(--accent);
border: 1px solid rgba(255, 140, 0, 0.35);
}
.add-words button.primary:hover,
.add-words-button:hover {
background: rgba(255, 140, 0, 0.18);
border-color: rgba(255, 140, 0, 0.5);
}
html.dark .add-words button.primary,
html.dark .add-words-button,
body.dark .add-words button.primary,
body.dark .add-words-button {
background: rgba(255, 140, 0, 0.12);
border-color: rgba(255, 140, 0, 0.4);
}
html.dark .add-words button.primary:hover,
html.dark .add-words-button:hover,
body.dark .add-words button.primary:hover,
body.dark .add-words-button:hover {
background: rgba(255, 140, 0, 0.2);
border-color: rgba(255, 140, 0, 0.55);
}
/* Word search input (shared) */
.word-search-input,
#wordSearch {
height: 32px;
padding: 0 10px;
font-size: 14px;
border-radius: 8px;
background: var(--input-bg);
border: 1px solid var(--input-border);
color: var(--text-color);
}
.word-search-input:focus,
#wordSearch:focus {
outline: none;
border-color: var(--accent);
}

50
src/background.ts Normal file
View File

@@ -0,0 +1,50 @@
import { StorageService } from './services/StorageService.js';
const POPUP_STATE_KEY = 'goose-popup-ui-state';
class BackgroundService {
constructor() {
this.initialize();
}
private initialize(): void {
this.setupTabUpdateListener();
this.setupInstallListener();
this.setupPopupStateListener();
}
private setupPopupStateListener(): void {
chrome.runtime.onMessage.addListener((msg: { type?: string; payload?: unknown }, _sender, sendResponse) => {
if (msg.type === 'SAVE_POPUP_STATE' && msg.payload !== undefined) {
chrome.storage.local.set({ [POPUP_STATE_KEY]: JSON.stringify(msg.payload) }).then(() => sendResponse(undefined)).catch(() => sendResponse(undefined));
return true;
}
return false;
});
}
private setupTabUpdateListener(): void {
chrome.tabs.onUpdated.addListener((tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab): void => {
if (changeInfo.status === 'complete' && tab.url && /^https?:/.test(tab.url)) {
chrome.scripting.executeScript({
target: { tabId },
files: ['dist/content-standalone.js']
}).catch((err: unknown) => {
console.warn('Injection failed:', err);
});
}
});
}
private setupInstallListener(): void {
chrome.runtime.onInstalled.addListener(async (): Promise<void> => {
const data = await StorageService.get(['exceptionsList']);
if (!data.exceptionsList) {
await StorageService.update('exceptionsList', []);
}
});
}
}
new BackgroundService();

View File

@@ -0,0 +1,139 @@
import { HighlightList, MessageData } from '../types.js';
import { StorageService } from '../services/StorageService.js';
import { MessageService } from '../services/MessageService.js';
import { HighlightEngine } from './HighlightEngine.js';
export class ContentScript {
private lists: HighlightList[] = [];
private isGlobalHighlightEnabled = true;
private exceptionsList: string[] = [];
private isCurrentSiteException = false;
private matchCase = false;
private matchWhole = false;
private highlightEngine: HighlightEngine;
private isProcessing = false;
constructor() {
this.highlightEngine = new HighlightEngine(() => this.processHighlights());
this.initialize();
}
private async initialize(): Promise<void> {
await this.loadSettings();
this.setupMessageListener();
this.processHighlights();
}
private async loadSettings(): Promise<void> {
const data = await StorageService.get([
'lists',
'globalHighlightEnabled',
'matchCaseEnabled',
'matchWholeEnabled',
'exceptionsList'
]);
this.lists = data.lists || [];
this.isGlobalHighlightEnabled = data.globalHighlightEnabled ?? true;
this.matchCase = data.matchCaseEnabled ?? false;
this.matchWhole = data.matchWholeEnabled ?? false;
this.exceptionsList = data.exceptionsList || [];
this.isCurrentSiteException = this.checkCurrentSiteException();
}
private checkCurrentSiteException(): boolean {
const currentHostname = window.location.hostname;
return this.exceptionsList.includes(currentHostname);
}
private setupMessageListener(): void {
MessageService.onMessage((message: MessageData, sender: any, sendResponse: (response?: any) => void) => {
switch (message.type) {
case 'WORD_LIST_UPDATED':
this.handleWordListUpdate();
return false;
case 'GLOBAL_TOGGLE_UPDATED':
this.handleGlobalToggleUpdate(message.enabled!);
return false;
case 'MATCH_OPTIONS_UPDATED':
this.handleMatchOptionsUpdate(message.matchCase!, message.matchWhole!);
return false;
case 'EXCEPTIONS_LIST_UPDATED':
this.handleExceptionsUpdate();
return false;
case 'GET_PAGE_HIGHLIGHTS':
this.handleGetPageHighlights(sendResponse);
return true;
case 'SCROLL_TO_HIGHLIGHT':
this.handleScrollToHighlight(message.word!, message.index!);
return false;
}
return false;
});
}
private async handleWordListUpdate(): Promise<void> {
const data = await StorageService.get(['lists']);
this.lists = data.lists || [];
this.processHighlights();
}
private handleGlobalToggleUpdate(enabled: boolean): void {
this.isGlobalHighlightEnabled = enabled;
this.processHighlights();
}
private handleMatchOptionsUpdate(matchCase: boolean, matchWhole: boolean): void {
this.matchCase = matchCase;
this.matchWhole = matchWhole;
this.processHighlights();
}
private async handleExceptionsUpdate(): Promise<void> {
const data = await StorageService.get(['exceptionsList']);
this.exceptionsList = data.exceptionsList || [];
this.isCurrentSiteException = this.checkCurrentSiteException();
this.processHighlights();
}
private processHighlights(): void {
if (this.isProcessing) return;
this.isProcessing = true;
try {
if (!this.isGlobalHighlightEnabled || this.isCurrentSiteException) {
this.highlightEngine.clearHighlights();
this.highlightEngine.stopObserving();
return;
}
this.highlightEngine.highlight(this.lists, this.matchCase, this.matchWhole);
} finally {
this.isProcessing = false;
}
}
private handleGetPageHighlights(sendResponse: (response: any) => void): void {
const activeWords: ActiveWord[] = [];
for (const list of this.lists) {
if (!list.active) continue;
for (const word of list.words) {
if (!word.active) continue;
activeWords.push({
text: word.wordStr,
background: word.background || list.background,
foreground: word.foreground || list.foreground
});
}
}
const highlights = this.highlightEngine.getPageHighlights(activeWords);
sendResponse({ highlights });
}
private handleScrollToHighlight(word: string, index: number): void {
this.highlightEngine.scrollToHighlight(word, index);
}
}

View File

@@ -0,0 +1,595 @@
import { HighlightList, ActiveWord, CONSTANTS } from '../types.js';
import { DOMUtils } from '../utils/DOMUtils.js';
export class HighlightEngine {
private static escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
private static renderHighlighted(text: string, pattern: RegExp): string {
let html = '';
let lastIndex = 0;
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(text)) !== null) {
html += HighlightEngine.escapeHtml(text.substring(lastIndex, match.index));
html += `<mark>${HighlightEngine.escapeHtml(match[0])}</mark>`;
lastIndex = match.index + match[0].length;
}
html += HighlightEngine.escapeHtml(text.substring(lastIndex));
return html;
}
private static createOrUpdateBadge(input: HTMLTextAreaElement | HTMLInputElement, pattern: RegExp): void {
const text = input.value;
let matchCount = 0;
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(text)) !== null) {
matchCount++;
}
const oldBadge = input.parentElement?.querySelector('.goose-highlighter-textarea-badge');
if (oldBadge) oldBadge.remove();
if (matchCount > 0) {
const badge = document.createElement('div');
badge.className = 'goose-highlighter-textarea-badge';
badge.textContent = matchCount.toString();
badge.setAttribute('data-round', matchCount > 9 ? 'false' : 'true');
badge.style.position = 'absolute';
badge.style.left = '4px';
badge.style.top = '4px';
badge.style.zIndex = '10000';
const parent = input.parentElement;
if (parent && window.getComputedStyle(parent).position === 'static') {
parent.style.position = 'relative';
}
parent?.appendChild(badge);
badge.addEventListener('click', () => {
document.querySelectorAll('.goose-highlighter-textarea-popup').forEach(p => p.remove());
const popup = document.createElement('div');
popup.className = 'goose-highlighter-textarea-popup';
popup.innerHTML = `
<div class="gh-popup-titlebar">
<button class="gh-popup-close" title="${chrome.i18n.getMessage('close') || 'Close'}">&times;</button>
</div>
<pre class="gh-popup-pre">${HighlightEngine.renderHighlighted(text, pattern)}</pre>
`;
document.body.appendChild(popup);
const closeBtn = popup.querySelector('.gh-popup-close');
closeBtn?.addEventListener('click', () => popup.remove());
});
}
}
private static attachBadgeListener(input: HTMLTextAreaElement | HTMLInputElement, pattern: RegExp): void {
const updateBadge = () => HighlightEngine.createOrUpdateBadge(input, pattern);
updateBadge();
input.removeEventListener('input', updateBadge);
input.addEventListener('input', updateBadge);
}
private _textareaMatchInfo: Array<{ input: HTMLTextAreaElement | HTMLInputElement; count: number; text: string }> = [];
private styleSheet: CSSStyleSheet | null = null;
private highlights = new Map<string, Highlight>();
private highlightsByWord = new Map<string, Range[]>();
private textareaMatchesByWord = new Map<string, Array<{ input: HTMLTextAreaElement | HTMLInputElement; position: number }>>();
private observer: MutationObserver;
private isHighlighting = false;
private currentMatchCase = false;
constructor(private onUpdate: () => void) {
this.observer = new MutationObserver(DOMUtils.debounce((mutations: MutationRecord[]) => {
if (this.isHighlighting) return;
const hasContentChanges = mutations.some((mutation: MutationRecord) => {
if (mutation.type !== 'childList') return false;
const allNodes = [...Array.from(mutation.addedNodes), ...Array.from(mutation.removedNodes)];
return allNodes.some(node => {
if (node.nodeType === Node.TEXT_NODE) return true;
if (node instanceof Element) return true;
return false;
});
});
if (hasContentChanges) {
this.onUpdate();
}
}, CONSTANTS.DEBOUNCE_DELAY));
}
private initializeStyleSheet(): void {
if (!this.styleSheet) {
const style = document.createElement('style');
style.id = 'goose-highlighter-styles';
document.head.appendChild(style);
this.styleSheet = style.sheet!;
}
}
private updateHighlightStyles(activeWords: ActiveWord[]): void {
this.initializeStyleSheet();
while (this.styleSheet!.cssRules.length > 0) {
this.styleSheet!.deleteRule(0);
}
const uniqueStyles = new Map<string, number>();
let styleIndex = 0;
for (const word of activeWords) {
const styleKey = `${word.background}-${word.foreground}`;
if (!uniqueStyles.has(styleKey)) {
uniqueStyles.set(styleKey, styleIndex);
const rule = `::highlight(gh-${styleIndex}) { background-color: ${word.background}; color: ${word.foreground}; }`;
this.styleSheet!.insertRule(rule, this.styleSheet!.cssRules.length);
styleIndex++;
}
}
}
clearHighlights(): void {
this.observer.disconnect();
this.clearHighlightsInternal();
}
private getTextNodes(): Text[] {
const textNodes: Text[] = [];
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node: Text) => {
if (node.parentNode && ['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME', 'TEXTAREA', 'INPUT'].includes(node.parentNode.nodeName)) {
return NodeFilter.FILTER_REJECT;
}
if (!node.nodeValue?.trim()) {
return NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
while (walker.nextNode()) {
textNodes.push(walker.currentNode as Text);
}
return textNodes;
}
private extractActiveWords(lists: HighlightList[]): ActiveWord[] {
const activeWords: ActiveWord[] = [];
for (const list of lists) {
if (!list.active) continue;
for (const word of list.words) {
if (!word.active) continue;
activeWords.push({
text: word.wordStr,
background: word.background || list.background,
foreground: word.foreground || list.foreground
});
}
}
return activeWords;
}
highlight(lists: HighlightList[], matchCase: boolean, matchWhole: boolean): void {
if (this.isHighlighting) return;
this.isHighlighting = true;
this.currentMatchCase = matchCase;
this.observer.disconnect();
this.clearHighlightsInternal();
const activeWords = this.extractActiveWords(lists);
if (activeWords.length === 0) {
this.startObserving();
this.isHighlighting = false;
return;
}
this.updateHighlightStyles(activeWords);
const styleMap = new Map<string, number>();
const uniqueStyles = new Map<string, number>();
let styleIndex = 0;
for (const word of activeWords) {
const styleKey = `${word.background}-${word.foreground}`;
if (!uniqueStyles.has(styleKey)) {
uniqueStyles.set(styleKey, styleIndex++);
}
const lookup = matchCase ? word.text : word.text.toLowerCase();
styleMap.set(lookup, uniqueStyles.get(styleKey)!);
}
const flags = matchCase ? 'gu' : 'giu';
let wordsPattern = Array.from(styleMap.keys()).map(DOMUtils.escapeRegex).join('|');
if (matchWhole) {
wordsPattern = `(?:(?<!\\p{L})|^)(${wordsPattern})(?:(?!\\p{L})|$)`;
}
try {
const pattern = new RegExp(`(${wordsPattern})`, flags);
const textNodes = this.getTextNodes();
const rangesByStyle = new Map<number, Range[]>();
this.highlightsByWord.clear();
this.textareaMatchesByWord.clear();
for (const node of textNodes) {
if (!node.nodeValue) continue;
const text = node.nodeValue;
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(text)) !== null) {
const lookup = matchCase ? match[0] : match[0].toLowerCase();
const styleIdx = styleMap.get(lookup);
if (styleIdx !== undefined) {
const range = new Range();
range.setStart(node, match.index);
range.setEnd(node, match.index + match[0].length);
if (!rangesByStyle.has(styleIdx)) {
rangesByStyle.set(styleIdx, []);
}
rangesByStyle.get(styleIdx)!.push(range);
if (!this.highlightsByWord.has(lookup)) {
this.highlightsByWord.set(lookup, []);
}
this.highlightsByWord.get(lookup)!.push(range);
}
}
}
for (const [styleIdx, ranges] of rangesByStyle) {
const highlight = new Highlight(...ranges);
const highlightName = `gh-${styleIdx}`;
this.highlights.set(highlightName, highlight);
CSS.highlights.set(highlightName, highlight);
}
this.highlightTextareas(pattern, styleMap, activeWords);
} catch (e) {
console.error('Regex error:', e);
}
this.startObserving();
this.isHighlighting = false;
}
private highlightTextareas(pattern: RegExp, styleMap: Map<string, number>, activeWords: ActiveWord[]): void {
if (!document.getElementById('gh-textarea-badge-style')) {
const style = document.createElement('style');
style.id = 'gh-textarea-badge-style';
style.textContent = `
:root {
--gh-badge-accent: #ec9c23;
--gh-badge-text: #000;
--gh-badge-border: #2d2d2d;
--gh-popup-bg: #161616;
--gh-popup-border: #ec9c23;
--gh-popup-radius: 10px;
--gh-popup-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.goose-highlighter-textarea-badge {
position: absolute !important;
left: 4px !important;
top: 4px !important;
background: var(--gh-badge-accent);
color: var(--gh-badge-text);
font-family: inherit;
font-weight: bold;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
cursor: pointer;
z-index: 10000;
padding: 0 10px;
height: 22px;
min-width: 22px;
border-radius: 12px;
border: 2px solid var(--gh-badge-border);
transition: box-shadow 0.2s, background 0.2s;
}
.goose-highlighter-textarea-badge[data-round="true"] {
border-radius: 50%;
padding: 0;
min-width: 22px;
}
.goose-highlighter-textarea-badge:hover {
box-shadow: 0 4px 12px rgba(236,156,35,0.25);
background: #ffb84d;
}
.goose-highlighter-textarea-popup {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: var(--gh-popup-bg);
border: 2px solid var(--gh-popup-border);
border-radius: var(--gh-popup-radius);
box-shadow: var(--gh-popup-shadow);
padding: 0 0 20px 0;
z-index: 10001;
max-width: 420px;
max-height: 70vh;
overflow: auto;
color: #e8e8e8;
font-family: 'Inter', 'Segoe UI', sans-serif;
}
.gh-popup-titlebar {
display: flex;
align-items: center;
justify-content: flex-end;
background: #191919;
border-top-left-radius: var(--gh-popup-radius);
border-top-right-radius: var(--gh-popup-radius);
height: 32px;
padding: 0 8px 0 0;
border-bottom: 1px solid #222;
position: relative;
}
.gh-popup-close {
background: none;
color: #e8e8e8;
border: none;
font-size: 22px;
font-weight: bold;
cursor: pointer;
padding: 0 6px;
border-radius: 4px;
transition: background 0.2s, color 0.2s;
z-index: 10002;
line-height: 1;
}
.gh-popup-close:hover {
background: #ec9c23;
color: #222;
}
.goose-highlighter-textarea-popup::-webkit-scrollbar {
width: 10px;
background: #222;
border-radius: 8px;
}
.goose-highlighter-textarea-popup::-webkit-scrollbar-thumb {
background: var(--gh-badge-accent);
border-radius: 8px;
border: 2px solid #222;
}
.goose-highlighter-textarea-popup::-webkit-scrollbar-thumb:hover {
background: #ffb84d;
}
.goose-highlighter-textarea-popup {
scrollbar-width: thin;
scrollbar-color: var(--gh-badge-accent) #222;
}
.gh-popup-pre {
background: #222;
border-radius: 7px;
padding: 10px 12px;
border: 1px solid #333;
color: #e8e8e8;
font-size: 15px;
font-family: inherit;
margin: 16px 16px 0 16px;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
.gh-popup-pre mark {
background: none;
color: #ec9c23;
font-weight: bold;
border-radius: 0;
padding: 0;
}
`;
document.head.appendChild(style);
}
const textareas = document.querySelectorAll('textarea, input[type="text"], input[type="search"], input[type="email"], input[type="url"]');
this._textareaMatchInfo = [];
document.querySelectorAll('.goose-highlighter-textarea-badge').forEach(badge => badge.remove());
for (const element of Array.from(textareas)) {
const input = element as HTMLTextAreaElement | HTMLInputElement;
HighlightEngine.attachBadgeListener(input, pattern);
}
}
private clearHighlightsInternal(): void {
for (const name of this.highlights.keys()) {
CSS.highlights.delete(name);
}
this.highlights.clear();
this.highlightsByWord.clear();
this.textareaMatchesByWord.clear();
if (this.styleSheet && this.styleSheet.cssRules.length > 0) {
while (this.styleSheet.cssRules.length > 0) {
this.styleSheet.deleteRule(0);
}
}
}
getPageHighlights(activeWords: ActiveWord[]): Array<{ word: string; count: number; background: string; foreground: string }> {
const seen = new Map<string, { word: string; count: number; background: string; foreground: string }>();
for (const activeWord of activeWords) {
const lookup = this.currentMatchCase ? activeWord.text : activeWord.text.toLowerCase();
const ranges = this.highlightsByWord.get(lookup);
const textareaMatches = this.textareaMatchesByWord.get(lookup);
const totalCount = (ranges?.length || 0) + (textareaMatches?.length || 0);
if (totalCount > 0 && !seen.has(lookup)) {
seen.set(lookup, {
word: activeWord.text,
count: totalCount,
background: activeWord.background,
foreground: activeWord.foreground
});
}
}
return Array.from(seen.values());
}
scrollToHighlight(word: string, index: number): void {
const lookup = this.currentMatchCase ? word : word.toLowerCase();
const ranges = this.highlightsByWord.get(lookup) || [];
const textareaMatches = this.textareaMatchesByWord.get(lookup) || [];
const totalMatches = ranges.length + textareaMatches.length;
if (totalMatches === 0) return;
const targetIndex = Math.min(index, totalMatches - 1);
try {
if (targetIndex >= ranges.length) {
const textareaIndex = targetIndex - ranges.length;
const textareaMatch = textareaMatches[textareaIndex];
this.scrollToTextareaMatch(textareaMatch.input, textareaMatch.position, word.length);
return;
}
const range = ranges[targetIndex];
if (!range) return;
// First, scroll any scrollable containers
const element = range.commonAncestorContainer.nodeType === Node.TEXT_NODE
? range.commonAncestorContainer.parentElement
: range.commonAncestorContainer as Element;
if (element) {
this.scrollIntoViewInContainers(element);
}
// Then scroll the main window
const rect = range.getBoundingClientRect();
const absoluteTop = window.pageYOffset + rect.top;
const middle = absoluteTop - (window.innerHeight / 2) + (rect.height / 2);
window.scrollTo({
top: middle,
behavior: 'smooth'
});
const flashHighlight = new Highlight(range);
CSS.highlights.set('gh-flash', flashHighlight);
if (this.styleSheet) {
const flashRule = '::highlight(gh-flash) { background-color: rgba(255, 165, 0, 0.8); box-shadow: 0 0 10px 3px rgba(255, 165, 0, 0.8); }';
const ruleIndex = this.styleSheet.insertRule(flashRule, this.styleSheet.cssRules.length);
setTimeout(() => {
CSS.highlights.delete('gh-flash');
if (this.styleSheet && ruleIndex < this.styleSheet.cssRules.length) {
this.styleSheet.deleteRule(ruleIndex);
}
}, 600);
}
} catch (e) {
console.error('Error scrolling to highlight:', e);
}
}
private scrollToTextareaMatch(input: HTMLTextAreaElement | HTMLInputElement, position: number, wordLength: number): void {
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
if (input.tagName === 'TEXTAREA') {
const textarea = input as HTMLTextAreaElement;
const text = textarea.value;
const beforeText = text.substring(0, position);
const lines = beforeText.split('\n');
const lineNumber = lines.length - 1;
const computedStyle = window.getComputedStyle(textarea);
const lineHeight = parseFloat(computedStyle.lineHeight) || parseFloat(computedStyle.fontSize) * 1.2;
const targetScrollTop = lineNumber * lineHeight - (textarea.clientHeight / 2);
textarea.scrollTop = Math.max(0, targetScrollTop);
}
input.focus();
input.setSelectionRange(position, position + wordLength);
}
private getTextPosition(overlay: HTMLElement, targetMark: HTMLElement): number {
let position = 0;
const walker = document.createTreeWalker(overlay, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
let node: Node | null;
while ((node = walker.nextNode())) {
if (node === targetMark) {
return position;
}
if (node.nodeType === Node.TEXT_NODE) {
position += node.textContent?.length || 0;
}
}
return position;
}
private scrollIntoViewInContainers(element: Element): void {
let current: Element | null = element;
while (current && current !== document.body) {
const parent: HTMLElement | null = current.parentElement;
if (!parent) break;
const parentStyle = window.getComputedStyle(parent);
const isScrollable = (
(parentStyle.overflow === 'auto' || parentStyle.overflow === 'scroll' ||
parentStyle.overflowY === 'auto' || parentStyle.overflowY === 'scroll' ||
parentStyle.overflowX === 'auto' || parentStyle.overflowX === 'scroll') &&
(parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth)
);
if (isScrollable) {
const parentRect = parent.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
if (elementRect.top < parentRect.top) {
parent.scrollTop -= (parentRect.top - elementRect.top) + 20;
} else if (elementRect.bottom > parentRect.bottom) {
parent.scrollTop += (elementRect.bottom - parentRect.bottom) + 20;
}
if (elementRect.left < parentRect.left) {
parent.scrollLeft -= (parentRect.left - elementRect.left) + 20;
} else if (elementRect.right > parentRect.right) {
parent.scrollLeft += (elementRect.right - parentRect.right) + 20;
}
}
current = parent;
}
}
stopObserving(): void {
this.observer.disconnect();
}
private startObserving(): void {
this.observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false
});
}
destroy(): void {
this.observer.disconnect();
this.clearHighlights();
}
}

3
src/main.ts Normal file
View File

@@ -0,0 +1,3 @@
import { ContentScript } from './content/ContentScript.js';
new ContentScript();

1423
src/popup/PopupController.ts Normal file

File diff suppressed because it is too large Load Diff

53
src/popup/popup.ts Normal file
View File

@@ -0,0 +1,53 @@
import { PopupController } from './PopupController.js';
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light');
} else {
document.documentElement.classList.add('dark');
}
function localizePage(): void {
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const message = (element as HTMLElement).dataset.i18n!;
const localizedText = chrome.i18n.getMessage(message);
if (localizedText) {
if ((element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') && (element as HTMLInputElement).hasAttribute('placeholder')) {
(element as HTMLInputElement).placeholder = localizedText;
} else {
element.textContent = localizedText;
}
}
});
const titleElements = document.querySelectorAll('[data-i18n-title]');
titleElements.forEach(element => {
const key = element.getAttribute('data-i18n-title');
if (key) {
const translation = chrome.i18n.getMessage(key);
if (translation) {
element.setAttribute('title', translation);
}
}
});
}
function displayVersion(): void {
const manifest = chrome.runtime.getManifest();
const versionElement = document.getElementById('version-number');
if (versionElement && manifest.version) {
versionElement.textContent = manifest.version;
}
}
document.addEventListener('DOMContentLoaded', async () => {
localizePage();
displayVersion();
const controller = new PopupController();
await controller.initialize();
const onClose = (): void => controller.captureScrollAndSave();
window.addEventListener('blur', onClose);
window.addEventListener('pagehide', onClose);
});

View File

@@ -0,0 +1,33 @@
import { MessageData } from '../types.js';
export class MessageService {
static sendToAllTabs(message: MessageData): void {
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, message).catch(() => {
// Ignore errors for tabs that can't receive messages
});
}
});
});
}
static sendToTab(tabId: number, message: MessageData): void {
chrome.tabs.sendMessage(tabId, message).catch(() => {
// Ignore errors for tabs that can't receive messages
});
}
static onMessage(callback: (message: MessageData, sender: any, sendResponse: (response?: any) => void) => void | boolean): void {
chrome.runtime.onMessage.addListener(callback);
}
static async sendToActiveTab(message: MessageData): Promise<any> {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab.id) {
return chrome.tabs.sendMessage(tab.id, message);
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
import { StorageData, DEFAULT_STORAGE } from '../types.js';
export class StorageService {
static async get<K extends keyof StorageData>(keys: K[]): Promise<Pick<StorageData, K>>;
static async get(): Promise<StorageData>;
static async get(keys?: (keyof StorageData)[]): Promise<any> {
const defaults = DEFAULT_STORAGE;
if (keys) {
const keyDefaults: any = {};
keys.forEach(key => {
keyDefaults[key] = defaults[key];
});
return chrome.storage.local.get(keyDefaults);
}
return chrome.storage.local.get(defaults);
}
static async set(data: Partial<StorageData>): Promise<void> {
return chrome.storage.local.set(data);
}
static async update<K extends keyof StorageData>(key: K, value: StorageData[K]): Promise<void> {
return this.set({ [key]: value } as Partial<StorageData>);
}
}

65
src/types.ts Normal file
View File

@@ -0,0 +1,65 @@
export interface HighlightWord {
wordStr: string;
background: string;
foreground: string;
active: boolean;
}
export interface HighlightList {
id: number;
name: string;
background: string;
foreground: string;
active: boolean;
words: HighlightWord[];
}
export interface StorageData {
lists: HighlightList[];
globalHighlightEnabled: boolean;
matchCaseEnabled: boolean;
matchWholeEnabled: boolean;
exceptionsList: string[];
}
export interface ActiveWord {
text: string;
background: string;
foreground: string;
}
export interface HighlightInfo {
word: string;
count: number;
background: string;
foreground: string;
}
export interface MessageData {
type: 'WORD_LIST_UPDATED' | 'GLOBAL_TOGGLE_UPDATED' | 'MATCH_OPTIONS_UPDATED' | 'EXCEPTIONS_LIST_UPDATED' | 'GET_PAGE_HIGHLIGHTS' | 'PAGE_HIGHLIGHTS_RESPONSE' | 'SCROLL_TO_HIGHLIGHT';
enabled?: boolean;
matchCase?: boolean;
matchWhole?: boolean;
highlights?: HighlightInfo[];
word?: string;
index?: number;
}
export interface ExportData {
lists: HighlightList[];
exceptionsList: string[];
}
export const DEFAULT_STORAGE: StorageData = {
lists: [],
globalHighlightEnabled: true,
matchCaseEnabled: false,
matchWholeEnabled: false,
exceptionsList: []
};
export const CONSTANTS = {
WORD_ITEM_HEIGHT: 32,
DEBOUNCE_DELAY: 150,
SCROLL_THROTTLE: 16
} as const;

27
src/types/css-highlights.d.ts vendored Normal file
View File

@@ -0,0 +1,27 @@
// CSS Highlights API type declarations
interface Highlight {
new(...ranges: Range[]): Highlight;
add(range: Range): void;
clear(): void;
delete(range: Range): boolean;
has(range: Range): boolean;
readonly size: number;
}
interface HighlightRegistry {
set(name: string, highlight: Highlight): void;
get(name: string): Highlight | undefined;
delete(name: string): boolean;
clear(): void;
has(name: string): boolean;
readonly size: number;
}
interface CSS {
highlights: HighlightRegistry;
}
declare var Highlight: {
prototype: Highlight;
new(...ranges: Range[]): Highlight;
};

41
src/utils/DOMUtils.ts Normal file
View File

@@ -0,0 +1,41 @@
export class DOMUtils {
static escapeHtml(str: string): string {
const escapeMap: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
return str.replace(/[&<>"']/g, (match) => escapeMap[match]);
}
static escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
static debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: number;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = window.setTimeout(() => func(...args), wait);
};
}
static throttle<T extends (...args: any[]) => void>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
}

View File

@@ -1,8 +0,0 @@
async function getLists() {
const { lists } = await chrome.storage.local.get("lists");
return lists || [];
}
async function saveLists(lists) {
await chrome.storage.local.set({ lists });
}

30
tsconfig.json Normal file
View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"noEmitOnError": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"exactOptionalPropertyTypes": false,
"types": ["chrome"]
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}