// CodeMirror 6 bundle entry point. // // Imports only the CM packages the editor needs (keep the bundle small for the // mobile/VPN path) and exposes them on window.CM for the global-style editor // scripts. The editor theme and markdown highlight palette draw from the app's // :root CSS variables so there are no hardcoded colors/spacing here. import { EditorState, EditorSelection, Compartment, Prec } from "@codemirror/state"; import { EditorView, keymap, drawSelection } from "@codemirror/view"; import { history, historyKeymap, defaultKeymap, indentWithTab, undo, redo, deleteLine } from "@codemirror/commands"; import { markdown, markdownLanguage, markdownKeymap } from "@codemirror/lang-markdown"; import { syntaxHighlighting, HighlightStyle, indentOnInput, StreamLanguage } from "@codemirror/language"; import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap, startCompletion, } from "@codemirror/autocomplete"; import { tags } from "@lezer/highlight"; // Editor chrome. Colors/spacing/fonts come from :root variables; var() works // here because HighlightStyle/theme emit real CSS rules. const theme = EditorView.theme( { "&": { backgroundColor: "var(--bg)", color: "var(--text)", fontSize: "0.9rem", border: "var(--border)", borderTop: "none", }, "&.cm-focused": { outline: "none" }, ".cm-scroller": { fontFamily: '"Iosevka Slab", monospace', lineHeight: "1.6", minHeight: "60vh", }, ".cm-content": { padding: "var(--space-4)", caretColor: "var(--text)", }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--text)" }, "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: "var(--bg-panel-hover)" }, ".cm-activeLine": { backgroundColor: "transparent" }, ".cm-tooltip": { backgroundColor: "var(--bg-panel)", border: "var(--border-dashed)", color: "var(--text)", }, ".cm-tooltip.cm-tooltip-autocomplete > ul": { fontFamily: '"Iosevka Slab", monospace', fontSize: "var(--font-sm)", }, ".cm-tooltip-autocomplete ul li[aria-selected]": { backgroundColor: "var(--bg-panel-hover)", color: "var(--text)", }, ".cm-completionDetail": { color: "var(--text-muted)", fontStyle: "normal", marginLeft: "var(--space-3)", }, }, { dark: true } ); const highlightStyle = HighlightStyle.define([ { tag: [tags.heading1, tags.heading2, tags.heading3, tags.heading4, tags.heading5, tags.heading6], color: "var(--secondary)", fontWeight: "bold" }, { tag: tags.strong, fontWeight: "bold", color: "var(--text)" }, { tag: tags.emphasis, fontStyle: "italic" }, { tag: tags.strikethrough, textDecoration: "line-through" }, { tag: [tags.link, tags.url], color: "var(--link)" }, { tag: tags.monospace, color: "var(--primary-hover)" }, { tag: tags.quote, color: "var(--text-muted)", fontStyle: "italic" }, { tag: [tags.list, tags.contentSeparator], color: "var(--secondary)" }, { tag: [tags.processingInstruction, tags.meta], color: "var(--text-muted)" }, ]); // todo.txt language for the left-rail todo widget (assets/todo-rail.js). // Line-oriented: `x ` done-prefix strikes the whole line; `(A)` priority, // `+project`, `@context` and ISO dates get their own colors. No parsing into // task objects — highlighting only. const todoLanguage = StreamLanguage.define({ token(stream) { if (stream.sol() && stream.match(/^x\s.*/)) return "todoDone"; if (stream.sol() && stream.match(/^\([A-Z]\)(?=\s|$)/)) return "todoPriority"; // +project / @context / dates only count at word starts (start of line // or after whitespace), per the todo.txt format. const atWordStart = stream.start === 0 || /\s/.test(stream.string.charAt(stream.start - 1)); if (atWordStart) { if (stream.match(/^\d{4}-\d{2}-\d{2}(?=\s|$)/)) return "todoDate"; if (stream.match(/^\+\S+/)) return "todoProject"; if (stream.match(/^@\S+/)) return "todoContext"; } // Consume whitespace OR one plain word — never both, or the word after // a space would be swallowed before it can be matched above. if (!stream.eatSpace()) { stream.next(); stream.eatWhile(/\S/); } return null; }, tokenTable: { todoDone: tags.strikethrough, todoPriority: tags.keyword, todoProject: tags.typeName, todoContext: tags.atom, todoDate: tags.meta, }, }); const todoHighlightStyle = HighlightStyle.define([ { tag: tags.strikethrough, textDecoration: "line-through", color: "var(--text-muted)" }, { tag: tags.keyword, color: "var(--secondary)", fontWeight: "bold" }, { tag: tags.typeName, color: "var(--link)" }, { tag: tags.atom, color: "var(--primary-hover)" }, { tag: tags.meta, color: "var(--text-muted)" }, ]); // Minimal chrome for the rail editor — the full editor `theme` above forces a // 60vh min-height and page-editor padding that don't fit a sidebar widget. // Sizing/height caps live in style.css under .todo-rail. const todoTheme = EditorView.theme( { "&": { backgroundColor: "transparent", color: "var(--text)", fontSize: "var(--font-xs)" }, "&.cm-focused": { outline: "none" }, ".cm-scroller": { fontFamily: '"Iosevka Slab", monospace', lineHeight: "1.5" }, ".cm-content": { caretColor: "var(--text)" }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--text)" }, "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: "var(--bg-panel-hover)" }, }, { dark: true } ); window.CM = { EditorState, EditorSelection, Compartment, Prec, EditorView, keymap, drawSelection, history, historyKeymap, defaultKeymap, indentWithTab, undo, redo, deleteLine, markdown, markdownLanguage, markdownKeymap, syntaxHighlighting, indentOnInput, autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap, startCompletion, theme, highlightStyle, todoLanguage, todoHighlightStyle, todoTheme, };