Reading List & Book Log Creator

Reading List & Book Log Creator

Reading List & Book Log Creator

View your reading progress. The Reading List shows books yet to be read, and the Book Log tracks your completed library, including ratings and completion dates.

Reading List (To Read)

Book Log (Completed)

Title / Author Genre Date Read Rating

${rlbl_escapeHTML(book.title)}

by ${rlbl_escapeHTML(book.author)} (${rlbl_escapeHTML(book.genre)})

`; pdfList.appendChild(li); }); } // Render PDF Book Log if (logBooks.length === 0) { pdfLogBody.innerHTML = `No books completed.`; } else { logBooks.forEach(book => { const tr = document.createElement('tr'); tr.className = 'border-b border-gray-200'; tr.innerHTML = ` ${rlbl_escapeHTML(book.title)}
by ${rlbl_escapeHTML(book.author)} ${rlbl_escapeHTML(book.genre)} ${rlbl_formatDate(book.dateRead)} ${rlbl_getRatingStars(book.rating)} `; pdfLogBody.appendChild(tr); }); } } /** * Generates and downloads a PDF of the log */ async function rlbl_downloadPDF() { if (rlbl_data.allBooks.length === 0) { alert("The log is empty. Please add books before downloading."); return; } if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') { alert("Error: PDF libraries failed to load."); return; } rlbl_renderPdfClone(); const { jsPDF } = window.jspdf; try { const canvas = await html2canvas(rlbl_pdfRenderClone, { scale: 1.5, useCORS: true }); const imgData = canvas.toDataURL('image/png'); const imgProps = { width: canvas.width, height: canvas.height }; const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const margin = 40; const contentWidth = pdfWidth - (margin * 2); const contentHeight = (contentWidth * imgProps.height) / imgProps.width; let heightLeft = contentHeight; let position = 0; pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight); heightLeft -= (pdfHeight - margin * 2); while (heightLeft > 0) { position -= (pdfHeight - margin * 2); pdf.addPage(); pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight); heightLeft -= (pdfHeight - margin * 2); } pdf.save('Reading_Log_and_List.pdf'); } catch (error) { console.error("PDF generation failed:", error); alert("An error occurred while generating the PDF."); } } // --- EVENT LISTENERS --- // Tab link clicks rlbl_tabLinks.forEach((link, index) => { link.addEventListener('click', () => rlbl_switchTab(index)); }); // Next/Prev button clicks if (rlbl_prevButton) { rlbl_prevButton.addEventListener('click', () => { if (rlbl_currentTab > 0) rlbl_switchTab(rlbl_currentTab - 1); }); } if (rlbl_nextButton) { rlbl_nextButton.addEventListener('click', () => { const targetIndex = rlbl_currentTab === 0 ? 1 : 0; rlbl_switchTab(targetIndex); }); } // PDF download if (rlbl_downloadPdfButton) { rlbl_downloadPdfButton.addEventListener('click', rlbl_downloadPDF); } // --- Dashboard Filters/Sort --- if (rlbl_filterGenre) { rlbl_filterGenre.addEventListener('change', (e) => { rlbl_currentFilterGenre = e.target.value; rlbl_renderDashboard(); }); } if (rlbl_sortRatingButton) { rlbl_sortRatingButton.addEventListener('click', () => { rlbl_sortDirection = rlbl_sortDirection === 'asc' ? 'desc' : 'asc'; rlbl_sortIndicator.textContent = rlbl_sortDirection === 'asc' ? 'â–²' : 'â–¼'; rlbl_renderDashboard(); }); } // --- Config Tab Listeners (Add/Manage) --- if (rlbl_submitAdd) { rlbl_submitAdd.addEventListener('click', rlbl_addBook); } if (rlbl_configTab) { rlbl_configTab.addEventListener('click', (e) => { const removeButton = e.target.closest('.rlbl-remove-book'); const toggleButton = e.target.closest('.rlbl-toggle-status'); if (removeButton) { const idToRemove = parseInt(removeButton.closest('div[data-id]').dataset.id); rlbl_data.allBooks = rlbl_data.allBooks.filter(b => b.id !== idToRemove); rlbl_renderManageList(); } if (toggleButton) { const idToToggle = parseInt(toggleButton.closest('div[data-id]').dataset.id); const book = rlbl_data.allBooks.find(b => b.id === idToToggle); if (book) { if (book.status === 'Read') { book.status = 'To Read'; book.dateRead = ''; book.rating = 'N/A'; } else { book.status = 'Read'; book.dateRead = new Date().toISOString().split('T')[0]; } } rlbl_renderManageList(); } }); // Handle date and rating changes rlbl_configTab.addEventListener('change', (e) => { const target = e.target; const isDate = target.classList.contains('rlbl-manage-date'); const isRating = target.classList.contains('rlbl-manage-rating'); if (isDate || isRating) { const id = parseInt(target.closest('div[data-id]').dataset.id); const book = rlbl_data.allBooks.find(b => b.id === id); if (book) { if (isDate) book.dateRead = target.value; if (isRating) book.rating = target.value === 'N/A' ? 'N/A' : parseInt(target.value); } } }); } // --- INITIALIZATION --- rlbl_initSampleData(); rlbl_populateGenreOptions(); rlbl_renderManageList(); rlbl_renderDashboard(); // Set initial tab state rlbl_tabPanes.forEach((pane, index) => { pane.classList.toggle('hidden', index !== 0); pane.classList.toggle('rlbl-active', index === 0); }); rlbl_tabLinks.forEach((link, index) => { TAB_CLASSES.active.forEach(cls => link.classList.remove(cls)); TAB_CLASSES.inactive.forEach(cls => link.classList.remove(cls)); if (index === 0) { TAB_CLASSES.active.forEach(cls => link.classList.add(cls)); } else { TAB_CLASSES.inactive.forEach(cls => link.classList.add(cls)); } }); });
Scroll to Top