Replace textarea with CodeMirror 6 editor
- Add CodeMirror 6 with oneDark theme and markdown language support - Bundle via esbuild into public/vendor/ (built on deploy, not committed) - Add codemirror-entry.mjs as bundle entry point - Fix SPA fallback to only serve index.html for /, not all paths - Fix mime type handling by mutating mime-types registry - Add Write/Preview tabs; preview renders on tab switch only - Multi-cursor via Shift+Alt+Up/Down; Alt+Click adds cursor - gitignore package-lock.json and public/vendor/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
import * as api from './lib/api.mjs';
|
||||
import { qs, clone, set_text, show, hide } from './lib/dom.mjs';
|
||||
import {
|
||||
EditorState,
|
||||
EditorView,
|
||||
keymap,
|
||||
placeholder,
|
||||
drawSelection,
|
||||
defaultKeymap,
|
||||
history as cm_history,
|
||||
historyKeymap,
|
||||
indentWithTab,
|
||||
addCursorAbove,
|
||||
addCursorBelow,
|
||||
markdown,
|
||||
syntaxHighlighting,
|
||||
oneDark,
|
||||
} from '/vendor/codemirror-bundle.mjs';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
@@ -818,11 +834,42 @@ function render_edit_view(container) {
|
||||
tab_bar.appendChild(write_tab);
|
||||
tab_bar.appendChild(preview_tab);
|
||||
|
||||
// Textarea
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.className = 'edit-textarea';
|
||||
textarea.value = entry?.body ?? '';
|
||||
textarea.placeholder = 'Body (markdown)';
|
||||
// CodeMirror editor
|
||||
const cm_host = document.createElement('div');
|
||||
cm_host.className = 'edit-cm-host';
|
||||
|
||||
const cm_editor = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: entry?.body ?? '',
|
||||
extensions: [
|
||||
cm_history(),
|
||||
drawSelection(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of(e => e.altKey),
|
||||
keymap.of([
|
||||
{ key: 'Shift-Alt-ArrowUp', run: addCursorAbove },
|
||||
{ key: 'Shift-Alt-ArrowDown', run: addCursorBelow },
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
indentWithTab,
|
||||
{ key: 'Ctrl-s', mac: 'Cmd-s', run: () => { save_btn.click(); return true; } },
|
||||
]),
|
||||
EditorView.lineWrapping,
|
||||
placeholder('Body (markdown)'),
|
||||
markdown(),
|
||||
oneDark,
|
||||
EditorView.theme({
|
||||
'&': { height: '100%' },
|
||||
'.cm-editor.cm-focused': { outline: 'none' },
|
||||
'.cm-gutters': { display: 'none' },
|
||||
'.cm-placeholder': { fontStyle: 'italic' },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
parent: cm_host,
|
||||
});
|
||||
|
||||
function get_editor_value() { return cm_editor.state.doc.toString(); }
|
||||
|
||||
// Preview pane
|
||||
const preview_el = document.createElement('div');
|
||||
@@ -832,17 +879,17 @@ function render_edit_view(container) {
|
||||
write_tab.addEventListener('click', () => {
|
||||
write_tab.classList.add('active');
|
||||
preview_tab.classList.remove('active');
|
||||
textarea.hidden = false;
|
||||
cm_host.hidden = false;
|
||||
preview_el.hidden = true;
|
||||
textarea.focus();
|
||||
cm_editor.focus();
|
||||
});
|
||||
|
||||
preview_tab.addEventListener('click', () => {
|
||||
preview_tab.classList.add('active');
|
||||
write_tab.classList.remove('active');
|
||||
textarea.hidden = true;
|
||||
cm_host.hidden = true;
|
||||
preview_el.hidden = false;
|
||||
const text = textarea.value;
|
||||
const text = get_editor_value();
|
||||
if (!text.trim()) {
|
||||
preview_el.innerHTML = '<em style="color:#555">Nothing to preview.</em>';
|
||||
} else {
|
||||
@@ -851,20 +898,10 @@ function render_edit_view(container) {
|
||||
}
|
||||
});
|
||||
|
||||
// Ctrl+S to save
|
||||
// Ctrl+S on title input
|
||||
title_input.addEventListener('keydown', e => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); save_btn.click(); }
|
||||
});
|
||||
textarea.addEventListener('keydown', e => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); save_btn.click(); }
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
textarea.value = textarea.value.slice(0, start) + '\t' + textarea.value.slice(end);
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Sidebar ---
|
||||
const sidebar = document.createElement('div');
|
||||
@@ -985,7 +1022,7 @@ function render_edit_view(container) {
|
||||
const editor_pane = document.createElement('div');
|
||||
editor_pane.className = 'edit-editor-pane';
|
||||
editor_pane.appendChild(tab_bar);
|
||||
editor_pane.appendChild(textarea);
|
||||
editor_pane.appendChild(cm_host);
|
||||
editor_pane.appendChild(preview_el);
|
||||
|
||||
body_area.appendChild(editor_pane);
|
||||
@@ -994,14 +1031,14 @@ function render_edit_view(container) {
|
||||
container.appendChild(view);
|
||||
|
||||
if (!title_input.value) { title_input.focus(); }
|
||||
else { textarea.focus(); }
|
||||
else { cm_editor.focus(); }
|
||||
|
||||
// Save handler
|
||||
save_btn.addEventListener('click', async () => {
|
||||
const data = {
|
||||
type: ae.mode === 'new' ? (type_el.value ?? ae.type) : entry.type,
|
||||
title: title_input.value.trim(),
|
||||
body: textarea.value.trim(),
|
||||
body: get_editor_value().trim(),
|
||||
status: status_sel.value,
|
||||
priority: priority_sel.value,
|
||||
tags: tags_input.value.split(',').map(s => s.trim()).filter(Boolean),
|
||||
|
||||
@@ -700,23 +700,19 @@ main.edit-mode {
|
||||
.edit-tab:hover { color: #aaa; background: transparent; }
|
||||
.edit-tab.active { color: #e0e0e0; border-bottom-color: #5588e0; background: transparent; }
|
||||
|
||||
.edit-textarea {
|
||||
.edit-cm-host {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
padding: 1rem;
|
||||
background: #111;
|
||||
border: none;
|
||||
color: #d0d0d0;
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
line-height: 1.6;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.edit-textarea:focus {
|
||||
outline: none;
|
||||
background: #141414;
|
||||
.edit-cm-host .cm-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.edit-cm-host .cm-scroller {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.edit-preview {
|
||||
|
||||
Reference in New Issue
Block a user