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

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

`; rlbl_readingListContainer.appendChild(li); }); } // --- Book Log Render --- rlbl_bookLogBody.innerHTML = ''; if (logBooks.length === 0) { rlbl_bookLogBody.innerHTML = `No books completed (or no matches for filter).`; } else { logBooks.forEach(book => { const tr = document.createElement('tr'); tr.className = 'border-b border-gray-200 hover:bg-emerald-50'; tr.innerHTML = ` ${rlbl_escapeHTML(book.title)}
by ${rlbl_escapeHTML(book.author)} ${rlbl_escapeHTML(book.genre)} ${rlbl_formatDate(book.dateRead)} ${rlbl_getRatingStars(book.rating)} `; rlbl_bookLogBody.appendChild(tr); }); } } /** * Renders a clone for PDF generation */ function rlbl_renderPdfClone() { rlbl_pdfRenderClone.innerHTML = `

Reading List & Book Log Summary

Reading List (To Read)

    Book Log (Completed)

    Title / Author Genre Date Read Rating
    `; // Render data into the clone's content area const pdfList = rlbl_pdfRenderClone.querySelector('#pdf-reading-list'); const pdfLogBody = rlbl_pdfRenderClone.querySelector('#pdf-book-log-body'); // Temporarily render all data to the PDF structure (ignoring filters/sort for a complete snapshot) const listBooks = rlbl_data.allBooks.filter(b => b.status === 'To Read'); const logBooks = rlbl_data.allBooks.filter(b => b.status === 'Read').sort((a, b) => a.title.localeCompare(b.title)); // Sort alphabetically for PDF consistency // Render PDF Reading List if (listBooks.length === 0) { pdfList.innerHTML = `
  • Reading list is empty.
  • `; } else { listBooks.forEach(book => { const li = document.createElement('li'); li.className = 'p-2 border-b border-sky-200 text-gray-700'; li.innerHTML = `

    ${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