From 134e96bc4ccfc0557811349b295dab3071d02d24 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 3 Jun 2025 02:14:00 +0400 Subject: [PATCH] Added link for a quick search of untagged implications --- src/content/listing.ts | 6 ++ .../components/listing/ImageListContainer.ts | 19 +++++ src/lib/components/listing/ImageListInfo.ts | 75 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/lib/components/listing/ImageListContainer.ts create mode 100644 src/lib/components/listing/ImageListInfo.ts diff --git a/src/content/listing.ts b/src/content/listing.ts index 63a436b..13733a6 100644 --- a/src/content/listing.ts +++ b/src/content/listing.ts @@ -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('.media-box'); +const imageListContainer = document.querySelector('#imagelist-container'); mediaBoxes.forEach(mediaBoxElement => { initializeMediaBox(mediaBoxElement, [ @@ -22,3 +24,7 @@ mediaBoxes.forEach(mediaBoxElement => { }); calculateMediaBoxesPositions(mediaBoxes); + +if (imageListContainer) { + initializeImageListContainer(imageListContainer); +} diff --git a/src/lib/components/listing/ImageListContainer.ts b/src/lib/components/listing/ImageListContainer.ts new file mode 100644 index 0000000..eb11ac1 --- /dev/null +++ b/src/lib/components/listing/ImageListContainer.ts @@ -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('.js-imagelist-info'); + + if (imageListInfoContainer) { + this.#info = new ImageListInfo(imageListInfoContainer); + this.#info.initialize(); + } + } +} + +export function initializeImageListContainer(element: HTMLElement) { + new ImageListContainer(element).initialize(); +} diff --git a/src/lib/components/listing/ImageListInfo.ts b/src/lib/components/listing/ImageListInfo.ts new file mode 100644 index 0000000..d75114d --- /dev/null +++ b/src/lib/components/listing/ImageListInfo.ts @@ -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('.tag.dropdown') ?? null; + + const labels = this.container + .querySelectorAll('.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:'; +}