Files
bookhoard/web/static/search.js
T
john-okeefe 58ddfed48c 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.
2026-03-06 20:18:11 -05:00

56 lines
6.1 KiB
JavaScript

(()=>{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<v){c();return}i=setTimeout(()=>{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=`
<div class="p-4 text-center" style="color: var(--text-secondary)">
<div class="inline-block animate-spin rounded-full h-6 w-6 border-b-2" style="border-color: var(--accent)"></div>
<p class="mt-2 text-sm">Searching...</p>
</div>
`,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=`
<div class="p-3 border-b" style="border-color: var(--border)">
<p class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-secondary)">
${e.length} result${e.length!==1?"s":""} for "${a(r)}"
</p>
</div>
<div class="max-h-96 overflow-y-auto">
`;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+=`
<div class="search-result-item p-3 border-b hover:bg-opacity-50 transition-colors cursor-pointer"
style="border-color: var(--border); background-color: var(--bg-secondary)"
data-index="${p}">
<a href="/bookshelf"
class="block"
onclick="window.selectLibraryAndBook('${o.library_id}', '${o.id}')">
<div class="flex items-start space-x-3">
<div class="text-2xl">${f}</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-medium truncate" style="color: var(--text-primary)">
${g}
</h4>
${u?`<p class="text-xs truncate" style="color: var(--text-secondary)">${u}</p>`:""}
<p class="text-xs mt-1" style="color: var(--text-secondary)">
${a(o.library_name)}
</p>
</div>
</div>
</a>
</div>
`}),n+=`
</div>
<div class="p-2 border-t text-center" style="border-color: var(--border)">
<p class="text-xs" style="color: var(--text-secondary)">
Press <kbd class="px-1 py-0.5 rounded" style="background-color: var(--bg-primary)">\u2191\u2193</kbd> to navigate,
<kbd class="px-1 py-0.5 rounded" style="background-color: var(--bg-primary)">Enter</kbd> to select
</p>
</div>
`,t.innerHTML=n,t.classList.remove("hidden")}function d(e){l();let r=document.getElementById("search-results");r&&(r.innerHTML=`
<div class="p-4 text-center">
<div class="text-4xl mb-2">\u{1F50D}</div>
<p class="text-sm" style="color: var(--text-primary)">No results found for "${a(e)}"</p>
<p class="text-xs mt-1" style="color: var(--text-secondary)">Try different keywords</p>
</div>
`,r.classList.remove("hidden"))}function S(){l();let e=document.getElementById("search-results");e&&(e.innerHTML=`
<div class="p-4 text-center">
<div class="text-4xl mb-2">\u26A0\uFE0F</div>
<p class="text-sm" style="color: var(--text-primary)">Search error</p>
<p class="text-xs mt-1" style="color: var(--text-secondary)">Please try again</p>
</div>
`,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,'<mark style="background-color: var(--accent); color: var(--bg-primary); padding: 0 2px; border-radius: 2px;">$1</mark>')}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