refactor(alpine): migrate remaining pages to x-init declarative initialization

- Remove DOMContentLoaded event listeners from analytics.ts and docs.ts
- Rely on x-init attribute in templates for page initialization
- Clean up unused exports from collections.ts Alpine data
- Add x-init calls to admin_library, analytics, and docs templates
- Normalize quote style in collections WebSocket script (single to double)
- Disable Add Books button in collection detail (pending implementation)
This commit is contained in:
2026-03-12 18:13:12 -04:00
parent 75c454b661
commit 06461a3202
11 changed files with 58 additions and 88 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ templ AdminLibrary(user User, libraries []LibraryData, users []User) {
<title>Library Management - Bookhoard</title> <title>Library Management - Bookhoard</title>
<link href="/static/style.css" rel="stylesheet"/> <link href="/static/style.css" rel="stylesheet"/>
</head> </head>
<body class="theme-{ user.Theme }"> <body x-data="library" x-init="initializeLibraryAdmin" class="theme-{ user.Theme }">
@Header(user, "/admin/library") @Header(user, "/admin/library")
<div class="flex min-h-screen" style="background-color: var(--bg-primary)"> <div class="flex min-h-screen" style="background-color: var(--bg-primary)">
@AdminSidebar(user, "/admin/library") @AdminSidebar(user, "/admin/library")
+1 -1
View File
@@ -29,7 +29,7 @@ func AdminLibrary(user User, libraries []LibraryData, users []User) templ.Compon
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Library Management - Bookhoard</title><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-{ user.Theme }\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Library Management - Bookhoard</title><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"library\" x-init=\"initializeLibraryAdmin\" class=\"theme-{ user.Theme }\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+1 -1
View File
@@ -29,7 +29,7 @@ func Admin(user User) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Admin Dashboard - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script>\n\t\t\t\tlet scanWs = null;\n\t\t\t\tfunction connectScanWebSocket() {\n\t\t\t\t\tconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n\t\t\t\t\tconst wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`;\n\t\t\t\t\tscanWs = new WebSocket(wsUrl);\n\n\t\t\t\t\tscanWs.onmessage = function(event) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst message = JSON.parse(event.data);\n\t\t\t\t\t\t\tswitch (message.type) {\n\t\t\t\t\t\t\t\tcase 'scan_progress':\n\t\t\t\t\t\t\t\t\tupdateScanProgress(message.data);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'scan_complete':\n\t\t\t\t\t\t\t\t\tshowScanComplete(message.data);\n\t\t\t\t\t\t\t\t\tstopScanStatusPolling();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'scan_error':\n\t\t\t\t\t\t\t\t\tshowScanError(message.data);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error('Failed to parse WebSocket message:', error);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tscanWs.onerror = function(error) {\n\t\t\t\t\t\tconsole.error('WebSocket error:', error);\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfunction updateScanProgress(data) {\n\t\t\t\t\tconst progressBar = document.getElementById('scan-progress-bar');\n\t\t\t\t\tif (progressBar) {\n\t\t\t\t\t\tprogressBar.style.width = (data.progress * 100) + '%';\n\t\t\t\t\t}\n\n\t\t\t\t\tconst progressText = document.getElementById('scan-progress-text');\n\t\t\t\t\tif (progressText) {\n\t\t\t\t\t\tprogressText.textContent = `${data.files_scanned} files scanned (${data.new_items} new)`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction showScanComplete(data) {\n\t\t\t\t\tconsole.log('Scan complete:', data);\n\t\t\t\t}\n\n\t\t\t\tfunction showScanError(data) {\n\t\t\t\t\tconsole.error('Scan error:', data);\n\t\t\t\t}\n\n\t\t\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\t\t\tconnectScanWebSocket();\n\t\t\t\t});\n\t\t\t</script></head><body class=\"theme-tokyo-night\" x-data=\"admin\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Admin Dashboard - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script>\n\t\t\t\tlet scanWs = null;\n\t\t\t\tfunction connectScanWebSocket() {\n\t\t\t\t\tconst protocol =\n\t\t\t\t\t\twindow.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\t\t\t\tconst wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`;\n\t\t\t\t\tscanWs = new WebSocket(wsUrl);\n\n\t\t\t\t\tscanWs.onmessage = function (event) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst message = JSON.parse(event.data);\n\t\t\t\t\t\t\tswitch (message.type) {\n\t\t\t\t\t\t\t\tcase \"scan_progress\":\n\t\t\t\t\t\t\t\t\tupdateScanProgress(message.data);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"scan_complete\":\n\t\t\t\t\t\t\t\t\tshowScanComplete(message.data);\n\t\t\t\t\t\t\t\t\tstopScanStatusPolling();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase \"scan_error\":\n\t\t\t\t\t\t\t\t\tshowScanError(message.data);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"Failed to parse WebSocket message:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tscanWs.onerror = function (error) {\n\t\t\t\t\t\tconsole.error(\"WebSocket error:\", error);\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfunction updateScanProgress(data) {\n\t\t\t\t\tconst progressBar = document.getElementById(\"scan-progress-bar\");\n\t\t\t\t\tif (progressBar) {\n\t\t\t\t\t\tprogressBar.style.width = data.progress * 100 + \"%\";\n\t\t\t\t\t}\n\n\t\t\t\t\tconst progressText = document.getElementById(\"scan-progress-text\");\n\t\t\t\t\tif (progressText) {\n\t\t\t\t\t\tprogressText.textContent = `${data.files_scanned} files scanned (${data.new_items} new)`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfunction showScanComplete(data) {\n\t\t\t\t\tconsole.log(\"Scan complete:\", data);\n\t\t\t\t}\n\n\t\t\t\tfunction showScanError(data) {\n\t\t\t\t\tconsole.error(\"Scan error:\", data);\n\t\t\t\t}\n\n\t\t\t\tdocument.addEventListener(\"DOMContentLoaded\", function () {\n\t\t\t\t\tconnectScanWebSocket();\n\t\t\t\t});\n\t\t\t</script></head><body x-data=\"admin\" x-init=\"loadWatchStatus()\" class=\"theme-tokyo-night\" x-data=\"admin\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+1 -1
View File
@@ -29,7 +29,7 @@ func Analytics(user User) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Reading Analytics - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"analytics\" class=\"theme-{ user.Theme }\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Reading Analytics - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"analytics\" x-init=\"loadAnalytics\" class=\"theme-{ user.Theme }\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
+36 -32
View File
@@ -18,69 +18,74 @@ templ Collection(user User, collections []CollectionData, errorMessage string) {
let libraryId = null; let libraryId = null;
function connectWebSocket() { function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol =
window.location.protocol === "https:" ? "wss:" : "ws:";
const wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`; const wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`;
ws = new WebSocket(wsUrl); ws = new WebSocket(wsUrl);
ws.onopen = function() { ws.onopen = function () {
console.log('WebSocket connected'); console.log("WebSocket connected");
}; };
ws.onmessage = function(event) { ws.onmessage = function (event) {
try { try {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
if (message.type === 'collection_updated' && message.data.collection_id === collectionId) { if (
const actionText = message.data.action === 'books_added' message.type === "collection_updated" &&
? `Added ${message.data.count || 0} book(s)` message.data.collection_id === collectionId
: message.data.action === 'book_removed' ) {
? 'Removed a book' const actionText =
: message.data.action === 'books_bulk_removed' message.data.action === "books_added"
? `Removed ${message.data.count || 0} book(s)` ? `Added ${message.data.count || 0} book(s)`
: 'Collection updated'; : message.data.action === "book_removed"
? "Removed a book"
: message.data.action === "books_bulk_removed"
? `Removed ${message.data.count || 0} book(s)`
: "Collection updated";
// Show toast notification // Show toast notification
if (window.showToast) { if (window.showToast) {
window.showToast(actionText, 'info'); window.showToast(actionText, "info");
} }
// Check if user is actively typing // Check if user is actively typing
const activeElement = document.activeElement; const activeElement = document.activeElement;
const isUserActive = activeElement && ( const isUserActive =
activeElement.tagName === 'INPUT' || activeElement &&
activeElement.tagName === 'TEXTAREA' || (activeElement.tagName === "INPUT" ||
activeElement.tagName === 'SELECT' || activeElement.tagName === "TEXTAREA" ||
activeElement.getAttribute('contenteditable') === 'true' activeElement.tagName === "SELECT" ||
); activeElement.getAttribute("contenteditable") === "true");
if (!isUserActive) { if (!isUserActive) {
setTimeout(function() { setTimeout(function () {
location.reload(); location.reload();
}, 1000); }, 1000);
} else { } else {
console.log('User actively typing - skipping auto-reload'); console.log("User actively typing - skipping auto-reload");
} }
} }
} catch (error) { } catch (error) {
console.error('Failed to parse WebSocket message:', error); console.error("Failed to parse WebSocket message:", error);
} }
}; };
ws.onclose = function() { ws.onclose = function () {
console.log('WebSocket disconnected, reconnecting in 5s...'); console.log("WebSocket disconnected, reconnecting in 5s...");
setTimeout(connectWebSocket, 5000); setTimeout(connectWebSocket, 5000);
}; };
ws.onerror = function(error) { ws.onerror = function (error) {
console.error('WebSocket error:', error); console.error("WebSocket error:", error);
}; };
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener("DOMContentLoaded", function () {
// Get collection and library IDs from data attributes // Get collection and library IDs from data attributes
const dataEl = document.getElementById('collection-data'); const dataEl = document.getElementById("collection-data");
if (dataEl) { if (dataEl) {
collectionId = dataEl.dataset.id || ''; collectionId = dataEl.dataset.id || "";
libraryId = dataEl.dataset.libraryId || ''; libraryId = dataEl.dataset.libraryId || "";
connectWebSocket(); connectWebSocket();
} }
}); });
@@ -220,13 +225,12 @@ templ CollectionDetail(user User, collection CollectionData, books []handlers.Bo
</div> </div>
<button <button
id="bulk-remove-btn" id="bulk-remove-btn"
@click="removebooksToAdd"
disabled disabled
class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed" class="btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
> >
🗑️ Remove Selected 🗑️ Remove Selected
</button> </button>
<button @click="showAddBooksModal" class="btn-primary px-4 py-2 rounded-lg"> <button type="button" class="btn-primary px-4 py-2 rounded-lg" style="opacity: 0.5; cursor: not-allowed;" disabled>
Add Books Add Books
</button> </button>
</div> </div>
+16 -16
View File
@@ -31,7 +31,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Collections - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script>\n\t\t\t\tlet ws = null;\n\t\t\t\tlet collectionId = null;\n\t\t\t\tlet libraryId = null;\n\n\t\t\t\tfunction connectWebSocket() {\n\t\t\t\t\tconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n\t\t\t\t\tconst wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`;\n\t\t\t\t\tws = new WebSocket(wsUrl);\n\n\t\t\t\t\tws.onopen = function() {\n\t\t\t\t\t\tconsole.log('WebSocket connected');\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onmessage = function(event) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst message = JSON.parse(event.data);\n\t\t\t\t\t\t\tif (message.type === 'collection_updated' && message.data.collection_id === collectionId) {\n\t\t\t\t\t\t\t\tconst actionText = message.data.action === 'books_added'\n\t\t\t\t\t\t\t\t\t? `Added ${message.data.count || 0} book(s)`\n\t\t\t\t\t\t\t\t\t: message.data.action === 'book_removed'\n\t\t\t\t\t\t\t\t\t? 'Removed a book'\n\t\t\t\t\t\t\t\t\t: message.data.action === 'books_bulk_removed'\n\t\t\t\t\t\t\t\t\t? `Removed ${message.data.count || 0} book(s)`\n\t\t\t\t\t\t\t\t\t: 'Collection updated';\n\n\t\t\t\t\t\t\t\t// Show toast notification\n\t\t\t\t\t\t\t\tif (window.showToast) {\n\t\t\t\t\t\t\t\t\twindow.showToast(actionText, 'info');\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if user is actively typing\n\t\t\t\t\t\t\t\tconst activeElement = document.activeElement;\n\t\t\t\t\t\t\t\tconst isUserActive = activeElement && (\n\t\t\t\t\t\t\t\t\tactiveElement.tagName === 'INPUT' ||\n\t\t\t\t\t\t\t\t\tactiveElement.tagName === 'TEXTAREA' ||\n\t\t\t\t\t\t\t\t\tactiveElement.tagName === 'SELECT' ||\n\t\t\t\t\t\t\t\t\tactiveElement.getAttribute('contenteditable') === 'true'\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (!isUserActive) {\n\t\t\t\t\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconsole.log('User actively typing - skipping auto-reload');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error('Failed to parse WebSocket message:', error);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onclose = function() {\n\t\t\t\t\t\tconsole.log('WebSocket disconnected, reconnecting in 5s...');\n\t\t\t\t\t\tsetTimeout(connectWebSocket, 5000);\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onerror = function(error) {\n\t\t\t\t\t\tconsole.error('WebSocket error:', error);\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\t\t\t// Get collection and library IDs from data attributes\n\t\t\t\t\tconst dataEl = document.getElementById('collection-data');\n\t\t\t\t\tif (dataEl) {\n\t\t\t\t\t\tcollectionId = dataEl.dataset.id || '';\n\t\t\t\t\t\tlibraryId = dataEl.dataset.libraryId || '';\n\t\t\t\t\t\tconnectWebSocket();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t</script></head><body x-data=\"collections\" class=\"theme-{ user.Theme }\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Collections - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/main.js\" defer></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script>\n\t\t\t\tlet ws = null;\n\t\t\t\tlet collectionId = null;\n\t\t\t\tlet libraryId = null;\n\n\t\t\t\tfunction connectWebSocket() {\n\t\t\t\t\tconst protocol =\n\t\t\t\t\t\twindow.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n\t\t\t\t\tconst wsUrl = `${protocol}//${window.location.host}/ws/sync?token={ user.Token }`;\n\t\t\t\t\tws = new WebSocket(wsUrl);\n\n\t\t\t\t\tws.onopen = function () {\n\t\t\t\t\t\tconsole.log(\"WebSocket connected\");\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onmessage = function (event) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst message = JSON.parse(event.data);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tmessage.type === \"collection_updated\" &&\n\t\t\t\t\t\t\t\tmessage.data.collection_id === collectionId\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tconst actionText =\n\t\t\t\t\t\t\t\t\tmessage.data.action === \"books_added\"\n\t\t\t\t\t\t\t\t\t\t? `Added ${message.data.count || 0} book(s)`\n\t\t\t\t\t\t\t\t\t\t: message.data.action === \"book_removed\"\n\t\t\t\t\t\t\t\t\t\t\t? \"Removed a book\"\n\t\t\t\t\t\t\t\t\t\t\t: message.data.action === \"books_bulk_removed\"\n\t\t\t\t\t\t\t\t\t\t\t\t? `Removed ${message.data.count || 0} book(s)`\n\t\t\t\t\t\t\t\t\t\t\t\t: \"Collection updated\";\n\n\t\t\t\t\t\t\t\t// Show toast notification\n\t\t\t\t\t\t\t\tif (window.showToast) {\n\t\t\t\t\t\t\t\t\twindow.showToast(actionText, \"info\");\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check if user is actively typing\n\t\t\t\t\t\t\t\tconst activeElement = document.activeElement;\n\t\t\t\t\t\t\t\tconst isUserActive =\n\t\t\t\t\t\t\t\t\tactiveElement &&\n\t\t\t\t\t\t\t\t\t(activeElement.tagName === \"INPUT\" ||\n\t\t\t\t\t\t\t\t\t\tactiveElement.tagName === \"TEXTAREA\" ||\n\t\t\t\t\t\t\t\t\t\tactiveElement.tagName === \"SELECT\" ||\n\t\t\t\t\t\t\t\t\t\tactiveElement.getAttribute(\"contenteditable\") === \"true\");\n\n\t\t\t\t\t\t\t\tif (!isUserActive) {\n\t\t\t\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconsole.log(\"User actively typing - skipping auto-reload\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"Failed to parse WebSocket message:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onclose = function () {\n\t\t\t\t\t\tconsole.log(\"WebSocket disconnected, reconnecting in 5s...\");\n\t\t\t\t\t\tsetTimeout(connectWebSocket, 5000);\n\t\t\t\t\t};\n\n\t\t\t\t\tws.onerror = function (error) {\n\t\t\t\t\t\tconsole.error(\"WebSocket error:\", error);\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tdocument.addEventListener(\"DOMContentLoaded\", function () {\n\t\t\t\t\t// Get collection and library IDs from data attributes\n\t\t\t\t\tconst dataEl = document.getElementById(\"collection-data\");\n\t\t\t\t\tif (dataEl) {\n\t\t\t\t\t\tcollectionId = dataEl.dataset.id || \"\";\n\t\t\t\t\t\tlibraryId = dataEl.dataset.libraryId || \"\";\n\t\t\t\t\t\tconnectWebSocket();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t</script></head><body x-data=\"collections\" class=\"theme-{ user.Theme }\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -57,7 +57,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 136, Col: 82} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 141, Col: 82}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -70,7 +70,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 140, Col: 30} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 145, Col: 30}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -83,7 +83,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 143, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 148, Col: 41}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -96,7 +96,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal") templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 146, Col: 60} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 151, Col: 60}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -109,7 +109,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "") templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 155, Col: 56} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 160, Col: 56}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -122,7 +122,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 165, Col: 92} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 170, Col: 92}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -135,7 +135,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 166, Col: 86} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 171, Col: 86}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -190,7 +190,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 183, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 188, Col: 27}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -211,7 +211,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 196, Col: 81} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 201, Col: 81}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -224,7 +224,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 198, Col: 90} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 203, Col: 90}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -237,13 +237,13 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 199, Col: 71} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 204, Col: 71}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p></div></div></div><div class=\"mb-6 flex justify-between items-center\"><div class=\"flex items-center gap-4\"><h2 class=\"text-xl font-semibold\" style=\"color: var(--text-primary)\">Books in this Collection</h2><span id=\"selected-count\" class=\"hidden px-3 py-1 text-sm rounded\" style=\"background-color: var(--accent); color: var(--bg-primary);\">0 selected</span></div><div class=\"flex gap-3\"><div class=\"flex-1 max-w-md\"><input type=\"text\" id=\"collection-search\" placeholder=\"Search within collection...\" onkeyup=\"filterCollectionBooks()\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><button id=\"bulk-remove-btn\" @click=\"removebooksToAdd\" disabled class=\"btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed\">🗑️ Remove Selected</button> <button @click=\"showAddBooksModal\" class=\"btn-primary px-4 py-2 rounded-lg\"> Add Books</button></div></div><div id=\"books-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p></div></div></div><div class=\"mb-6 flex justify-between items-center\"><div class=\"flex items-center gap-4\"><h2 class=\"text-xl font-semibold\" style=\"color: var(--text-primary)\">Books in this Collection</h2><span id=\"selected-count\" class=\"hidden px-3 py-1 text-sm rounded\" style=\"background-color: var(--accent); color: var(--bg-primary);\">0 selected</span></div><div class=\"flex gap-3\"><div class=\"flex-1 max-w-md\"><input type=\"text\" id=\"collection-search\" placeholder=\"Search within collection...\" onkeyup=\"filterCollectionBooks()\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><button id=\"bulk-remove-btn\" disabled class=\"btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed\">🗑️ Remove Selected</button> <button type=\"button\" class=\"btn-primary px-4 py-2 rounded-lg\" style=\"opacity: 0.5; cursor: not-allowed;\" disabled> Add Books</button></div></div><div id=\"books-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -261,7 +261,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 257, Col: 22} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 261, Col: 22}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -279,7 +279,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 264, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 268, Col: 27}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -302,7 +302,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 272, Col: 36} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 276, Col: 36}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
+1 -1
View File
@@ -13,7 +13,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
<link rel="stylesheet" href="/static/highlight-dark.min.css"/> <link rel="stylesheet" href="/static/highlight-dark.min.css"/>
<script src="/static/main.js" defer></script> <script src="/static/main.js" defer></script>
</head> </head>
<body class={ "theme-" + user.Theme + " page-docs bg-background-primary text-text-primary font-sans antialiased" }> <body x-data="docs" x-init="initializeSearch" class={ "theme-" + user.Theme + " page-docs bg-background-primary text-text-primary font-sans antialiased" }>
@Header(user, currentPath) @Header(user, currentPath)
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
<button <button
+1 -1
View File
@@ -53,7 +53,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<body class=\"") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<body x-data=\"docs\" x-init=\"initializeSearch\" class=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
-2
View File
@@ -122,8 +122,6 @@ function renderPopularBooks(popular: PopularBooksResponse): void {
.join(""); .join("");
} }
document.addEventListener("DOMContentLoaded", loadAnalytics);
export { loadAnalytics }; export { loadAnalytics };
Alpine.data("analytics", () => ({ Alpine.data("analytics", () => ({
-28
View File
@@ -405,60 +405,32 @@ function filterIcons(searchTerm: string): void {
// Export functions globally // Export functions globally
export { export {
addbooksToAdd,
backToCollections,
closeCollectionModal, closeCollectionModal,
createRule, createRule,
deleteRule, deleteRule,
filterCollectionBooks,
filterIcons, filterIcons,
hideAddBooksModal,
initCollectionDetail,
initColorSelection, initColorSelection,
initIconSelection,
loadCollectionRules, loadCollectionRules,
loadCollections, loadCollections,
navigateToCollection, navigateToCollection,
populateIconGrid, populateIconGrid,
removeBook,
removebooksToAdd,
searchBooksForCollections,
selectColor, selectColor,
selectIcon, selectIcon,
showAddBooksModal,
showAllIcons,
setupHTMXAuth, setupHTMXAuth,
testRule, testRule,
toggleBookForRemoval,
toggleBookSelection,
updateSelectedCount,
}; };
Alpine.data("collections", () => ({ Alpine.data("collections", () => ({
addbooksToAdd,
backToCollections,
closeCollectionModal, closeCollectionModal,
createRule, createRule,
deleteRule, deleteRule,
filterCollectionBooks,
filterIcons, filterIcons,
hideAddBooksModal,
initCollectionDetail,
initColorSelection, initColorSelection,
initIconSelection,
loadCollectionRules, loadCollectionRules,
loadCollections, loadCollections,
navigateToCollection, navigateToCollection,
populateIconGrid, populateIconGrid,
removeBook,
removebooksToAdd,
searchBooksForCollections,
selectColor, selectColor,
selectIcon, selectIcon,
showAddBooksModal,
showAllIcons,
testRule, testRule,
toggleBookForRemoval,
toggleBookSelection,
updateSelectedCount,
})); }));
-4
View File
@@ -89,10 +89,6 @@ function performDocsSearch(query: string): void {
} }
} }
document.addEventListener("DOMContentLoaded", () => {
initializeDocsSearch();
});
Alpine.data("docs", () => ({ Alpine.data("docs", () => ({
toggleSidebar, toggleSidebar,
initializeSearch: initializeDocsSearch, initializeSearch: initializeDocsSearch,