mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2026-02-06 23:32:58 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab625d0181 | |||
| c59d8f55f0 | |||
| 8dfc5f49f9 | |||
| 2ecd37512f | |||
| c8ff80d445 | |||
| 38cbd725d9 | |||
| 26f09c7c46 | |||
| 64be6a6e15 | |||
| cb22b2deab | |||
| 5c5e0812dc | |||
| 70129d7a0e | |||
| 5fd6dee999 | |||
| ec41ba5030 | |||
| 55624285e1 | |||
| b97255ccd6 | |||
| ef76560bfb | |||
| faa909a0db | |||
| 3955e3191e | |||
| 17dab5854c | |||
| a20632e58e | |||
| 5f4a1a6c00 | |||
| 48fc58f042 | |||
| 8356956b2e | |||
| 3833cada1e |
BIN
.github/assets/colors-in-editor.png
vendored
Normal file
BIN
.github/assets/colors-in-editor.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
BIN
.github/assets/groups-showcase-0.png
vendored
Normal file
BIN
.github/assets/groups-showcase-0.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 210 KiB |
BIN
.github/assets/groups-showcase-1.png
vendored
Normal file
BIN
.github/assets/groups-showcase-1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
63
.github/workflows/build-extensions.yml
vendored
Normal file
63
.github/workflows/build-extensions.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Build Extensions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build-extensions:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
site: [furbooru, derpibooru]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build extension for ${{ matrix.site }}
|
||||
run: |
|
||||
if [ "${{ matrix.site }}" = "derpibooru" ]; then
|
||||
npm run build:derpibooru
|
||||
else
|
||||
npm run build
|
||||
fi
|
||||
|
||||
- name: Create extension zip
|
||||
run: |
|
||||
cd build
|
||||
zip -r "../${{ matrix.site }}-tagging-assistant-extension.zip" .
|
||||
|
||||
- name: Upload extension artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.site }}-tagging-assistant-extension
|
||||
path: ${{ matrix.site }}-tagging-assistant-extension.zip
|
||||
retention-days: 30
|
||||
|
||||
create-release-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-extensions
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Create combined artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: all-extensions
|
||||
path: artifacts/
|
||||
retention-days: 90
|
||||
@@ -51,6 +51,7 @@ function wrapScriptIntoIIFE() {
|
||||
function makeAliases(rootDir) {
|
||||
return {
|
||||
"$config": path.resolve(rootDir, 'src/config'),
|
||||
"$content": path.resolve(rootDir, 'src/content'),
|
||||
"$lib": path.resolve(rootDir, 'src/lib'),
|
||||
"$entities": path.resolve(rootDir, 'src/lib/extension/entities'),
|
||||
"$styles": path.resolve(rootDir, 'src/styles'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Furbooru Tagging Assistant",
|
||||
"description": "Small experimental extension for slightly quicker tagging experience. Furbooru Edition.",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.3",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "furbooru-tagging-assistant@thecore.city"
|
||||
|
||||
2168
package-lock.json
generated
2168
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "furbooru-tagging-assistant",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build:popup && npm run build:extension",
|
||||
@@ -13,26 +13,26 @@
|
||||
"test:watch": "vitest watch --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.21.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/chrome": "^0.0.326",
|
||||
"@types/node": "^22.15.29",
|
||||
"@vitest/coverage-v8": "^3.2.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"cross-env": "^10.0.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"sass": "^1.89.1",
|
||||
"svelte": "^5.33.14",
|
||||
"svelte-check": "^4.2.1",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.0"
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.3",
|
||||
"@types/chrome": "^0.1.32",
|
||||
"@types/node": "^25.0.3",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"cheerio": "^1.1.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"svelte-check": "^4.3.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.49.4",
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"amd-lite": "^1.0.1",
|
||||
"lz-string": "^1.5.0"
|
||||
"lz-string": "^1.5.0",
|
||||
"sass": "^1.97.2",
|
||||
"svelte": "^5.46.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
let { profile }: ProfileViewProps = $props();
|
||||
|
||||
const sortedTagsList = profile.settings.tags.sort((a, b) => a.localeCompare(b));
|
||||
const sortedTagsList = $derived(profile.settings.tags.sort((a, b) => a.localeCompare(b)));
|
||||
</script>
|
||||
|
||||
<div class="block">
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { PLUGIN_NAME } from "$lib/constants";
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<a href="/">{__CURRENT_SITE_NAME__} Tagging Assistant</a>
|
||||
<a href="/">{PLUGIN_NAME}</a>
|
||||
</header>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import SelectField from "$components/ui/forms/SelectField.svelte";
|
||||
import { categories } from "$lib/booru/tag-categories";
|
||||
import { categories } from "$config/tags";
|
||||
|
||||
interface TagCategorySelectFieldProps {
|
||||
value?: string;
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
/**
|
||||
* List of categories defined by the sites.
|
||||
*/
|
||||
export const categories: string[] = [
|
||||
'rating',
|
||||
'spoiler',
|
||||
'origin',
|
||||
'oc',
|
||||
'error',
|
||||
'character',
|
||||
'content-official',
|
||||
'content-fanmade',
|
||||
'species',
|
||||
'body-type',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const tagsBlacklist: string[] = (__CURRENT_SITE__ === 'furbooru' ? [
|
||||
"anthro art",
|
||||
"anthro artist",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import MiscSettings, { type FullscreenViewerSize } from "$lib/extension/settings/MiscSettings";
|
||||
import { emit, on } from "$lib/components/events/comms";
|
||||
import { EVENT_SIZE_LOADED } from "$lib/components/events/fullscreen-viewer-events";
|
||||
import { emit, on } from "$content/components/events/comms";
|
||||
import { EVENT_SIZE_LOADED } from "$content/components/events/fullscreen-viewer-events";
|
||||
|
||||
export class FullscreenViewer extends BaseComponent {
|
||||
#videoElement: HTMLVideoElement = document.createElement('video');
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import MiscSettings from "$lib/extension/settings/MiscSettings";
|
||||
import { FullscreenViewer } from "$lib/components/FullscreenViewer";
|
||||
import type { MediaBoxTools } from "$lib/components/MediaBoxTools";
|
||||
import { FullscreenViewer } from "$content/components/FullscreenViewer";
|
||||
import type { MediaBoxTools } from "$content/components/MediaBoxTools";
|
||||
|
||||
export class ImageShowFullscreenButton extends BaseComponent {
|
||||
#mediaBoxTools: MediaBoxTools | null = null;
|
||||
@@ -1,16 +1,16 @@
|
||||
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 { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI";
|
||||
import { tagsBlacklist } from "$config/tags";
|
||||
import { emitterAt } from "$lib/components/events/comms";
|
||||
import { emitterAt } from "$content/components/events/comms";
|
||||
import {
|
||||
EVENT_ACTIVE_PROFILE_CHANGED,
|
||||
EVENT_MAINTENANCE_STATE_CHANGED,
|
||||
EVENT_TAGS_UPDATED
|
||||
} from "$lib/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxTools } from "$lib/components/MediaBoxTools";
|
||||
} from "$content/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxTools } from "$content/components/MediaBoxTools";
|
||||
|
||||
class BlackListedTagsEncounteredError extends Error {
|
||||
constructor(tagName: string) {
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { on } from "$lib/components/events/comms";
|
||||
import { EVENT_MAINTENANCE_STATE_CHANGED } from "$lib/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxTools } from "$lib/components/MediaBoxTools";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { on } from "$content/components/events/comms";
|
||||
import { EVENT_MAINTENANCE_STATE_CHANGED } from "$content/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxTools } from "$content/components/MediaBoxTools";
|
||||
|
||||
export class MaintenanceStatusIcon extends BaseComponent {
|
||||
#mediaBoxTools: MediaBoxTools | null = null;
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { EVENT_ACTIVE_PROFILE_CHANGED } from "$lib/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxWrapper } from "$lib/components/MediaBoxWrapper";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { MaintenancePopup } from "$content/components/MaintenancePopup";
|
||||
import { on } from "$content/components/events/comms";
|
||||
import { EVENT_ACTIVE_PROFILE_CHANGED } from "$content/components/events/maintenance-popup-events";
|
||||
import type { MediaBoxWrapper } from "$content/components/MediaBoxWrapper";
|
||||
import type MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
|
||||
export class MediaBoxTools extends BaseComponent {
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { buildTagsAndAliasesMap } from "$lib/booru/tag-utils";
|
||||
import { on } from "$lib/components/events/comms";
|
||||
import { EVENT_TAGS_UPDATED } from "$lib/components/events/maintenance-popup-events";
|
||||
import { on } from "$content/components/events/comms";
|
||||
import { EVENT_TAGS_UPDATED } from "$content/components/events/maintenance-popup-events";
|
||||
|
||||
export class MediaBoxWrapper extends BaseComponent {
|
||||
#thumbnailContainer: HTMLElement | null = null;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import CustomCategoriesResolver from "$lib/extension/CustomCategoriesResolver";
|
||||
import { on } from "$lib/components/events/comms";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$lib/components/events/tags-form-events";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
|
||||
import { on } from "$content/components/events/comms";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$content/components/events/tags-form-events";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$content/components/events/tag-dropdown-events";
|
||||
import type TagGroup from "$entities/TagGroup";
|
||||
|
||||
const categoriesResolver = new CustomCategoriesResolver();
|
||||
@@ -148,11 +148,10 @@ export class TagDropdownWrapper extends BaseComponent {
|
||||
profileSpecificButtonText = `Remove from profile "${profileName}"`;
|
||||
}
|
||||
|
||||
// Derpibooru has icons in dropdown. Make sure to only edit text and keep the icon untouched. Also, add the space
|
||||
// before the text to make space between text and icon.
|
||||
if (__CURRENT_SITE__ === 'derpibooru' && this.#toggleOnExistingButton.lastChild instanceof Text) {
|
||||
if (this.#toggleOnExistingButton.lastChild instanceof Text) {
|
||||
this.#toggleOnExistingButton.lastChild.textContent = ` ${profileSpecificButtonText}`;
|
||||
} else {
|
||||
// Just in case last child is missing, then update the text on the full element.
|
||||
this.#toggleOnExistingButton.textContent = profileSpecificButtonText;
|
||||
}
|
||||
|
||||
@@ -251,16 +250,11 @@ export class TagDropdownWrapper extends BaseComponent {
|
||||
dropdownLink.href = '#';
|
||||
dropdownLink.className = 'tag__dropdown__link';
|
||||
|
||||
// Derpibooru has an icon in dropdown item. Create the icon and place the text with additional space in front of it.
|
||||
if (__CURRENT_SITE__ === 'derpibooru') {
|
||||
const dropdownLinkIcon = document.createElement('i');
|
||||
dropdownLinkIcon.classList.add('fa', 'fa-tags');
|
||||
const dropdownLinkIcon = document.createElement('i');
|
||||
dropdownLinkIcon.classList.add('fa', 'fa-tags');
|
||||
|
||||
dropdownLink.textContent = ` ${text}`;
|
||||
dropdownLink.insertAdjacentElement('afterbegin', dropdownLinkIcon);
|
||||
} else {
|
||||
dropdownLink.textContent = text;
|
||||
}
|
||||
dropdownLink.textContent = ` ${text}`;
|
||||
dropdownLink.insertAdjacentElement('afterbegin', dropdownLinkIcon);
|
||||
|
||||
dropdownLink.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { emit, on, type UnsubscribeFunction } from "$lib/components/events/comms";
|
||||
import { EVENT_FETCH_COMPLETE } from "$lib/components/events/booru-events";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$lib/components/events/tags-form-events";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { emit, on, type UnsubscribeFunction } from "$content/components/events/comms";
|
||||
import { EVENT_FETCH_COMPLETE } from "$content/components/events/booru-events";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$content/components/events/tags-form-events";
|
||||
|
||||
export class TagsForm extends BaseComponent {
|
||||
protected init() {
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import type TagGroup from "$entities/TagGroup";
|
||||
import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
|
||||
import { on } from "$lib/components/events/comms";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$lib/components/events/tags-form-events";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
|
||||
import type { TagDropdownWrapper } from "$content/components/TagDropdownWrapper";
|
||||
import { on } from "$content/components/events/comms";
|
||||
import { EVENT_FORM_EDITOR_UPDATED } from "$content/components/events/tags-form-events";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$content/components/events/tag-dropdown-events";
|
||||
import TagSettings from "$lib/extension/settings/TagSettings";
|
||||
|
||||
export class TagsListBlock extends BaseComponent {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { bindComponent } from "$lib/components/base/component-utils";
|
||||
import { bindComponent } from "$content/components/base/component-utils";
|
||||
|
||||
type ComponentEventListener<EventName extends keyof HTMLElementEventMap> =
|
||||
(this: HTMLElement, event: HTMLElementEventMap[EventName]) => void;
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import type { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
|
||||
const instanceSymbol = Symbol.for('instance');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MaintenancePopupEventsMap } from "$lib/components/events/maintenance-popup-events";
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import type { FullscreenViewerEventsMap } from "$lib/components/events/fullscreen-viewer-events";
|
||||
import type { BooruEventsMap } from "$lib/components/events/booru-events";
|
||||
import type { TagsFormEventsMap } from "$lib/components/events/tags-form-events";
|
||||
import type { TagDropdownEvents } from "$lib/components/events/tag-dropdown-events";
|
||||
import type { MaintenancePopupEventsMap } from "$content/components/events/maintenance-popup-events";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import type { FullscreenViewerEventsMap } from "$content/components/events/fullscreen-viewer-events";
|
||||
import type { BooruEventsMap } from "$content/components/events/booru-events";
|
||||
import type { TagsFormEventsMap } from "$content/components/events/tags-form-events";
|
||||
import type { TagDropdownEvents } from "$content/components/events/tag-dropdown-events";
|
||||
|
||||
type EventsMapping =
|
||||
MaintenancePopupEventsMap
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { ImageListInfo } from "$lib/components/listing/ImageListInfo";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { ImageListInfo } from "$content/components/listing/ImageListInfo";
|
||||
|
||||
export class ImageListContainer extends BaseComponent {
|
||||
#info: ImageListInfo | null = null;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
|
||||
export class ImageListInfo extends BaseComponent {
|
||||
#tagElement: HTMLElement | null = null;
|
||||
@@ -1,9 +1,9 @@
|
||||
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";
|
||||
import { initializeImageListContainer } from "$lib/components/listing/ImageListContainer";
|
||||
import { createMaintenancePopup } from "$content/components/MaintenancePopup";
|
||||
import { createMediaBoxTools } from "$content/components/MediaBoxTools";
|
||||
import { calculateMediaBoxesPositions, initializeMediaBox } from "$content/components/MediaBoxWrapper";
|
||||
import { createMaintenanceStatusIcon } from "$content/components/MaintenanceStatusIcon";
|
||||
import { createImageShowFullscreenButton } from "$content/components/ImageShowFullscreenButton";
|
||||
import { initializeImageListContainer } from "$content/components/listing/ImageListContainer";
|
||||
|
||||
const mediaBoxes = document.querySelectorAll<HTMLElement>('.media-box');
|
||||
const imageListContainer = document.querySelector<HTMLElement>('#imagelist-container');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TagsForm } from "$lib/components/TagsForm";
|
||||
import { initializeAllTagsLists, watchForUpdatedTagLists } from "$lib/components/TagsListBlock";
|
||||
import { TagsForm } from "$content/components/TagsForm";
|
||||
import { initializeAllTagsLists, watchForUpdatedTagLists } from "$content/components/TagsListBlock";
|
||||
|
||||
initializeAllTagsLists();
|
||||
watchForUpdatedTagLists();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { watchTagDropdownsInTagsEditor, wrapTagDropdown } from "$lib/components/TagDropdownWrapper";
|
||||
import { watchTagDropdownsInTagsEditor, wrapTagDropdown } from "$content/components/TagDropdownWrapper";
|
||||
|
||||
for (let tagDropdownElement of document.querySelectorAll<HTMLElement>('.tag.dropdown')) {
|
||||
wrapTagDropdown(tagDropdownElement);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export const categories = [
|
||||
'rating',
|
||||
'spoiler',
|
||||
'origin',
|
||||
'oc',
|
||||
'error',
|
||||
'character',
|
||||
'content-official',
|
||||
'content-fanmade',
|
||||
'species',
|
||||
'body-type',
|
||||
];
|
||||
4
src/lib/constants.ts
Normal file
4
src/lib/constants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Automatically generated name of the plugin.
|
||||
*/
|
||||
export const PLUGIN_NAME = __CURRENT_SITE_NAME__ + ' Tagging Assistant';
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
|
||||
import type { TagDropdownWrapper } from "$content/components/TagDropdownWrapper";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { escapeRegExp } from "$lib/utils";
|
||||
import { emit } from "$lib/components/events/comms";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
|
||||
import { emit } from "$content/components/events/comms";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$content/components/events/tag-dropdown-events";
|
||||
|
||||
export default class CustomCategoriesResolver {
|
||||
#exactGroupMatches = new Map<string, TagGroup>();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Footer from "$components/layout/Footer.svelte";
|
||||
import { initializeLinksReplacement } from "$lib/popup-links";
|
||||
import { onDestroy } from "svelte";
|
||||
import { headTitle } from "$stores/popup";
|
||||
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
@@ -22,6 +23,10 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$headTitle}</title>
|
||||
</svelte:head>
|
||||
|
||||
<Header/>
|
||||
<main>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = null;
|
||||
|
||||
let activeProfile = $derived<MaintenanceProfile | null>(
|
||||
$maintenanceProfiles.find(profile => profile.id === $activeProfileStore) || null
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { PLUGIN_NAME } from "$lib/constants";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'About';
|
||||
|
||||
let currentSiteUrl = 'https://furbooru.org';
|
||||
|
||||
@@ -14,7 +18,7 @@
|
||||
<hr>
|
||||
</Menu>
|
||||
<h1>
|
||||
{__CURRENT_SITE_NAME__} Tagging Assistant
|
||||
{PLUGIN_NAME}
|
||||
</h1>
|
||||
<p>
|
||||
This is a small tool to make tagging on {__CURRENT_SITE_NAME__} just a little bit more convenient. Group tags with
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Tag Groups';
|
||||
|
||||
let groups = $derived<TagGroup[]>($tagGroups.sort((a, b) => a.settings.name.localeCompare(b.settings.name)));
|
||||
</script>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let groupId = $derived<string>(page.params.id);
|
||||
let group = $derived<TagGroup | null>($tagGroups.find(group => group.id === groupId) || null);
|
||||
@@ -19,6 +20,8 @@
|
||||
if (!group) {
|
||||
console.warn(`Group ${groupId} not found.`);
|
||||
goto('/features/groups');
|
||||
} else {
|
||||
$popupTitle = `Tag Group: ${group.settings.name}`;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import type TagGroup from "$entities/TagGroup";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
const groupId = $derived<string>(page.params.id);
|
||||
const targetGroup = $derived<TagGroup | undefined>($tagGroups.find(group => group.id === groupId));
|
||||
@@ -12,6 +13,8 @@
|
||||
$effect(() => {
|
||||
if (!targetGroup) {
|
||||
goto('/features/groups');
|
||||
} else {
|
||||
$popupTitle = `Deleting Tag Group: ${targetGroup.settings.name}`;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let groupId = $derived(page.params.id);
|
||||
|
||||
@@ -32,6 +33,7 @@
|
||||
|
||||
$effect(() => {
|
||||
if (groupId === 'new') {
|
||||
$popupTitle = 'Create Tag Group';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,6 +42,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$popupTitle = `Edit Tag Group: ${targetGroup.settings.name}`;
|
||||
|
||||
groupName = targetGroup.settings.name;
|
||||
tagsList = [...targetGroup.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
prefixesList = [...targetGroup.settings.prefixes].sort((a, b) => a.localeCompare(b));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let isEncodedGroupShown = $state(true);
|
||||
|
||||
@@ -17,6 +18,8 @@
|
||||
$effect(() => {
|
||||
if (!group) {
|
||||
goto('/features/groups');
|
||||
} else {
|
||||
$popupTitle = `Export Tag Group: ${group.settings.name}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
const groupTransporter = new EntitiesTransporter(TagGroup);
|
||||
|
||||
@@ -17,6 +18,12 @@
|
||||
let candidateGroup = $state<TagGroup | null>(null);
|
||||
let existingGroup = $state<TagGroup | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
$popupTitle = candidateGroup
|
||||
? 'Confirm Imported Tag Group'
|
||||
: 'Import Tag Group';
|
||||
});
|
||||
|
||||
function tryImportingGroup() {
|
||||
candidateGroup = null;
|
||||
existingGroup = null;
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
import MenuRadioItem from "$components/ui/menu/MenuRadioItem.svelte";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Tagging Profiles';
|
||||
|
||||
let profiles = $derived<MaintenanceProfile[]>(
|
||||
$maintenanceProfiles.sort((a, b) => a.settings.name.localeCompare(b.settings.name))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy';
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { page } from "$app/state";
|
||||
@@ -8,7 +7,7 @@
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { onMount } from "svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let profileId = $derived(page.params.id);
|
||||
let profile = $derived<MaintenanceProfile|null>(
|
||||
@@ -24,6 +23,8 @@
|
||||
if (!profile) {
|
||||
console.warn(`Profile ${profileId} not found.`);
|
||||
goto('/features/maintenance');
|
||||
} else {
|
||||
$popupTitle = `Tagging Profile: ${profile.settings.name}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { page } from "$app/state";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
const profileId = $derived(page.params.id);
|
||||
const targetProfile = $derived<MaintenanceProfile | null>(
|
||||
@@ -14,6 +15,8 @@
|
||||
$effect(() => {
|
||||
if (!targetProfile) {
|
||||
goto('/features/maintenance');
|
||||
} else {
|
||||
$popupTitle = `Deleting Tagging Profile: ${targetProfile.settings.name}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
|
||||
let profileId = $derived(page.params.id);
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
$effect(() => {
|
||||
if (profileId === 'new') {
|
||||
$popupTitle = 'Create Tagging Profile';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,6 +36,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$popupTitle = `Edit Tagging Profile: ${targetProfile.settings.name}`;
|
||||
|
||||
profileName = targetProfile.settings.name;
|
||||
tagsList = [...targetProfile.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let isCompressedProfileShown = $state(true);
|
||||
|
||||
@@ -19,6 +20,8 @@
|
||||
$effect(() => {
|
||||
if (!profile) {
|
||||
goto('/features/maintenance/');
|
||||
} else {
|
||||
$popupTitle = `Export Tagging Profile: ${profile.settings.name}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import { goto } from "$app/navigation";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
|
||||
@@ -17,6 +18,11 @@
|
||||
let candidateProfile = $state<MaintenanceProfile | null>(null);
|
||||
let existingProfile = $state<MaintenanceProfile | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
$popupTitle = candidateProfile
|
||||
? 'Confirm Imported Tagging Profile'
|
||||
: 'Import Tagging Profile';
|
||||
})
|
||||
function tryImportingProfile() {
|
||||
candidateProfile = null;
|
||||
existingProfile = null;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Preferences';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Debugging Tools';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import { storagesCollection } from "$stores/debug";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Storage Inspector';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import StorageViewer from "$components/debugging/StorageViewer.svelte";
|
||||
import { page } from "$app/state";
|
||||
import { goto } from "$app/navigation";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let pathArray = $derived.by<string[]>(() => {
|
||||
const pathString = page.params.path;
|
||||
@@ -30,6 +31,10 @@
|
||||
if (!storageName) {
|
||||
goto("/preferences/debug/storage");
|
||||
}
|
||||
|
||||
$popupTitle = storageName
|
||||
? 'Inspecting: ' + storageName
|
||||
: 'Storage Inspector';
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import { fullScreenViewerEnabled } from "$stores/preferences/misc";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Misc Preferences';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { stripBlacklistedTagsEnabled } from "$stores/preferences/maintenance";
|
||||
import { shouldSeparateTagGroups } from "$stores/preferences/tag";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Tagging Preferences';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Import/Export';
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import type StorageEntity from "$lib/extension/base/StorageEntity";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
const bulkTransporter = new BulkEntitiesTransporter();
|
||||
|
||||
@@ -46,6 +47,12 @@
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
$popupTitle = displayExportedString
|
||||
? 'Exported String'
|
||||
: 'Select Entities to Export';
|
||||
});
|
||||
|
||||
function refreshAreAllEntitiesChecked() {
|
||||
requestAnimationFrame(() => {
|
||||
exportAllProfiles = $maintenanceProfiles.every(profile => exportedEntities.profiles[profile.id]);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import type { SameSiteStatus } from "$lib/extension/EntitiesTransporter";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
let importedString = $state('');
|
||||
let errorMessage = $state('');
|
||||
@@ -49,6 +50,16 @@
|
||||
Boolean(importedProfiles.length || importedGroups.length)
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
$popupTitle = hasImportedEntities
|
||||
? (
|
||||
previewedEntity
|
||||
? 'Preview of Imported Entity'
|
||||
: 'Select & Preview Imported Entities'
|
||||
)
|
||||
: 'Import';
|
||||
});
|
||||
|
||||
const transporter = new BulkEntitiesTransporter();
|
||||
|
||||
let lastImportStatus = $state<SameSiteStatus>(null);
|
||||
|
||||
15
src/stores/popup.ts
Normal file
15
src/stores/popup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { derived, writable } from "svelte/store";
|
||||
import { PLUGIN_NAME } from "$lib/constants";
|
||||
|
||||
/**
|
||||
* Store containing the name of the subpage. This name will be added to the title alongside the name of the plugin.
|
||||
*/
|
||||
export const popupTitle = writable<string | null>(null);
|
||||
|
||||
/**
|
||||
* Name of the current route with the name of the plugin appended to it.
|
||||
*/
|
||||
export const headTitle = derived(
|
||||
popupTitle,
|
||||
$popupTitle => ($popupTitle ? `${$popupTitle} | ` : '') + PLUGIN_NAME
|
||||
);
|
||||
@@ -68,15 +68,8 @@
|
||||
.tag {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
// Derpibooru has slight differences in how tags are displayed.
|
||||
@if environment.$current-site == 'derpibooru' {
|
||||
padding: 0 5px;
|
||||
gap: 0;
|
||||
}
|
||||
@else {
|
||||
padding: 5px;
|
||||
}
|
||||
padding: 0 5px;
|
||||
gap: 0;
|
||||
|
||||
&:hover {
|
||||
background: booru-vars.$resolved-tag-color;
|
||||
|
||||
@@ -6,18 +6,8 @@ h2.tag-category-headline {
|
||||
$base-margin-top: .83em;
|
||||
$base-margin-bottom: .62em;
|
||||
|
||||
// Tag List element was updated to use flex & gaps. This should be applied to Furbooru later, once updates will be
|
||||
// applied from the base Philomena version.
|
||||
@if environment.$current-site == 'derpibooru' {
|
||||
margin: {
|
||||
top: calc(#{$base-margin-top} - #{booru-vars.$padding-small});
|
||||
bottom: calc(#{$base-margin-bottom} - #{booru-vars.$padding-small});
|
||||
}
|
||||
}
|
||||
@else {
|
||||
margin: {
|
||||
top: $base-margin-top;
|
||||
bottom: $base-margin-bottom;
|
||||
}
|
||||
margin: {
|
||||
top: calc(#{$base-margin-top} - #{booru-vars.$padding-small});
|
||||
bottom: calc(#{$base-margin-bottom} - #{booru-vars.$padding-small});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const config = {
|
||||
},
|
||||
alias: {
|
||||
"$config": "./src/config",
|
||||
"$content": "./src/content",
|
||||
"$components": "./src/components",
|
||||
"$styles": "./src/styles",
|
||||
"$stores": "./src/stores",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseComponent } from "$lib/components/base/BaseComponent";
|
||||
import { getComponent } from "$lib/components/base/component-utils";
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { randomString } from "$tests/utils";
|
||||
|
||||
describe('BaseComponent', () => {
|
||||
Reference in New Issue
Block a user