`; const blob = new Blob([html], {type:'text/html'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${proj.name.toLowerCase().replace(/\s+/g,'-')}-changelog.html`; a.click(); showToast('HTML exported!'); } // Settings function showSettings() { document.getElementById('setTitle').value = state.settings.title; document.getElementById('setSubtitle').value = state.settings.subtitle; document.getElementById('setSubscribeCta').value = state.settings.subscribeCta || ''; openModal('settingsModal'); } function showProjects() {} function saveSettings() { state.settings.title = document.getElementById('setTitle').value.trim() || 'Changelog'; state.settings.subtitle = document.getElementById('setSubtitle').value.trim(); state.settings.subscribeCta = document.getElementById('setSubscribeCta').value.trim(); save(); render(); closeModal('settingsModal'); } // Import/Export function exportAll() { const blob = new Blob([JSON.stringify(state, null, 2)], {type:'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'changelog-studio-backup.json'; a.click(); showToast('Backup exported!'); } function importAll() { document.getElementById('importFile').click(); } function handleImport(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(ev) { try { const d = JSON.parse(ev.target.result); if (d.projects) { state = { ...state, ...d }; save(); render(); showToast('Imported!'); } } catch(err) { alert('Invalid file'); } }; reader.readAsText(file); e.target.value = ''; } // Toast let toastTimer; function showToast(msg) { let t = document.getElementById('toast'); if (!t) { t = document.createElement('div'); t.id = 'toast'; t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);padding:10px 20px;background:var(--accent);color:white;border-radius:8px;font-size:13px;font-weight:500;z-index:9999;opacity:0;transition:opacity 0.3s;pointer-events:none'; document.body.appendChild(t); } t.textContent = msg; t.style.opacity = 1; clearTimeout(toastTimer); toastTimer = setTimeout(() => t.style.opacity = 0, 2000); } // Utils function escHtml(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function escAttr(s) { return String(s||'').replace(/"/g,'"').replace(/&/g,'&'); } function formatDate(d) { try { return new Date(d+'T00:00').toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'}); } catch(e) { return d; } } // Keyboard shortcuts document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') { if (e.key === 'Escape') { e.target.blur(); document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show')); } return; } if (e.key === 'Escape') { document.querySelectorAll('.modal-overlay.show').forEach(m => m.classList.remove('show')); } if (e.key === 'n' || e.key === 'N') { e.preventDefault(); newRelease(); } if (e.key === '/') { e.preventDefault(); document.getElementById('searchInput').focus(); } if (e.key === '1') setView('releases'); if (e.key === '2') setView('timeline'); if (e.key === '3') setView('preview'); if (e.key === '4') setView('stats'); if (e.key === 'e' || e.key === 'E') { e.preventDefault(); exportAll(); } }); // Init load(); render();