feat: styles
This commit is contained in:
@@ -1,11 +1,55 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { EditorView, keymap } from "@codemirror/view";
|
import { EditorView, keymap, lineNumbers, highlightActiveLine, Decoration, ViewPlugin } from "@codemirror/view";
|
||||||
import { EditorState } from "@codemirror/state";
|
import { EditorState, RangeSetBuilder } from "@codemirror/state";
|
||||||
import type { Extension } from "@codemirror/state";
|
import type { Extension } from "@codemirror/state";
|
||||||
|
import type { DecorationSet } from "@codemirror/view";
|
||||||
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
||||||
import { searchKeymap } from "@codemirror/search";
|
import { searchKeymap } from "@codemirror/search";
|
||||||
import { oneDark } from "./oneDarkTheme";
|
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 {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
@@ -25,6 +69,9 @@ export default function EditorPane({ value, onChange, extensions = [] }: Props)
|
|||||||
doc: value,
|
doc: value,
|
||||||
extensions: [
|
extensions: [
|
||||||
history(),
|
history(),
|
||||||
|
lineNumbers({ formatNumber: toRoman }),
|
||||||
|
highlightActiveLine(),
|
||||||
|
subtleWhitespace,
|
||||||
keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap]),
|
keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap]),
|
||||||
oneDark,
|
oneDark,
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
@@ -49,6 +96,20 @@ export default function EditorPane({ value, onChange, extensions = [] }: Props)
|
|||||||
borderLeftColor: "var(--primary)",
|
borderLeftColor: "var(--primary)",
|
||||||
borderLeftWidth: "0.5em",
|
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,
|
...extensions,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,26 +3,27 @@ import { EditorView } from "@codemirror/view";
|
|||||||
export const oneDark = EditorView.theme(
|
export const oneDark = EditorView.theme(
|
||||||
{
|
{
|
||||||
"&": {
|
"&": {
|
||||||
backgroundColor: "#0d1117",
|
backgroundColor: "var(--background)",
|
||||||
color: "#c9d1d9",
|
color: "var(--foreground)",
|
||||||
},
|
},
|
||||||
".cm-cursor, .cm-cursor-primary": {
|
".cm-cursor, .cm-cursor-primary": {
|
||||||
borderLeftColor: "var(--primary)",
|
borderLeftColor: "var(--primary)",
|
||||||
borderLeftWidth: "0.5em",
|
borderLeftWidth: "0.5em",
|
||||||
},
|
},
|
||||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
|
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
|
||||||
backgroundColor: "#264f78",
|
backgroundColor: "color-mix(in oklch, var(--primary) 25%, transparent)",
|
||||||
},
|
},
|
||||||
".cm-activeLine": {
|
".cm-activeLine": {
|
||||||
backgroundColor: "#161b2266",
|
backgroundColor: "color-mix(in oklch, var(--primary) 8%, transparent)",
|
||||||
},
|
},
|
||||||
".cm-gutters": {
|
".cm-gutters": {
|
||||||
backgroundColor: "#0d1117",
|
backgroundColor: "var(--background)",
|
||||||
color: "#484f58",
|
color: "var(--muted-foreground)",
|
||||||
borderRight: "1px solid #21262d",
|
borderRight: "1px solid var(--border)",
|
||||||
},
|
},
|
||||||
".cm-activeLineGutter": {
|
".cm-activeLineGutter": {
|
||||||
backgroundColor: "#161b2266",
|
backgroundColor: "color-mix(in oklch, var(--primary) 8%, transparent)",
|
||||||
|
color: "var(--primary)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ dark: true }
|
{ dark: true }
|
||||||
|
|||||||
Reference in New Issue
Block a user