168 lines
6.4 KiB
JavaScript
168 lines
6.4 KiB
JavaScript
// 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,
|
|
};
|