|
1 | 1 | document.addEventListener('DOMContentLoaded', () => { |
2 | | - fetch('https://raw.githubusercontent.com/Ctoic/Lisbook/refs/heads/main/data/books.json') |
3 | | - .then(response => response.json()) |
4 | | - .then(data => { |
5 | | - const bookContainer = document.getElementById('book-container'); |
6 | | - data.forEach(book => { |
7 | | - const card = document.createElement('div'); |
8 | | - card.className = 'card'; |
9 | | - card.innerHTML = ` |
10 | | - <div class="card-inner"> |
11 | | - <div class="card-front"> |
12 | | - <img src="${book.cover}" alt="${book.title}"> |
13 | | - <h3>${book.title}</h3> |
14 | | - </div> |
15 | | - <div class="card-back"> |
16 | | - <p>${book.description}</p> |
17 | | - </div> |
| 2 | + // Try to load from local data first, then fallback to remote |
| 3 | + const loadBooksData = async () => { |
| 4 | + try { |
| 5 | + // First try to load local data |
| 6 | + const response = await fetch('./data/books.json'); |
| 7 | + if (response.ok) { |
| 8 | + return await response.json(); |
| 9 | + } |
| 10 | + throw new Error('Local data not found'); |
| 11 | + } catch (error) { |
| 12 | + console.warn('Loading from local data failed, trying remote:', error); |
| 13 | + // Fallback to remote data |
| 14 | + try { |
| 15 | + const response = await fetch('https://raw.githubusercontent.com/Ctoic/Lisbook/refs/heads/main/data/books.json'); |
| 16 | + return await response.json(); |
| 17 | + } catch (remoteError) { |
| 18 | + console.error('Failed to load book data from both sources:', remoteError); |
| 19 | + return []; |
| 20 | + } |
| 21 | + } |
| 22 | + }; |
| 23 | + |
| 24 | + const createBookCard = (book) => { |
| 25 | + const card = document.createElement('div'); |
| 26 | + card.className = 'card'; |
| 27 | + |
| 28 | + // Create image element with error handling |
| 29 | + const createImageWithFallback = (src, alt) => { |
| 30 | + const img = document.createElement('img'); |
| 31 | + img.alt = alt; |
| 32 | + img.loading = 'lazy'; |
| 33 | + |
| 34 | + // Add loading placeholder |
| 35 | + img.style.backgroundColor = '#555'; |
| 36 | + img.style.minHeight = '200px'; |
| 37 | + img.style.display = 'flex'; |
| 38 | + img.style.alignItems = 'center'; |
| 39 | + img.style.justifyContent = 'center'; |
| 40 | + |
| 41 | + // Handle image load error |
| 42 | + img.onerror = function() { |
| 43 | + console.warn(`Failed to load image: ${src}`); |
| 44 | + // Create a placeholder with book title |
| 45 | + this.style.display = 'none'; |
| 46 | + const placeholder = document.createElement('div'); |
| 47 | + placeholder.className = 'image-placeholder'; |
| 48 | + placeholder.innerHTML = ` |
| 49 | + <div class="placeholder-content"> |
| 50 | + <i class="bi bi-book" style="font-size: 3rem; color: #10b981; margin-bottom: 1rem;"></i> |
| 51 | + <p style="font-size: 0.9rem; text-align: center; margin: 0;">${alt}</p> |
18 | 52 | </div> |
19 | 53 | `; |
20 | | - bookContainer.appendChild(card); |
21 | | - }); |
22 | | - }) |
23 | | - .catch(error => console.error('Error loading book data:', error)); |
| 54 | + this.parentNode.insertBefore(placeholder, this); |
| 55 | + }; |
| 56 | + |
| 57 | + img.onload = function() { |
| 58 | + // Remove loading styles when image loads successfully |
| 59 | + this.style.backgroundColor = ''; |
| 60 | + this.style.minHeight = ''; |
| 61 | + }; |
| 62 | + |
| 63 | + img.src = src; |
| 64 | + return img; |
| 65 | + }; |
| 66 | + |
| 67 | + card.innerHTML = ` |
| 68 | + <div class="card-inner"> |
| 69 | + <div class="card-front"> |
| 70 | + <div class="image-container"> |
| 71 | + ${createImageWithFallback(book.cover, book.title).outerHTML} |
| 72 | + </div> |
| 73 | + <div class="book-info"> |
| 74 | + <h3 class="book-title">${book.title}</h3> |
| 75 | + <p class="book-author">by ${book.author}</p> |
| 76 | + <span class="book-genre">${book.genre || 'Unknown'}</span> |
| 77 | + </div> |
| 78 | + </div> |
| 79 | + <div class="card-back"> |
| 80 | + <div class="book-description"> |
| 81 | + <h4>${book.title}</h4> |
| 82 | + <p class="author-name">by ${book.author}</p> |
| 83 | + <p class="description-text">${book.description}</p> |
| 84 | + <div class="book-actions"> |
| 85 | + <button class="btn-read" onclick="readBook('${book.id}')"> |
| 86 | + <i class="bi bi-book-open"></i> Read |
| 87 | + </button> |
| 88 | + <button class="btn-listen" onclick="listenBook('${book.id}')"> |
| 89 | + <i class="bi bi-headphones"></i> Listen |
| 90 | + </button> |
| 91 | + </div> |
| 92 | + </div> |
| 93 | + </div> |
| 94 | + </div> |
| 95 | + `; |
| 96 | + |
| 97 | + return card; |
| 98 | + }; |
| 99 | + |
| 100 | + // Load and display books |
| 101 | + loadBooksData().then(data => { |
| 102 | + const bookContainer = document.getElementById('book-container'); |
| 103 | + |
| 104 | + if (data.length === 0) { |
| 105 | + bookContainer.innerHTML = ` |
| 106 | + <div class="no-books-message"> |
| 107 | + <i class="bi bi-exclamation-triangle" style="font-size: 3rem; color: #f59e0b;"></i> |
| 108 | + <h3>No books available</h3> |
| 109 | + <p>Unable to load book data. Please try again later.</p> |
| 110 | + </div> |
| 111 | + `; |
| 112 | + return; |
| 113 | + } |
| 114 | + |
| 115 | + data.forEach(book => { |
| 116 | + const card = createBookCard(book); |
| 117 | + bookContainer.appendChild(card); |
| 118 | + }); |
| 119 | + |
| 120 | + // Re-apply error handling to dynamically created images |
| 121 | + const images = bookContainer.querySelectorAll('img'); |
| 122 | + images.forEach(img => { |
| 123 | + if (!img.complete) { |
| 124 | + img.addEventListener('error', function() { |
| 125 | + console.warn(`Failed to load image: ${this.src}`); |
| 126 | + const placeholder = document.createElement('div'); |
| 127 | + placeholder.className = 'image-placeholder'; |
| 128 | + placeholder.innerHTML = ` |
| 129 | + <div class="placeholder-content"> |
| 130 | + <i class="bi bi-book" style="font-size: 3rem; color: #10b981; margin-bottom: 1rem;"></i> |
| 131 | + <p style="font-size: 0.9rem; text-align: center; margin: 0;">${this.alt}</p> |
| 132 | + </div> |
| 133 | + `; |
| 134 | + this.parentNode.replaceChild(placeholder, this); |
| 135 | + }); |
| 136 | + } |
| 137 | + }); |
| 138 | + }); |
24 | 139 | }); |
| 140 | + |
| 141 | +// Placeholder functions for book actions |
| 142 | +function readBook(bookId) { |
| 143 | + console.log(`Reading book with ID: ${bookId}`); |
| 144 | + // Add your read book logic here |
| 145 | + alert(`Opening book reader for book ID: ${bookId}`); |
| 146 | +} |
| 147 | + |
| 148 | +function listenBook(bookId) { |
| 149 | + console.log(`Listening to book with ID: ${bookId}`); |
| 150 | + // Add your audio book logic here |
| 151 | + alert(`Opening audio player for book ID: ${bookId}`); |
| 152 | +} |
0 commit comments