feat(typescript): add feature modules for template conversion
- Add search.ts - header search with keyboard navigation - Debounced search with 300ms delay - Arrow key navigation through results - Escape to close, Enter to select - Library type icons and highlighting - Add collections.ts - collection and rule management - Rule CRUD operations (create, update, delete) - Rule testing functionality - Bulk collection operations - Add bookshelf.ts - book display and navigation - Library selection state management - Book viewing interactions - Pagination logic - Add linking.ts - book matching and manual linking - Search and match functionality - Manual link modal - Bulk auto-link and suggestions - Add api-explorer.ts - API testing interface - Request/response display - cURL command generation - History tracking - Add admin.ts - admin dashboard actions - Library scan triggers - System statistics display - Profile management - Add analytics.ts - analytics data loading - Chart.js integration - Daily reading minutes chart - Device usage and popular books display - Add queue.ts - sync queue management - Process pending items - Clear failed/all items - Filter by status, type, device - Add conflicts.ts - conflict resolution - Individual and bulk resolve operations - Winner device selection - Manual override inputs - Add docs.ts - documentation search - Lunr.js search integration - Sidebar toggle for mobile
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
function selectLibrary(libraryId: string): void {
|
||||
localStorage.setItem('selectedLibrary', libraryId);
|
||||
|
||||
document.querySelectorAll('.library-item').forEach(el => {
|
||||
el.classList.remove('ring-2');
|
||||
el.classList.remove('ring-accent');
|
||||
});
|
||||
|
||||
const selected = document.querySelector(`[data-library-id="${libraryId}"]`);
|
||||
if (selected) {
|
||||
selected.classList.add('ring-2');
|
||||
selected.classList.add('ring-accent');
|
||||
}
|
||||
|
||||
loadBookshelf(libraryId);
|
||||
}
|
||||
|
||||
async function loadBookshelf(libraryId: string): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/libraries/${libraryId}/books`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
renderBooks(data.books || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load bookshelf:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderBooks(books: unknown[]): void {
|
||||
const container = document.getElementById('books-grid');
|
||||
if (!container) return;
|
||||
|
||||
if (books.length === 0) {
|
||||
container.innerHTML = '<p class="text-center p-8" style="color: var(--text-secondary)">No books in this library</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = books.map((book: any) => `
|
||||
<div class="book-card p-3 rounded-lg border transition-transform hover:scale-105 cursor-pointer"
|
||||
style="background-color: var(--bg-secondary); border-color: var(--border)"
|
||||
onclick="window.selectBook('${book.id}')">
|
||||
${book.cover_image_path ?
|
||||
`<img src="/covers/${book.cover_image_path}" alt="${book.title}" class="w-full h-48 object-cover rounded mb-2">` :
|
||||
`<div class="w-full h-48 rounded mb-2 flex items-center justify-center" style="background-color: var(--bg-primary)">
|
||||
<span class="text-4xl">📖</span>
|
||||
</div>`
|
||||
}
|
||||
<h3 class="font-medium text-sm truncate" style="color: var(--text-primary)">${book.title}</h3>
|
||||
<p class="text-xs truncate" style="color: var(--text-secondary)">${book.author || 'Unknown Author'}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function selectBook(bookId: string): void {
|
||||
localStorage.setItem('selectedBook', bookId);
|
||||
window.location.href = `/books/${bookId}`;
|
||||
}
|
||||
|
||||
function changePage(page: number): void {
|
||||
const libraryId = localStorage.getItem('selectedLibrary');
|
||||
if (!libraryId) return;
|
||||
|
||||
const offset = (page - 1) * 50;
|
||||
loadBookshelfPaginated(libraryId, offset);
|
||||
}
|
||||
|
||||
async function loadBookshelfPaginated(libraryId: string, offset: number): Promise<void> {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/libraries/${libraryId}/books?offset=${offset}&limit=50`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
renderBooks(data.books || []);
|
||||
updatePagination(data.total, offset);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load bookshelf:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePagination(total: number, offset: number): void {
|
||||
const container = document.getElementById('pagination');
|
||||
if (!container) return;
|
||||
|
||||
const limit = 50;
|
||||
const currentPage = Math.floor(offset / limit) + 1;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="flex justify-center space-x-2">
|
||||
${currentPage > 1 ? `<button onclick="window.changePage(${currentPage - 1})" class="btn-secondary px-3 py-1 rounded">Previous</button>` : ''}
|
||||
<span class="px-3 py-1" style="color: var(--text-secondary)">Page ${currentPage} of ${totalPages}</span>
|
||||
${currentPage < totalPages ? `<button onclick="window.changePage(${currentPage + 1})" class="btn-secondary px-3 py-1 rounded">Next</button>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
(window as any).selectLibrary = selectLibrary;
|
||||
(window as any).loadBookshelf = loadBookshelf;
|
||||
(window as any).selectBook = selectBook;
|
||||
(window as any).changePage = changePage;
|
||||
Reference in New Issue
Block a user