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

Merge pull request #86 from koloml/release/0.4.1

Release: 0.4.1
This commit is contained in:
2025-02-15 18:58:27 -05:00
committed by GitHub
81 changed files with 897 additions and 504 deletions

View File

@@ -204,7 +204,7 @@ ij_javascript_spaces_within_brackets = false
ij_javascript_spaces_within_catch_parentheses = false
ij_javascript_spaces_within_for_parentheses = false
ij_javascript_spaces_within_if_parentheses = false
ij_javascript_spaces_within_imports = false
ij_javascript_spaces_within_imports = true
ij_javascript_spaces_within_interpolation_expressions = false
ij_javascript_spaces_within_method_call_parentheses = false
ij_javascript_spaces_within_method_parentheses = false
@@ -221,7 +221,7 @@ ij_javascript_ternary_operation_wrap = off
ij_javascript_union_types_wrap = on_every_item
ij_javascript_use_chained_calls_group_indents = false
ij_javascript_use_double_quotes = true
ij_javascript_use_explicit_js_extension = auto
ij_javascript_use_explicit_js_extension = never
ij_javascript_use_import_type = auto
ij_javascript_use_path_mapping = always
ij_javascript_use_public_modifier = false
@@ -376,7 +376,7 @@ ij_typescript_spaces_within_brackets = false
ij_typescript_spaces_within_catch_parentheses = false
ij_typescript_spaces_within_for_parentheses = false
ij_typescript_spaces_within_if_parentheses = false
ij_typescript_spaces_within_imports = false
ij_typescript_spaces_within_imports = true
ij_typescript_spaces_within_interpolation_expressions = false
ij_typescript_spaces_within_method_call_parentheses = false
ij_typescript_spaces_within_method_parentheses = false
@@ -393,7 +393,7 @@ ij_typescript_ternary_operation_wrap = off
ij_typescript_union_types_wrap = on_every_item
ij_typescript_use_chained_calls_group_indents = false
ij_typescript_use_double_quotes = true
ij_typescript_use_explicit_js_extension = auto
ij_typescript_use_explicit_js_extension = never
ij_typescript_use_import_type = auto
ij_typescript_use_path_mapping = always
ij_typescript_use_public_modifier = false

View File

@@ -112,6 +112,9 @@ export async function buildStyle(buildOptions) {
}
},
emptyOutDir: false,
},
resolve: {
alias: makeAliases(buildOptions.rootDir)
}
});

View File

@@ -1,7 +1,7 @@
{
"name": "Furbooru Tagging Assistant",
"description": "Experimental extension with a set of tools to make the tagging faster and easier. Made specifically for Furbooru.",
"version": "0.4.0",
"version": "0.4.1",
"browser_specific_settings": {
"gecko": {
"id": "furbooru-tagging-assistant@thecore.city"

369
package-lock.json generated
View File

@@ -1,28 +1,28 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "furbooru-tagging-assistant",
"version": "0.4.0",
"version": "0.4.1",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.1",
"@fortawesome/fontawesome-free": "^6.7.2",
"lz-string": "^1.5.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.15.3",
"@sveltejs/kit": "^2.17.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/chrome": "^0.0.262",
"@types/chrome": "^0.0.304",
"cheerio": "^1.0.0",
"sass": "^1.83.4",
"svelte": "^5.18.0",
"sass": "^1.85.0",
"svelte": "^5.20.1",
"svelte-check": "^4.1.4",
"typescript": "^5.7.3",
"vite": "^6.0.7"
"vite": "^6.1.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -54,7 +54,9 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.1",
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
@@ -186,8 +188,206 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz",
"integrity": "sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz",
"integrity": "sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz",
"integrity": "sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz",
"integrity": "sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz",
"integrity": "sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz",
"integrity": "sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz",
"integrity": "sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz",
"integrity": "sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz",
"integrity": "sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz",
"integrity": "sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz",
"integrity": "sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz",
"integrity": "sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz",
"integrity": "sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz",
"integrity": "sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.24.0",
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz",
"integrity": "sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==",
"cpu": [
"x64"
],
@@ -199,7 +399,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.24.0",
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz",
"integrity": "sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==",
"cpu": [
"x64"
],
@@ -210,6 +412,48 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz",
"integrity": "sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz",
"integrity": "sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz",
"integrity": "sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sveltejs/adapter-auto": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-4.0.0.tgz",
@@ -232,11 +476,11 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.3.tgz",
"integrity": "sha512-yI1iF1ldC+nyMFuA1cV+IMOROaGu6ogW0WNlTuPU5/3tGk8pQlwOtlbguGY4Fsp8Qf6pQWmG/ZUXRDrhAt62dg==",
"version": "2.17.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.17.2.tgz",
"integrity": "sha512-Vypk02baf7qd3SOB1uUwUC/3Oka+srPo2J0a8YN3EfJypRshDkNx9HzNKjSmhOnGWwT+SSO06+N0mAb8iVTmTQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
@@ -248,8 +492,7 @@
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
"sirv": "^3.0.0",
"tiny-glob": "^0.2.9"
"sirv": "^3.0.0"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@@ -302,7 +545,9 @@
}
},
"node_modules/@types/chrome": {
"version": "0.0.262",
"version": "0.0.304",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.304.tgz",
"integrity": "sha512-ms9CLILU+FEMK7gcmgz/Mtn2E81YQWiMIzCFF8ktp98EVNIIfoqaDTD4+ailOCq1sGjbnEmfJxQ1FAsQtk5M3A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -674,15 +919,20 @@
"node": ">=8"
}
},
"node_modules/globalyzer": {
"version": "0.1.0",
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"license": "MIT"
},
"node_modules/globrex": {
"version": "0.1.2",
"dev": true,
"license": "MIT"
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/htmlparser2": {
"version": "9.1.0",
@@ -956,7 +1206,9 @@
}
},
"node_modules/rollup": {
"version": "4.24.0",
"version": "4.34.7",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.7.tgz",
"integrity": "sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -970,22 +1222,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"@rollup/rollup-android-arm-eabi": "4.34.7",
"@rollup/rollup-android-arm64": "4.34.7",
"@rollup/rollup-darwin-arm64": "4.34.7",
"@rollup/rollup-darwin-x64": "4.34.7",
"@rollup/rollup-freebsd-arm64": "4.34.7",
"@rollup/rollup-freebsd-x64": "4.34.7",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.7",
"@rollup/rollup-linux-arm-musleabihf": "4.34.7",
"@rollup/rollup-linux-arm64-gnu": "4.34.7",
"@rollup/rollup-linux-arm64-musl": "4.34.7",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.7",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.7",
"@rollup/rollup-linux-riscv64-gnu": "4.34.7",
"@rollup/rollup-linux-s390x-gnu": "4.34.7",
"@rollup/rollup-linux-x64-gnu": "4.34.7",
"@rollup/rollup-linux-x64-musl": "4.34.7",
"@rollup/rollup-win32-arm64-msvc": "4.34.7",
"@rollup/rollup-win32-ia32-msvc": "4.34.7",
"@rollup/rollup-win32-x64-msvc": "4.34.7",
"fsevents": "~2.3.2"
}
},
@@ -1006,7 +1261,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.83.4",
"version": "1.85.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz",
"integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1051,10 +1308,11 @@
}
},
"node_modules/svelte": {
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.18.0.tgz",
"integrity": "sha512-/Eb81lB8bVUxQPmkPVNBYrU9cZ544+9hE91ZUUXTMf7eWcGW84N1hS3gvv/XsUNOWLLg3IicXP2qa8W3KpTUHA==",
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.20.1.tgz",
"integrity": "sha512-aCARru2WTdzJl55Ws8SK27+kvQwd8tijl4kY7NoDUXUHtTHhxMa8Lf6QNZKmU7cuPu3jjFloDO1j5HgYJNIIWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1126,15 +1384,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tiny-glob": {
"version": "0.2.9",
"dev": true,
"license": "MIT",
"dependencies": {
"globalyzer": "0.1.0",
"globrex": "^0.1.2"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"dev": true,
@@ -1177,13 +1426,15 @@
}
},
"node_modules/vite": {
"version": "6.0.7",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz",
"integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.4.49",
"rollup": "^4.23.0"
"postcss": "^8.5.1",
"rollup": "^4.30.1"
},
"bin": {
"vite": "bin/vite.js"

View File

@@ -1,6 +1,6 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.4.0",
"version": "0.4.1",
"private": true,
"scripts": {
"build": "npm run build:popup && npm run build:extension",
@@ -12,19 +12,19 @@
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.15.3",
"@sveltejs/kit": "^2.17.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/chrome": "^0.0.262",
"@types/chrome": "^0.0.304",
"cheerio": "^1.0.0",
"sass": "^1.83.4",
"svelte": "^5.18.0",
"sass": "^1.85.0",
"svelte": "^5.20.1",
"svelte-check": "^4.1.4",
"typescript": "^5.7.3",
"vite": "^6.0.7"
"vite": "^6.1.0"
},
"type": "module",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.1",
"@fortawesome/fontawesome-free": "^6.7.2",
"lz-string": "^1.5.0"
}
}

4
src/app.d.ts vendored
View File

@@ -1,7 +1,7 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import type TagGroup from "$entities/TagGroup.ts";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import type TagGroup from "$entities/TagGroup";
declare global {
namespace App {

View File

@@ -1,9 +1,9 @@
<script>
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import {storagesCollection} from "$stores/debug.js";
import {goto} from "$app/navigation";
import {findDeepObject} from "$lib/utils.js";
import { storagesCollection } from "$stores/debug";
import { goto } from "$app/navigation";
import { findDeepObject } from "$lib/utils";
/** @type {string} */
export let storage;

View File

@@ -2,7 +2,7 @@
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
/**
* @type {import('$entities/TagGroup.ts').default}
* @type {import('$entities/TagGroup').default}
*/
export let group;

View File

@@ -1,5 +1,5 @@
<script>
/** @type {import('$entities/MaintenanceProfile.ts').default} */
/** @type {import('$entities/MaintenanceProfile').default} */
export let profile;
const sortedTagsList = profile.settings.tags.sort((a, b) => a.localeCompare(b));

View File

@@ -1,5 +1,5 @@
<script>
import {version} from "$app/environment";
import { version } from "$app/environment";
</script>
<footer>

View File

@@ -1,6 +1,6 @@
<script>
import SelectField from "$components/ui/forms/SelectField.svelte";
import { categories } from "$lib/booru/tag-categories.js";
import { categories } from "$lib/booru/tag-categories";
/** @type {string} */
export let value = '';

View File

@@ -1,4 +1,4 @@
import {initializeSiteHeader} from "$lib/components/SiteHeaderWrapper.js";
import { initializeSiteHeader } from "$lib/components/SiteHeaderWrapper";
const siteHeader = document.querySelector('.header');

View File

@@ -1,8 +1,8 @@
import {createMaintenancePopup} from "$lib/components/MaintenancePopup.js";
import {createMediaBoxTools} from "$lib/components/MediaBoxTools.js";
import {calculateMediaBoxesPositions, initializeMediaBox} from "$lib/components/MediaBoxWrapper.js";
import {createMaintenanceStatusIcon} from "$lib/components/MaintenanceStatusIcon.js";
import {createImageShowFullscreenButton} from "$lib/components/ImageShowFullscreenButton.js";
import { createMaintenancePopup } from "$lib/components/MaintenancePopup";
import { createMediaBoxTools } from "$lib/components/MediaBoxTools";
import { calculateMediaBoxesPositions, initializeMediaBox } from "$lib/components/MediaBoxWrapper";
import { createMaintenanceStatusIcon } from "$lib/components/MaintenanceStatusIcon";
import { createImageShowFullscreenButton } from "$lib/components/ImageShowFullscreenButton";
/** @type {NodeListOf<HTMLElement>} */
const mediaBoxes = document.querySelectorAll('.media-box');

View File

@@ -1,3 +1,3 @@
import {TagsForm} from "$lib/components/TagsForm.js";
import { TagsForm } from "$lib/components/TagsForm";
TagsForm.watchForEditors();

View File

@@ -1,4 +1,4 @@
import {watchTagDropdownsInTagsEditor, wrapTagDropdown} from "$lib/components/TagDropdownWrapper.js";
import { watchTagDropdownsInTagsEditor, wrapTagDropdown } from "$lib/components/TagDropdownWrapper";
for (let tagDropdownElement of document.querySelectorAll('.tag.dropdown')) {
wrapTagDropdown(tagDropdownElement);

View File

@@ -1,34 +0,0 @@
/**
* Build the map containing both real tags and their aliases.
*
* @param {string[]} realAndAliasedTags List combining aliases and tag names.
* @param {string[]} realTags List of actual tag names, excluding aliases.
*
* @return {Map<string, string>} Map where key is a tag or alias and value is an actual tag name.
*/
export function buildTagsAndAliasesMap(realAndAliasedTags, realTags) {
/** @type {Map<string, string>} */
const tagsAndAliasesMap = new Map();
for (let tagName of realTags) {
tagsAndAliasesMap.set(tagName, tagName);
}
let realTagName = null;
for (let tagNameOrAlias of realAndAliasedTags) {
if (tagsAndAliasesMap.has(tagNameOrAlias)) {
realTagName = tagNameOrAlias;
continue;
}
if (!realTagName) {
console.warn('No real tag found for the alias:', tagNameOrAlias);
continue;
}
tagsAndAliasesMap.set(tagNameOrAlias, realTagName);
}
return tagsAndAliasesMap;
}

View File

@@ -1,4 +1,4 @@
import PostParser from "$lib/booru/scraped/parsing/PostParser.js";
import PostParser from "$lib/booru/scraped/parsing/PostParser";
export default class ScrapedAPI {
/**

View File

@@ -1,5 +1,5 @@
import PageParser from "$lib/booru/scraped/parsing/PageParser.js";
import {buildTagsAndAliasesMap} from "$lib/booru/TagsUtils.js";
import PageParser from "$lib/booru/scraped/parsing/PageParser";
import { buildTagsAndAliasesMap } from "$lib/booru/tag-utils";
export default class PostParser extends PageParser {
/** @type {HTMLFormElement} */

View File

@@ -1,8 +1,8 @@
export class Token {
index;
value;
readonly index: number;
readonly value: string;
constructor(index, value) {
constructor(index: number, value: string) {
this.index = index;
this.value = value;
}
@@ -28,12 +28,9 @@ export class BoostToken extends Token {
}
export class QuotedTermToken extends Token {
/**
* @type {string}
*/
#quotedValue;
readonly #quotedValue: string;
constructor(index, value, quotedValue) {
constructor(index: number, value: string, quotedValue: string) {
super(index, value);
this.#quotedValue = quotedValue;
@@ -43,19 +40,11 @@ export class QuotedTermToken extends Token {
return QuotedTermToken.decode(this.#quotedValue);
}
/**
* @param {string} value
* @return {string}
*/
static decode(value) {
static decode(value: string): string {
return value.replace(/\\([\\"])/g, "$1");
}
/**
* @param {string} value
* @return {string}
*/
static encode(value) {
static encode(value: string): string {
return value.replace(/[\\"]/g, "\\$&");
}
}
@@ -63,6 +52,10 @@ export class QuotedTermToken extends Token {
export class TermToken extends Token {
}
type MatchResultCarry = {
match?: RegExpMatchArray | null
}
/**
* Search query tokenizer. Should mostly work for the cases of parsing and finding the selected term for
* auto-completion. Follows the rules described in the Philomena booru engine.
@@ -70,38 +63,28 @@ export class TermToken extends Token {
export class QueryLexer {
/**
* The original value to be parsed.
* @type {string}
*/
#value;
readonly #value: string;
/**
* Current position of the parser in the value.
* @type {number}
*/
#index = 0;
#index: number = 0;
/**
* @param {string} value
*/
constructor(value) {
constructor(value: string) {
this.#value = value;
}
/**
* Parse the query and get the list of tokens.
*
* @return {Token[]} List of tokens.
* @return List of tokens.
*/
parse() {
/** @type {Token[]} */
const tokens = [];
parse(): Token[] {
const tokens: Token[] = [];
const result: MatchResultCarry = {};
/**
* @type {{match: RegExpMatchArray|null}}
*/
const result = {};
let dirtyText;
let dirtyText: string;
while (this.#index < this.#value.length) {
if (this.#value[this.#index] === QueryLexer.#commaCharacter) {
@@ -111,26 +94,26 @@ export class QueryLexer {
}
if (this.#match(QueryLexer.#negotiationOperator, result)) {
tokens.push(new NotToken(this.#index, result.match[0]));
this.#index += result.match[0].length;
tokens.push(new NotToken(this.#index, result.match![0]));
this.#index += result.match![0].length;
continue;
}
if (this.#match(QueryLexer.#andOperator, result)) {
tokens.push(new AndToken(this.#index, result.match[0]));
this.#index += result.match[0].length;
tokens.push(new AndToken(this.#index, result.match![0]));
this.#index += result.match![0].length;
continue;
}
if (this.#match(QueryLexer.#orOperator, result)) {
tokens.push(new OrToken(this.#index, result.match[0]));
this.#index += result.match[0].length;
tokens.push(new OrToken(this.#index, result.match![0]));
this.#index += result.match![0].length;
continue;
}
if (this.#match(QueryLexer.#notOperator, result)) {
tokens.push(new NotToken(this.#index, result.match[0]));
this.#index += result.match[0].length;
tokens.push(new NotToken(this.#index, result.match![0]));
this.#index += result.match![0].length;
continue;
}
@@ -147,19 +130,19 @@ export class QueryLexer {
}
if (this.#match(QueryLexer.#boostOperator, result)) {
tokens.push(new BoostToken(this.#index, result.match[0]));
this.#index += result.match[0].length;
tokens.push(new BoostToken(this.#index, result.match![0]));
this.#index += result.match![0].length;
continue;
}
if (this.#match(QueryLexer.#whitespaces, result)) {
this.#index += result.match[0].length;
this.#index += result.match![0].length;
continue;
}
if (this.#match(QueryLexer.#quotedText, result)) {
tokens.push(new QuotedTermToken(this.#index, result.match[0], result.match[1]));
this.#index += result.match[0].length;
tokens.push(new QuotedTermToken(this.#index, result.match![0], result.match![1]));
this.#index += result.match![0].length;
continue;
}
@@ -180,25 +163,25 @@ export class QueryLexer {
/**
* Match the provided regular expression on the string with the current parser position.
*
* @param {RegExp} targetRegExp Target RegExp to parse with.
* @param {{match: any}} [resultCarrier] Object for passing the results into.
* @param targetRegExp Target RegExp to parse with.
* @param [resultCarrier] Object for passing the results into.
*
* @return {boolean} Is there a match?
* @return Is there a match?
*/
#match(targetRegExp, resultCarrier = {}) {
#match(targetRegExp: RegExp, resultCarrier: MatchResultCarry = {}): boolean {
return this.#matchAt(targetRegExp, this.#index, resultCarrier);
}
/**
* Match the provided regular expression in the string with the specific index.
*
* @param {RegExp} targetRegExp Target RegExp to parse with.
* @param {number} index Index to match the expression from.
* @param {{match: any}} [resultCarrier] Object for passing the results into.
* @param targetRegExp Target RegExp to parse with.
* @param index Index to match the expression from.
* @param [resultCarrier] Object for passing the results into.
*
* @return {boolean} Is there a match?
* @return Is there a match?
*/
#matchAt(targetRegExp, index, resultCarrier = {}) {
#matchAt(targetRegExp: RegExp, index: number, resultCarrier: MatchResultCarry = {}): boolean {
targetRegExp.lastIndex = index;
resultCarrier.match = this.#value.match(targetRegExp);
@@ -212,11 +195,10 @@ export class QueryLexer {
*
* @return {string} Matched text.
*/
#parseDirtyText(index) {
let resultValue = '';
#parseDirtyText(index: number): string {
let resultValue: string = '';
/** @type {{match: RegExpMatchArray|null}} */
const result = {match: null};
const result: MatchResultCarry = {match: null};
// Loop over
while (index < this.#value.length) {
@@ -226,8 +208,8 @@ export class QueryLexer {
}
if (this.#matchAt(QueryLexer.#dirtyTextContent, index, result)) {
resultValue += result.match[0];
index += result.match[0].length;
resultValue += result.match![0];
index += result.match![0].length;
continue;
}

View File

@@ -0,0 +1,33 @@
/**
* Build the map containing both real tags and their aliases.
*
* @param realAndAliasedTags List combining aliases and tag names.
* @param realTags List of actual tag names, excluding aliases.
*
* @return Map where key is a tag or alias and value is an actual tag name.
*/
export function buildTagsAndAliasesMap(realAndAliasedTags: string[], realTags: string[]): Map<string, string> {
const tagsAndAliasesMap: Map<string, string> = new Map();
for (const tagName of realTags) {
tagsAndAliasesMap.set(tagName, tagName);
}
let realTagName: string | null = null;
for (const tagNameOrAlias of realAndAliasedTags) {
if (tagsAndAliasesMap.has(tagNameOrAlias)) {
realTagName = tagNameOrAlias;
continue;
}
if (!realTagName) {
console.warn('No real tag found for the alias:', tagNameOrAlias);
continue;
}
tagsAndAliasesMap.set(tagNameOrAlias, realTagName);
}
return tagsAndAliasesMap;
}

View File

@@ -1,57 +0,0 @@
/**
* Helper class to read and write JSON objects to the local storage.
* @class
*/
class StorageHelper {
/**
* @type {import('@types/chrome').storage.StorageArea}
*/
#storageArea;
/**
* @param {import('@types/chrome').storage.StorageArea} storageArea
*/
constructor(storageArea) {
this.#storageArea = storageArea;
}
/**
* Read the following entry from the local storage as a JSON object.
*
* @param {string} key Key of the entry to read.
* @param {any} defaultValue Default value to return if the entry does not exist.
*
* @return {Promise<any>} The JSON object or the default value if the entry does not exist.
*/
async read(key, defaultValue = null) {
return (await this.#storageArea.get(key))?.[key] || defaultValue;
}
/**
* Write the following JSON object to the local storage.
*
* @param {string} key Key of the entry to write.
* @param {any} value JSON object to write.
*/
write(key, value) {
void this.#storageArea.set({[key]: value});
}
/**
* Subscribe to changes in the local storage.
* @param {function(Record<string, StorageChange>): void} callback
*/
subscribe(callback) {
this.#storageArea.onChanged.addListener(callback);
}
/**
* Unsubscribe from changes in the local storage.
* @param {function(Record<string, StorageChange>): void} callback
*/
unsubscribe(callback) {
this.#storageArea.onChanged.removeListener(callback);
}
}
export default StorageHelper;

View File

@@ -0,0 +1,53 @@
/**
* Changes subscribe function. It receives changes with old and new value for keys of the storage.
*/
export type StorageChangeSubscriber = (changes: Record<string, chrome.storage.StorageChange>) => void;
/**
* Helper class to read and write JSON objects to the local storage.
*/
export default class StorageHelper {
readonly #storageArea: chrome.storage.StorageArea;
constructor(storageArea: chrome.storage.StorageArea) {
this.#storageArea = storageArea;
}
/**
* Read the following entry from the local storage as a JSON object.
*
* @param key Key of the entry to read.
* @param defaultValue Default value to return if the entry does not exist.
*
* @return The JSON object or the default value if the entry does not exist.
*/
async read<Type = any, DefaultType = any>(key: string, defaultValue: DefaultType | null = null): Promise<Type | DefaultType> {
return (await this.#storageArea.get(key))?.[key] || defaultValue;
}
/**
* Write the following JSON object to the local storage.
*
* @param key Key of the entry to write.
* @param value Value to write.
*/
write(key: string, value: any): void {
void this.#storageArea.set({[key]: value});
}
/**
* Subscribe to changes in the local storage.
* @param callback Listener function to receive changes.
*/
subscribe(callback: StorageChangeSubscriber): void {
this.#storageArea.onChanged.addListener(callback);
}
/**
* Unsubscribe from changes in the local storage.
* @param callback Reference to the callback for unsubscribing.
*/
unsubscribe(callback: StorageChangeSubscriber): void {
this.#storageArea.onChanged.removeListener(callback);
}
}

View File

@@ -1,5 +1,5 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import MiscSettings from "$lib/extension/settings/MiscSettings.ts";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import MiscSettings from "$lib/extension/settings/MiscSettings";
export class FullscreenViewer extends BaseComponent {
/** @type {HTMLVideoElement} */
@@ -189,7 +189,7 @@ export class FullscreenViewer extends BaseComponent {
}
/**
* @param {import("$lib/extension/settings/MiscSettings.js").FullscreenViewerSize} size
* @param {import("$lib/extension/settings/MiscSettings").FullscreenViewerSize} size
*/
#onSizeResolved(size) {
this.#sizeSelectorElement.value = size;
@@ -325,7 +325,7 @@ export class FullscreenViewer extends BaseComponent {
static #minRequiredDistance = 50;
/**
* @type {Record<import("$lib/extension/settings/MiscSettings.js").FullscreenViewerSize, string>}
* @type {Record<import("$lib/extension/settings/MiscSettings").FullscreenViewerSize, string>}
*/
static #previewSizes = {
full: 'Full',

View File

@@ -1,13 +1,13 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import MiscSettings from "$lib/extension/settings/MiscSettings.ts";
import {FullscreenViewer} from "$lib/components/FullscreenViewer.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
import MiscSettings from "$lib/extension/settings/MiscSettings";
import { FullscreenViewer } from "$lib/components/FullscreenViewer";
export class ImageShowFullscreenButton extends BaseComponent {
/**
* @type {MediaBoxTools}
* @type {import('./MediaBoxTools').MediaBoxTools|null}
*/
#mediaBoxTools;
#mediaBoxTools= null;
#isFullscreenButtonEnabled = false;
build() {

View File

@@ -1,9 +1,15 @@
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI.js";
import {tagsBlacklist} from "$config/tags.ts";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI";
import { tagsBlacklist } from "$config/tags";
import { emitterAt } from "$lib/components/events/comms";
import {
eventActiveProfileChanged,
eventMaintenanceStateChanged,
eventTagsUpdated
} from "$lib/components/events/maintenance-popup-events";
class BlackListedTagsEncounteredError extends Error {
/**
@@ -27,7 +33,7 @@ export class MaintenancePopup extends BaseComponent {
/** @type {MaintenanceProfile|null} */
#activeProfile = null;
/** @type {import('$lib/components/MediaBoxTools.js').MediaBoxTools} */
/** @type {import('$lib/components/MediaBoxTools').MediaBoxTools} */
#mediaBoxTools = null;
/** @type {Set<string>} */
@@ -45,6 +51,8 @@ export class MaintenancePopup extends BaseComponent {
/** @type {number|null} */
#tagsSubmissionTimer = null;
#emitter = emitterAt(this);
/**
* @protected
*/
@@ -95,7 +103,8 @@ export class MaintenancePopup extends BaseComponent {
this.#activeProfile = activeProfile;
this.container.classList.toggle('is-active', activeProfile !== null);
this.#refreshTagsList();
this.emit('active-profile-changed', activeProfile);
this.#emitter.emit(eventActiveProfileChanged, activeProfile);
}
#refreshTagsList() {
@@ -181,7 +190,7 @@ export class MaintenancePopup extends BaseComponent {
}
this.#isPlanningToSubmit = true;
this.emit('maintenance-state-change', 'waiting');
this.#emitter.emit(eventMaintenanceStateChanged, 'waiting');
}
}
@@ -208,7 +217,7 @@ export class MaintenancePopup extends BaseComponent {
this.#isPlanningToSubmit = false;
this.#isSubmitting = true;
this.emit('maintenance-state-change', 'processing');
this.#emitter.emit(eventMaintenanceStateChanged, 'processing');
let maybeTagsAndAliasesAfterUpdate;
@@ -249,17 +258,18 @@ export class MaintenancePopup extends BaseComponent {
}
MaintenancePopup.#notifyAboutPendingSubmission(false);
this.emit('maintenance-state-change', 'failed');
this.#emitter.emit(eventMaintenanceStateChanged, 'failed');
this.#isSubmitting = false;
return;
}
if (maybeTagsAndAliasesAfterUpdate) {
this.emit('tags-updated', maybeTagsAndAliasesAfterUpdate);
this.#emitter.emit(eventTagsUpdated, maybeTagsAndAliasesAfterUpdate);
}
this.emit('maintenance-state-change', 'complete');
this.#emitter.emit(eventMaintenanceStateChanged, 'complete');
this.#tagsToAdd.clear();
this.#tagsToRemove.clear();

View File

@@ -1,8 +1,10 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
import { on } from "$lib/components/events/comms";
import { eventMaintenanceStateChanged } from "$lib/components/events/maintenance-popup-events";
export class MaintenanceStatusIcon extends BaseComponent {
/** @type {import('MediaBoxTools.js').MediaBoxTools} */
/** @type {import('./MediaBoxTools').MediaBoxTools} */
#mediaBoxTools;
build() {
@@ -16,7 +18,7 @@ export class MaintenanceStatusIcon extends BaseComponent {
throw new Error('Status icon element initialized outside of the media box!');
}
this.#mediaBoxTools.on('maintenance-state-change', this.#onMaintenanceStateChanged.bind(this));
on(this.#mediaBoxTools, eventMaintenanceStateChanged, this.#onMaintenanceStateChanged.bind(this));
}
/**

View File

@@ -1,9 +1,11 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import {MaintenancePopup} from "$lib/components/MaintenancePopup.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
import { MaintenancePopup } from "$lib/components/MaintenancePopup";
import { on } from "$lib/components/events/comms";
import { eventActiveProfileChanged } from "$lib/components/events/maintenance-popup-events";
export class MediaBoxTools extends BaseComponent {
/** @type {import('MediaBoxWrapper.js').MediaBoxWrapper|null} */
/** @type {import('./MediaBoxWrapper').MediaBoxWrapper|null} */
#mediaBox;
/** @type {MaintenancePopup|null} */
@@ -34,11 +36,11 @@ export class MediaBoxTools extends BaseComponent {
}
}
this.on('active-profile-changed', this.#onActiveProfileChanged.bind(this));
on(this, eventActiveProfileChanged, this.#onActiveProfileChanged.bind(this));
}
/**
* @param {CustomEvent<MaintenanceProfile|null>} profileChangedEvent
* @param {CustomEvent<import('$entities/MaintenanceProfile').default|null>} profileChangedEvent
*/
#onActiveProfileChanged(profileChangedEvent) {
this.container.classList.toggle('has-active-profile', profileChangedEvent.detail !== null);
@@ -52,7 +54,7 @@ export class MediaBoxTools extends BaseComponent {
}
/**
* @return {import('MediaBoxWrapper.js').MediaBoxWrapper|null}
* @return {import('./MediaBoxWrapper').MediaBoxWrapper|null}
*/
get mediaBox() {
return this.#mediaBox;
@@ -61,7 +63,7 @@ export class MediaBoxTools extends BaseComponent {
/**
* Create a maintenance popup element.
* @param {HTMLElement} childrenElements List of children elements to append to the component.
* @param {HTMLElement[]} childrenElements List of children elements to append to the component.
* @return {HTMLElement} The maintenance popup element.
*/
export function createMediaBoxTools(...childrenElements) {

View File

@@ -1,6 +1,8 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import {buildTagsAndAliasesMap} from "$lib/booru/TagsUtils.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
import { buildTagsAndAliasesMap } from "$lib/booru/tag-utils";
import { on } from "$lib/components/events/comms";
import { eventTagsUpdated } from "$lib/components/events/maintenance-popup-events";
export class MediaBoxWrapper extends BaseComponent {
#thumbnailContainer = null;
@@ -13,11 +15,11 @@ export class MediaBoxWrapper extends BaseComponent {
this.#thumbnailContainer = this.container.querySelector('.image-container');
this.#imageLinkElement = this.#thumbnailContainer.querySelector('a');
this.on('tags-updated', this.#onTagsUpdatedRefreshTagsAndAliases.bind(this));
on(this, eventTagsUpdated, this.#onTagsUpdatedRefreshTagsAndAliases.bind(this));
}
/**
* @param {CustomEvent<Map<string,string>>} tagsUpdatedEvent
* @param {CustomEvent<Map<string,string>|null>} tagsUpdatedEvent
*/
#onTagsUpdatedRefreshTagsAndAliases(tagsUpdatedEvent) {
const updatedMap = tagsUpdatedEvent.detail;

View File

@@ -1,6 +1,6 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {QueryLexer, QuotedTermToken, TermToken, Token} from "$lib/booru/search/QueryLexer.js";
import SearchSettings from "$lib/extension/settings/SearchSettings.ts";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { QueryLexer, QuotedTermToken, TermToken, Token } from "$lib/booru/search/QueryLexer";
import SearchSettings from "$lib/extension/settings/SearchSettings";
export class SearchWrapper extends BaseComponent {
/** @type {HTMLInputElement|null} */

View File

@@ -1,5 +1,5 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {SearchWrapper} from "$lib/components/SearchWrapper.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { SearchWrapper } from "$lib/components/SearchWrapper";
class SiteHeaderWrapper extends BaseComponent {
/** @type {SearchWrapper|null} */

View File

@@ -1,7 +1,7 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
import { getComponent } from "$lib/components/base/component-utils";
import CustomCategoriesResolver from "$lib/extension/CustomCategoriesResolver";
const isTagEditorProcessedKey = Symbol();

View File

@@ -1,5 +1,5 @@
import {BaseComponent} from "$lib/components/base/BaseComponent.js";
import {getComponent} from "$lib/components/base/ComponentUtils.js";
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { getComponent } from "$lib/components/base/component-utils";
export class TagsForm extends BaseComponent {
/**

View File

@@ -1,4 +1,4 @@
import {bindComponent} from "$lib/components/base/ComponentUtils.js";
import { bindComponent } from "$lib/components/base/component-utils";
/**
* @abstract
@@ -45,7 +45,6 @@ export class BaseComponent {
/**
* @return {HTMLElement}
* @protected
*/
get container() {
return this.#container;

View File

@@ -1,22 +0,0 @@
const instanceSymbol = Symbol('instance');
/**
* @param {HTMLElement} element
* @return {import('./BaseComponent.js').BaseComponent|null}
*/
export function getComponent(element) {
return element[instanceSymbol] || null;
}
/**
* Bind the component to the selected element.
* @param {HTMLElement} element The element to bind the component to.
* @param {import('./BaseComponent.js').BaseComponent} instance The component instance.
*/
export function bindComponent(element, instance) {
if (element[instanceSymbol]) {
throw new Error('The element is already bound to a component.');
}
element[instanceSymbol] = instance;
}

View File

@@ -0,0 +1,29 @@
import type { BaseComponent } from "$lib/components/base/BaseComponent";
const instanceSymbol = Symbol('instance');
interface ElementWithComponent extends HTMLElement {
[instanceSymbol]?: BaseComponent;
}
/**
* Get the component from the element, if there is one.
* @param {HTMLElement} element
* @return
*/
export function getComponent(element: ElementWithComponent): BaseComponent | null {
return element[instanceSymbol] || null;
}
/**
* Bind the component to the selected element.
* @param element The element to bind the component to.
* @param instance The component instance.
*/
export function bindComponent(element: ElementWithComponent, instance: BaseComponent): void {
if (element[instanceSymbol]) {
throw new Error('The element is already bound to a component.');
}
element[instanceSymbol] = instance;
}

View File

@@ -0,0 +1,92 @@
import type { MaintenancePopupEventsMap } from "$lib/components/events/maintenance-popup-events";
import { BaseComponent } from "$lib/components/base/BaseComponent";
interface EventsMapping extends MaintenancePopupEventsMap {
}
type EventCallback<EventDetails> = (event: CustomEvent<EventDetails>) => void;
type UnsubscribeFunction = () => void;
type ResolvableTarget = EventTarget | BaseComponent;
function resolveTarget(componentOrElement: ResolvableTarget): EventTarget {
if (componentOrElement instanceof BaseComponent) {
return componentOrElement.container;
}
return componentOrElement;
}
export function emit<Event extends keyof EventsMapping>(
targetOrComponent: ResolvableTarget,
event: Event,
details: EventsMapping[Event]
) {
const target = resolveTarget(targetOrComponent);
target.dispatchEvent(
new CustomEvent(event, {
detail: details,
bubbles: true
})
);
}
export function on<Event extends keyof EventsMapping>(
targetOrComponent: ResolvableTarget,
eventName: Event,
callback: EventCallback<EventsMapping[Event]>,
options: AddEventListenerOptions | null = null
): UnsubscribeFunction {
const target = resolveTarget(targetOrComponent);
const controller = new AbortController();
target.addEventListener(
eventName,
callback as EventListener,
{
signal: controller.signal,
once: options?.once
}
);
return () => controller.abort();
}
const onceOptions = {once: true};
export function once<Event extends keyof EventsMapping>(
targetOrComponent: ResolvableTarget,
eventName: Event,
callback: EventCallback<EventsMapping[Event]>
): UnsubscribeFunction {
return on(
targetOrComponent,
eventName,
callback,
onceOptions
);
}
class TargetedEmitter {
readonly #element: ResolvableTarget;
constructor(targetOrComponent: ResolvableTarget) {
this.#element = targetOrComponent;
}
emit<Event extends keyof EventsMapping>(eventName: Event, details: EventsMapping[Event]): void {
emit(this.#element, eventName, details);
}
on<Event extends keyof EventsMapping>(eventName: Event, callback: EventCallback<EventsMapping[Event]>, options: AddEventListenerOptions | null = null): UnsubscribeFunction {
return on(this.#element, eventName, callback, options);
}
once<Event extends keyof EventsMapping>(eventName: Event, callback: EventCallback<EventsMapping[Event]>): UnsubscribeFunction {
return once(this.#element, eventName, callback);
}
}
export function emitterAt(targetOrComponent: ResolvableTarget): TargetedEmitter {
return new TargetedEmitter(targetOrComponent);
}

View File

@@ -0,0 +1,13 @@
import type MaintenanceProfile from "$entities/MaintenanceProfile";
export const eventActiveProfileChanged = 'active-profile-changed';
export const eventMaintenanceStateChanged = 'maintenance-state-change';
export const eventTagsUpdated = 'tags-updated';
type MaintenanceState = 'processing' | 'failed' | 'complete' | 'waiting';
export interface MaintenancePopupEventsMap {
[eventActiveProfileChanged]: MaintenanceProfile | null;
[eventMaintenanceStateChanged]: MaintenanceState;
[eventTagsUpdated]: Map<string, string> | null;
}

View File

@@ -1,25 +1,24 @@
import StorageHelper from "$lib/browser/StorageHelper.js";
import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/StorageHelper";
export default class ConfigurationController {
/** @type {string} */
#configurationName;
readonly #configurationName: string;
/**
* @param {string} configurationName Name of the configuration to work with.
*/
constructor(configurationName) {
constructor(configurationName: string) {
this.#configurationName = configurationName;
}
/**
* Read the setting with the given name.
*
* @param {string} settingName Setting name.
* @param {any} [defaultValue] Default value to return if the setting does not exist. Defaults to `null`.
* @param settingName Setting name.
* @param [defaultValue] Default value to return if the setting does not exist. Defaults to `null`.
*
* @return {Promise<any|null>} The setting value or the default value if the setting does not exist.
* @return The setting value or the default value if the setting does not exist.
*/
async readSetting(settingName, defaultValue = null) {
async readSetting<Type = any, DefaultType = any>(settingName: string, defaultValue: DefaultType | null = null): Promise<Type | DefaultType> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
return settings[settingName] ?? defaultValue;
}
@@ -27,12 +26,12 @@ export default class ConfigurationController {
/**
* Write the given value to the setting.
*
* @param {string} settingName Setting name.
* @param {any} value Value to write.
* @param settingName Setting name.
* @param value Value to write.
*
* @return {Promise<void>}
*/
async writeSetting(settingName, value) {
async writeSetting(settingName: string, value: any): Promise<void> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
settings[settingName] = value;
@@ -44,10 +43,8 @@ export default class ConfigurationController {
* Delete the specific setting.
*
* @param {string} settingName Setting name to delete.
*
* @return {Promise<void>}
*/
async deleteSetting(settingName) {
async deleteSetting(settingName: string): Promise<void> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
delete settings[settingName];
@@ -63,9 +60,8 @@ export default class ConfigurationController {
*
* @return {function(): void} Unsubscribe function.
*/
subscribeToChanges(callback) {
/** @param {Record<string, StorageChange>} changes */
const changesSubscriber = changes => {
subscribeToChanges(callback: (record: Record<string, any>) => void): () => void {
const subscriber: StorageChangeSubscriber = changes => {
if (!changes[this.#configurationName]) {
return;
}
@@ -73,9 +69,9 @@ export default class ConfigurationController {
callback(changes[this.#configurationName].newValue);
}
ConfigurationController.#storageHelper.subscribe(changesSubscriber);
ConfigurationController.#storageHelper.subscribe(subscriber);
return () => ConfigurationController.#storageHelper.unsubscribe(changesSubscriber);
return () => ConfigurationController.#storageHelper.unsubscribe(subscriber);
}
static #storageHelper = new StorageHelper(chrome.storage.local);

View File

@@ -1,6 +1,6 @@
import type {TagDropdownWrapper} from "$lib/components/TagDropdownWrapper";
import TagGroup from "$entities/TagGroup.ts";
import {escapeRegExp} from "$lib/utils";
import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
import TagGroup from "$entities/TagGroup";
import { escapeRegExp } from "$lib/utils";
export default class CustomCategoriesResolver {
#tagCategories = new Map<string, string>();

View File

@@ -1,5 +1,5 @@
import StorageHelper from "$lib/browser/StorageHelper.js";
import type StorageEntity from "$lib/extension/base/StorageEntity.ts";
import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/StorageHelper";
import type StorageEntity from "$lib/extension/base/StorageEntity";
export default class EntitiesController {
static #storageHelper = new StorageHelper(chrome.storage.local);
@@ -71,7 +71,7 @@ export default class EntitiesController {
/**
* Watch the changes made to the storage and call the callback when the entity changes.
*/
const storageChangesSubscriber = (changes: Record<string, chrome.storage.StorageChange>) => {
const subscriber: StorageChangeSubscriber = changes => {
if (!changes[entityName]) {
return;
}
@@ -80,8 +80,8 @@ export default class EntitiesController {
.then(callback);
}
this.#storageHelper.subscribe(storageChangesSubscriber);
this.#storageHelper.subscribe(subscriber);
return () => this.#storageHelper.unsubscribe(storageChangesSubscriber);
return () => this.#storageHelper.unsubscribe(subscriber);
}
}

View File

@@ -1,7 +1,7 @@
import {validateImportedEntity} from "$lib/extension/transporting/validators.js";
import {exportEntityToObject} from "$lib/extension/transporting/exporters.ts";
import StorageEntity from "$lib/extension/base/StorageEntity.ts";
import {compressToEncodedURIComponent, decompressFromEncodedURIComponent} from "lz-string";
import { validateImportedEntity } from "$lib/extension/transporting/validators";
import { exportEntityToObject } from "$lib/extension/transporting/exporters";
import StorageEntity from "$lib/extension/base/StorageEntity";
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from "lz-string";
export default class EntitiesTransporter<EntityType> {
readonly #targetEntityConstructor: new (...any: any[]) => EntityType;

View File

@@ -1,4 +1,4 @@
import ConfigurationController from "$lib/extension/ConfigurationController.js";
import ConfigurationController from "$lib/extension/ConfigurationController";
export default class CacheableSettings<Fields> {
#controller: ConfigurationController;

View File

@@ -1,4 +1,4 @@
import EntitiesController from "$lib/extension/EntitiesController.js";
import EntitiesController from "$lib/extension/EntitiesController";
export default abstract class StorageEntity<SettingsType extends Object = {}> {
/**

View File

@@ -1,5 +1,4 @@
import StorageEntity from "$lib/extension/base/StorageEntity.ts";
import EntitiesController from "$lib/extension/EntitiesController.ts";
import StorageEntity from "$lib/extension/base/StorageEntity";
export interface MaintenanceProfileSettings {
name: string;

View File

@@ -1,4 +1,4 @@
import StorageEntity from "$lib/extension/base/StorageEntity.ts";
import StorageEntity from "$lib/extension/base/StorageEntity";
export interface TagGroupSettings {
name: string;

View File

@@ -1,5 +1,5 @@
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import CacheableSettings from "$lib/extension/base/CacheableSettings.ts";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import CacheableSettings from "$lib/extension/base/CacheableSettings";
interface MaintenanceSettingsFields {
activeProfile: string | null;

View File

@@ -1,4 +1,4 @@
import CacheableSettings from "$lib/extension/base/CacheableSettings.ts";
import CacheableSettings from "$lib/extension/base/CacheableSettings";
export type FullscreenViewerSize = 'small' | 'medium' | 'large' | 'full';

View File

@@ -1,4 +1,4 @@
import CacheableSettings from "$lib/extension/base/CacheableSettings.ts";
import CacheableSettings from "$lib/extension/base/CacheableSettings";
interface SearchSettingsFields {
suggestProperties: boolean;

View File

@@ -1,4 +1,4 @@
import StorageEntity from "$lib/extension/base/StorageEntity.ts";
import StorageEntity from "$lib/extension/base/StorageEntity";
type ExportersMap = {
[EntityName in keyof App.EntityNamesMap]: (entity: App.EntityNamesMap[EntityName]) => Record<string, any>

View File

@@ -1,39 +0,0 @@
/**
* Map of validators for each entity. Function should throw the error if validation failed.
* @type {Map<keyof App.EntityNamesMap|string, ((importedObject: Object) => void)>}
*/
const entitiesValidators = new Map([
['profiles', importedObject => {
if (importedObject.v !== 1) {
throw new Error('Unsupported version!');
}
if (
!importedObject.id
|| typeof importedObject.id !== "string"
|| !importedObject.name
|| typeof importedObject.name !== "string"
|| !importedObject.tags
|| !Array.isArray(importedObject.tags)
) {
throw new Error('Invalid profile format detected!');
}
}]
])
/**
* Validate the structure of the entity.
* @param {Object} importedObject Object imported from JSON.
* @param {string} entityName Name of the entity to validate. Should be loaded from the entity class.
* @throws {Error} Error in case validation failed with the reason stored in the message.
*/
export function validateImportedEntity(importedObject, entityName) {
if (!entitiesValidators.has(entityName)) {
console.error(`Trying to validate entity without the validator present! Entity name: ${entityName}`);
return;
}
entitiesValidators
.get(entityName)
.call(null, importedObject);
}

View File

@@ -0,0 +1,74 @@
import type StorageEntity from "$lib/extension/base/StorageEntity";
/**
* Base information on the object which should be present on every entity.
*/
interface BaseImportableObject {
/**
* Numeric version of the entity for upgrading.
*/
v: number;
/**
* Unique ID of the entity.
*/
id: string;
}
/**
* Utility type which combines base importable object and the entity type interfaces together. It strips away any types
* defined for the properties, since imported object can not be trusted and should be type-checked by the validators.
*/
type ImportableObject<EntityType extends StorageEntity> = { [ObjectKey in keyof BaseImportableObject]: any }
& { [SettingKey in keyof EntityType["settings"]]: any };
/**
* Function for validating the entities.
* @todo Probably would be better to replace the throw-catch method with some kind of result-error returning type.
* Errors are only properly definable in the JSDoc.
*/
type ValidationFunction<EntityType extends StorageEntity> = (importedObject: ImportableObject<EntityType>) => void;
/**
* Mapping of validation functions for all entities present in the extension. Key is a name of entity and value is a
* function which throws an error when validation is failed.
*/
type EntitiesValidationMap = {
[EntityKey in keyof App.EntityNamesMap]?: ValidationFunction<App.EntityNamesMap[EntityKey]>;
};
/**
* Map of validators for each entity. Function should throw the error if validation failed.
*/
const entitiesValidators: EntitiesValidationMap = {
profiles: importedObject => {
if (importedObject.v !== 1) {
throw new Error('Unsupported version!');
}
if (
!importedObject.id
|| typeof importedObject.id !== "string"
|| !importedObject.name
|| typeof importedObject.name !== "string"
|| !importedObject.tags
|| !Array.isArray(importedObject.tags)
) {
throw new Error('Invalid profile format detected!');
}
}
};
/**
* Validate the structure of the entity.
* @param importedObject Object imported from JSON.
* @param entityName Name of the entity to validate. Should be loaded from the entity class.
* @throws {Error} Error in case validation failed with the reason stored in the message.
*/
export function validateImportedEntity(importedObject: any, entityName: string) {
if (!entitiesValidators.hasOwnProperty(entityName)) {
console.error(`Trying to validate entity without the validator present! Entity name: ${entityName}`);
return;
}
entitiesValidators[entityName as keyof EntitiesValidationMap]!.call(null, importedObject);
}

View File

@@ -1,13 +1,13 @@
/**
* Traverse and find the object using the key path.
* @param {Object} targetObject Target object to traverse into.
* @param {string[]} path Path of keys to traverse deep into the object.
* @return {Object|null} Resulting object or null if nothing found (or target entry is not an object.
* @param targetObject Target object to traverse into.
* @param path Path of keys to traverse deep into the object.
* @return Resulting object or null if nothing found (or target entry is not an object).
*/
export function findDeepObject(targetObject, path) {
export function findDeepObject(targetObject: Record<string, any>, path: string[]): Object|null {
let result = targetObject;
for (let key of path) {
for (const key of path) {
if (!result || typeof result !== 'object') {
return null;
}
@@ -27,17 +27,15 @@ export function findDeepObject(targetObject, path) {
*
* Gathered from right here: https://stackoverflow.com/a/3561711/16048617. Because I don't want to introduce some
* library for that.
*
* @type {RegExp}
*/
const unsafeRegExpCharacters = /[/\-\\^$*+?.()|[\]{}]/g;
const unsafeRegExpCharacters: RegExp = /[/\-\\^$*+?.()|[\]{}]/g;
/**
* Escape all the RegExp syntax-related characters in the following value.
* @param {string} value Original value.
* @return {string} Resulting value with all needed characters escaped.
* @param value Original value.
* @return Resulting value with all needed characters escaped.
*/
export function escapeRegExp(value) {
export function escapeRegExp(value: string): string {
unsafeRegExpCharacters.lastIndex = 0;
return value.replace(unsafeRegExpCharacters, "\\$&");
}

View File

@@ -1,10 +1,10 @@
<script>
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { activeProfileStore, maintenanceProfilesStore } from "$stores/maintenance-profiles-store.js";
import { activeProfileStore, maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
/** @type {import('$entities/MaintenanceProfile.ts').default|undefined} */
/** @type {import('$entities/MaintenanceProfile').default|undefined} */
let activeProfile;
$: activeProfile = $maintenanceProfilesStore.find(profile => profile.id === $activeProfileStore);

View File

@@ -1,9 +1,9 @@
<script>
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { tagGroupsStore } from "$stores/tag-groups-store.js";
import { tagGroupsStore } from "$stores/tag-groups-store";
/** @type {import('$entities/TagGroup.ts').default[]} */
/** @type {import('$entities/TagGroup').default[]} */
let groups = [];
$: groups = $tagGroupsStore.sort((a, b) => a.settings.name.localeCompare(b.settings.name));

View File

@@ -4,21 +4,21 @@
import GroupView from "$components/features/GroupView.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { tagGroupsStore } from "$stores/tag-groups-store.js";
import { tagGroupsStore } from "$stores/tag-groups-store";
const groupId = $page.params.id;
/** @type {import('$entities/TagGroup.ts').default|null} */
/** @type {import('$entities/TagGroup').default|null} */
let group = null;
if (groupId==='new') {
if (groupId === 'new') {
goto('/features/groups/new/edit');
}
$: {
group = $tagGroupsStore.find(group => group.id===groupId) || null;
group = $tagGroupsStore.find(group => group.id === groupId) || null;
if (!group) {
console.warn(`Group ${ groupId } not found.`);
console.warn(`Group ${groupId} not found.`);
goto('/features/groups');
}
}

View File

@@ -3,10 +3,10 @@
import { page } from "$app/stores";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { tagGroupsStore } from "$stores/tag-groups-store.js";
import { tagGroupsStore } from "$stores/tag-groups-store";
const groupId = $page.params.id;
const targetGroup = $tagGroupsStore.find(group => group.id===groupId);
const targetGroup = $tagGroupsStore.find(group => group.id === groupId);
if (!targetGroup) {
void goto('/features/groups');

View File

@@ -1,6 +1,6 @@
<script>
import {goto} from "$app/navigation";
import {page} from "$app/stores";
import { goto } from "$app/navigation";
import { page } from "$app/stores";
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
@@ -8,9 +8,9 @@
import TextField from "$components/ui/forms/TextField.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import TagsEditor from "$components/web-components/TagsEditor.svelte";
import TagGroup from "$entities/TagGroup.ts";
import {tagGroupsStore} from "$stores/tag-groups-store.js";
import TagsEditor from "$components/tags/TagsEditor.svelte";
import TagGroup from "$entities/TagGroup";
import { tagGroupsStore } from "$stores/tag-groups-store";
const groupId = $page.params.id;
/** @type {TagGroup|null} */
@@ -23,10 +23,10 @@
let prefixesList = [];
let tagCategory = '';
if (groupId==='new') {
if (groupId === 'new') {
targetGroup = new TagGroup(crypto.randomUUID(), {});
} else {
targetGroup = $tagGroupsStore.find(group => group.id===groupId) || null;
targetGroup = $tagGroupsStore.find(group => group.id === groupId) || null;
if (targetGroup) {
groupName = targetGroup.settings.name;

View File

@@ -5,13 +5,13 @@
import FormControl from "$components/ui/forms/FormControl.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import TagGroup from "$entities/TagGroup.ts";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
import { tagGroupsStore } from "$stores/tag-groups-store.js";
import TagGroup from "$entities/TagGroup";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
import { tagGroupsStore } from "$stores/tag-groups-store";
const groupId = $page.params.id;
const groupTransporter = new EntitiesTransporter(TagGroup);
const group = $tagGroupsStore.find(group => group.id===groupId);
const group = $tagGroupsStore.find(group => group.id === groupId);
/** @type {string} */
let rawExportedGroup;

View File

@@ -5,9 +5,9 @@
import FormControl from "$components/ui/forms/FormControl.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import TagGroup from "$entities/TagGroup.ts";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
import { tagGroupsStore } from "$stores/tag-groups-store.js";
import TagGroup from "$entities/TagGroup";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
import { tagGroupsStore } from "$stores/tag-groups-store";
const groupTransporter = new EntitiesTransporter(TagGroup);
@@ -42,11 +42,11 @@
} catch (error) {
errorMessage = error instanceof Error
? error.message
:'Unknown error';
: 'Unknown error';
}
if (candidateGroup) {
existingGroup = $tagGroupsStore.find(group => group.id===candidateGroup?.id) ?? null;
existingGroup = $tagGroupsStore.find(group => group.id === candidateGroup?.id) ?? null;
}
}
@@ -66,7 +66,7 @@
}
const clonedProfile = new TagGroup(crypto.randomUUID(), candidateGroup.settings);
clonedProfile.settings.name += ` (Clone ${ new Date().toISOString() })`;
clonedProfile.settings.name += ` (Clone ${new Date().toISOString()})`;
clonedProfile.save().then(() => {
goto(`/features/groups`);
});

View File

@@ -2,9 +2,9 @@
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import MenuRadioItem from "$components/ui/menu/MenuRadioItem.svelte";
import {activeProfileStore, maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
import { activeProfileStore, maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
/** @type {import('$entities/MaintenanceProfile.ts').default[]} */
/** @type {import('$entities/MaintenanceProfile').default[]} */
let profiles = [];
$: profiles = $maintenanceProfilesStore.sort((a, b) => a.settings.name.localeCompare(b.settings.name));

View File

@@ -1,22 +1,22 @@
<script>
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import {page} from "$app/stores";
import {goto} from "$app/navigation";
import {activeProfileStore, maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
import ProfileView from "$components/maintenance/ProfileView.svelte";
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { activeProfileStore, maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
import ProfileView from "$components/features/ProfileView.svelte";
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
const profileId = $page.params.id;
/** @type {import('$entities/MaintenanceProfile.ts').default|null} */
/** @type {import('$entities/MaintenanceProfile').default|null} */
let profile = null;
if (profileId==='new') {
if (profileId === 'new') {
goto('/features/maintenance/new/edit');
}
$: {
const resolvedProfile = $maintenanceProfilesStore.find(profile => profile.id===profileId);
const resolvedProfile = $maintenanceProfilesStore.find(profile => profile.id === profileId);
if (resolvedProfile) {
profile = resolvedProfile;
@@ -26,14 +26,14 @@
}
}
let isActiveProfile = $activeProfileStore===profileId;
let isActiveProfile = $activeProfileStore === profileId;
$: {
if (isActiveProfile && $activeProfileStore!==profileId) {
if (isActiveProfile && $activeProfileStore !== profileId) {
$activeProfileStore = profileId;
}
if (!isActiveProfile && $activeProfileStore===profileId) {
if (!isActiveProfile && $activeProfileStore === profileId) {
$activeProfileStore = null;
}
}

View File

@@ -3,10 +3,10 @@
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { page } from "$app/stores";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store.js";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
const profileId = $page.params.id;
const targetProfile = $maintenanceProfilesStore.find(profile => profile.id===profileId);
const targetProfile = $maintenanceProfilesStore.find(profile => profile.id === profileId);
if (!targetProfile) {
void goto('/features/maintenance');

View File

@@ -1,14 +1,14 @@
<script>
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import TagsEditor from "$components/web-components/TagsEditor.svelte";
import TagsEditor from "$components/tags/TagsEditor.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
import TextField from "$components/ui/forms/TextField.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import {page} from "$app/stores";
import {goto} from "$app/navigation";
import {maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
import MaintenanceProfile from "$entities/MaintenanceProfile";
/** @type {string} */
let profileId = $page.params.id;

View File

@@ -1,13 +1,13 @@
<script>
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store.js";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
import MaintenanceProfile from "$entities/MaintenanceProfile";
const profileId = $page.params.id;
const profile = $maintenanceProfilesStore.find(profile => profile.id === profileId);

View File

@@ -2,12 +2,12 @@
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import FormControl from "$components/ui/forms/FormControl.svelte";
import ProfileView from "$components/maintenance/ProfileView.svelte";
import {maintenanceProfilesStore} from "$stores/maintenance-profiles-store.js";
import {goto} from "$app/navigation";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter.ts";
import ProfileView from "$components/features/ProfileView.svelte";
import { maintenanceProfilesStore } from "$stores/maintenance-profiles-store";
import { goto } from "$app/navigation";
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);

View File

@@ -1,7 +1,7 @@
<script>
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import {storagesCollection} from "$stores/debug.js";
import { storagesCollection } from "$stores/debug";
</script>
<Menu>

View File

@@ -1,7 +1,7 @@
<script>
import StorageViewer from "$components/debugging/StorageViewer.svelte";
import {page} from "$app/stores";
import {goto} from "$app/navigation";
import { page } from "$app/stores";
import { goto } from "$app/navigation";
let pathString = '';
/** @type {string[]} */

View File

@@ -4,7 +4,7 @@
import FormContainer from "$components/ui/forms/FormContainer.svelte";
import FormControl from "$components/ui/forms/FormControl.svelte";
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
import {fullScreenViewerEnabled} from "$stores/misc-preferences.js";
import { fullScreenViewerEnabled } from "$stores/misc-preferences";
</script>
<Menu>

View File

@@ -6,7 +6,7 @@
import {
searchPropertiesSuggestionsEnabled,
searchPropertiesSuggestionsPosition
} from "$stores/search-preferences.js";
} from "$stores/search-preferences";
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
import SelectField from "$components/ui/forms/SelectField.svelte";

View File

@@ -4,7 +4,7 @@
import FormControl from "$components/ui/forms/FormControl.svelte";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { stripBlacklistedTagsEnabled } from "$stores/maintenance-preferences.ts";
import { stripBlacklistedTagsEnabled } from "$stores/maintenance-preferences";
</script>
<Menu>

View File

@@ -1,4 +1,4 @@
import {writable} from "svelte/store";
import { writable } from "svelte/store";
/**
* This is readable version of storages. Any changes made to these objects will not be sent to the local storage.

View File

@@ -1,5 +1,5 @@
import {writable} from "svelte/store";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts";
import { writable } from "svelte/store";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
export const stripBlacklistedTagsEnabled = writable(true);

View File

@@ -1,6 +1,6 @@
import {writable} from "svelte/store";
import MaintenanceProfile from "$entities/MaintenanceProfile.ts";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts";
import { writable } from "svelte/store";
import MaintenanceProfile from "$entities/MaintenanceProfile";
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
/**
* Store for working with maintenance profiles in the Svelte popup.

View File

@@ -1,5 +1,5 @@
import {writable} from "svelte/store";
import MiscSettings from "$lib/extension/settings/MiscSettings.ts";
import { writable } from "svelte/store";
import MiscSettings from "$lib/extension/settings/MiscSettings";
export const fullScreenViewerEnabled = writable(true);

View File

@@ -1,5 +1,5 @@
import {writable} from "svelte/store";
import SearchSettings from "$lib/extension/settings/SearchSettings.ts";
import { writable } from "svelte/store";
import SearchSettings from "$lib/extension/settings/SearchSettings";
export const searchPropertiesSuggestionsEnabled = writable(false);

View File

@@ -1,5 +1,5 @@
import {writable} from "svelte/store";
import TagGroup from "$entities/TagGroup.ts";
import { writable } from "svelte/store";
import TagGroup from "$entities/TagGroup";
/** @type {import('svelte/store').Writable<TagGroup[]>} */
export const tagGroupsStore = writable([]);

View File

@@ -0,0 +1,8 @@
$background-color: var(--background-color);
$media-border: var(--media-border);
$media-box-color: var(--media-box-color);
// These variables are defined dynamically based on the category of the tag
$resolved-tag-background: var(--tag-background);
$resolved-tag-border: var(--tag-border);
$resolved-tag-color: var(--tag-color);

View File

@@ -1,4 +1,5 @@
@use '../colors';
@use '$styles/colors';
@use '$styles/booru-vars';
// This will fix wierd misplacing of the modified media boxes in the listing.
.js-resizable-media-container {
@@ -18,22 +19,22 @@
top: -1px;
bottom: 0;
z-index: 8;
background: colors.$footer;
border-top: 23px solid colors.$media-box-border;
background: booru-vars.$background-color;
border-top: 23px solid booru-vars.$media-box-color;
}
&:before {
right: calc(100% - 1px);
left: -50%;
border-left: 1px solid colors.$media-box-border;
box-shadow: colors.$footer -10px 0 10px;
border-left: booru-vars.$media-border;
box-shadow: booru-vars.$background-color -10px 0 10px;
}
&:after {
left: calc(100% - 1px);
right: -50%;
border-right: 1px solid colors.$media-box-border;
box-shadow: colors.$footer 10px 0 10px;
border-right: booru-vars.$media-border;
box-shadow: booru-vars.$background-color 10px 0 10px;
}
}
@@ -45,9 +46,12 @@
left: -50%;
right: -50%;
z-index: 8;
background: colors.$footer;
border: 1px solid colors.$media-box-border;
border-top: 0;
background: booru-vars.$background-color;
border: {
left: booru-vars.$media-border;
right: booru-vars.$media-border;
bottom: booru-vars.$media-border;
};
.tags-list {
display: flex;
@@ -62,17 +66,12 @@
.tag {
cursor: pointer;
padding: 0 6px;
padding: 5px;
user-select: none;
&:hover {
background: colors.$tag-text;
color: colors.$tag-background;
}
&[data-tag-category=error]:hover {
background: colors.$tag-error-text;
color: colors.$tag-error-background;
background: booru-vars.$resolved-tag-color;
color: booru-vars.$resolved-tag-background;
}
&.is-missing:not(.is-added),

View File

@@ -10,6 +10,6 @@
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
"allowImportingTsExtensions": false,
}
}