From 58ddfed48ca9bdadeafa86f3a56944cd9187750c Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 6 Mar 2026 20:18:11 -0500 Subject: [PATCH] build(frontend): rebuild static JavaScript with esbuild Update header.js and search.js to reflect the new esbuild build pipeline. The header.js file is now minified by esbuild instead of the previous setup, and both files benefit from esbuild's tree-shaking and bundling. --- web/static/header.js | 79 +------------- web/static/search.js | 250 ++++--------------------------------------- 2 files changed, 21 insertions(+), 308 deletions(-) diff --git a/web/static/header.js b/web/static/header.js index d48ba45..c8f072a 100644 --- a/web/static/header.js +++ b/web/static/header.js @@ -1,77 +1,2 @@ -"use strict"; -// Header functionality -const toggleThemeDropdown = () => { - const dropdown = document.getElementById("theme-dropdown"); - if (dropdown) { - dropdown.classList.toggle("hidden"); - // Close user menu if open - const userMenu = document.getElementById("user-menu"); - if (userMenu && !dropdown.classList.contains("hidden")) { - userMenu.classList.add("hidden"); - } - } -}; -const toggleUserMenu = () => { - const menu = document.getElementById("user-menu"); - if (menu) { - menu.classList.toggle("hidden"); - // Close theme dropdown if open - const themeDropdown = document.getElementById("theme-dropdown"); - if (themeDropdown && !menu.classList.contains("hidden")) { - themeDropdown.classList.add("hidden"); - } - } -}; -const changeThemeTo = (theme) => { - // Apply the theme using the consolidated function from theme.ts - if (window.applyTheme) { - window.applyTheme(theme); - } - // Save to server if logged in - const token = localStorage.getItem("token"); - if (token) { - fetch("/api/auth/theme", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ theme }), - }).catch((err) => console.log("Theme save failed", err)); - } - // Close dropdown - const dropdown = document.getElementById("theme-dropdown"); - if (dropdown) { - dropdown.classList.add("hidden"); - } -}; -const logout = () => { - localStorage.removeItem("token"); - localStorage.removeItem("user"); - window.location.href = "/"; -}; -// Close dropdowns when clicking outside -document.addEventListener("click", (e) => { - const target = e.target; - const themeDropdown = document.getElementById("theme-dropdown"); - const userMenu = document.getElementById("user-menu"); - const themeButton = target?.closest('button[onclick="toggleThemeDropdown()"]'); - const userButton = target?.closest('button[onclick="toggleUserMenu()"]'); - if (!themeButton && - themeDropdown && - !themeDropdown.classList.contains("hidden")) { - if (!themeDropdown.contains(target)) { - themeDropdown.classList.add("hidden"); - } - } - if (!userButton && userMenu && !userMenu.classList.contains("hidden")) { - if (!userMenu.contains(target)) { - userMenu.classList.add("hidden"); - } - } -}); -// Make functions available globally -window.toggleThemeDropdown = toggleThemeDropdown; -window.toggleUserMenu = toggleUserMenu; -window.changeThemeTo = changeThemeTo; -window.logout = logout; +(()=>{var i=()=>{let t=document.getElementById("theme-dropdown");if(t){t.classList.toggle("hidden");let e=document.getElementById("user-menu");e&&!t.classList.contains("hidden")&&e.classList.add("hidden")}},a=()=>{let t=document.getElementById("user-menu");if(t){t.classList.toggle("hidden");let e=document.getElementById("theme-dropdown");e&&!t.classList.contains("hidden")&&e.classList.add("hidden")}},c=t=>{window.applyTheme&&window.applyTheme(t);let e=localStorage.getItem("token");e&&fetch("/api/auth/theme",{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({theme:t})}).catch(o=>console.log("Theme save failed",o));let n=document.getElementById("theme-dropdown");n&&n.classList.add("hidden")},l=()=>{localStorage.removeItem("token"),localStorage.removeItem("user"),window.location.href="/"};document.addEventListener("click",t=>{let e=t.target,n=document.getElementById("theme-dropdown"),o=document.getElementById("user-menu"),s=e?.closest('button[onclick="toggleThemeDropdown()"]'),d=e?.closest('button[onclick="toggleUserMenu()"]');!s&&n&&!n.classList.contains("hidden")&&(n.contains(e)||n.classList.add("hidden")),!d&&o&&!o.classList.contains("hidden")&&(o.contains(e)||o.classList.add("hidden"))});window.toggleThemeDropdown=i;window.toggleUserMenu=a;window.changeThemeTo=c;window.logout=l;})(); +//# sourceMappingURL=header.js.map diff --git a/web/static/search.js b/web/static/search.js index afc775f..daeb2d2 100644 --- a/web/static/search.js +++ b/web/static/search.js @@ -1,267 +1,55 @@ -"use strict"; -let searchInputTimeout = null; -const SEARCH_DEBOUNCE_MS = 300; -const SEARCH_MIN_CHARS = 2; -function initializeSearch() { - const searchInput = document.getElementById("header-search"); - if (!searchInput) { - console.warn("Search input not found"); - return; - } - searchInput.addEventListener("input", handleSearchInput); - searchInput.addEventListener("keydown", handleSearchKeydown); - searchInput.addEventListener("focus", () => { - if (searchInput.value.length >= SEARCH_MIN_CHARS) { - performSearch(searchInput.value); - } - }); - document.addEventListener("click", (e) => { - const searchResults = document.getElementById("search-results"); - const searchInputEl = document.getElementById("header-search"); - if (searchResults && - !searchResults.contains(e.target) && - e.target !== searchInputEl) { - hideSearchResults(); - } - }); -} -function handleSearchInput(e) { - const target = e.target; - const query = target.value.trim(); - if (searchInputTimeout) { - clearTimeout(searchInputTimeout); - } - if (query.length < SEARCH_MIN_CHARS) { - hideSearchResults(); - return; - } - searchInputTimeout = setTimeout(() => { - performSearch(query); - }, SEARCH_DEBOUNCE_MS); -} -function handleSearchKeydown(e) { - const searchResults = document.getElementById("search-results"); - if (!searchResults || searchResults.classList.contains("hidden")) { - return; - } - const items = searchResults.querySelectorAll(".search-result-item"); - const currentIndex = parseInt(searchResults.dataset.selectedIndex || "-1"); - if (e.key === "ArrowDown") { - e.preventDefault(); - const nextIndex = Math.min(currentIndex + 1, items.length - 1); - selectSearchResult(items, nextIndex); - } - else if (e.key === "ArrowUp") { - e.preventDefault(); - const prevIndex = Math.max(currentIndex - 1, -1); - selectSearchResult(items, prevIndex); - } - else if (e.key === "Enter") { - e.preventDefault(); - if (currentIndex >= 0 && items[currentIndex]) { - const link = items[currentIndex].querySelector("a"); - if (link) - link.click(); - } - } - else if (e.key === "Escape") { - hideSearchResults(); - } -} -function selectSearchResult(items, index) { - items.forEach((item, i) => { - if (i === index) { - item.classList.add("bg-opacity-80"); - } - else { - item.classList.remove("bg-opacity-80"); - } - }); - const searchResults = document.getElementById("search-results"); - if (searchResults) { - searchResults.dataset.selectedIndex = index.toString(); - } -} -function performSearch(query) { - const token = localStorage.getItem("token"); - if (!token) { - console.warn("No authentication token found"); - return; - } - showSearchLoading(); - fetch(`/api/media-items/search?q=${encodeURIComponent(query)}`, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .then((response) => { - if (response.status === 404) { - return { error: "no results found", results: [] }; - } - return response.json(); - }) - .then((data) => { - hideSearchLoading(); - if (data && "error" in data && data.error === "no results found") { - showNoResults(query); - } - else if (Array.isArray(data) && data.length > 0) { - showSearchResults(data, query); - } - else if (Array.isArray(data)) { - showNoResults(query); - } - else { - showNoResults(query); - } - }) - .catch((error) => { - hideSearchLoading(); - console.error("Search error:", error); - showSearchError(); - }); -} -function showSearchLoading() { - createSearchResultsContainer(); - const searchResults = document.getElementById("search-results"); - if (!searchResults) - return; - searchResults.innerHTML = ` +(()=>{var i=null,x=300,v=2;function b(){let e=document.getElementById("header-search");if(!e){console.warn("Search input not found");return}e.addEventListener("input",E),e.addEventListener("keydown",I),e.addEventListener("focus",()=>{e.value.length>=v&&y(e.value)}),document.addEventListener("click",r=>{let t=document.getElementById("search-results"),s=document.getElementById("header-search");t&&!t.contains(r.target)&&r.target!==s&&c()})}function E(e){let t=e.target.value.trim();if(i&&clearTimeout(i),t.length{y(t)},x)}function I(e){let r=document.getElementById("search-results");if(!r||r.classList.contains("hidden"))return;let t=r.querySelectorAll(".search-result-item"),s=parseInt(r.dataset.selectedIndex||"-1");if(e.key==="ArrowDown"){e.preventDefault();let n=Math.min(s+1,t.length-1);h(t,n)}else if(e.key==="ArrowUp"){e.preventDefault();let n=Math.max(s-1,-1);h(t,n)}else if(e.key==="Enter"){if(e.preventDefault(),s>=0&&t[s]){let n=t[s].querySelector("a");n&&n.click()}}else e.key==="Escape"&&c()}function h(e,r){e.forEach((s,n)=>{n===r?s.classList.add("bg-opacity-80"):s.classList.remove("bg-opacity-80")});let t=document.getElementById("search-results");t&&(t.dataset.selectedIndex=r.toString())}function y(e){let r=localStorage.getItem("token");if(!r){console.warn("No authentication token found");return}k(),fetch(`/api/media-items/search?q=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}}).then(t=>t.status===404?{error:"no results found",results:[]}:t.json()).then(t=>{t&&"error"in t&&t.error==="no results found"?d(e):Array.isArray(t)&&t.length>0?L(t,e):(Array.isArray(t),d(e))}).catch(t=>{console.error("Search error:",t),S()})}function k(){l();let e=document.getElementById("search-results");e&&(e.innerHTML=`

Searching...

- `; - searchResults.classList.remove("hidden"); -} -function hideSearchLoading() { } -function showSearchResults(results, query) { - createSearchResultsContainer(); - const searchResults = document.getElementById("search-results"); - if (!searchResults) - return; - searchResults.dataset.selectedIndex = "-1"; - const libraryIconMap = { - ebooks: "📚", - comics: "📖", - manga: "🗾", - }; - let html = ` + `,e.classList.remove("hidden"))}function L(e,r){l();let t=document.getElementById("search-results");if(!t)return;t.dataset.selectedIndex="-1";let s={ebooks:"\u{1F4DA}",comics:"\u{1F4D6}",manga:"\u{1F5FE}"},n=`

- ${results.length} result${results.length !== 1 ? "s" : ""} for "${searchEscapeHtml(query)}" + ${e.length} result${e.length!==1?"s":""} for "${a(r)}"

- `; - results.forEach((item, index) => { - const icon = libraryIconMap[item.library_type_name] || "📁"; - const titleHtml = highlightMatch(item.title, query); - const authorHtml = item.author ? highlightMatch(item.author, query) : ""; - html += ` + `;e.forEach((o,p)=>{let f=s[o.library_type_name]||"\u{1F4C1}",g=m(o.title,r),u=o.author?m(o.author,r):"";n+=` - `; - }); - html += ` + `}),n+=`

- Press ↑↓ to navigate, + Press \u2191\u2193 to navigate, Enter to select

- `; - searchResults.innerHTML = html; - searchResults.classList.remove("hidden"); -} -function showNoResults(query) { - createSearchResultsContainer(); - const searchResults = document.getElementById("search-results"); - if (!searchResults) - return; - searchResults.innerHTML = ` + `,t.innerHTML=n,t.classList.remove("hidden")}function d(e){l();let r=document.getElementById("search-results");r&&(r.innerHTML=`
-
🔍
-

No results found for "${searchEscapeHtml(query)}"

+
\u{1F50D}
+

No results found for "${a(e)}"

Try different keywords

- `; - searchResults.classList.remove("hidden"); -} -function showSearchError() { - createSearchResultsContainer(); - const searchResults = document.getElementById("search-results"); - if (!searchResults) - return; - searchResults.innerHTML = ` + `,r.classList.remove("hidden"))}function S(){l();let e=document.getElementById("search-results");e&&(e.innerHTML=`
-
⚠️
+
\u26A0\uFE0F

Search error

Please try again

- `; - searchResults.classList.remove("hidden"); -} -function hideSearchResults() { - const searchResults = document.getElementById("search-results"); - if (searchResults) { - searchResults.classList.add("hidden"); - } -} -function createSearchResultsContainer() { - let searchResults = document.getElementById("search-results"); - if (!searchResults) { - searchResults = document.createElement("div"); - searchResults.id = "search-results"; - searchResults.className = - "hidden absolute z-50 w-full max-w-2xl mt-2 rounded-lg shadow-lg border"; - searchResults.style.cssText = - "background-color: var(--bg-secondary); border-color: var(--border)"; - const searchInput = document.getElementById("header-search"); - if (searchInput) { - const searchContainer = searchInput.closest(".relative"); - if (searchContainer) { - searchContainer.appendChild(searchResults); - } - } - } -} -function highlightMatch(text, query) { - if (!text) - return ""; - const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const regex = new RegExp(`(${escapedQuery})`, "gi"); - return searchEscapeHtml(text).replace(regex, '$1'); -} -function searchEscapeHtml(text) { - const div = document.createElement("div"); - div.textContent = text; - return div.innerHTML; -} -function selectLibraryAndBook(libraryId, bookId) { - localStorage.setItem("selectedLibrary", libraryId); - localStorage.setItem("selectedBook", bookId); - hideSearchResults(); -} -document.addEventListener("DOMContentLoaded", initializeSearch); -window.selectLibraryAndBook = selectLibraryAndBook; + `,e.classList.remove("hidden"))}function c(){let e=document.getElementById("search-results");e&&e.classList.add("hidden")}function l(){let e=document.getElementById("search-results");if(!e){e=document.createElement("div"),e.id="search-results",e.className="hidden absolute z-50 w-full max-w-2xl mt-2 rounded-lg shadow-lg border",e.style.cssText="background-color: var(--bg-secondary); border-color: var(--border)";let r=document.getElementById("header-search");if(r){let t=r.closest(".relative");t&&t.appendChild(e)}}}function m(e,r){if(!e)return"";let t=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),s=new RegExp(`(${t})`,"gi");return a(e).replace(s,'$1')}function a(e){let r=document.createElement("div");return r.textContent=e,r.innerHTML}function w(e,r){localStorage.setItem("selectedLibrary",e),localStorage.setItem("selectedBook",r),c()}document.addEventListener("DOMContentLoaded",b);window.selectLibraryAndBook=w;})(); +//# sourceMappingURL=search.js.map