From b01eec39253310ca1db2f171aa198f770e3056de Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 6 Jun 2026 16:54:27 +0200 Subject: [PATCH] refactor: update anime page scripts --- static/anime.ts | 142 +++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 73 deletions(-) diff --git a/static/anime.ts b/static/anime.ts index 41e25f0..e67081d 100644 --- a/static/anime.ts +++ b/static/anime.ts @@ -1,84 +1,80 @@ -import { parseClassList } from "./utils"; +import { closestFocusable, onReady } from "./utils"; const initSynopsisToggle = (): void => { - document.addEventListener("click", (e) => { - const target = e.target; + document.addEventListener("click", (event) => { + const target = event.target; if (!(target instanceof Element)) return; - const btn = target.closest("[data-synopsis-toggle]"); - if (!btn) return; - const container = document.getElementById("synopsis-container"); + const button = target.closest("[data-synopsis-toggle]"); + if (!button) return; + + const section = button.closest("section"); + const container = section?.querySelector("[data-synopsis-container]"); if (!container) return; const isClamped = container.classList.contains("line-clamp-6"); - if (isClamped) { - container.classList.remove("line-clamp-6"); - btn.textContent = "Show less"; - return; - } - container.classList.add("line-clamp-6"); - btn.textContent = "Read more"; + container.classList.toggle("line-clamp-6", !isClamped); + button.textContent = isClamped ? "Read more" : "Show less"; + }); +}; + +const initThemesDialog = (): void => { + onReady(() => { + const dialog = document.querySelector("[data-themes-dialog]"); + const openButton = document.querySelector("[data-themes-open]"); + const closeButton = document.querySelector("[data-themes-close]"); + const content = document.querySelector("[data-themes-content]"); + const loader = document.querySelector("[data-themes-loader]"); + if (!dialog || !openButton || !content || !loader) return; + + let themesRequested = false; + let lastFocused: HTMLElement | null = null; + + const open = (): void => { + lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null; + dialog.classList.remove("hidden"); + dialog.classList.add("flex"); + dialog.setAttribute("aria-hidden", "false"); + closestFocusable(dialog)?.focus(); + + if (themesRequested) return; + themesRequested = true; + content.textContent = "Loading theme songs..."; + const htmxApi = ( + window as Window & { htmx?: { trigger: (target: Element, name: string) => void } } + ).htmx; + htmxApi?.trigger(document.body, "theme-songs:load"); + }; + + const close = (): void => { + dialog.classList.add("hidden"); + dialog.classList.remove("flex"); + dialog.setAttribute("aria-hidden", "true"); + lastFocused?.focus(); + }; + + openButton.addEventListener("click", open); + closeButton?.addEventListener("click", close); + dialog.addEventListener("click", (event) => { + if (event.target === dialog) { + close(); + } + }); + document.addEventListener("keydown", (event) => { + if (event.key !== "Escape") return; + if (dialog.classList.contains("hidden")) return; + event.preventDefault(); + close(); + }); + + loader.addEventListener("htmx:responseError", () => { + themesRequested = false; + }); + loader.addEventListener("htmx:sendError", () => { + themesRequested = false; + }); }); }; initSynopsisToggle(); - -const setDropdownMenuState = (menu: HTMLElement, isOpen: boolean): void => { - // data attributes store the class lists to add/remove - const openClasses = parseClassList(menu.getAttribute("data-dropdown-open-classes")); - const closedClasses = parseClassList(menu.getAttribute("data-dropdown-closed-classes")); - - if (isOpen) { - menu.classList.remove(...closedClasses); - menu.classList.add(...openClasses); - return; - } - - menu.classList.remove(...openClasses); - menu.classList.add(...closedClasses); -}; - -const setWatchlistDropdownState = (isOpen: boolean): void => { - const dropdown = document.getElementById("watchlist-dropdown"); - if (!dropdown) { - return; - } - - dropdown.classList.toggle("open", isOpen); - const menu = dropdown.querySelector("[data-dropdown-menu]"); - if (menu instanceof HTMLElement) { - setDropdownMenuState(menu, isOpen); - } -}; - -const toggleWatchlistDropdown = (): void => { - const dropdown = document.getElementById("watchlist-dropdown"); - if (!dropdown) { - return; - } - - setWatchlistDropdownState(!dropdown.classList.contains("open")); -}; - -const closeDropdownOnOutsideClick = (event: MouseEvent): void => { - const dropdown = document.getElementById("watchlist-dropdown"); - if (!dropdown) { - return; - } - - const target = event.target; - if (!(target instanceof Node)) { - return; - } - - if (!dropdown.contains(target)) { - setWatchlistDropdownState(false); - } -}; - -const initWatchlistDropdown = (): void => { - (window as Window & { toggleDropdown?: () => void }).toggleDropdown = toggleWatchlistDropdown; - document.addEventListener("click", closeDropdownOnOutsideClick); -}; - -initWatchlistDropdown(); +initThemesDialog();