Add dashboard redesign, custom section builder, and enhanced search functionality
Features: - Complete dashboard redesign with improved UI components and layout - Implement custom section builder for personalized book organization - Add new events tracking system for user interactions - Enhance search functionality with better static search.js - Update TypeScript type definitions for API responses Backend: - Update Go dependencies in go.mod - Add new frontend routes in router Templates: - Update admin and dashboard templates with new components Frontend: - Refactor analytics, collections, conflicts, and queue modules - Add new documentation features in docs.ts - Implement linking between books and collections - Add toast notifications for user feedback - Include placeholder book SVG asset This commit consolidates multiple feature additions and improvements across the entire stack including backend, templates, and frontend.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 192" width="128" height="192">
|
||||
<defs>
|
||||
<linearGradient id="bookGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4B5563"/>
|
||||
<stop offset="100%" style="stop-color:#1F2937"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="128" height="192" rx="8" fill="url(#bookGrad)"/>
|
||||
<rect x="8" y="8" width="112" height="176" rx="4" fill="none" stroke="#6B7280" stroke-width="2"/>
|
||||
<text x="64" y="100" text-anchor="middle" fill="#9CA3AF" font-family="sans-serif" font-size="40">📚</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
+9
-10
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
let searchTimeout = null;
|
||||
let searchInputTimeout = null;
|
||||
const SEARCH_DEBOUNCE_MS = 300;
|
||||
const SEARCH_MIN_CHARS = 2;
|
||||
function initializeSearch() {
|
||||
@@ -27,14 +26,14 @@ function initializeSearch() {
|
||||
function handleSearchInput(e) {
|
||||
const target = e.target;
|
||||
const query = target.value.trim();
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
if (searchInputTimeout) {
|
||||
clearTimeout(searchInputTimeout);
|
||||
}
|
||||
if (query.length < SEARCH_MIN_CHARS) {
|
||||
hideSearchResults();
|
||||
return;
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
searchInputTimeout = setTimeout(() => {
|
||||
performSearch(query);
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
}
|
||||
@@ -150,7 +149,7 @@ function showSearchResults(results, query) {
|
||||
let html = `
|
||||
<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)">
|
||||
${results.length} result${results.length !== 1 ? 's' : ''} for "${escapeHtml(query)}"
|
||||
${results.length} result${results.length !== 1 ? 's' : ''} for "${searchEscapeHtml(query)}"
|
||||
</p>
|
||||
</div>
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
@@ -174,7 +173,7 @@ function showSearchResults(results, query) {
|
||||
</h4>
|
||||
${authorHtml ? `<p class="text-xs truncate" style="color: var(--text-secondary)">${authorHtml}</p>` : ''}
|
||||
<p class="text-xs mt-1" style="color: var(--text-secondary)">
|
||||
${escapeHtml(item.library_name)}
|
||||
${searchEscapeHtml(item.library_name)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,7 +201,7 @@ function showNoResults(query) {
|
||||
searchResults.innerHTML = `
|
||||
<div class="p-4 text-center">
|
||||
<div class="text-4xl mb-2">🔍</div>
|
||||
<p class="text-sm" style="color: var(--text-primary)">No results found for "${escapeHtml(query)}"</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>
|
||||
</div>
|
||||
`;
|
||||
@@ -249,9 +248,9 @@ function highlightMatch(text, query) {
|
||||
return '';
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||
return escapeHtml(text).replace(regex, '<mark style="background-color: var(--accent); color: var(--bg-primary); padding: 0 2px; border-radius: 2px;">$1</mark>');
|
||||
return searchEscapeHtml(text).replace(regex, '<mark style="background-color: var(--accent); color: var(--bg-primary); padding: 0 2px; border-radius: 2px;">$1</mark>');
|
||||
}
|
||||
function escapeHtml(text) {
|
||||
function searchEscapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
|
||||
Reference in New Issue
Block a user