feat: styles

This commit is contained in:
2026-04-03 16:53:27 +02:00
parent 0e7e435edd
commit 911f3fb57d
2 changed files with 72 additions and 10 deletions

View File

@@ -1,11 +1,55 @@
import { useEffect, useRef } from "react";
import { EditorView, keymap } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { EditorView, keymap, lineNumbers, highlightActiveLine, Decoration, ViewPlugin } from "@codemirror/view";
import { EditorState, RangeSetBuilder } from "@codemirror/state";
import type { Extension } from "@codemirror/state";
import type { DecorationSet } from "@codemirror/view";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { searchKeymap } from "@codemirror/search";
import { oneDark } from "./oneDarkTheme";
function toRoman(n: number): string {
const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
const syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"];
let result = "";
for (let i = 0; i < vals.length; i++) {
while (n >= vals[i]) {
result += syms[i];
n -= vals[i];
}
}
return result;
}
const dotDeco = Decoration.mark({ class: "cm-dot-space" });
function buildSpaceDeco(view: EditorView): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
for (const { from, to } of view.visibleRanges) {
const text = view.state.sliceDoc(from, to);
for (let i = 0; i < text.length; i++) {
if (text[i] === " ") {
builder.add(from + i, from + i + 1, dotDeco);
}
}
}
return builder.finish();
}
const subtleWhitespace = ViewPlugin.fromClass(
class {
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = buildSpaceDeco(view);
}
update(update: { docChanged: boolean; viewportChanged: boolean; view: EditorView }) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildSpaceDeco(update.view);
}
}
},
{ decorations: (v) => v.decorations },
);
interface Props {
value: string;
onChange: (value: string) => void;
@@ -25,6 +69,9 @@ export default function EditorPane({ value, onChange, extensions = [] }: Props)
doc: value,
extensions: [
history(),
lineNumbers({ formatNumber: toRoman }),
highlightActiveLine(),
subtleWhitespace,
keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap]),
oneDark,
EditorView.updateListener.of((update) => {
@@ -49,6 +96,20 @@ export default function EditorPane({ value, onChange, extensions = [] }: Props)
borderLeftColor: "var(--primary)",
borderLeftWidth: "0.5em",
},
".cm-dot-space": {
color: "transparent",
position: "relative",
},
".cm-dot-space:before": {
content: '"\\22C5"',
position: "absolute",
left: "0",
right: "0",
textAlign: "center",
color: "var(--muted-foreground)",
opacity: "0.5",
pointerEvents: "none",
},
}),
...extensions,
],

View File

@@ -3,26 +3,27 @@ import { EditorView } from "@codemirror/view";
export const oneDark = EditorView.theme(
{
"&": {
backgroundColor: "#0d1117",
color: "#c9d1d9",
backgroundColor: "var(--background)",
color: "var(--foreground)",
},
".cm-cursor, .cm-cursor-primary": {
borderLeftColor: "var(--primary)",
borderLeftWidth: "0.5em",
},
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
backgroundColor: "#264f78",
backgroundColor: "color-mix(in oklch, var(--primary) 25%, transparent)",
},
".cm-activeLine": {
backgroundColor: "#161b2266",
backgroundColor: "color-mix(in oklch, var(--primary) 8%, transparent)",
},
".cm-gutters": {
backgroundColor: "#0d1117",
color: "#484f58",
borderRight: "1px solid #21262d",
backgroundColor: "var(--background)",
color: "var(--muted-foreground)",
borderRight: "1px solid var(--border)",
},
".cm-activeLineGutter": {
backgroundColor: "#161b2266",
backgroundColor: "color-mix(in oklch, var(--primary) 8%, transparent)",
color: "var(--primary)",
},
},
{ dark: true }