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

24 Commits
0.4.3 ... 0.4.5

Author SHA1 Message Date
6098a11115 Bumped version to 0.4.5
Woops, forgot to bump the version.
2025-06-03 13:48:49 +04:00
a87d8b94b8 Merge pull request #125 from koloml/release/0.4.5
Release: 0.4.5
2025-06-03 13:46:57 +04:00
c283b96285 Updating dependencies (#126)
* Updated `sass` from 1.86.3 to 1.89.1

* Updated `svelte` from 5.25.6 to 5.33.14

* Updated `svelte-check` from 4.1.5 to 4.2.1

* Updated `@sveltejs/kit` from 2.20.3 to 2.21.1

* Removed `@sveltejs/adapter-auto`

Looks like it was left over from initial commit. We're using static
adapter.

* Updated `vite` from 6.2.5 to 6.3.5

* Updated `vitest` from 3.1.1 to 3.2.0

* Updated `@vitest/coverage-v8` from 3.1.1 to 3.2.0

* Updated `typescript` from 5.8.2 to 5.8.3

* Updated `jsdom` from 26.0.0 to 26.1.0

* Updated `@types/node` from 22.14.0 to 22.15.29

* Updated `@types/chrome` from 0.0.313 to 0.0.326
2025-06-03 13:45:17 +04:00
02478f0bf0 Merge pull request #124 from koloml/bugfix/popup-header-z-index
Fixed header in popup having wrong z-index
2025-06-03 02:41:23 +04:00
59c15f27eb Merge pull request #123 from koloml/feature/quick-query-untagged-implications
Tag Page: Added link for quick search of untagged implications
2025-06-03 02:41:01 +04:00
134e96bc4c Added link for a quick search of untagged implications 2025-06-03 02:14:00 +04:00
1c05159ddf Fixed popup's header z-index position appearing behind some elements 2025-04-15 23:50:02 +04:00
bb14492578 Merge pull request #116 from koloml/release/0.4.4
Release: 0.4.4
2025-04-04 14:38:37 +04:00
30320e7283 Bumped version to 0.4.4 2025-04-04 14:37:20 +04:00
8839373292 Updating dependencies (#118)
* Updated `vite` to 6.2.5

* Updated `sass` to 1.86.3

* Updated `svelte` to 5.25.6

* Updated `@sveltejs/kit` to 2.20.3

* Updated `vitest` to 3.1.1

* Updated `@vitest/coverage-v8` to 3.1.1

* Updated `@sveltejs/adapter-auto` to 6.0.0

* Updated `@types/chrome` to 0.0.313

* Updated `@types/node` to 22.14.0
2025-04-04 14:35:54 +04:00
0e35d1d0ba Merge pull request #117 from koloml/feature/name-events-as-constants
Changing the naming of custom events to be different from the usual variables
2025-04-04 14:15:45 +04:00
bca21da6d1 Merge remote-tracking branch 'origin/release/0.4.4' into feature/name-events-as-constants
# Conflicts:
#	src/lib/components/TagsListBlock.ts
2025-04-04 14:15:02 +04:00
60491f57d4 Merge pull request #115 from koloml/feature/grouping-button
Tag Groups: Added button to the tags list component to quckly toggle the sepeartion on and off
2025-03-26 21:06:25 +04:00
c26c4bcf62 Merge pull request #114 from koloml/bugfix/missing-re-initialization-for-tags-list
Tags List: Fixed re-initialization of the component after tags were submitted
2025-03-26 21:06:15 +04:00
1b4b646024 Merge pull request #113 from koloml/bugfix/last-mediabox-position
Fixed last media box on the page showing its popup outside of the viewport
2025-03-26 21:05:29 +04:00
928fe5ddb0 Removed unnecessary import 2025-03-26 20:46:51 +04:00
6586141134 Fixed missed re-initialization of tags list after tag form was submitted 2025-03-26 20:43:19 +04:00
d587bd2453 Added button to the tags list to toggle separation of groups 2025-03-26 20:39:30 +04:00
e2eb8a0ca7 Fixed last media box on the page not being marked as the last in a row 2025-03-26 20:03:04 +04:00
0876e5f001 Changed naming for event name constants to differentiate them with variables 2025-03-26 19:01:18 +04:00
d5ad66d902 Merge pull request #112 from koloml/release/0.4.3.1
Release: 0.4.3.1
2025-03-12 19:34:05 +04:00
cb6b5f4f9d Bumped version to 0.4.3.1 2025-03-12 19:32:27 +04:00
193941b0ac Merge pull request #111 from koloml/hotfix/tag-groups-not-applying-for-tag-editor
Fixed tag group colors & grouping not applying in Firefox
2025-03-12 19:31:52 +04:00
562274b3d8 Fixed Firefox not applying tag groups due to invalid scripts order 2025-03-12 19:27:24 +04:00
21 changed files with 610 additions and 568 deletions

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.3",
"version": "0.4.5",
"browser_specific_settings": {
"gecko": {
"id": "furbooru-tagging-assistant@thecore.city"
@@ -44,6 +44,14 @@
"src/styles/content/header.scss"
]
},
{
"matches": [
"*://*.furbooru.org/images/*"
],
"js": [
"src/content/tags-editor.ts"
]
},
{
"matches": [
"*://*.furbooru.org/images?*",
@@ -61,14 +69,6 @@
"js": [
"src/content/tags.ts"
]
},
{
"matches": [
"*://*.furbooru.org/images/*"
],
"js": [
"src/content/tags-editor.ts"
]
}
],
"action": {

889
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "furbooru-tagging-assistant",
"version": "0.4.3",
"version": "0.4.5",
"private": true,
"scripts": {
"build": "npm run build:popup && npm run build:extension",
@@ -12,21 +12,20 @@
"test:watch": "vitest watch --coverage"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.19.0",
"@sveltejs/kit": "^2.21.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/chrome": "^0.0.309",
"@types/node": "^22.13.10",
"@vitest/coverage-v8": "^3.0.8",
"@types/chrome": "^0.0.326",
"@types/node": "^22.15.29",
"@vitest/coverage-v8": "^3.2.0",
"cheerio": "^1.0.0",
"jsdom": "^26.0.0",
"sass": "^1.85.1",
"svelte": "^5.23.0",
"svelte-check": "^4.1.5",
"typescript": "^5.8.2",
"vite": "^6.1.1",
"vitest": "^3.0.8"
"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"
},
"type": "module",
"dependencies": {

View File

@@ -13,6 +13,7 @@
top: 0;
left: 0;
right: 0;
z-index: 10;
a {
color: colors.$text;

View File

@@ -3,8 +3,10 @@ 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";
const mediaBoxes = document.querySelectorAll<HTMLElement>('.media-box');
const imageListContainer = document.querySelector<HTMLElement>('#imagelist-container');
mediaBoxes.forEach(mediaBoxElement => {
initializeMediaBox(mediaBoxElement, [
@@ -22,3 +24,7 @@ mediaBoxes.forEach(mediaBoxElement => {
});
calculateMediaBoxesPositions(mediaBoxes);
if (imageListContainer) {
initializeImageListContainer(imageListContainer);
}

View File

@@ -1,7 +1,7 @@
import { BaseComponent } from "$lib/components/base/BaseComponent";
import MiscSettings, { type FullscreenViewerSize } from "$lib/extension/settings/MiscSettings";
import { emit, on } from "$lib/components/events/comms";
import { eventSizeLoaded } from "$lib/components/events/fullscreen-viewer-events";
import { EVENT_SIZE_LOADED } from "$lib/components/events/fullscreen-viewer-events";
export class FullscreenViewer extends BaseComponent {
#videoElement: HTMLVideoElement = document.createElement('video');
@@ -173,7 +173,7 @@ export class FullscreenViewer extends BaseComponent {
this.#sizeSelectorElement.value = size;
this.#isSizeFetched = true;
emit(this.container, eventSizeLoaded, size);
emit(this.container, EVENT_SIZE_LOADED, size);
}
#watchForSizeSelectionChanges() {
@@ -224,7 +224,7 @@ export class FullscreenViewer extends BaseComponent {
await new Promise(
resolve => on(
this.container,
eventSizeLoaded,
EVENT_SIZE_LOADED,
resolve
),
);

View File

@@ -6,9 +6,9 @@ import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI";
import { tagsBlacklist } from "$config/tags";
import { emitterAt } from "$lib/components/events/comms";
import {
eventActiveProfileChanged,
eventMaintenanceStateChanged,
eventTagsUpdated
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";
@@ -83,7 +83,7 @@ export class MaintenancePopup extends BaseComponent {
this.container.classList.toggle('is-active', activeProfile !== null);
this.#refreshTagsList();
this.#emitter.emit(eventActiveProfileChanged, activeProfile);
this.#emitter.emit(EVENT_ACTIVE_PROFILE_CHANGED, activeProfile);
}
#refreshTagsList() {
@@ -177,7 +177,7 @@ export class MaintenancePopup extends BaseComponent {
}
this.#isPlanningToSubmit = true;
this.#emitter.emit(eventMaintenanceStateChanged, 'waiting');
this.#emitter.emit(EVENT_MAINTENANCE_STATE_CHANGED, 'waiting');
}
}
@@ -204,7 +204,7 @@ export class MaintenancePopup extends BaseComponent {
this.#isPlanningToSubmit = false;
this.#isSubmitting = true;
this.#emitter.emit(eventMaintenanceStateChanged, 'processing');
this.#emitter.emit(EVENT_MAINTENANCE_STATE_CHANGED, 'processing');
let maybeTagsAndAliasesAfterUpdate;
@@ -246,17 +246,17 @@ export class MaintenancePopup extends BaseComponent {
MaintenancePopup.#notifyAboutPendingSubmission(false);
this.#emitter.emit(eventMaintenanceStateChanged, 'failed');
this.#emitter.emit(EVENT_MAINTENANCE_STATE_CHANGED, 'failed');
this.#isSubmitting = false;
return;
}
if (maybeTagsAndAliasesAfterUpdate) {
this.#emitter.emit(eventTagsUpdated, maybeTagsAndAliasesAfterUpdate);
this.#emitter.emit(EVENT_TAGS_UPDATED, maybeTagsAndAliasesAfterUpdate);
}
this.#emitter.emit(eventMaintenanceStateChanged, 'complete');
this.#emitter.emit(EVENT_MAINTENANCE_STATE_CHANGED, 'complete');
this.#tagsToAdd.clear();
this.#tagsToRemove.clear();

View File

@@ -1,7 +1,7 @@
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";
import { EVENT_MAINTENANCE_STATE_CHANGED } from "$lib/components/events/maintenance-popup-events";
import type { MediaBoxTools } from "$lib/components/MediaBoxTools";
export class MaintenanceStatusIcon extends BaseComponent {
@@ -22,7 +22,7 @@ export class MaintenanceStatusIcon extends BaseComponent {
throw new Error('Status icon element initialized outside of the media box!');
}
on(this.#mediaBoxTools, eventMaintenanceStateChanged, this.#onMaintenanceStateChanged.bind(this));
on(this.#mediaBoxTools, EVENT_MAINTENANCE_STATE_CHANGED, this.#onMaintenanceStateChanged.bind(this));
}
#onMaintenanceStateChanged(stateChangeEvent: CustomEvent<string>) {

View File

@@ -2,7 +2,7 @@ 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";
import { EVENT_ACTIVE_PROFILE_CHANGED } from "$lib/components/events/maintenance-popup-events";
import type { MediaBoxWrapper } from "$lib/components/MediaBoxWrapper";
import type MaintenanceProfile from "$entities/MaintenanceProfile";
@@ -39,7 +39,7 @@ export class MediaBoxTools extends BaseComponent {
}
}
on(this, eventActiveProfileChanged, this.#onActiveProfileChanged.bind(this));
on(this, EVENT_ACTIVE_PROFILE_CHANGED, this.#onActiveProfileChanged.bind(this));
}
#onActiveProfileChanged(profileChangedEvent: CustomEvent<MaintenanceProfile | null>) {

View File

@@ -2,7 +2,7 @@ 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";
import { EVENT_TAGS_UPDATED } from "$lib/components/events/maintenance-popup-events";
export class MediaBoxWrapper extends BaseComponent {
#thumbnailContainer: HTMLElement | null = null;
@@ -13,7 +13,7 @@ export class MediaBoxWrapper extends BaseComponent {
this.#thumbnailContainer = this.container.querySelector('.image-container');
this.#imageLinkElement = this.#thumbnailContainer?.querySelector('a') || null;
on(this, eventTagsUpdated, this.#onTagsUpdatedRefreshTagsAndAliases.bind(this));
on(this, EVENT_TAGS_UPDATED, this.#onTagsUpdatedRefreshTagsAndAliases.bind(this));
}
#onTagsUpdatedRefreshTagsAndAliases(tagsUpdatedEvent: CustomEvent<Map<string, string> | null>) {
@@ -90,5 +90,10 @@ export function calculateMediaBoxesPositions(mediaBoxesList: NodeListOf<HTMLElem
lastMediaBox = mediaBoxElement;
lastMediaBoxPosition = yPosition;
}
// Last-ever media box is checked separately
if (lastMediaBox && !lastMediaBox.nextElementSibling) {
lastMediaBox.classList.add('media-box--last');
}
})
}

View File

@@ -4,8 +4,8 @@ import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings";
import { getComponent } from "$lib/components/base/component-utils";
import CustomCategoriesResolver from "$lib/extension/CustomCategoriesResolver";
import { on } from "$lib/components/events/comms";
import { eventFormEditorUpdated } from "$lib/components/events/tags-form-events";
import { eventTagCustomGroupResolved } from "$lib/components/events/tag-dropdown-events";
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 type TagGroup from "$entities/TagGroup";
const categoriesResolver = new CustomCategoriesResolver();
@@ -54,7 +54,7 @@ export class TagDropdownWrapper extends BaseComponent {
}
});
on(this, eventTagCustomGroupResolved, this.#onTagGroupResolved.bind(this));
on(this, EVENT_TAG_GROUP_RESOLVED, this.#onTagGroupResolved.bind(this));
}
#onTagGroupResolved(resolvedGroupEvent: CustomEvent<TagGroup | null>) {
@@ -302,7 +302,7 @@ export function watchTagDropdownsInTagsEditor() {
});
// When form is submitted, its DOM is completely updated. We need to fetch those tags in this case.
on(document.body, eventFormEditorUpdated, event => {
on(document.body, EVENT_FORM_EDITOR_UPDATED, event => {
for (const tagDropdownElement of event.detail.querySelectorAll<HTMLElement>('.tag.dropdown')) {
wrapTagDropdown(tagDropdownElement);
}

View File

@@ -1,15 +1,15 @@
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 { eventFetchComplete } from "$lib/components/events/booru-events";
import { eventFormEditorUpdated } from "$lib/components/events/tags-form-events";
import { EVENT_FETCH_COMPLETE } from "$lib/components/events/booru-events";
import { EVENT_FORM_EDITOR_UPDATED } from "$lib/components/events/tags-form-events";
export class TagsForm extends BaseComponent {
protected init() {
// Site sending the event when form is submitted vie Fetch API. We use this event to reload our logic here.
const unsubscribe = on(
this.container,
eventFetchComplete,
EVENT_FETCH_COMPLETE,
() => this.#waitAndDetectUpdatedForm(unsubscribe),
);
}
@@ -36,7 +36,7 @@ export class TagsForm extends BaseComponent {
const fullTagEditor = tagFormComponent.parentTagEditorElement;
if (fullTagEditor) {
emit(document.body, eventFormEditorUpdated, fullTagEditor);
emit(document.body, EVENT_FORM_EDITOR_UPDATED, fullTagEditor);
} else {
console.info('Tag form is not in the tag editor. Event is not sent.');
}

View File

@@ -2,13 +2,18 @@ import { BaseComponent } from "$lib/components/base/BaseComponent";
import type TagGroup from "$entities/TagGroup";
import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
import { on } from "$lib/components/events/comms";
import { eventFormEditorUpdated } from "$lib/components/events/tags-form-events";
import { EVENT_FORM_EDITOR_UPDATED } from "$lib/components/events/tags-form-events";
import { getComponent } from "$lib/components/base/component-utils";
import { eventTagCustomGroupResolved } from "$lib/components/events/tag-dropdown-events";
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
import TagSettings from "$lib/extension/settings/TagSettings";
export class TagsListBlock extends BaseComponent {
#tagsListButtonsContainer: HTMLElement | null = null;
#tagsListContainer: HTMLElement | null = null;
#toggleGroupingButton = document.createElement('a');
#toggleGroupingButtonIcon = document.createElement('i');
#tagSettings = new TagSettings();
#shouldDisplaySeparation = false;
@@ -21,7 +26,21 @@ export class TagsListBlock extends BaseComponent {
#isReorderingPlanned = false;
protected build() {
this.#tagsListButtonsContainer = this.container.querySelector('.block.tagsauce .block__header__buttons');
this.#tagsListContainer = this.container.querySelector('.tag-list');
this.#toggleGroupingButton.innerText = ' Grouping';
this.#toggleGroupingButton.href = 'javascript:void(0)';
this.#toggleGroupingButton.classList.add('button', 'button--link', 'button--inline');
this.#toggleGroupingButton.title = 'Toggle the global groups separation option. This will only toggle global ' +
'setting without changing the separation of specific groups.';
this.#toggleGroupingButtonIcon.classList.add('fas', TagsListBlock.#iconGroupingDisabled);
this.#toggleGroupingButton.prepend(this.#toggleGroupingButtonIcon);
if (this.#tagsListButtonsContainer) {
this.#tagsListButtonsContainer.append(this.#toggleGroupingButton);
}
}
init() {
@@ -32,9 +51,11 @@ export class TagsListBlock extends BaseComponent {
on(
this,
eventTagCustomGroupResolved,
EVENT_TAG_GROUP_RESOLVED,
this.#onTagDropdownCustomGroupResolved.bind(this)
);
this.#toggleGroupingButton.addEventListener('click', this.#onToggleGroupingClicked.bind(this));
}
#onTagSeparationChange(isSeparationEnabled: boolean) {
@@ -44,6 +65,12 @@ export class TagsListBlock extends BaseComponent {
this.#shouldDisplaySeparation = isSeparationEnabled;
this.#reorderSeparatedGroups();
this.#updateToggleSeparationButton();
}
#updateToggleSeparationButton() {
this.#toggleGroupingButtonIcon.classList.toggle(TagsListBlock.#iconGroupingEnabled, this.#shouldDisplaySeparation);
this.#toggleGroupingButtonIcon.classList.toggle(TagsListBlock.#iconGroupingDisabled, !this.#shouldDisplaySeparation);
}
#onTagDropdownCustomGroupResolved(resolvedCustomGroupEvent: CustomEvent<TagGroup | null>) {
@@ -74,6 +101,11 @@ export class TagsListBlock extends BaseComponent {
}
}
#onToggleGroupingClicked(event: Event) {
event.preventDefault();
void this.#tagSettings.setGroupSeparation(!this.#shouldDisplaySeparation);
}
#handleTagGroupChanges(tagGroup: TagGroup) {
const groupId = tagGroup.id;
const processedGroup = this.#separatedGroups.get(groupId);
@@ -181,6 +213,9 @@ export class TagsListBlock extends BaseComponent {
static #orderCssVariableForGroup(groupId: string): string {
return `--ta-order-${groupId}`;
}
static #iconGroupingDisabled = 'fa-folder';
static #iconGroupingEnabled = 'fa-folder-tree';
}
export function initializeAllTagsLists() {
@@ -195,7 +230,14 @@ export function initializeAllTagsLists() {
}
export function watchForUpdatedTagLists() {
on(document, eventFormEditorUpdated, event => {
event.detail.closest('#image_tags_and_source')
on(document, EVENT_FORM_EDITOR_UPDATED, event => {
const tagsListElement = event.detail.closest<HTMLElement>('#image_tags_and_source');
if (!tagsListElement || getComponent(tagsListElement)) {
return;
}
new TagsListBlock(tagsListElement)
.initialize();
});
}

View File

@@ -1,5 +1,5 @@
export const eventFetchComplete = 'fetchcomplete';
export const EVENT_FETCH_COMPLETE = 'fetchcomplete';
export interface BooruEventsMap {
[eventFetchComplete]: null; // Site sends the response, but extension will not get it due to isolation.
[EVENT_FETCH_COMPLETE]: null; // Site sends the response, but extension will not get it due to isolation.
}

View File

@@ -1,7 +1,7 @@
import type { FullscreenViewerSize } from "$lib/extension/settings/MiscSettings";
export const eventSizeLoaded = 'size-loaded';
export const EVENT_SIZE_LOADED = 'size-loaded';
export interface FullscreenViewerEventsMap {
[eventSizeLoaded]: FullscreenViewerSize;
[EVENT_SIZE_LOADED]: FullscreenViewerSize;
}

View File

@@ -1,13 +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';
export const EVENT_ACTIVE_PROFILE_CHANGED = 'active-profile-changed';
export const EVENT_MAINTENANCE_STATE_CHANGED = 'maintenance-state-change';
export const EVENT_TAGS_UPDATED = 'tags-updated';
type MaintenanceState = 'processing' | 'failed' | 'complete' | 'waiting';
export interface MaintenancePopupEventsMap {
[eventActiveProfileChanged]: MaintenanceProfile | null;
[eventMaintenanceStateChanged]: MaintenanceState;
[eventTagsUpdated]: Map<string, string> | null;
[EVENT_ACTIVE_PROFILE_CHANGED]: MaintenanceProfile | null;
[EVENT_MAINTENANCE_STATE_CHANGED]: MaintenanceState;
[EVENT_TAGS_UPDATED]: Map<string, string> | null;
}

View File

@@ -1,7 +1,7 @@
import type TagGroup from "$entities/TagGroup";
export const eventTagCustomGroupResolved = 'tag-group-resolved';
export const EVENT_TAG_GROUP_RESOLVED = 'tag-group-resolved';
export interface TagDropdownEvents {
[eventTagCustomGroupResolved]: TagGroup | null;
[EVENT_TAG_GROUP_RESOLVED]: TagGroup | null;
}

View File

@@ -1,5 +1,5 @@
export const eventFormEditorUpdated = 'tags-form-updated';
export const EVENT_FORM_EDITOR_UPDATED = 'tags-form-updated';
export interface TagsFormEventsMap {
[eventFormEditorUpdated]: HTMLElement;
[EVENT_FORM_EDITOR_UPDATED]: HTMLElement;
}

View File

@@ -0,0 +1,19 @@
import { BaseComponent } from "$lib/components/base/BaseComponent";
import { ImageListInfo } from "$lib/components/listing/ImageListInfo";
export class ImageListContainer extends BaseComponent {
#info: ImageListInfo | null = null;
protected build() {
const imageListInfoContainer = this.container.querySelector<HTMLElement>('.js-imagelist-info');
if (imageListInfoContainer) {
this.#info = new ImageListInfo(imageListInfoContainer);
this.#info.initialize();
}
}
}
export function initializeImageListContainer(element: HTMLElement) {
new ImageListContainer(element).initialize();
}

View File

@@ -0,0 +1,75 @@
import { BaseComponent } from "$lib/components/base/BaseComponent";
export class ImageListInfo extends BaseComponent {
#tagElement: HTMLElement | null = null;
#impliedTags: string[] = [];
#showUntaggedImplicationsButton: HTMLAnchorElement = document.createElement('a');
protected build() {
const sectionAfterImage = this.container.querySelector('.tag-info__image + .flex__grow');
this.#tagElement = sectionAfterImage?.querySelector<HTMLElement>('.tag.dropdown') ?? null;
const labels = this.container
.querySelectorAll<HTMLElement>('.tag-info__image + .flex__grow strong');
let targetElementToInsertBefore: HTMLElement | null = null;
for (const potentialListStarter of labels) {
if (potentialListStarter.innerText === ImageListInfo.#implicationsStarterText) {
targetElementToInsertBefore = potentialListStarter;
this.#collectImplicationsFromListStarter(potentialListStarter);
break;
}
}
if (this.#impliedTags.length && targetElementToInsertBefore) {
this.#showUntaggedImplicationsButton.href = '#';
this.#showUntaggedImplicationsButton.innerText = '(Q)';
this.#showUntaggedImplicationsButton.title =
'Query untagged implications\n\n' +
'This will open the search results with all untagged implications for the current tag.';
this.#showUntaggedImplicationsButton.classList.add('detail-link');
targetElementToInsertBefore.insertAdjacentElement('beforebegin', this.#showUntaggedImplicationsButton);
}
}
protected init() {
this.#showUntaggedImplicationsButton.addEventListener('click', this.#onShowUntaggedImplicationsClicked.bind(this));
}
#collectImplicationsFromListStarter(listStarter: HTMLElement) {
let targetElement: Element | null = listStarter.nextElementSibling;
while (targetElement) {
if (targetElement instanceof HTMLAnchorElement) {
this.#impliedTags.push(targetElement.innerText.trim());
}
// First line break is considered the end of the list.
if (targetElement instanceof HTMLBRElement) {
break;
}
targetElement = targetElement.nextElementSibling;
}
}
#onShowUntaggedImplicationsClicked(event: Event) {
event.preventDefault();
const url = new URL(window.location.href);
url.pathname = '/search';
url.search = '';
const currentTagName = this.#tagElement?.dataset.tagName;
url.searchParams.set('q', `${currentTagName}, !(${this.#impliedTags.join(", ")})`);
location.assign(url.href);
}
static #implicationsStarterText = 'Implies:';
}

View File

@@ -2,7 +2,7 @@ import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
import TagGroup from "$entities/TagGroup";
import { escapeRegExp } from "$lib/utils";
import { emit } from "$lib/components/events/comms";
import { eventTagCustomGroupResolved } from "$lib/components/events/tag-dropdown-events";
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
export default class CustomCategoriesResolver {
#exactGroupMatches = new Map<string, TagGroup>();
@@ -58,7 +58,7 @@ export default class CustomCategoriesResolver {
emit(
tagDropdown,
eventTagCustomGroupResolved,
EVENT_TAG_GROUP_RESOLVED,
this.#exactGroupMatches.get(tagName)!
);
@@ -75,7 +75,7 @@ export default class CustomCategoriesResolver {
emit(
tagDropdown,
eventTagCustomGroupResolved,
EVENT_TAG_GROUP_RESOLVED,
this.#regExpGroupMatches.get(targetRegularExpression)!
);
@@ -119,7 +119,7 @@ export default class CustomCategoriesResolver {
static #resetToOriginalCategory(tagDropdown: TagDropdownWrapper): void {
emit(
tagDropdown,
eventTagCustomGroupResolved,
EVENT_TAG_GROUP_RESOLVED,
null,
);
}