From 5289ae307fcba3b851955d7ce37603c894b4734a Mon Sep 17 00:00:00 2001 From: mwiegand Date: Sun, 17 May 2026 01:56:26 +0200 Subject: [PATCH] feat(editor-v2): light + dark themes + syntax highlight style themes.js exports four extensions: - editorLightTheme / editorDarkTheme: EditorView.theme() variants keyed to the --cm-* CSS variables defined in tokens.css (light) and its prefers-color-scheme: dark block. - editorHighlightStyle: HighlightStyle bound to Lezer tags (comment, string, number, keyword, variableName). - editorHighlighting: syntaxHighlighting(editorHighlightStyle) ready to drop into the EditorState extensions array. @lezer/highlight comes in transitively via @codemirror/language; no new package.json dependency needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- l4d2web/scripts/editor-src/themes.js | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 l4d2web/scripts/editor-src/themes.js diff --git a/l4d2web/scripts/editor-src/themes.js b/l4d2web/scripts/editor-src/themes.js new file mode 100644 index 0000000..8123a23 --- /dev/null +++ b/l4d2web/scripts/editor-src/themes.js @@ -0,0 +1,49 @@ +import { EditorView } from "@codemirror/view"; +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; +import { tags as t } from "@lezer/highlight"; + +// CSS variables are defined in static/css/tokens.css (light) and the +// `prefers-color-scheme: dark` block. Both themes route through the +// same --cm-* variable names; the OS toggle swaps the underlying values. +// +// Two named themes so we can also force-pick light/dark in tests if needed. + +const baseRules = { + "&": { + backgroundColor: "var(--cm-bg)", + color: "var(--cm-fg)", + fontFamily: "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)", + fontSize: "14px", + }, + ".cm-content": { caretColor: "var(--cm-fg)", padding: "8px" }, + ".cm-cursor": { borderLeftColor: "var(--cm-fg)" }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": { + backgroundColor: "var(--cm-selection)", + }, + ".cm-gutters": { + backgroundColor: "var(--cm-bg)", + color: "var(--fg-muted, #888)", + border: "none", + }, + ".cm-tooltip": { + backgroundColor: "var(--cm-bg)", + border: "1px solid var(--border-strong, #444)", + color: "var(--cm-fg)", + }, + ".cm-tooltip-autocomplete > ul > li[aria-selected]": { + backgroundColor: "var(--cm-selection)", + }, +}; + +export const editorLightTheme = EditorView.theme(baseRules, { dark: false }); +export const editorDarkTheme = EditorView.theme(baseRules, { dark: true }); + +export const editorHighlightStyle = HighlightStyle.define([ + { tag: t.comment, color: "var(--cm-comment)" }, + { tag: t.string, color: "var(--cm-string)" }, + { tag: t.number, color: "var(--cm-number)" }, + { tag: t.keyword, color: "var(--cm-keyword)" }, + { tag: t.variableName, color: "var(--cm-fg)" }, +]); + +export const editorHighlighting = syntaxHighlighting(editorHighlightStyle);