📝 Changelog Generator

Git Commits → Beautiful Changelog

Paste your git log or bullet notes. Get a structured, categorized changelog in Markdown, HTML, or rendered format instantly.

📋 Input
✨ Rendered
📄 Markdown
💻 HTML
📝

Paste commits and click Generate to see your changelog.

Markdown output will appear here.
HTML output will appear here.
\n`; document.getElementById('htmlOutput').textContent = htmlOut; btn.disabled=false; btn.textContent='✨ Regenerate'; switchOut('render'); }) .catch(err => { btn.disabled=false; btn.textContent='✨ Generate Changelog'; alert('Generation failed: ' + err.message + '. Please try again in a moment.'); }); } // Minimal, safe markdown → HTML renderer for AI brain output (escapes HTML first). function esc(s){ return String(s).replace(/&/g,'&').replace(//g,'>'); } function mdToHtml(md){ let s = esc(String(md)); const blocks = []; s = s.replace(/```(\w*)\n?([\s\S]*?)```/g, (m, lang, code) => { blocks.push('
' + code.replace(/\n$/,'') + '
'); return 'ABZBLOCK' + (blocks.length - 1) + 'ENDABZ'; }); s = s.replace(/`([^`\n]+)`/g, '$1'); s = s.replace(/^######\s+(.*)$/gm, '
$1
') .replace(/^#####\s+(.*)$/gm, '
$1
') .replace(/^####\s+(.*)$/gm, '

$1

') .replace(/^###\s+(.*)$/gm, '

$1

') .replace(/^##\s+(.*)$/gm, '

$1

') .replace(/^#\s+(.*)$/gm, '

$1

'); s = s.replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/(^|[^*])\*([^*\n]+)\*/g, '$1$2'); s = s.replace(/(?:^[ \t]*(?:[-*]|\d+\.)\s+.*(?:\n|$))+/gm, (block) => { const ordered = /^\s*\d+\.\s/.test(block); const items = block.trim().split(/\n/).map(l => l.replace(/^\s*(?:[-*]|\d+\.)\s+/, '').trim()).filter(Boolean); return '<' + (ordered?'ol':'ul') + ' style="padding-left:20px;margin:8px 0">' + items.map(i=>'
  • '+i+'
  • ').join('') + ''; }); s = s.split(/\n{2,}/).map(p => { p = p.trim(); if (!p) return ''; if (/^ABZBLOCK\d+ENDABZ$/.test(p)) return p; if (/^\s*<(h\d|ul|ol|pre)/.test(p)) return p; return '

    ' + p.replace(/\n/g, '
    ') + '

    '; }).join('\n'); s = s.replace(/(?:]*>)?\s*ABZBLOCK(\d+)ENDABZ\s*(?:<\/p>)?/g, (m, i) => blocks[+i]); return s; } let activeOut = 'render'; function switchOut(tab) { activeOut = tab; ['render','md','html'].forEach(t=>{ document.getElementById(`tab-${t}`).classList.toggle('active',t===tab); document.getElementById(`pane-${t}`).classList.toggle('active',t===tab); }); } function copyActive() { let text = ''; if(activeOut==='md') text = document.getElementById('mdOutput').textContent; else if(activeOut==='html') text = document.getElementById('htmlOutput').textContent; else text = document.getElementById('renderedOutput').innerText; navigator.clipboard.writeText(text).then(()=>{ const btn=document.querySelector('.copy-btn'); btn.textContent='✓ Copied!'; setTimeout(()=>btn.textContent='📋 Copy',2000); }); } function downloadActive() { let content='',ext='txt',mime='text/plain'; if(activeOut==='md'){content=document.getElementById('mdOutput').textContent;ext='md';} else if(activeOut==='html'){content=document.getElementById('htmlOutput').textContent;ext='html';mime='text/html';} else{content=document.getElementById('renderedOutput').innerHTML;ext='html';mime='text/html';} const blob=new Blob([content],{type:mime}); const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`CHANGELOG.${ext}`;a.click(); }