${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));
}
});
});