No items added to this release yet.
`;
return;
}
draftList.innerHTML = draft.items.map((item, index) => `
${item.category}: ${item.text}
Delete
`).join('');
draftList.querySelectorAll('.rng-draft-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
draft.items.splice(parseInt(e.target.dataset.index, 10), 1);
renderDraftList();
});
});
}
// --- Generated Notes (Tab 1) ---
function handleGenerateNotes() {
draft.version = versionInput.value;
draft.date = dateInput.value;
if (!draft.version || !draft.date || draft.items.length === 0) {
// Using a custom modal/message box instead of alert()
showMessageModal("Please set a version, date, and add at least one item before generating.");
return;
}
renderGeneratedNotes();
renderSummaryChart();
showTab(0);
}
function renderGeneratedNotes() {
let html = `
${draft.version}
${draft.date}
`;
const grouped = db.categories.reduce((acc, cat) => {
const items = draft.items.filter(item => item.category === cat);
if (items.length > 0) {
acc[cat] = items;
}
return acc;
}, {});
for (const category in grouped) {
html += `
${category} `;
grouped[category].forEach(item => {
html += `${item.text} `;
});
html += ` `;
}
pdfContent.innerHTML = html;
pdfDownloadBtn.style.display = 'inline-block';
copyBtn.style.display = 'inline-block';
}
function renderSummaryChart() {
if (!summaryChartCanvas) return;
const counts = db.categories.reduce((acc, cat) => {
acc[cat] = 0;
return acc;
}, {});
draft.items.forEach(item => {
if (counts.hasOwnProperty(item.category)) {
counts[item.category]++;
}
});
if (summaryChartInstance) {
summaryChartInstance.destroy();
}
summaryChartInstance = new Chart(summaryChartCanvas.getContext('2d'), {
type: 'doughnut',
data: {
labels: Object.keys(counts),
datasets: [{
label: 'Item Count',
data: Object.values(counts),
backgroundColor: ['#3b82f6', '#10b981', '#ef4444', '#f59e0b', '#6366f1', '#8b5cf6'],
borderColor: '#ffffff',
borderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false, // Per spec
plugins: {
legend: { position: 'bottom' },
tooltip: {
callbacks: {
label: (context) => `${context.label}: ${context.raw} items`
}
}
}
}
});
}
// --- Utility Functions ---
function showMessageModal(message) {
// A non-blocking alternative to alert()
let modal = document.getElementById('rng-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'rng-modal';
modal.className = 'rng-pdf-hide fixed inset-0 bg-gray-800 bg-opacity-50 flex items-center justify-center p-4';
modal.style.zIndex = '1000';
modal.innerHTML = `
`;
document.body.appendChild(modal);
modal.querySelector('#rng-modal-close').addEventListener('click', () => {
modal.style.display = 'none';
});
}
modal.querySelector('#rng-modal-message').textContent = message;
modal.style.display = 'flex';
}
async function downloadPDF() {
// This function is designed to be robust (Spec 9)
pdfTarget.classList.add('rng-pdf-view');
try {
const canvas = await html2canvas(pdfTarget, {
scale: 2,
logging: false,
useCORS: true,
onclone: (doc) => {
// Copy the live chart canvas to the cloned document
const originalCanvas = summaryChartCanvas;
const clonedCanvas = doc.getElementById('rng-summary-chart');
if (clonedCanvas) {
clonedCanvas.getContext('2d').drawImage(originalCanvas, 0, 0);
}
}
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
const pageMargin = 15;
const contentWidth = pdfWidth - (pageMargin * 2);
const contentHeight = (canvas.height * contentWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', pageMargin, pageMargin, contentWidth, contentHeight);
pdf.save(`Release_Notes_${draft.version || 'v1.0'}.pdf`);
} catch (error) {
console.error("Error generating PDF:", error);
showMessageModal("An error occurred while generating the PDF. Please try again.");
} finally {
pdfTarget.classList.remove('rng-pdf-view');
}
}
function copyToClipboard() {
// Use document.execCommand for iframe compatibility (Spec II.C.4)
const el = document.createElement('textarea');
let text = `Release Notes: ${draft.version} (${draft.date})\n\n`;
const grouped = db.categories.reduce((acc, cat) => {
const items = draft.items.filter(item => item.category === cat);
if (items.length > 0) acc[cat] = items;
return acc;
}, {});
for (const category in grouped) {
text += `--- ${category} ---\n`;
grouped[category].forEach(item => { text += `• ${item.text}\n`; });
text += `\n`;
}
el.value = text;
document.body.appendChild(el);
el.select();
try {
document.execCommand('copy');
showMessageModal('Release notes copied to clipboard!');
} catch (err) {
showMessageModal('Failed to copy text. Please try again.');
}
document.body.removeChild(el);
}
function renderAll() {
renderConfig();
populateCategoryDropdowns();
renderDraftList();
}
function loadSampleData() {
db.categories = ["🚀 New Features", "🐛 Bug Fixes", "✨ Improvements", "🧹 Maintenance"];
draft.items = [
{ id: 1, text: "Added user profile avatars to the dashboard.", category: "🚀 New Features" },
{ id: 2, text: "Fixed a bug where users in California (USA) could not log in.", category: "🐛 Bug Fixes" },
{ id: 3, text: "Improved database query speed for large reports.", category: "✨ Improvements" }
];
draft.version = "v1.0.0-sample";
draft.date = "2025-10-27";
versionInput.value = draft.version;
dateInput.value = draft.date;
renderAll();
}
// --- 4. EVENT BINDING & INITIALIZATION ---
navTabs.forEach((tab, index) => {
tab.addEventListener('click', () => showTab(index));
});
addCategoryForm.addEventListener('submit', handleAddCategory);
addItemForm.addEventListener('submit', handleAddItem);
generateBtn.addEventListener('click', handleGenerateNotes);
pdfDownloadBtn.addEventListener('click', downloadPDF);
copyBtn.addEventListener('click', copyToClipboard);
// Set default date
dateInput.value = new Date().toISOString().split('T')[0];
// Initial Load
loadSampleData();
showTab(1); // Start on the build tab
});