feat(ui): page header actions — Edit (accent) + Revisions menu (view/restore) + Export, top-right
Editor refactored to expose controls; Edit moved into the doc header as the orange primary action; new Revisions dropdown lists page_revisions with a modal preview + Restore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
// 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.
|
||||
|
||||
// Single-pane Markdown view with an Edit/Done toggle. Returns the pane plus its
|
||||
// controls (toggle + save) so the page can place them in the header. Edit is the
|
||||
// accent (orange) action; Save appears only in edit mode.
|
||||
import { marked } from '../vendor/marked.esm.js';
|
||||
import DOMPurify from '../vendor/purify.es.mjs';
|
||||
import { el } from '../dom.js';
|
||||
@@ -15,63 +12,44 @@ export function markdownEditor({ initial = '', save }) {
|
||||
|
||||
const ta = el('textarea', {
|
||||
style: {
|
||||
width: '100%', minHeight: '420px', resize: 'vertical',
|
||||
width: '100%', minHeight: '460px', 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
|
||||
}
|
||||
const rerender = () => { preview.innerHTML = DOMPurify.sanitize(marked.parse(ta.value)); };
|
||||
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' },
|
||||
class: 'ghost', 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;
|
||||
}
|
||||
stamp.textContent = updated?.updated_at ? 'Saved ' + new Date(updated.updated_at).toLocaleTimeString() : 'Saved';
|
||||
} catch (e) { stamp.textContent = 'Save failed: ' + e.message; }
|
||||
finally { saveBtn.disabled = false; }
|
||||
}
|
||||
}, 'Save');
|
||||
|
||||
const toggle = el('button', {
|
||||
class: 'ghost',
|
||||
class: 'primary',
|
||||
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';
|
||||
}
|
||||
if (editing) { pane.replaceChild(ta, preview); ta.focus(); toggle.textContent = 'Done'; 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)
|
||||
);
|
||||
return {
|
||||
pane,
|
||||
controls: el('span', { class: 'ed-controls' }, toggle, saveBtn, stamp),
|
||||
setValue: (md) => { ta.value = md; if (!editing) rerender(); },
|
||||
value: () => ta.value
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user