Release Notes Generator

Release Notes Generator

Release Notes Generator

Generate clean, professional release notes from your feature and bug lists.

Your Release Notes

Summary

Build Your Release

Add Items

Current Release Items

Configuration

Manage the categories for your release note items. These will appear in the dropdown when adding items and as headers in the final notes.

Manage Categories

No items added to this release yet.

`; return; } draftList.innerHTML = draft.items.map((item, index) => `
${item.category}: ${item.text}
`).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 = `

Notification

`; 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 });
Scroll to Top