// Single-pane Markdown view with an Edit/Preview toggle. // - Default: rendered HTML (marked → DOMPurify). // - Click "Edit" to replace the pane in place with a raw-markdown textarea + Save. // - Click "Preview" to re-render and go back to read mode. // save(value) is caller-supplied and returns a promise resolving to the updated row. import { marked } from '../vendor/marked.esm.js'; import DOMPurify from '../vendor/purify.es.mjs'; import { el } from '../dom.js'; marked.setOptions({ gfm: true, breaks: true }); export function markdownEditor({ initial = '', save }) { let editing = false; const ta = el('textarea', { style: { width: '100%', minHeight: '420px', resize: 'vertical', fontFamily: 'var(--font-mono)', fontSize: '13px', lineHeight: '1.5' } }); ta.value = initial; const preview = el('div', { class: 'md-preview' }); function rerender() { preview.innerHTML = DOMPurify.sanitize(marked.parse(ta.value)); // sanitized } rerender(); // The pane shows EITHER the preview (read) or the textarea (edit). const pane = el('div', {}, preview); const stamp = el('span', { class: 'muted', style: { fontSize: '11px' } }, ''); const saveBtn = el('button', { class: 'primary', style: { display: 'none' }, onclick: async () => { saveBtn.disabled = true; try { const updated = await save(ta.value); stamp.textContent = updated?.updated_at ? 'Saved ' + new Date(updated.updated_at).toLocaleString() : 'Saved'; } catch (e) { stamp.textContent = 'Save failed: ' + e.message; } finally { saveBtn.disabled = false; } } }, 'Save'); const toggle = el('button', { class: 'ghost', onclick: () => { editing = !editing; if (editing) { pane.replaceChild(ta, preview); ta.focus(); toggle.textContent = 'Preview'; saveBtn.style.display = ''; } else { rerender(); pane.replaceChild(preview, ta); toggle.textContent = 'Edit'; saveBtn.style.display = 'none'; } } }, 'Edit'); return el('div', {}, el('div', { style: { display: 'flex', gap: '12px', alignItems: 'center', marginBottom: '8px' } }, toggle, saveBtn, stamp ), el('div', { class: 'card' }, pane) ); }