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:
@@ -1,5 +1,3 @@
|
||||
import type { BookInfo } from './types/api';
|
||||
|
||||
interface FilterField {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -176,7 +174,7 @@ const FILTER_FIELDS: FilterField[] = [
|
||||
|
||||
let ruleCounter = 0;
|
||||
let selectedBooks: Map<string, BookInfo> = new Map();
|
||||
let searchTimeout: number | null = null;
|
||||
let customSectionTimeout: number | null = null;
|
||||
|
||||
function initCustomSectionBuilder(): void {
|
||||
const addRuleBtn = document.getElementById('add-rule-btn');
|
||||
@@ -312,10 +310,10 @@ function removeFilterRule(ruleId: string): void {
|
||||
}
|
||||
|
||||
function onBookSearchInput(): void {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
if (customSectionTimeout) {
|
||||
clearTimeout(customSectionTimeout);
|
||||
}
|
||||
searchTimeout = window.setTimeout(() => {
|
||||
customSectionTimeout = window.setTimeout(() => {
|
||||
searchBooks();
|
||||
}, 300);
|
||||
}
|
||||
@@ -363,13 +361,13 @@ function displaySearchResults(books: BookInfo[]): void {
|
||||
resultsContainer.innerHTML = books.map(book => `
|
||||
<div class="flex items-center gap-2 p-2 hover:bg-gray-700 rounded cursor-pointer"
|
||||
data-book-id="${book.media_item_id}"
|
||||
onclick="addBookToSelection('${book.media_item_id}', '${escapeHtml(book.title)}', '${escapeHtml(book.author)}')">
|
||||
onclick="addBookToSelection('${book.media_item_id}', '${builderEscapeHtml(book.title)}', '${builderEscapeHtml(book.author)}')">
|
||||
<img src="${book.cover_image_path || '/static/placeholder-book.svg'}"
|
||||
alt="${escapeHtml(book.title)}"
|
||||
alt="${builderEscapeHtml(book.title)}"
|
||||
class="w-10 h-15 object-cover rounded">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium" style="color: var(--text-primary);">${escapeHtml(book.title)}</p>
|
||||
<p class="text-xs" style="color: var(--text-secondary);">${escapeHtml(book.author)}</p>
|
||||
<p class="text-sm font-medium" style="color: var(--text-primary);">${builderEscapeHtml(book.title)}</p>
|
||||
<p class="text-xs" style="color: var(--text-secondary);">${builderEscapeHtml(book.author)}</p>
|
||||
</div>
|
||||
<button type="button" class="text-green-500 hover:text-green-700 text-xl">+</button>
|
||||
</div>
|
||||
@@ -412,7 +410,7 @@ function updateSelectedBooksDisplay(): void {
|
||||
container.innerHTML = Array.from(selectedBooks.values()).map(book => `
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 m-1 rounded-full text-sm"
|
||||
style="background-color: var(--accent);">
|
||||
<span>${escapeHtml(book.title)}</span>
|
||||
<span>${builderEscapeHtml(book.title)}</span>
|
||||
<button type="button" onclick="removeBookFromSelection('${book.media_item_id}')"
|
||||
class="hover:opacity-70">×</button>
|
||||
</div>
|
||||
@@ -495,13 +493,13 @@ function displayPreview(items: BookInfo[]): void {
|
||||
<div class="flex-shrink-0 w-32">
|
||||
<div class="aspect-[2/3] rounded-lg overflow-hidden shadow-lg mb-2">
|
||||
<img src="${item.cover_image_path || '/static/placeholder-book.svg'}"
|
||||
alt="${escapeHtml(item.title)}"
|
||||
alt="${builderEscapeHtml(item.title)}"
|
||||
class="w-full h-full object-cover">
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold line-clamp-2" style="color: var(--text-primary);">
|
||||
${escapeHtml(item.title)}
|
||||
${builderEscapeHtml(item.title)}
|
||||
</h3>
|
||||
${item.author ? `<p class="text-xs line-clamp-1" style="color: var(--text-secondary);">${escapeHtml(item.author)}</p>` : ''}
|
||||
${item.author ? `<p class="text-xs line-clamp-1" style="color: var(--text-secondary);">${builderEscapeHtml(item.author)}</p>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
@@ -560,7 +558,7 @@ async function saveCustomSection(event: Event): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
function builderEscapeHtml(text: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
|
||||
Reference in New Issue
Block a user