`;
});
previewContainer.innerHTML = `
`;
listItemsContainer.appendChild(newItem);
updateItemNumbers();
}
listItemsContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-item-button')) {
if (listItemsContainer.children.length > 1) {
e.target.closest('.list-item-entry').remove();
updateItemNumbers();
}
}
});
function showMessage(text = 'Copied to clipboard!') {
messageBox.textContent = text;
messageBox.classList.remove('opacity-0', 'translate-y-10');
messageBox.classList.add('opacity-100', 'translate-y-0');
setTimeout(() => {
messageBox.classList.remove('opacity-100', 'translate-y-0');
messageBox.classList.add('opacity-0', 'translate-y-10');
}, 2000);
}
async function getImageAsBase64(url) {
try {
const response = await fetch(url);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
} catch (error) {
console.error('Error fetching image for PDF:', error);
return null;
}
}
async function generateListiclePdf() {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const data = getFormData();
const statusEl = document.getElementById('pdf-status');
pdfDownloadButton.disabled = true;
if(statusEl) statusEl.textContent = 'Preparing PDF...';
const pageW = pdf.internal.pageSize.getWidth();
const margin = 15;
const contentWidth = pageW - (margin * 2);
let y = margin;
const lineH = 7;
const addHeaderFooter = () => {
const pageCount = pdf.internal.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) {
pdf.setPage(i);
pdf.setFontSize(8);
pdf.setTextColor('#6b7280');
pdf.text(data.title, margin, pageH - 8);
pdf.text(`Page ${i} of ${pageCount}`, pageW - margin, pageH - 8, { align: 'right' });
}
const pageH = pdf.internal.pageSize.getHeight();
};
const checkBreak = (needed = 20) => {
if (y + needed > pdf.internal.pageSize.getHeight() - margin) {
pdf.addPage();
y = margin;
}
};
const addText = (text, options = {}) => {
if (!text) return;
const { size=10, style='normal', color='#374151', indent=0, spacing=1 } = options;
checkBreak();
pdf.setFontSize(size);
pdf.setFont(undefined, style);
pdf.setTextColor(color);
const splitText = pdf.splitTextToSize(text, contentWidth - indent);
pdf.text(splitText, margin + indent, y);
y += (splitText.length * lineH * 0.7) + (lineH * spacing);
};
// --- Build PDF ---
addText(data.title, { size: 20, style: 'bold', color: '#1f2937' });
if(data.author) addText(`By ${data.author}`, { size: 10, style: 'italic', color: '#4b5563' });
y += lineH;
addText(data.introduction, { style: 'italic' });
let loadedImages = 0;
const imagePromises = data.items.map(item => item.imageUrl ? getImageAsBase64(item.imageUrl) : Promise.resolve(null));
const imageResults = await Promise.all(imagePromises.map(p => p.catch(e => null))); // prevent one failure from stopping all
if(statusEl) statusEl.textContent = `Processing ${data.items.length} items...`;
for(let i = 0; i < data.items.length; i++) {
const item = data.items[i];
const base64Image = imageResults[i];
y += lineH * 1.5;
checkBreak(30);
addText(`${i + 1}. ${item.title}`, { size: 14, style: 'bold', color: '#6d28d9' });
if (base64Image) {
try {
const imgProps = pdf.getImageProperties(base64Image);
const imgRatio = imgProps.height / imgProps.width;
const imgWidth = contentWidth * 0.8;
const imgHeight = imgWidth * imgRatio;
checkBreak(imgHeight + 5);
pdf.addImage(base64Image, imgProps.fileType, margin + (contentWidth * 0.1), y, imgWidth, imgHeight);
y += imgHeight + 5;
} catch (e) {
console.error("Error adding image to PDF:", e);
addText(`[Image could not be loaded: ${item.imageUrl}]`, {size: 8, style: 'italic'});
}
}
addText(item.description);
}
if (data.conclusion) {
y += lineH * 2;
addText('Conclusion', {size: 14, style: 'bold', color: '#6d28d9'});
addText(data.conclusion);
}
addHeaderFooter();
pdf.save(`${data.title.replace(/\s+/g, '_').toLowerCase()}.pdf`);
if(statusEl) statusEl.textContent = '';
pdfDownloadButton.disabled = false;
}
// --- Event Listeners ---
tabs.forEach(tab => tab.addEventListener('click', () => goToTab(parseInt(tab.dataset.tab))));
prevButton.addEventListener('click', () => goToTab(currentTab - 1));
nextButton.addEventListener('click', () => goToTab(currentTab + 1));
addListItemButton.addEventListener('click', addListItemBlock);
copyButton.addEventListener('click', () => {
const data = getFormData();
let text = `${data.title}\n\n`;
text += `${data.introduction}\n\n`;
data.items.forEach((item, index) => {
text += `${index + 1}. ${item.title}\n`;
if(item.imageUrl) text += `[Image: ${item.imageUrl}]\n`;
text += `${item.description}\n\n`;
});
text += `Conclusion\n${data.conclusion}`;
navigator.clipboard.writeText(text).then(() => {
showMessage();
}).catch(err => console.error('Copy failed: ', err));
});
pdfDownloadButton.addEventListener('click', generateListiclePdf);
updateTabUI();
});
${data.title}
${data.introduction.replace(/\n/g, '
')}
Conclusion
${data.conclusion.replace(/\n/g, '
')}
