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