1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2026-03-24 23:02:58 +00:00

45 Commits
0.5.3 ... 0.6.1

Author SHA1 Message Date
94733c9ff3 Merge pull request #160 from koloml/release/0.6.1
Release: 0.6.1
2026-02-26 04:27:47 +04:00
d11cc2a9c5 Bumped version to 0.6.1 2026-02-26 04:27:01 +04:00
f4e30c60ad Merge pull request #159 from koloml/feature/support-replacing-tag-link-texts
Tag Links: Support replaceting text of links to the decoded tag name
2026-02-26 03:38:08 +04:00
9031055ec9 Replace the tag link text to resolved tag name when possible 2026-02-26 03:30:10 +04:00
8194a84ef7 Fix pluses not being decoded from the path 2026-02-26 03:28:48 +04:00
2829ac022f Merge pull request #157 from koloml/feature/tantabus-namespaces
Tantabus: Adding 3 more namespaces unique to Tantabus for auto-coloring of tags
2026-02-26 02:39:59 +04:00
5aac85dcaa Merge pull request #158 from koloml/feature/support-tag-links-from-search
Tag Links Replacement: Support links pointintg to `/search?q=`
2026-02-26 02:39:50 +04:00
9a14a5568d Merge pull request #156 from koloml/bugfix/tantabus-swaps-in-content-scripts
Tantabus: Fixed content scripts not properly receiving proper constants
2026-02-26 02:35:37 +04:00
a2ab0d4e7c Adding 3 more namespaces unique to Tantabus 2026-02-26 02:34:06 +04:00
5123b57320 Fixed content scripts not properly receiving Tantabus constant 2026-02-26 02:33:15 +04:00
2bdb789777 Support links to /search?q= when detecting tag links to replace 2026-02-25 20:47:22 +04:00
486ab9cafa Merge pull request #155 from koloml/release/0.6.0.1
Release: 0.6.0.1
2026-02-23 20:28:04 +04:00
ba7b96d888 Bumped version to 0.6.0.1 2026-02-23 20:27:48 +04:00
b08937e47b Merge pull request #154 from koloml/bugfix/specify-no-data-collection
Firefox: Specify no data collection required by the extension
2026-02-23 20:26:04 +04:00
1d332ea7d1 Firefox: Specify no data collection required by the extension
This extension doesn't track anything about the user or their activities
inside the sites (Furbooru/Derpibooru/Tantabus) or inside the extension.
2026-02-23 20:24:40 +04:00
2920946015 Merge pull request #151 from koloml/release/0.6.0
Release: 0.6.0
2026-02-23 20:10:37 +04:00
f879c45517 Bumping version to 0.6.0 2026-02-23 20:09:30 +04:00
00083fdadb Updating dependencies (#153)
* Updated `@sveltejs/kit` from 2.50.2 to 2.53.0

* Updated `svelte` from 5.50.0 to 5.53.3

* Updated `@fortawesome/fontawesome-free` from 7.1.0 to 7.2.0

* Move `devDependencies` to the bottom

* Updated `jsdom` from 28.0.0 to 28.1.0

* Updated `svelte-check` from 4.3.6 to 4.4.3

* Updated `@types/chrome` from 0.1.36 to 0.1.37

* Updated `@types/node` from 25.2.2 to 25.3.0
2026-02-23 20:05:27 +04:00
7ffee170c3 Merge pull request #149 from koloml/feature/tantabus-support
Added Tantabus version of extension
2026-02-23 19:03:19 +04:00
db34b361b3 Merge pull request #152 from koloml/feature/color-tags-in-tagging-popup
Tagging Popup: Automatically color tags by their tag namespaces
2026-02-23 18:45:44 +04:00
bf81b7111f Tagging Popup: Automatically color tags by their tag namespaces 2026-02-23 18:38:07 +04:00
dc79959b8f Merge pull request #150 from koloml/feature/decorated-tag-links-in-forum
Added option to decorate tag links in forum posts
2026-02-23 18:25:46 +04:00
dfdab180ee Added option to decorate tag links in forum posts 2026-02-23 17:51:27 +04:00
b768f9072c Updating README to include info on Tantabus as well 2026-02-22 20:08:42 +04:00
72a731aaff Updating CI to build for Tantabus on release as well 2026-02-22 20:07:57 +04:00
d181509d6f Preparing extension for Tantabus 2026-02-22 01:49:31 +04:00
03b0763db4 Merge pull request #146 from koloml/release/0.5.4
Release: 0.5.4
2026-02-09 11:29:08 +04:00
a7e0aefe6b Merge pull request #148 from koloml/feature/centralized-component-for-errors-and-warnings
Popup: Combining warnings and error messages into single component
2026-02-09 11:27:43 +04:00
687c12a8f4 Putting errors and warnings into separate component 2026-02-09 11:24:01 +04:00
9b7ba4a6e2 Bumped version to 0.5.4 2026-02-09 11:05:42 +04:00
8d7b151911 Merge pull request #147 from koloml/bugfix/update-tags-on-last-group-deleted
Fixed custom categories not refreshing on tags once last group is deleted
2026-02-09 11:01:50 +04:00
fccd79292d Fixed tags list didn't update itself once last group was deleted 2026-02-09 10:59:40 +04:00
8041f2d2a1 Merge pull request #145 from koloml/chore/dependencies
Updating dependencies
2026-02-09 10:56:00 +04:00
3fac472ae0 Merge pull request #144 from koloml/bugfix/bulk-import
Fixed bulk import only adding one entry from the list
2026-02-09 10:55:50 +04:00
44aca3120c Updated @types/node from 25.0.3 to 25.2.2 2026-02-09 10:50:01 +04:00
3aee3defba Updated @types/chrome from 0.1.32 to 0.1.36 2026-02-09 10:49:33 +04:00
b7a9dc2a2b Updated jsdom from 27.4.0 to 28.0.0 2026-02-09 10:48:56 +04:00
242dfc5972 Updated cheerio from 1.1.2 to 1.2.0 2026-02-09 10:48:01 +04:00
b6840996b6 Updated sass from 1.97.2 to 1.97.3 2026-02-09 10:47:22 +04:00
4c5b796f1d Updated @sveltejs/vite-plugin-svelte from 6.2.3 to 6.2.4 2026-02-09 10:46:35 +04:00
7f2e06a1b1 Updated vitest and @vitest/coverage-v8 from 4.0.16 to 4.0.18 2026-02-09 10:45:40 +04:00
31a33131cd Updated svelte-check from 4.3.5 to 4.3.6 2026-02-09 10:44:56 +04:00
7063459622 Updated @sveltejs/kit from 2.49.4 to 2.50.2 2026-02-09 10:44:03 +04:00
5a82b8751d Updated svelte from 5.46.1 to 5.50.0 2026-02-09 10:43:25 +04:00
9318bd51fa Fixed bulk import only saving last entry 2026-02-09 10:41:10 +04:00
25 changed files with 789 additions and 414 deletions

View File

@@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
site: [furbooru, derpibooru]
site: [furbooru, derpibooru, tantabus]
steps:
- name: Checkout repository
@@ -27,10 +27,10 @@ jobs:
- name: Build extension for ${{ matrix.site }}
run: |
if [ "${{ matrix.site }}" = "derpibooru" ]; then
npm run build:derpibooru
else
if [ "${{ matrix.site }}" = "furbooru" ]; then
npm run build
else
npm run build:${{ matrix.site }}
fi
- name: Create extension zip

View File

@@ -174,6 +174,15 @@ export async function buildScriptsAndStyles(buildOptions) {
}
});
const tantabusSwapPlugin = SwapDefinedVariablesPlugin({
envVariable: 'SITE',
expectedValue: 'tantabus',
define: {
__CURRENT_SITE__: JSON.stringify('tantabus'),
__CURRENT_SITE_NAME__: JSON.stringify('Tantabus'),
}
});
// Building all scripts together with AMD loader in mind
await build({
configFile: false,
@@ -209,6 +218,7 @@ export async function buildScriptsAndStyles(buildOptions) {
?.push(...dependencies);
}),
derpibooruSwapPlugin,
tantabusSwapPlugin,
],
define: defineConstants,
});
@@ -235,6 +245,7 @@ export async function buildScriptsAndStyles(buildOptions) {
wrapScriptIntoIIFE(),
ScssViteReadEnvVariableFunctionPlugin(),
derpibooruSwapPlugin,
tantabusSwapPlugin,
],
define: defineConstants,
});

View File

@@ -67,13 +67,24 @@ export async function packExtension(settings) {
return entry;
})
if (process.env.SITE === 'derpibooru') {
manifest.replaceHostTo([
'derpibooru.org',
'trixiebooru.org'
]);
manifest.replaceBooruNameWith('Derpibooru');
manifest.setGeckoIdentifier('derpibooru-tagging-assistant@thecore.city');
switch (process.env.SITE) {
case 'derpibooru':
manifest.replaceHostTo([
'derpibooru.org',
'trixiebooru.org'
]);
manifest.replaceBooruNameWith('Derpibooru');
manifest.setGeckoIdentifier('derpibooru-tagging-assistant@thecore.city');
break;
case 'tantabus':
manifest.replaceHostTo('tantabus.ai');
manifest.replaceBooruNameWith('Tantabus');
manifest.setGeckoIdentifier('tantabus-tagging-assistant@thecore.city');
break;
default:
console.warn('No replacement set up for site: ' + process.env.SITE);
}
manifest.passVersionFromPackage(path.resolve(settings.rootDir, 'package.json'));

View File

@@ -1,8 +1,8 @@
# Philomena Tagging Assistant
This is a browser extension written for the [Furbooru](https://furbooru.org) and [Derpibooru](https://derpibooru.org)
image-boards. It gives you the ability to manually go over the list of images and apply tags to them without opening
each individual image.
This is a browser extension written for the [Furbooru](https://furbooru.org), [Derpibooru](https://derpibooru.org) and
[Tantabus](https://tantabus.ai) image-boards. It gives you the ability to manually go over the list of images and apply
tags to them without opening each individual image.
## Installation
@@ -59,14 +59,17 @@ npm install --save-dev
Second, you need to run the `build` command. It will first build the popup using SvelteKit and then build all the
content scripts/stylesheets and copy the manifest afterward.
Extension can currently be built for 2 different imageboards using one of the following commands:
Extension can currently be built for multiple different imageboards using one of the following commands:
```shell
# To build the extension for Furbooru, use:
# Furbooru:
npm run build
# To build the extension for Derpbooru, use:
# Derpibooru:
npm run build:derpibooru
# Tantabus:
npm run build:tantabus
```
When build is complete, extension files can be found in the `/build` directory. These files can be either used

View File

@@ -1,10 +1,15 @@
{
"name": "Furbooru Tagging Assistant",
"description": "Small experimental extension for slightly quicker tagging experience. Furbooru Edition.",
"version": "0.5.3",
"version": "0.6.1",
"browser_specific_settings": {
"gecko": {
"id": "furbooru-tagging-assistant@thecore.city"
"id": "furbooru-tagging-assistant@thecore.city",
"data_collection_permissions": {
"required": [
"none"
]
}
}
},
"icons": {
@@ -69,6 +74,19 @@
"js": [
"src/content/tags.ts"
]
},
{
"matches": [
"*://*.furbooru.org/posts",
"*://*.furbooru.org/posts?*",
"*://*.furbooru.org/forums/*/topics/*"
],
"js": [
"src/content/posts.ts"
],
"css": [
"src/styles/content/posts.scss"
]
}
],
"action": {

540
package-lock.json generated
View File

@@ -1,70 +1,63 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.5.3",
"version": "0.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "furbooru-tagging-assistant",
"version": "0.5.3",
"version": "0.6.1",
"dependencies": {
"@fortawesome/fontawesome-free": "^7.1.0",
"@fortawesome/fontawesome-free": "^7.2.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.4",
"@sveltejs/kit": "^2.53.0",
"amd-lite": "^1.0.1",
"lz-string": "^1.5.0",
"sass": "^1.97.2",
"svelte": "^5.46.1"
"sass": "^1.97.3",
"svelte": "^5.53.3"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.3",
"@types/chrome": "^0.1.32",
"@types/node": "^25.0.3",
"@vitest/coverage-v8": "^4.0.16",
"cheerio": "^1.1.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@types/chrome": "^0.1.37",
"@types/node": "^25.3.0",
"@vitest/coverage-v8": "^4.0.18",
"cheerio": "^1.2.0",
"cross-env": "^10.1.0",
"jsdom": "^27.4.0",
"svelte-check": "^4.3.5",
"jsdom": "^28.1.0",
"svelte-check": "^4.4.3",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.16"
"vitest": "^4.0.18"
}
},
"node_modules/@acemir/cssom": {
"version": "0.9.30",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz",
"integrity": "sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==",
"version": "0.9.31",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz",
"integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==",
"dev": true,
"license": "MIT"
},
"node_modules/@asamuzakjp/css-color": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz",
"integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz",
"integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.4",
"@csstools/css-color-parser": "^3.1.0",
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4",
"lru-cache": "^11.2.4"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
"dev": true,
"license": "BlueOak-1.0.0",
"@csstools/css-calc": "^3.1.1",
"@csstools/css-color-parser": "^4.0.2",
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0",
"lru-cache": "^11.2.6"
},
"engines": {
"node": "20 || >=22"
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "6.7.6",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz",
"integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==",
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz",
"integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -72,17 +65,7 @@
"bidi-js": "^1.0.3",
"css-tree": "^3.1.0",
"is-potential-custom-element-name": "^1.0.1",
"lru-cache": "^11.2.4"
}
},
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
"lru-cache": "^11.2.6"
}
},
"node_modules/@asamuzakjp/nwsapi": {
@@ -152,10 +135,23 @@
"node": ">=18"
}
},
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-tree": "^3.0.0"
},
"bin": {
"specificity": "bin/cli.js"
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
"dev": true,
"funding": [
{
@@ -169,13 +165,13 @@
],
"license": "MIT-0",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
"integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
"dev": true,
"funding": [
{
@@ -189,17 +185,17 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz",
"integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==",
"dev": true,
"funding": [
{
@@ -213,21 +209,21 @@
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
"@csstools/color-helpers": "^6.0.2",
"@csstools/css-calc": "^3.1.1"
},
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"dev": true,
"funding": [
{
@@ -241,16 +237,16 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
"version": "1.0.23",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.23.tgz",
"integrity": "sha512-YEmgyklR6l/oKUltidNVYdjSmLSW88vMsKx0pmiS3r71s8ZZRpd8A0Yf0U+6p/RzElmMnPBv27hNWjDQMSZRtQ==",
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz",
"integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==",
"dev": true,
"funding": [
{
@@ -262,15 +258,12 @@
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=18"
}
"license": "MIT-0"
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
"funding": [
{
@@ -284,7 +277,7 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@epic-web/invariant": {
@@ -711,27 +704,27 @@
}
},
"node_modules/@exodus/bytes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.8.0.tgz",
"integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==",
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.12.0.tgz",
"integrity": "sha512-BuCOHA/EJdPN0qQ5MdgAiJSt9fYDHbghlgrj33gRdy/Yp1/FMCDhU6vJfcKrLC0TPWGSrfH3vYXBQWmFHxlddw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@exodus/crypto": "^1.0.0-rc.4"
"@noble/hashes": "^1.8.0 || ^2.0.0"
},
"peerDependenciesMeta": {
"@exodus/crypto": {
"@noble/hashes": {
"optional": true
}
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.1.0.tgz",
"integrity": "sha512-+WxNld5ZCJHvPQCr/GnzCTVREyStrAJjisUPtUxG5ngDA8TMlPnKp6dddlTpai4+1GNmltAeuk1hJEkBohwZYA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.2.0.tgz",
"integrity": "sha512-3DguDv/oUE+7vjMeTSOjCSG+KeawgVQOHrKRnvUuqYh1mfArrh7s+s8hXW3e4RerBA1+Wh+hBqf8sJNpqNrBWg==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
@@ -1150,9 +1143,9 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.49.4",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.4.tgz",
"integrity": "sha512-JFtOqDoU0DI/+QSG8qnq5bKcehVb3tCHhOG4amsSYth5/KgO4EkJvi42xSAiyKmXAAULW1/Zdb6lkgGEgSxdZg==",
"version": "2.53.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.0.tgz",
"integrity": "sha512-Brh/9h8QEg7rWIj+Nnz/2sC49NUeS8g3Qd9H5dTO3EbWG8vCEUl06jE+r5jQVDMHdr1swmCkwZkONFsWelGTpQ==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
@@ -1160,13 +1153,12 @@
"@types/cookie": "^0.6.0",
"acorn": "^8.14.1",
"cookie": "^0.6.0",
"devalue": "^5.3.2",
"devalue": "^5.6.3",
"esm-env": "^1.2.2",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"set-cookie-parser": "^3.0.0",
"sirv": "^3.0.0"
},
"bin": {
@@ -1177,10 +1169,10 @@
},
"peerDependencies": {
"@opentelemetry/api": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
"typescript": "^5.3.3",
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
@@ -1192,9 +1184,9 @@
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.3.tgz",
"integrity": "sha512-a+uxqQ9j6Lxmq4plbGaNdM9hgDCZyxAv/yvuyF5iWoA2H5icZkqD3rdK155ZQgFLX2lc3NvahHG4OgKpYqYPiQ==",
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz",
"integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==",
"license": "MIT",
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
@@ -1240,9 +1232,9 @@
}
},
"node_modules/@types/chrome": {
"version": "0.1.32",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.32.tgz",
"integrity": "sha512-n5Cqlh7zyAqRLQWLXkeV5K/1BgDZdVcO/dJSTa8x+7w+sx7m73UrDmduAptg4KorMtyTW2TNnPu8RGeaDMKNGg==",
"version": "0.1.37",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.37.tgz",
"integrity": "sha512-IJE4ceuDO7lrEuua7Pow47zwNcI8E6qqkowRP7aFPaZ0lrjxh6y836OPqqkIZeTX64FTogbw+4RNH0+QrweCTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1286,28 +1278,33 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.0.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
"version": "25.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
"integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
"undici-types": "~7.18.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@vitest/coverage-v8": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz",
"integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
"integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.0.16",
"ast-v8-to-istanbul": "^0.3.8",
"@vitest/utils": "4.0.18",
"ast-v8-to-istanbul": "^0.3.10",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-lib-source-maps": "^5.0.6",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.1",
"obug": "^2.1.1",
@@ -1318,8 +1315,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.0.16",
"vitest": "4.0.16"
"@vitest/browser": "4.0.18",
"vitest": "4.0.18"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -1328,16 +1325,16 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz",
"integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
"integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.0.16",
"@vitest/utils": "4.0.16",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -1346,13 +1343,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz",
"integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
"integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.0.16",
"@vitest/spy": "4.0.18",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1373,9 +1370,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz",
"integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
"integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1386,13 +1383,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz",
"integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
"integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.0.16",
"@vitest/utils": "4.0.18",
"pathe": "^2.0.3"
},
"funding": {
@@ -1400,13 +1397,13 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz",
"integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
"integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.16",
"@vitest/pretty-format": "4.0.18",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1415,9 +1412,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz",
"integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
"integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1425,13 +1422,13 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz",
"integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
"integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.0.16",
"@vitest/pretty-format": "4.0.18",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -1466,9 +1463,10 @@
"license": "MIT"
},
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
"license": "Apache-2.0",
"engines": {
"node": ">= 0.4"
}
@@ -1540,9 +1538,9 @@
}
},
"node_modules/cheerio": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
"integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1551,11 +1549,11 @@
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.1",
"htmlparser2": "^10.0.0",
"htmlparser2": "^10.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.12.0",
"undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
@@ -1684,41 +1682,41 @@
}
},
"node_modules/cssstyle": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
"integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz",
"integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^4.1.1",
"@csstools/css-syntax-patches-for-csstree": "^1.0.21",
"@asamuzakjp/css-color": "^5.0.0",
"@csstools/css-syntax-patches-for-csstree": "^1.0.28",
"css-tree": "^3.1.0",
"lru-cache": "^11.2.4"
"lru-cache": "^11.2.6"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cssstyle/node_modules/lru-cache": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/data-urls": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
"integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^15.0.0"
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/data-urls/node_modules/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
}
@@ -1766,9 +1764,9 @@
}
},
"node_modules/devalue": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
"integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz",
"integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==",
"license": "MIT"
},
"node_modules/dom-serializer": {
@@ -1903,9 +1901,9 @@
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
},
"node_modules/esrap": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz",
"integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.3.tgz",
"integrity": "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -1987,9 +1985,9 @@
"license": "MIT"
},
"node_modules/htmlparser2": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
"integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
@@ -2002,14 +2000,14 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.1",
"entities": "^6.0.0"
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -2136,21 +2134,6 @@
"node": ">=10"
}
},
"node_modules/istanbul-lib-source-maps": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.23",
"debug": "^4.1.1",
"istanbul-lib-coverage": "^3.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -2173,17 +2156,18 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "27.4.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"version": "28.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz",
"integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6",
"@exodus/bytes": "^1.6.0",
"cssstyle": "^5.3.4",
"data-urls": "^6.0.0",
"@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.8.1",
"@bramus/specificity": "^2.4.2",
"@exodus/bytes": "^1.11.0",
"cssstyle": "^6.0.1",
"data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
"http-proxy-agent": "^7.0.2",
@@ -2193,11 +2177,11 @@
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
"undici": "^7.21.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.0",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^15.1.0",
"ws": "^8.18.3",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -2238,6 +2222,16 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/jsdom/node_modules/whatwg-mimetype": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/kleur": {
"version": "4.1.5",
"license": "MIT",
@@ -2249,6 +2243,16 @@
"version": "3.0.0",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"license": "MIT",
@@ -2314,6 +2318,7 @@
},
"node_modules/mri": {
"version": "1.2.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -2559,6 +2564,7 @@
},
"node_modules/sade": {
"version": "1.8.1",
"dev": true,
"license": "MIT",
"dependencies": {
"mri": "^1.1.0"
@@ -2573,9 +2579,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.97.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz",
"integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==",
"version": "1.97.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz",
"integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.0",
@@ -2619,7 +2625,9 @@
}
},
"node_modules/set-cookie-parser": {
"version": "2.6.0",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz",
"integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==",
"license": "MIT"
},
"node_modules/shebang-command": {
@@ -2699,22 +2707,23 @@
}
},
"node_modules/svelte": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.1.tgz",
"integrity": "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==",
"version": "5.53.3",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.3.tgz",
"integrity": "sha512-pRUBr6j6uQDgBi208gHnGRMykw0Rf2Yr1HmLyRucsvcaYgIUxswJkT93WZJflsmezu5s8Lq+q78EoyLv2yaFCg==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5",
"@types/estree": "^1.0.5",
"@types/trusted-types": "^2.0.7",
"acorn": "^8.12.1",
"aria-query": "^5.3.1",
"aria-query": "5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"devalue": "^5.5.0",
"devalue": "^5.6.3",
"esm-env": "^1.2.1",
"esrap": "^2.2.1",
"esrap": "^2.2.2",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
@@ -2725,9 +2734,9 @@
}
},
"node_modules/svelte-check": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz",
"integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz",
"integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2934,9 +2943,9 @@
}
},
"node_modules/undici": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.13.0.tgz",
"integrity": "sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA==",
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
"integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2944,9 +2953,9 @@
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"devOptional": true,
"license": "MIT"
},
@@ -3073,19 +3082,19 @@
}
},
"node_modules/vitest": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz",
"integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==",
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.0.16",
"@vitest/mocker": "4.0.16",
"@vitest/pretty-format": "4.0.16",
"@vitest/runner": "4.0.16",
"@vitest/snapshot": "4.0.16",
"@vitest/spy": "4.0.16",
"@vitest/utils": "4.0.16",
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
"@vitest/pretty-format": "4.0.18",
"@vitest/runner": "4.0.18",
"@vitest/snapshot": "4.0.18",
"@vitest/spy": "4.0.18",
"@vitest/utils": "4.0.18",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
@@ -3113,10 +3122,10 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.0.16",
"@vitest/browser-preview": "4.0.16",
"@vitest/browser-webdriverio": "4.0.16",
"@vitest/ui": "4.0.16",
"@vitest/browser-playwright": "4.0.18",
"@vitest/browser-preview": "4.0.18",
"@vitest/browser-webdriverio": "4.0.18",
"@vitest/ui": "4.0.18",
"happy-dom": "*",
"jsdom": "*"
},
@@ -3177,9 +3186,9 @@
}
},
"node_modules/webidl-conversions": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -3206,17 +3215,18 @@
}
},
"node_modules/whatwg-url": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
"integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz",
"integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@exodus/bytes": "^1.11.0",
"tr46": "^6.0.0",
"webidl-conversions": "^8.0.0"
"webidl-conversions": "^8.0.1"
},
"engines": {
"node": ">=20"
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -3252,28 +3262,6 @@
"node": ">=8"
}
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",

View File

@@ -1,10 +1,12 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.5.3",
"version": "0.6.1",
"private": true,
"type": "module",
"scripts": {
"build": "npm run build:popup && npm run build:extension",
"build:derpibooru": "cross-env SITE=derpibooru npm run build",
"build:tantabus": "cross-env SITE=tantabus npm run build",
"build:popup": "vite build",
"build:extension": "node build-extension.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -12,27 +14,26 @@
"test": "vitest run --coverage",
"test:watch": "vitest watch --coverage"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.3",
"@types/chrome": "^0.1.32",
"@types/node": "^25.0.3",
"@vitest/coverage-v8": "^4.0.16",
"cheerio": "^1.1.2",
"cross-env": "^10.1.0",
"jsdom": "^27.4.0",
"svelte-check": "^4.3.5",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.16"
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.4",
"@fortawesome/fontawesome-free": "^7.1.0",
"@sveltejs/kit": "^2.53.0",
"@fortawesome/fontawesome-free": "^7.2.0",
"amd-lite": "^1.0.1",
"lz-string": "^1.5.0",
"sass": "^1.97.2",
"svelte": "^5.46.1"
"sass": "^1.97.3",
"svelte": "^5.53.3"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@types/chrome": "^0.1.37",
"@types/node": "^25.3.0",
"@vitest/coverage-v8": "^4.0.18",
"cheerio": "^1.2.0",
"cross-env": "^10.1.0",
"jsdom": "^28.1.0",
"svelte-check": "^4.4.3",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.18"
}
}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
interface MessageProps {
children?: import('svelte').Snippet;
level: 'warning' | 'error';
}
let { children, level }: MessageProps = $props();
</script>
<p class="{level}">
{@render children?.()}
</p>
<style lang="scss">
@use '$styles/colors';
p {
padding: 5px 24px;
margin: {
left: -24px;
right: -24px;
}
}
.warning {
background: colors.$warning-background;
}
.error {
background: colors.$error-background;
}
</style>

View File

@@ -14,6 +14,33 @@ export const categories: string[] = [
'body-type',
];
/**
* Mapping of namespaces to their respective categories. These namespaces are automatically assigned to them, so we can
* automatically assume categories of tags which start with them. Mapping is extracted from Philomena directly.
*
* This mapping may differ between boorus.
*
* @see https://github.com/philomena-dev/philomena/blob/6086757b654da8792ae52adb2a2f501ea6c30d12/lib/philomena/tags/tag.ex#L33-L45
*/
export const namespaceCategories: Map<string, string> = new Map([
['artist', 'origin'],
['art pack', 'content-fanmade'],
['colorist', 'origin'],
['comic', 'content-fanmade'],
['editor', 'origin'],
['fanfic', 'content-fanmade'],
['oc', 'oc'],
['photographer', 'origin'],
['series', 'content-fanmade'],
['spoiler', 'spoiler'],
['video', 'content-fanmade'],
...(__CURRENT_SITE__ === 'tantabus' ? <const> [
["prompter", "origin"],
["creator", "origin"],
["generator", "origin"]
] : [])
]);
/**
* List of tags which marked by the site as blacklisted. These tags are blocked from being added by the tag editor and
* should usually just be removed automatically.

View File

@@ -0,0 +1,133 @@
import { BaseComponent } from "$content/components/base/BaseComponent";
import TagSettings from "$lib/extension/settings/TagSettings";
import { getComponent } from "$content/components/base/component-utils";
import { resolveTagNameFromLink, resolveTagCategoryFromTagName } from "$lib/booru/tag-utils";
export class BlockCommunication extends BaseComponent {
#contentSection: HTMLElement | null = null;
#tagLinks: HTMLAnchorElement[] = [];
#tagLinksReplaced: boolean | null = null;
#linkTextReplaced: boolean | null = null;
protected build() {
this.#contentSection = this.container.querySelector('.communication__content');
this.#tagLinks = this.#findAllTagLinks();
}
protected init() {
Promise.all([
BlockCommunication.#tagSettings.resolveReplaceLinks(),
BlockCommunication.#tagSettings.resolveReplaceLinkText(),
]).then(([replaceLinks, replaceLinkText]) => {
this.#onReplaceLinkSettingResolved(
replaceLinks,
replaceLinkText
);
});
BlockCommunication.#tagSettings.subscribe(settings => {
this.#onReplaceLinkSettingResolved(
settings.replaceLinks ?? false,
settings.replaceLinkText ?? true
);
});
}
#onReplaceLinkSettingResolved(haveToReplaceLinks: boolean, shouldReplaceLinkText: boolean) {
if (
!this.#tagLinks.length
|| this.#tagLinksReplaced === haveToReplaceLinks
&& this.#linkTextReplaced === shouldReplaceLinkText
) {
return;
}
for (const linkElement of this.#tagLinks) {
linkElement.classList.toggle('tag', haveToReplaceLinks);
// Sometimes tags are being decorated with the code block inside. It should be fine to replace it right away.
if (linkElement.childElementCount === 1 && linkElement.children[0].tagName === 'CODE') {
linkElement.textContent = linkElement.children[0].textContent;
}
/**
* Resolved tag name. It should be stored for the text replacement.
*/
let tagName: string | undefined;
if (haveToReplaceLinks) {
tagName = resolveTagNameFromLink(new URL(linkElement.href)) ?? '';
linkElement.dataset.tagCategory = resolveTagCategoryFromTagName(tagName) ?? '';
} else {
linkElement.dataset.tagCategory = '';
}
this.#toggleTagLinkText(
linkElement,
haveToReplaceLinks && shouldReplaceLinkText,
tagName,
);
}
this.#tagLinksReplaced = haveToReplaceLinks;
this.#linkTextReplaced = shouldReplaceLinkText;
}
/**
* Swap the link text with the tag name or restore it back to original content. This function will only perform
* replacement on links without any additional tags inside. This will ensure link won't break original content.
* @param linkElement Element to swap the text on.
* @param shouldSwapToTagName Should we swap the text to tag name or retore it back from memory.
* @param tagName Tag name to swap the text to. If not provided, text will be swapped back.
* @private
*/
#toggleTagLinkText(linkElement: HTMLElement, shouldSwapToTagName: boolean, tagName?: string) {
if (linkElement.childElementCount) {
return;
}
// Make sure we save the original text to memory.
if (!BlockCommunication.#originalTagLinkTexts.has(linkElement)) {
BlockCommunication.#originalTagLinkTexts.set(linkElement, linkElement.textContent);
}
if (shouldSwapToTagName && tagName) {
linkElement.textContent = tagName;
} else {
linkElement.textContent = BlockCommunication.#originalTagLinkTexts.get(linkElement) ?? linkElement.textContent;
}
}
#findAllTagLinks(): HTMLAnchorElement[] {
return Array
.from(this.#contentSection?.querySelectorAll('a') || [])
.filter(
link =>
// Support links pointing to the tag page.
link.pathname.startsWith('/tags/')
// Also capture link which point to the search results with single tag.
|| link.pathname.startsWith('/search')
&& link.search.includes('q=')
);
}
static #tagSettings = new TagSettings();
/**
* Map of links to their original texts. These texts need to be stored here to make them restorable. Keys is a link
* element and value is a text.
* @private
*/
static #originalTagLinkTexts: WeakMap<HTMLElement, string> = new WeakMap();
static findAndInitializeAll() {
for (const container of document.querySelectorAll<HTMLElement>('.block.communication')) {
if (getComponent(container)) {
continue;
}
new BlockCommunication(container).initialize();
}
}
}

View File

@@ -11,6 +11,7 @@ import {
EVENT_TAGS_UPDATED
} from "$content/components/events/maintenance-popup-events";
import type { MediaBoxTools } from "$content/components/MediaBoxTools";
import { resolveTagCategoryFromTagName } from "$lib/booru/tag-utils";
class BlackListedTagsEncounteredError extends Error {
constructor(tagName: string) {
@@ -121,8 +122,13 @@ export class MaintenancePopup extends BaseComponent {
// Just to prevent duplication, we need to include this tag to the map of suggested invalid tags
if (tagsBlacklist.includes(tagName)) {
MaintenancePopup.#markTagAsInvalid(tagElement);
MaintenancePopup.#markTagElementWithCategory(tagElement, 'error');
this.#suggestedInvalidTags.set(tagName, tagElement);
} else {
MaintenancePopup.#markTagElementWithCategory(
tagElement,
resolveTagCategoryFromTagName(tagName) ?? '',
);
}
});
}
@@ -287,7 +293,7 @@ export class MaintenancePopup extends BaseComponent {
}
const tagElement = MaintenancePopup.#buildTagElement(tagName);
MaintenancePopup.#markTagAsInvalid(tagElement);
MaintenancePopup.#markTagElementWithCategory(tagElement, 'error');
tagElement.classList.add('is-present');
this.#suggestedInvalidTags.set(tagName, tagElement);
@@ -315,12 +321,13 @@ export class MaintenancePopup extends BaseComponent {
}
/**
* Marks the tag with red color.
* Mark the tag element with specified category.
* @param tagElement Element to mark.
* @param category Code name of category to mark.
*/
static #markTagAsInvalid(tagElement: HTMLElement) {
tagElement.dataset.tagCategory = 'error';
tagElement.setAttribute('data-tag-category', 'error');
static #markTagElementWithCategory(tagElement: HTMLElement, category: string) {
tagElement.dataset.tagCategory = category;
tagElement.setAttribute('data-tag-category', category);
}
/**

3
src/content/posts.ts Normal file
View File

@@ -0,0 +1,3 @@
import { BlockCommunication } from "$content/components/BlockCommunication";
BlockCommunication.findAndInitializeAll();

View File

@@ -1,3 +1,6 @@
import { namespaceCategories } from "$config/tags";
import { QueryLexer, QuotedTermToken, TermToken } from "$lib/booru/search/QueryLexer";
/**
* Build the map containing both real tags and their aliases.
*
@@ -31,3 +34,85 @@ export function buildTagsAndAliasesMap(realAndAliasedTags: string[], realTags: s
return tagsAndAliasesMap;
}
const tagLinkRegExp = /\/tags\/(?<encodedTagName>[^/?#]+)/;
/**
* List of encoded characters from Philomena.
*
* @see https://github.com/philomena-dev/philomena/blob/6086757b654da8792ae52adb2a2f501ea6c30d12/lib/philomena/slug.ex#L52-L57
*/
const slugEncodedCharacters: Map<string, string> = new Map([
['-dash-', '-'],
['-fwslash-', '/'],
['-bwslash-', '\\'],
['-colon-', ':'],
['-dot-', '.'],
['-plus-', '+'],
]);
/**
* Try to parse the tag name from the search query URL. It uses the same tokenizer as the booru. It only returns the
* tag name if query contains only one single tag without any additional conditions.
*
* @param searchLink Link with search query.
*
* @return Tag name or NULL if query contains more than 1 tag or doesn't have any tags at all.
*/
function parseTagNameFromSearchQuery(searchLink: URL): string | null {
const lexer = new QueryLexer(searchLink.searchParams.get('q') || '');
const parsedQuery = lexer.parse();
if (parsedQuery.length !== 1) {
return null;
}
const [token] = parsedQuery;
switch (true) {
case token instanceof TermToken:
return token.value;
case token instanceof QuotedTermToken:
return token.decodedValue;
}
return null;
}
/**
* Decode the tag name from the following link.
*
* @param tagLink Search link or link to the tag to parse the tag name from.
*
* @return Tag name or NULL if function is failed to parse the name of the tag.
*/
export function resolveTagNameFromLink(tagLink: URL): string | null {
if (tagLink.pathname.startsWith('/search') && tagLink.searchParams.has('q')) {
return parseTagNameFromSearchQuery(tagLink);
}
tagLinkRegExp.lastIndex = 0;
const result = tagLinkRegExp.exec(tagLink.pathname);
const encodedTagName = result?.groups?.encodedTagName;
if (!encodedTagName) {
return null;
}
return decodeURIComponent(encodedTagName)
.replaceAll(/-[a-z]+-/gi, match => slugEncodedCharacters.get(match) ?? match)
.replaceAll('-', ' ')
.replaceAll('+', ' ');
}
/**
* Try to resolve the category from the tag name.
*
* @param tagName Name of the tag.
*/
export function resolveTagCategoryFromTagName(tagName: string): string | null {
const namespace = tagName.split(':')[0];
return namespaceCategories.get(namespace) ?? null;
}

View File

@@ -90,6 +90,7 @@ export default class CustomCategoriesResolver {
this.#regExpGroupMatches.clear();
if (!tagGroups.length) {
this.#queueUpdatingTags();
return;
}

View File

@@ -2,6 +2,8 @@ import CacheableSettings from "$lib/extension/base/CacheableSettings";
interface TagSettingsFields {
groupSeparation: boolean;
replaceLinks: boolean;
replaceLinkText: boolean;
}
export default class TagSettings extends CacheableSettings<TagSettingsFields> {
@@ -13,7 +15,23 @@ export default class TagSettings extends CacheableSettings<TagSettingsFields> {
return this._resolveSetting("groupSeparation", true);
}
async resolveReplaceLinks() {
return this._resolveSetting("replaceLinks", false);
}
async resolveReplaceLinkText() {
return this._resolveSetting("replaceLinkText", true);
}
async setGroupSeparation(value: boolean) {
return this._writeSetting("groupSeparation", Boolean(value));
}
async setReplaceLinks(value: boolean) {
return this._writeSetting("replaceLinks", Boolean(value));
}
async setReplaceLinkText(value: boolean) {
return this._writeSetting("replaceLinkText", Boolean(value));
}
}

View File

@@ -11,6 +11,10 @@
if (__CURRENT_SITE__ === 'derpibooru') {
currentSiteUrl = 'https://derpibooru.org';
}
if (__CURRENT_SITE__ === 'tantabus') {
currentSiteUrl = 'https://tantabus.ai';
}
</script>
<Menu>

View File

@@ -9,6 +9,7 @@
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
import { tagGroups } from "$stores/entities/tag-groups";
import { popupTitle } from "$stores/popup";
import Notice from "$components/ui/Notice.svelte";
const groupTransporter = new EntitiesTransporter(TagGroup);
@@ -81,7 +82,7 @@
<hr>
</Menu>
{#if errorMessage}
<p class="error">Failed to import: {errorMessage}</p>
<Notice level="error">Failed to import: {errorMessage}</Notice>
<Menu>
<hr>
</Menu>
@@ -98,9 +99,10 @@
</Menu>
{:else}
{#if existingGroup}
<p class="warning">
<Notice level="warning">
This group will replace the existing "{existingGroup.settings.name}" group, since it have the same ID.
</p>
</Notice>
<br>
{/if}
<GroupView group={candidateGroup}></GroupView>
<Menu>
@@ -114,24 +116,3 @@
<MenuItem onclick={() => candidateGroup = null}>Cancel</MenuItem>
</Menu>
{/if}
<style lang="scss">
@use '$styles/colors';
.error, .warning {
padding: 5px 24px;
margin: {
left: -24px;
right: -24px;
}
}
.error {
background: colors.$error-background;
}
.warning {
background: colors.$warning-background;
margin-bottom: .5em;
}
</style>

View File

@@ -9,6 +9,7 @@
import { goto } from "$app/navigation";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
import { popupTitle } from "$stores/popup";
import Notice from "$components/ui/Notice.svelte";
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
@@ -80,7 +81,7 @@
<hr>
</Menu>
{#if errorMessage}
<p class="error">Failed to import: {errorMessage}</p>
<Notice level="error">Failed to import: {errorMessage}</Notice>
<Menu>
<hr>
</Menu>
@@ -97,9 +98,10 @@
</Menu>
{:else}
{#if existingProfile}
<p class="warning">
<Notice level="warning">
This profile will replace the existing "{existingProfile.settings.name}" profile, since it have the same ID.
</p>
</Notice>
<br>
{/if}
<ProfileView profile={candidateProfile}></ProfileView>
<Menu>
@@ -113,24 +115,3 @@
<MenuItem onclick={() => candidateProfile = null}>Cancel</MenuItem>
</Menu>
{/if}
<style lang="scss">
@use '$styles/colors';
.error, .warning {
padding: 5px 24px;
margin: {
left: -24px;
right: -24px;
}
}
.error {
background: colors.$error-background;
}
.warning {
background: colors.$warning-background;
margin-bottom: .5em;
}
</style>

View File

@@ -5,7 +5,11 @@
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { stripBlacklistedTagsEnabled } from "$stores/preferences/maintenance";
import { shouldSeparateTagGroups } from "$stores/preferences/tag";
import {
shouldReplaceLinksOnForumPosts,
shouldReplaceTextOfTagLinks,
shouldSeparateTagGroups
} from "$stores/preferences/tag";
import { popupTitle } from "$stores/popup";
$popupTitle = 'Tagging Preferences';
@@ -26,4 +30,16 @@
Enable separation of custom tag groups on the image pages
</CheckboxField>
</FormControl>
<FormControl>
<CheckboxField bind:checked={$shouldReplaceLinksOnForumPosts}>
Find and replace links to the tags in the forum posts
</CheckboxField>
</FormControl>
{#if $shouldReplaceLinksOnForumPosts}
<FormControl>
<CheckboxField bind:checked={$shouldReplaceTextOfTagLinks}>
Try to replace text on links pointing to tags in forum posts
</CheckboxField>
</FormControl>
{/if}
</FormContainer>

View File

@@ -15,6 +15,7 @@
import { goto } from "$app/navigation";
import type { SameSiteStatus } from "$lib/extension/EntitiesTransporter";
import { popupTitle } from "$stores/popup";
import Notice from "$components/ui/Notice.svelte";
let importedString = $state('');
let errorMessage = $state('');
@@ -25,6 +26,8 @@
let saveAllProfiles = $state(false);
let saveAllGroups = $state(false);
let isSaving = $state(false);
let selectedEntities: Record<keyof App.EntityNamesMap, Record<string, boolean>> = $state({
profiles: {},
groups: {},
@@ -145,31 +148,42 @@
}
}
function saveSelectedEntities() {
Promise.allSettled([
Promise.allSettled(
importedProfiles
.filter(profile => selectedEntities.profiles[profile.id])
.map(profile => profile.save())
),
Promise.allSettled(
importedGroups
.filter(group => selectedEntities.groups[group.id])
.map(group => group.save())
),
]).then(() => {
goto("/transporting");
});
async function saveSelectedEntities() {
if (isSaving) {
return;
}
isSaving = true;
for (const profile of importedProfiles) {
if (!selectedEntities.profiles[profile.id]) {
continue;
}
await profile.save();
}
for (const group of importedGroups) {
if (!selectedEntities.groups[group.id]) {
continue;
}
await group.save();
}
await goto("/transporting");
}
</script>
{#if !hasImportedEntities}
{#if isSaving}
<p>Saving imported entities...</p>
{:else if !hasImportedEntities}
<Menu>
<MenuItem href="/transporting" icon="arrow-left">Back</MenuItem>
<hr>
</Menu>
{#if errorMessage}
<p class="error">{errorMessage}</p>
<Notice level="error">{errorMessage}</Notice>
<Menu>
<hr>
</Menu>
@@ -201,18 +215,18 @@
{/if}
</Menu>
{#if lastImportStatus === "different"}
<p class="warning">
<Notice level="warning">
<b>Warning!</b>
Looks like these entities were exported for the different extension! There are many differences between tagging
systems of Furobooru and Derpibooru, so make sure to check if these settings are correct before using them!
</p>
</Notice>
{/if}
{#if lastImportStatus === 'unknown'}
<p class="warning">
<Notice level="warning">
<b>Warning!</b>
We couldn't verify if these settings are meant for this site or not. There are many differences between tagging
systems of Furbooru and Derpibooru, so make sure to check if these settings are correct before using them.
</p>
</Notice>
{/if}
<Menu>
{#if importedProfiles.length}
@@ -264,23 +278,3 @@
</MenuItem>
</Menu>
{/if}
<style lang="scss">
@use '$styles/colors';
.error, .warning {
padding: 5px 24px;
margin: {
left: -24px;
right: -24px;
}
}
.warning {
background: colors.$warning-background;
}
.error {
background: colors.$error-background;
}
</style>

View File

@@ -4,15 +4,31 @@ import TagSettings from "$lib/extension/settings/TagSettings";
const tagSettings = new TagSettings();
export const shouldSeparateTagGroups = writable(false);
export const shouldReplaceLinksOnForumPosts = writable(false);
export const shouldReplaceTextOfTagLinks = writable(true);
tagSettings.resolveGroupSeparation()
.then(value => shouldSeparateTagGroups.set(value))
Promise
.allSettled([
tagSettings.resolveGroupSeparation().then(value => shouldSeparateTagGroups.set(value)),
tagSettings.resolveReplaceLinks().then(value => shouldReplaceLinksOnForumPosts.set(value)),
tagSettings.resolveReplaceLinkText().then(value => shouldReplaceTextOfTagLinks.set(value)),
])
.then(() => {
shouldSeparateTagGroups.subscribe(value => {
void tagSettings.setGroupSeparation(value);
});
shouldReplaceLinksOnForumPosts.subscribe(value => {
void tagSettings.setReplaceLinks(value);
});
shouldReplaceTextOfTagLinks.subscribe(value => {
void tagSettings.setReplaceLinkText(value);
});
tagSettings.subscribe(settings => {
shouldSeparateTagGroups.set(Boolean(settings.groupSeparation));
shouldReplaceLinksOnForumPosts.set(Boolean(settings.replaceLinks));
shouldReplaceTextOfTagLinks.set(Boolean(settings.replaceLinkText));
});
})
});

View File

@@ -95,3 +95,31 @@ $warning-border: #95562c;
$input-background: #282e39;
$input-border: #575e6b;
}
@if environment.$current-site == 'tantabus' {
$background: #221117;
$text: #e0e0e0;
$text-gray: #bb90a6;
$link: #ee157a;
$link-hover: #b099dd;
$header: #811242;
$header-toolbar: #501e36;
$header-hover-background: #5d0d30;
$header-mobile-link-hover: #995470;
$footer: #2f1d26;
$footer-text: $text-gray;
$block-header: $header-toolbar;
$block-border: $header-toolbar;
$block-background: #2f1d26;
$block-background-alternate: #26171e;
$media-box-border: #573142;
$input-background: #392833;
$input-border: #6b5764;
}

View File

@@ -0,0 +1,9 @@
@use '$styles/booru-vars';
.block.communication {
.tag {
&:hover {
color: booru-vars.$resolved-tag-color;
}
}
}

View File

@@ -9,7 +9,7 @@
padding: 0 4px;
display: flex;
@if environment.$current-site == 'derpibooru' {
@if environment.$current-site == 'derpibooru' or environment.$current-site == 'tantabus' {
border: 1px solid colors.$tag-border;
line-height: 24px;
}

View File

@@ -20,6 +20,14 @@ export default defineConfig(() => {
__CURRENT_SITE_NAME__: JSON.stringify('Derpibooru'),
}
}),
SwapDefinedVariablesPlugin({
envVariable: 'SITE',
expectedValue: 'tantabus',
define: {
__CURRENT_SITE__: JSON.stringify('tantabus'),
__CURRENT_SITE_NAME__: JSON.stringify('Tantabus'),
}
}),
],
test: {
globals: true,