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

26 Commits

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
20 changed files with 579 additions and 178 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.4",
"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": {

263
package-lock.json generated
View File

@@ -1,30 +1,30 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.5.4",
"version": "0.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "furbooru-tagging-assistant",
"version": "0.5.4",
"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.50.2",
"@sveltejs/kit": "^2.53.0",
"amd-lite": "^1.0.1",
"lz-string": "^1.5.0",
"sass": "^1.97.3",
"svelte": "^5.50.0"
"svelte": "^5.53.3"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@types/chrome": "^0.1.36",
"@types/node": "^25.2.2",
"@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.0.0",
"svelte-check": "^4.3.6",
"jsdom": "^28.1.0",
"svelte-check": "^4.4.3",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.18"
@@ -38,33 +38,26 @@
"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": {
@@ -729,9 +722,9 @@
}
},
"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.50.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz",
"integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==",
"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,12 +1153,11 @@
"@types/cookie": "^0.6.0",
"acorn": "^8.14.1",
"cookie": "^0.6.0",
"devalue": "^5.6.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": "^3.0.0",
"sirv": "^3.0.0"
},
@@ -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": {
@@ -1240,9 +1232,9 @@
}
},
"node_modules/@types/chrome": {
"version": "0.1.36",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.36.tgz",
"integrity": "sha512-BvHbuyGttYXnGt5Gpwa4769KIinKHY1iLjlAPrrMBS2GI9m/XNMPtdsq0NgQalyuUdxvlMN/0OyGw0shFVIoUQ==",
"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,15 +1278,21 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.2.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
"integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==",
"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.18",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
@@ -1465,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"
}
@@ -1683,31 +1682,21 @@
}
},
"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": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
@@ -1775,9 +1764,9 @@
}
},
"node_modules/devalue": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
"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": {
@@ -2167,16 +2156,17 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz",
"integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
"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.31",
"@asamuzakjp/dom-selector": "^6.7.6",
"@asamuzakjp/dom-selector": "^6.8.1",
"@bramus/specificity": "^2.4.2",
"@exodus/bytes": "^1.11.0",
"cssstyle": "^5.3.7",
"cssstyle": "^6.0.1",
"data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
@@ -2187,7 +2177,7 @@
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
"undici": "^7.20.0",
"undici": "^7.21.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
@@ -2253,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",
@@ -2318,6 +2318,7 @@
},
"node_modules/mri": {
"version": "1.2.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -2563,6 +2564,7 @@
},
"node_modules/sade": {
"version": "1.8.1",
"dev": true,
"license": "MIT",
"dependencies": {
"mri": "^1.1.0"
@@ -2705,20 +2707,21 @@
}
},
"node_modules/svelte": {
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.50.0.tgz",
"integrity": "sha512-FR9kTLmX5i0oyeQ5j/+w8DuagIkQ7MWMuPpPVioW2zx9Dw77q+1ufLzF1IqNtcTXPRnIIio4PlasliVn43OnbQ==",
"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.6.2",
"devalue": "^5.6.3",
"esm-env": "^1.2.1",
"esrap": "^2.2.2",
"is-reference": "^3.0.3",
@@ -2731,9 +2734,9 @@
}
},
"node_modules/svelte-check": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.6.tgz",
"integrity": "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q==",
"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": {
@@ -2950,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"
},

View File

@@ -1,10 +1,12 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.5.4",
"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.4",
"@types/chrome": "^0.1.36",
"@types/node": "^25.2.2",
"@vitest/coverage-v8": "^4.0.18",
"cheerio": "^1.2.0",
"cross-env": "^10.1.0",
"jsdom": "^28.0.0",
"svelte-check": "^4.3.6",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.18"
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.50.2",
"@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.3",
"svelte": "^5.50.0"
"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

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

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

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

@@ -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,