1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-24 07:12:57 +00:00

Merge remote-tracking branch 'refs/remotes/origin/release/0.4.3' into feature/workaronud-for-opening-in-new-tab

This commit is contained in:
2025-02-28 03:28:13 +04:00
17 changed files with 305 additions and 38 deletions

3
src/app.d.ts vendored
View File

@@ -4,6 +4,9 @@ import MaintenanceProfile from "$entities/MaintenanceProfile";
import type TagGroup from "$entities/TagGroup";
declare global {
// Helper type to not deal with differences between the setTimeout of @types/node and usual web browser's type.
type Timeout = ReturnType<typeof setTimeout>;
namespace App {
// interface Error {}
// interface Locals {}

View File

@@ -1,6 +1,6 @@
import { initializeSiteHeader } from "$lib/components/SiteHeaderWrapper";
const siteHeader = document.querySelector('.header');
const siteHeader = document.querySelector<HTMLElement>('.header');
if (siteHeader) {
initializeSiteHeader(siteHeader);

View File

@@ -4,8 +4,7 @@ import { calculateMediaBoxesPositions, initializeMediaBox } from "$lib/component
import { createMaintenanceStatusIcon } from "$lib/components/MaintenanceStatusIcon";
import { createImageShowFullscreenButton } from "$lib/components/ImageShowFullscreenButton";
/** @type {NodeListOf<HTMLElement>} */
const mediaBoxes = document.querySelectorAll('.media-box');
const mediaBoxes = document.querySelectorAll<HTMLElement>('.media-box');
mediaBoxes.forEach(mediaBoxElement => {
initializeMediaBox(mediaBoxElement, [

View File

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

View File

@@ -47,7 +47,7 @@ export class ImageShowFullscreenButton extends BaseComponent {
}
#onButtonClicked() {
const imageLinks = this.#mediaBoxTools?.mediaBox.imageLinks;
const imageLinks = this.#mediaBoxTools?.mediaBox?.imageLinks;
if (!imageLinks) {
throw new Error('Failed to resolve image links from media box tools!');

View File

@@ -30,7 +30,7 @@ export class MaintenancePopup extends BaseComponent {
#tagsToAdd: Set<string> = new Set();
#isPlanningToSubmit: boolean = false;
#isSubmitting: boolean = false;
#tagsSubmissionTimer: number | null = null;
#tagsSubmissionTimer: Timeout | null = null;
#emitter = emitterAt(this);
/**
@@ -70,6 +70,10 @@ export class MaintenancePopup extends BaseComponent {
const mediaBox = this.#mediaBoxTools.mediaBox;
if (!mediaBox) {
throw new Error('Media box component not found!');
}
mediaBox.on('mouseout', this.#onMouseLeftArea.bind(this));
mediaBox.on('mouseover', this.#onMouseEnteredArea.bind(this));
}
@@ -83,7 +87,7 @@ export class MaintenancePopup extends BaseComponent {
}
#refreshTagsList() {
if (!this.#mediaBoxTools) {
if (!this.#mediaBoxTools?.mediaBox) {
return;
}
@@ -109,11 +113,11 @@ export class MaintenancePopup extends BaseComponent {
this.#tagsList[index] = tagElement;
this.#tagsListElement.appendChild(tagElement);
const isPresent = currentPostTags.has(tagName);
const isPresent = currentPostTags?.has(tagName);
tagElement.classList.toggle('is-present', isPresent);
tagElement.classList.toggle('is-missing', !isPresent);
tagElement.classList.toggle('is-aliased', isPresent && currentPostTags.get(tagName) !== tagName);
tagElement.classList.toggle('is-aliased', isPresent && currentPostTags?.get(tagName) !== tagName);
// Just to prevent duplication, we need to include this tag to the map of suggested invalid tags
if (tagsBlacklist.includes(tagName)) {
@@ -193,7 +197,7 @@ export class MaintenancePopup extends BaseComponent {
}
async #onSubmissionTimerPassed() {
if (!this.#isPlanningToSubmit || this.#isSubmitting || !this.#mediaBoxTools) {
if (!this.#isPlanningToSubmit || this.#isSubmitting || !this.#mediaBoxTools?.mediaBox) {
return;
}
@@ -264,7 +268,7 @@ export class MaintenancePopup extends BaseComponent {
}
#revealInvalidTags() {
if (!this.#mediaBoxTools) {
if (!this.#mediaBoxTools?.mediaBox) {
return;
}

View File

@@ -2,12 +2,16 @@ import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/Storag
export default class ConfigurationController {
readonly #configurationName: string;
readonly #storage: StorageHelper;
/**
* @param {string} configurationName Name of the configuration to work with.
* @param {StorageHelper} [storage] Selected storage where the settings are stored in. If not provided, local storage
* is used.
*/
constructor(configurationName: string) {
constructor(configurationName: string, storage: StorageHelper = new StorageHelper(chrome.storage.local)) {
this.#configurationName = configurationName;
this.#storage = storage;
}
/**
@@ -19,7 +23,7 @@ export default class ConfigurationController {
* @return The setting value or the default value if the setting does not exist.
*/
async readSetting<Type = any, DefaultType = any>(settingName: string, defaultValue: DefaultType | null = null): Promise<Type | DefaultType> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
const settings = await this.#storage.read(this.#configurationName, {});
return settings[settingName] ?? defaultValue;
}
@@ -32,11 +36,11 @@ export default class ConfigurationController {
* @return {Promise<void>}
*/
async writeSetting(settingName: string, value: any): Promise<void> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
const settings = await this.#storage.read(this.#configurationName, {});
settings[settingName] = value;
ConfigurationController.#storageHelper.write(this.#configurationName, settings);
this.#storage.write(this.#configurationName, settings);
}
/**
@@ -45,11 +49,11 @@ export default class ConfigurationController {
* @param {string} settingName Setting name to delete.
*/
async deleteSetting(settingName: string): Promise<void> {
const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {});
const settings = await this.#storage.read(this.#configurationName, {});
delete settings[settingName];
ConfigurationController.#storageHelper.write(this.#configurationName, settings);
this.#storage.write(this.#configurationName, settings);
}
/**
@@ -69,10 +73,8 @@ export default class ConfigurationController {
callback(changes[this.#configurationName].newValue);
}
ConfigurationController.#storageHelper.subscribe(subscriber);
this.#storage.subscribe(subscriber);
return () => ConfigurationController.#storageHelper.unsubscribe(subscriber);
return () => this.#storage.unsubscribe(subscriber);
}
static #storageHelper = new StorageHelper(chrome.storage.local);
}

View File

@@ -6,7 +6,7 @@ export default class CustomCategoriesResolver {
#tagCategories = new Map<string, string>();
#compiledRegExps = new Map<RegExp, string>();
#tagDropdowns: TagDropdownWrapper[] = [];
#nextQueuedUpdate = -1;
#nextQueuedUpdate: Timeout | null = null;
constructor() {
TagGroup.subscribe(this.#onTagGroupsReceived.bind(this));
@@ -24,7 +24,9 @@ export default class CustomCategoriesResolver {
}
#queueUpdatingTags() {
clearTimeout(this.#nextQueuedUpdate);
if (this.#nextQueuedUpdate) {
clearTimeout(this.#nextQueuedUpdate);
}
this.#nextQueuedUpdate = setTimeout(
this.#updateUnprocessedTags.bind(this),