1
0
mirror of https://github.com/koloml/philomena-tagging-assistant.git synced 2026-09-23 04:19:39 +00:00

Merge remote-tracking branch 'origin/master' into feature/bulk-import-and-export

# Conflicts:
#	src/lib/extension/transporting/exporters.ts
This commit is contained in:
2025-04-04 18:25:27 +04:00
63 changed files with 4106 additions and 756 deletions

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

@@ -1,12 +1,14 @@
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 { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
export default class CustomCategoriesResolver {
#tagCategories = new Map<string, string>();
#compiledRegExps = new Map<RegExp, string>();
#exactGroupMatches = new Map<string, TagGroup>();
#regExpGroupMatches = new Map<RegExp, TagGroup>();
#tagDropdowns: TagDropdownWrapper[] = [];
#nextQueuedUpdate = -1;
#nextQueuedUpdate: Timeout | null = null;
constructor() {
TagGroup.subscribe(this.#onTagGroupsReceived.bind(this));
@@ -16,7 +18,7 @@ export default class CustomCategoriesResolver {
public addElement(tagDropdown: TagDropdownWrapper): void {
this.#tagDropdowns.push(tagDropdown);
if (!this.#tagCategories.size && !this.#compiledRegExps.size) {
if (!this.#exactGroupMatches.size && !this.#regExpGroupMatches.size) {
return;
}
@@ -24,7 +26,9 @@ export default class CustomCategoriesResolver {
}
#queueUpdatingTags() {
clearTimeout(this.#nextQueuedUpdate);
if (this.#nextQueuedUpdate) {
clearTimeout(this.#nextQueuedUpdate);
}
this.#nextQueuedUpdate = setTimeout(
this.#updateUnprocessedTags.bind(this),
@@ -34,7 +38,6 @@ export default class CustomCategoriesResolver {
#updateUnprocessedTags() {
this.#tagDropdowns
.filter(CustomCategoriesResolver.#skipTagsWithOriginalCategory)
.filter(this.#applyCustomCategoryForExactMatches.bind(this))
.filter(this.#matchCustomCategoryByRegExp.bind(this))
.forEach(CustomCategoriesResolver.#resetToOriginalCategory);
@@ -49,23 +52,33 @@ export default class CustomCategoriesResolver {
#applyCustomCategoryForExactMatches(tagDropdown: TagDropdownWrapper): boolean {
const tagName = tagDropdown.tagName!;
if (!this.#tagCategories.has(tagName)) {
if (!this.#exactGroupMatches.has(tagName)) {
return true;
}
tagDropdown.tagCategory = this.#tagCategories.get(tagName)!;
emit(
tagDropdown,
EVENT_TAG_GROUP_RESOLVED,
this.#exactGroupMatches.get(tagName)!
);
return false;
}
#matchCustomCategoryByRegExp(tagDropdown: TagDropdownWrapper) {
const tagName = tagDropdown.tagName!;
for (const targetRegularExpression of this.#compiledRegExps.keys()) {
for (const targetRegularExpression of this.#regExpGroupMatches.keys()) {
if (!targetRegularExpression.test(tagName)) {
continue;
}
tagDropdown.tagCategory = this.#compiledRegExps.get(targetRegularExpression)!;
emit(
tagDropdown,
EVENT_TAG_GROUP_RESOLVED,
this.#regExpGroupMatches.get(targetRegularExpression)!
);
return false;
}
@@ -73,24 +86,29 @@ export default class CustomCategoriesResolver {
}
#onTagGroupsReceived(tagGroups: TagGroup[]) {
this.#tagCategories.clear();
this.#compiledRegExps.clear();
this.#exactGroupMatches.clear();
this.#regExpGroupMatches.clear();
if (!tagGroups.length) {
return;
}
for (const tagGroup of tagGroups) {
const categoryName = tagGroup.settings.category;
for (const tagName of tagGroup.settings.tags) {
this.#tagCategories.set(tagName, categoryName);
this.#exactGroupMatches.set(tagName, tagGroup);
}
for (const tagPrefix of tagGroup.settings.prefixes) {
this.#compiledRegExps.set(
this.#regExpGroupMatches.set(
new RegExp(`^${escapeRegExp(tagPrefix)}`),
categoryName
tagGroup,
);
}
for (let tagSuffix of tagGroup.settings.suffixes) {
this.#regExpGroupMatches.set(
new RegExp(`${escapeRegExp(tagSuffix)}$`),
tagGroup,
);
}
}
@@ -98,12 +116,12 @@ export default class CustomCategoriesResolver {
this.#queueUpdatingTags();
}
static #skipTagsWithOriginalCategory(tagDropdown: TagDropdownWrapper): boolean {
return !tagDropdown.originalCategory;
}
static #resetToOriginalCategory(tagDropdown: TagDropdownWrapper): void {
tagDropdown.tagCategory = tagDropdown.originalCategory;
emit(
tagDropdown,
EVENT_TAG_GROUP_RESOLVED,
null,
);
}
static #unprocessedTagsTimeout = 0;

View File

@@ -4,7 +4,9 @@ export interface TagGroupSettings {
name: string;
tags: string[];
prefixes: string[];
suffixes: string[];
category: string;
separate: boolean;
}
export default class TagGroup extends StorageEntity<TagGroupSettings> {
@@ -13,7 +15,9 @@ export default class TagGroup extends StorageEntity<TagGroupSettings> {
name: settings.name || '',
tags: settings.tags || [],
prefixes: settings.prefixes || [],
category: settings.category || ''
suffixes: settings.suffixes || [],
category: settings.category || '',
separate: Boolean(settings.separate),
});
}

View File

@@ -1,6 +1,6 @@
import CacheableSettings from "$lib/extension/base/CacheableSettings";
export type FullscreenViewerSize = 'small' | 'medium' | 'large' | 'full';
export type FullscreenViewerSize = keyof App.ImageURIs;
interface MiscSettingsFields {
fullscreenViewer: boolean;

View File

@@ -0,0 +1,19 @@
import CacheableSettings from "$lib/extension/base/CacheableSettings";
interface TagSettingsFields {
groupSeparation: boolean;
}
export default class TagSettings extends CacheableSettings<TagSettingsFields> {
constructor() {
super("tag");
}
async resolveGroupSeparation() {
return this._resolveSetting("groupSeparation", true);
}
async setGroupSeparation(value: boolean) {
return this._writeSetting("groupSeparation", Boolean(value));
}
}

View File

@@ -27,7 +27,9 @@ const entitiesExporters: ExportersMap = {
name: entity.settings.name,
tags: entity.settings.tags,
prefixes: entity.settings.prefixes,
suffixes: entity.settings.suffixes,
category: entity.settings.category,
separate: entity.settings.separate,
}
}
};