mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2026-02-06 23:32:58 +00:00
Compare commits
7 Commits
feature/pr
...
0.4.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 6098a11115 | |||
| a87d8b94b8 | |||
| c283b96285 | |||
| 02478f0bf0 | |||
| 59c15f27eb | |||
| 134e96bc4c | |||
| 1c05159ddf |
@@ -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.4",
|
||||
"version": "0.4.5",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "furbooru-tagging-assistant@thecore.city"
|
||||
|
||||
836
package-lock.json
generated
836
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "furbooru-tagging-assistant",
|
||||
"version": "0.4.4",
|
||||
"version": "0.4.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build:popup && npm run build:extension",
|
||||
@@ -12,26 +12,24 @@
|
||||
"test:watch": "vitest watch --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.20.3",
|
||||
"@sveltejs/kit": "^2.21.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/chrome": "^0.0.313",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"@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.86.3",
|
||||
"svelte": "^5.25.6",
|
||||
"svelte-check": "^4.1.5",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.5",
|
||||
"vitest": "^3.1.1"
|
||||
"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": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@std/regexp": "npm:@jsr/std__regexp@^1.0.1",
|
||||
"lz-string": "^1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
a {
|
||||
color: colors.$text;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
19
src/lib/components/listing/ImageListContainer.ts
Normal file
19
src/lib/components/listing/ImageListContainer.ts
Normal 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();
|
||||
}
|
||||
75
src/lib/components/listing/ImageListInfo.ts
Normal file
75
src/lib/components/listing/ImageListInfo.ts
Normal 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:';
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TagDropdownWrapper } from "$lib/components/TagDropdownWrapper";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { escape as escapeRegExp } from "@std/regexp";
|
||||
import { escapeRegExp } from "$lib/utils";
|
||||
import { emit } from "$lib/components/events/comms";
|
||||
import { EVENT_TAG_GROUP_RESOLVED } from "$lib/components/events/tag-dropdown-events";
|
||||
|
||||
|
||||
@@ -21,3 +21,21 @@ export function findDeepObject(targetObject: Record<string, any>, path: string[]
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches all the characters needing replacement.
|
||||
*
|
||||
* Gathered from right here: https://stackoverflow.com/a/3561711/16048617. Because I don't want to introduce some
|
||||
* library for that.
|
||||
*/
|
||||
const unsafeRegExpCharacters: RegExp = /[/\-\\^$*+?.()|[\]{}]/g;
|
||||
|
||||
/**
|
||||
* Escape all the RegExp syntax-related characters in the following value.
|
||||
* @param value Original value.
|
||||
* @return Resulting value with all needed characters escaped.
|
||||
*/
|
||||
export function escapeRegExp(value: string): string {
|
||||
unsafeRegExpCharacters.lastIndex = 0;
|
||||
return value.replace(unsafeRegExpCharacters, "\\$&");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user