build: Add HTMX copy step to build:ts script

- Modified package.json build:ts to copy htmx.min.js from node_modules to web/static/
- This fixes the 404 error for /static/htmx.min.js that occurred after ESBuild migration
- Added htmx.min.js to static files

See ESBUILD_MIGRATION_PLAN.md for migration context.
This commit is contained in:
john-okeefe committed 2026-03-08 21:35:35 -04:00
1 parent a84ffb253e
commit 0af319fef9
4 files changed
+322 -26

No files matched your search

+8 -7
View File
@@ -5,24 +5,25 @@
"scripts": { "scripts": {
"build:css": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --watch", "build:css": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --watch",
"build:css:prod": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --minify", "build:css:prod": "tailwindcss -i ./web/static/input.css -o ./web/static/style.css --minify",
"build:ts": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify", "build:ts": "cp node_modules/htmx.org/dist/htmx.min.js web/static/htmx.min.js && esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --minify",
"build:ts:dev": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020", "build:ts:dev": "cp node_modules/htmx.org/dist/htmx.min.js web/static/htmx.min.js && esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020",
"build:ts:watch": "esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --watch", "build:ts:watch": "cp node_modules/htmx.org/dist/htmx.min.js web/static/htmx.min.js && esbuild web/src/main.ts --bundle --outfile=web/static/main.js --sourcemap --target=es2020 --watch",
"build": "npm run build:ts && npm run build:css:prod", "build": "npm run build:ts && npm run build:css:prod",
"dev": "npm run build:ts:dev && npm run build:css" "dev": "npm run build:ts:dev && npm run build:css"
}, },
"dependencies": { "dependencies": {
"alpinejs": "^3.15.8", "alpinejs": "^3.15.8",
"lunr": "^2.3.9", "chart.js": "^4.5.1",
"highlight.js": "^11.11.1",
"htmx.org": "^2.0.8", "htmx.org": "^2.0.8",
"highlight.js": "^11.11.1" "lunr": "^2.3.9"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.27", "autoprefixer": "^10.4.27",
"esbuild": "^0.27.3",
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"typescript": "^5.9.3", "typescript": "^5.9.3"
"esbuild": "^0.27.3"
} }
} }
+83 -1
View File
@@ -1,2 +1,84 @@
(()=>{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;})(); // Header functionality
import { Alpine } from "./alpine";
import { updateThemeIndicators } from "./themeDropdown";
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");
}
}
});
export { toggleThemeDropdown, toggleUserMenu, changeThemeTo, logout };
Alpine.global("header", {
logout,
toggleThemeDropdown,
toggleUserMenu,
changeThemeTo: (theme) => {
changeThemeTo(theme);
updateThemeIndicators(); // Call themeDropdown function
},
});
//# sourceMappingURL=header.js.map //# sourceMappingURL=header.js.map
+1
View File
@@ -0,0 +1 @@
var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=dn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true,historyRestoreAsHxRequest:true,reportValidityOfForms:false},parseInterval:null,location:location,_:null,version:"2.0.8"};Q.onLoad=V;Q.process=Ft;Q.on=xe;Q.off=be;Q.trigger=ae;Q.ajax=Ln;Q.find=f;Q.findAll=x;Q.closest=g;Q.remove=_;Q.addClass=K;Q.removeClass=G;Q.toggleClass=W;Q.takeClass=Z;Q.swap=ze;Q.defineExtension=_n;Q.removeExtension=zn;Q.logAll=j;Q.logNone=$;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:se,canAccessLocalStorage:X,findThisElement:Se,filterValues:yn,swap:ze,hasAttribute:s,getAttributeValue:a,getClosestAttributeValue:ne,getClosestMatch:q,getExpressionVars:Rn,getHeaders:mn,getInputValues:dn,getInternalData:oe,getSwapSpecification:bn,getTriggerSpecs:st,getTarget:Ee,makeFragment:D,mergeObjects:le,makeSettleInfo:Sn,oobSwap:Te,querySelectorExt:ue,settleImmediately:Yt,shouldCancel:ht,triggerEvent:ae,triggerErrorEvent:fe,withExtensions:Vt};const de=["get","post","put","delete","patch"];const R=de.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function a(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function te(){return document}function y(e,t){return e.getRootNode?e.getRootNode({composed:t}):te()}function q(e,t){while(e&&!t(e)){e=u(e)}return e||null}function o(e,t,n){const r=a(t,n);const o=a(t,"hx-disinherit");var i=a(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function ne(t,n){let r=null;q(t,function(e){return!!(r=o(t,ce(e),n))});if(r!=="unset"){return r}}function h(e,t){return e instanceof Element&&e.matches(t)}function A(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function L(e){if("parseHTMLUnsafe"in Document){return Document.parseHTMLUnsafe(e)}const t=new DOMParser;return t.parseFromString(e,"text/html")}function N(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function r(e){const t=te().createElement("script");ie(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function i(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function I(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(i(e)){const t=r(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){H(e)}finally{e.remove()}}})}function D(e){const t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,"");const n=A(t);let r;if(n==="html"){r=new DocumentFragment;const i=L(e);N(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=L(t);N(r,i.body);r.title=i.title}else{const i=L('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){I(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function re(e){if(e){e()}}function t(e,t){return Object.protoLine truncated
+230 -18
View File
@@ -1,55 +1,267 @@
(()=>{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=` 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 = `
<div class="p-4 text-center" style="color: var(--text-secondary)"> <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> <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> <p class="mt-2 text-sm">Searching...</p>
</div> </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=` `;
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 = `
<div class="p-3 border-b" style="border-color: var(--border)"> <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)"> <p class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-secondary)">
${e.length} result${e.length!==1?"s":""} for "${a(r)}" ${results.length} result${results.length !== 1 ? "s" : ""} for "${searchEscapeHtml(query)}"
</p> </p>
</div> </div>
<div class="max-h-96 overflow-y-auto"> <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+=` `;
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 += `
<div class="search-result-item p-3 border-b hover:bg-opacity-50 transition-colors cursor-pointer" <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)" style="border-color: var(--border); background-color: var(--bg-secondary)"
data-index="${p}"> data-index="${index}">
<a href="/bookshelf" <a href="/bookshelf"
class="block" class="block"
onclick="window.selectLibraryAndBook('${o.library_id}', '${o.id}')"> onclick="window.selectLibraryAndBook('${item.library_id}', '${item.id}')">
<div class="flex items-start space-x-3"> <div class="flex items-start space-x-3">
<div class="text-2xl">${f}</div> <div class="text-2xl">${icon}</div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h4 class="text-sm font-medium truncate" style="color: var(--text-primary)"> <h4 class="text-sm font-medium truncate" style="color: var(--text-primary)">
${g} ${titleHtml}
</h4> </h4>
${u?`<p class="text-xs truncate" style="color: var(--text-secondary)">${u}</p>`:""} ${authorHtml ? `<p class="text-xs truncate" style="color: var(--text-secondary)">${authorHtml}</p>` : ""}
<p class="text-xs mt-1" style="color: var(--text-secondary)"> <p class="text-xs mt-1" style="color: var(--text-secondary)">
${a(o.library_name)} ${searchEscapeHtml(item.library_name)}
</p> </p>
</div> </div>
</div> </div>
</a> </a>
</div> </div>
`}),n+=` `;
});
html += `
</div> </div>
<div class="p-2 border-t text-center" style="border-color: var(--border)"> <div class="p-2 border-t text-center" style="border-color: var(--border)">
<p class="text-xs" style="color: var(--text-secondary)"> <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, Press <kbd class="px-1 py-0.5 rounded" style="background-color: var(--bg-primary)">↑↓</kbd> to navigate,
<kbd class="px-1 py-0.5 rounded" style="background-color: var(--bg-primary)">Enter</kbd> to select <kbd class="px-1 py-0.5 rounded" style="background-color: var(--bg-primary)">Enter</kbd> to select
</p> </p>
</div> </div>
`,t.innerHTML=n,t.classList.remove("hidden")}function d(e){l();let r=document.getElementById("search-results");r&&(r.innerHTML=` `;
searchResults.innerHTML = html;
searchResults.classList.remove("hidden");
}
function showNoResults(query) {
createSearchResultsContainer();
const searchResults = document.getElementById("search-results");
if (!searchResults)
return;
searchResults.innerHTML = `
<div class="p-4 text-center"> <div class="p-4 text-center">
<div class="text-4xl mb-2">\u{1F50D}</div> <div class="text-4xl mb-2">🔍</div>
<p class="text-sm" style="color: var(--text-primary)">No results found for "${a(e)}"</p> <p class="text-sm" style="color: var(--text-primary)">No results found for "${searchEscapeHtml(query)}"</p>
<p class="text-xs mt-1" style="color: var(--text-secondary)">Try different keywords</p> <p class="text-xs mt-1" style="color: var(--text-secondary)">Try different keywords</p>
</div> </div>
`,r.classList.remove("hidden"))}function S(){l();let e=document.getElementById("search-results");e&&(e.innerHTML=` `;
searchResults.classList.remove("hidden");
}
function showSearchError() {
createSearchResultsContainer();
const searchResults = document.getElementById("search-results");
if (!searchResults)
return;
searchResults.innerHTML = `
<div class="p-4 text-center"> <div class="p-4 text-center">
<div class="text-4xl mb-2">\u26A0\uFE0F</div> <div class="text-4xl mb-2">⚠️</div>
<p class="text-sm" style="color: var(--text-primary)">Search error</p> <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> <p class="text-xs mt-1" style="color: var(--text-secondary)">Please try again</p>
</div> </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;})(); `;
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, '<mark style="background-color: var(--accent); color: var(--bg-primary); padding: 0 2px; border-radius: 2px;">$1</mark>');
}
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;
//# sourceMappingURL=search.js.map //# sourceMappingURL=search.js.map