@coldsmirk/inkstone-react 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -5
- package/dist/codemirror-B27C5np1.js +604 -0
- package/dist/codemirror-B3GlIVaX.d.ts +348 -0
- package/dist/codemirror.d.ts +2 -0
- package/dist/codemirror.js +2 -0
- package/dist/index.d.ts +4 -393
- package/dist/index.js +5 -566
- package/dist/monaco-CP5BIXrw.js +403 -0
- package/dist/monaco-DXifh803.d.ts +360 -0
- package/dist/monaco.d.ts +2 -0
- package/dist/monaco.js +2 -0
- package/dist/shiki-D-IjmhhO.d.ts +20 -0
- package/dist/shiki.d.ts +2 -0
- package/dist/shiki.js +2 -0
- package/dist/use-latest-BhihMdOp.js +19 -0
- package/dist/use-shiki-highlighter-CzSQcX2f.js +27 -0
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @coldsmirk/inkstone-react
|
|
2
2
|
|
|
3
|
-
Drop-in React code editors with the integration chores solved once: a preconfigured Monaco **`<MonacoEditor>`** (offline workers, Shiki catppuccin themes, dark switching, opt-in zh-CN UI) and a controlled CodeMirror 6 **`<CodeMirrorEditor>`** (catppuccin themes, restyled chrome, MiniJinja support, sizing). Drop in a component; the plumbing is already wired.
|
|
3
|
+
Drop-in React code editors with the integration chores solved once: a preconfigured Monaco **`<MonacoEditor>`** (offline workers, Shiki catppuccin themes, dark switching, opt-in zh-CN UI) and a controlled CodeMirror 6 **`<CodeMirrorEditor>`** (catppuccin themes, restyled chrome, MiniJinja support, sizing) — plus **`<MonacoDiffEditor>`** and **`<CodeMirrorDiffEditor>`**, the same assemblies around each engine's side-by-side diff view. Drop in a component; the plumbing is already wired.
|
|
4
4
|
|
|
5
5
|
Part of [inkstone](https://github.com/coldsmirk/inkstone) — this package composes [`@coldsmirk/inkstone-core`](https://www.npmjs.com/package/@coldsmirk/inkstone-core) (shared Shiki highlighter), [`@coldsmirk/inkstone-monaco`](https://www.npmjs.com/package/@coldsmirk/inkstone-monaco) (offline Monaco host), and [`@coldsmirk/inkstone-codemirror`](https://www.npmjs.com/package/@coldsmirk/inkstone-codemirror) (controlled CM6 host + MiniJinja). See the [repository README](https://github.com/coldsmirk/inkstone#readme) for the full guide; the highlights below are what you touch daily.
|
|
6
6
|
|
|
@@ -11,12 +11,14 @@ pnpm add @coldsmirk/inkstone-react monaco-editor react \
|
|
|
11
11
|
@codemirror/state @codemirror/view @codemirror/language @codemirror/autocomplete @codemirror/commands @codemirror/search
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`monaco-editor`, `react`, and the `@codemirror/*` packages are **peer dependencies** — your app owns the single copy of each (`@codemirror/state` breaks at runtime if two copies load). Node.js >=
|
|
14
|
+
`monaco-editor`, `react`, and the `@codemirror/*` packages are **peer dependencies** — your app owns the single copy of each (`@codemirror/state` breaks at runtime if two copies load). Node.js >= 24 for build / SSR hosts.
|
|
15
|
+
|
|
16
|
+
Import from the engine-specific entry points below so a CodeMirror-only build never traverses or emits Monaco (and vice versa). The aggregate `@coldsmirk/inkstone-react` entry remains available for existing consumers; `@coldsmirk/inkstone-react/shiki` exposes the standalone highlighter hook.
|
|
15
17
|
|
|
16
18
|
## `<MonacoEditor>`
|
|
17
19
|
|
|
18
20
|
```tsx
|
|
19
|
-
import { MonacoEditor } from "@coldsmirk/inkstone-react";
|
|
21
|
+
import { MonacoEditor } from "@coldsmirk/inkstone-react/monaco";
|
|
20
22
|
|
|
21
23
|
<MonacoEditor
|
|
22
24
|
language="javascript"
|
|
@@ -39,7 +41,7 @@ Never `import "monaco-editor"` statically anywhere in the app — it locks the U
|
|
|
39
41
|
## `<CodeMirrorEditor>`
|
|
40
42
|
|
|
41
43
|
```tsx
|
|
42
|
-
import { CodeMirrorEditor } from "@coldsmirk/inkstone-react";
|
|
44
|
+
import { CodeMirrorEditor } from "@coldsmirk/inkstone-react/codemirror";
|
|
43
45
|
|
|
44
46
|
<CodeMirrorEditor
|
|
45
47
|
language="json"
|
|
@@ -54,11 +56,31 @@ A controlled CM6 editor: create-once view, external value changes reconcile with
|
|
|
54
56
|
|
|
55
57
|
- **`language`** lazily imports the matching grammar as its own chunk — the official Lezer parsers plus the `@codemirror/legacy-modes` catalog (`shell`, `dockerfile`, `ini` / `env`, `toml`, `nginx`, …). Includes the MiniJinja family — `minijinja` standalone or `minijinja-<host>` mixed variants (`minijinja-yaml`, `-json`, `-html`, `-sql`, `-shell`, `-dockerfile`, …).
|
|
56
58
|
- **`context`** — pass `{ schema }` (JSON Schema) or `{ sample }` (representative value) and MiniJinja completion suggests the render context's variables with member access. Memoize/hoist it — a fresh identity each render re-normalizes.
|
|
59
|
+
- **`sqlSchema`** — pass table/column metadata to the Lezer-backed SQL languages (`sql`, `mysql`, `pgsql`, `sqlite`, `mssql`, `mariadb`, `plsql`, `cassandra`) and their `minijinja-*` variants for dialect-aware table, alias, and column completion. Updates in place; legacy stream-mode SQL languages do not consume schemas.
|
|
57
60
|
- **`search`** (opt-in) adds the standard search keymap and panel; `locale="zh-cn"` localizes it per editor.
|
|
58
61
|
- **Metrics & layout** — `fontSize` 14 and `lineHeight` 1.6 by default; `height` / `width` size the editor; `lineWrapping`, `showLineNumbers`, `folding` are opt-in.
|
|
62
|
+
- **Accessibility** — the textbox defaults to `ariaLabel="Editor content"`; pass a workflow-specific `ariaLabel` when needed. Focus presentation belongs to the surrounding application chrome.
|
|
59
63
|
- **Extensions** — `extensions` is create-once; `dynamicExtensions` reconfigures live. The catppuccin theme sits at low precedence, so a caller theme in `dynamicExtensions` overrides it.
|
|
60
64
|
|
|
61
|
-
`useShikiHighlighter`
|
|
65
|
+
`useShikiHighlighter` is exported from `@coldsmirk/inkstone-react/shiki`; `codeMirrorLanguages` / `normalizeContext` / `searchPhrasesZhCn` live on the CodeMirror entry, and `monacoLanguages` lives on the Monaco entry. The aggregate root re-exports them for compatibility.
|
|
66
|
+
|
|
67
|
+
## `<MonacoDiffEditor>` / `<CodeMirrorDiffEditor>`
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { MonacoDiffEditor } from "@coldsmirk/inkstone-react/monaco";
|
|
71
|
+
import { CodeMirrorDiffEditor } from "@coldsmirk/inkstone-react/codemirror";
|
|
72
|
+
|
|
73
|
+
<MonacoDiffEditor language="typescript" dark={dark} original={before} modified={after} onChange={setAfter} />;
|
|
74
|
+
|
|
75
|
+
<CodeMirrorDiffEditor language="json" dark={dark} original={before} modified={after} onChange={setAfter} />;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The pair speaks the same core contract on both engines: the **original** side (left) is read-only and follows its prop; the **modified** side is the controlled, editable document behind `modified` / `onChange` — set `readOnly` for a pure diff viewer. External changes to either prop reconcile into the live view as one minimal replace — no rebuild, off the undo stack, carets and selections outside the changed region kept — and prop reconciliations never echo back through `onChange`. Shared toggles: `collapseUnchanged` folds long unchanged stretches behind expandable bars, `lineWrapping`, and `showLineNumbers` (on by default — a diff reads by line reference).
|
|
79
|
+
|
|
80
|
+
Each component keeps its engine's conventions from the single-editor sibling:
|
|
81
|
+
|
|
82
|
+
- **`<MonacoDiffEditor>`** — the `<MonacoEditor>` assembly (offline host, Shiki themes, `locale`, `loading` / `failure`, `hostOptions`) around `monaco.editor.createDiffEditor`. `options` takes raw `IDiffEditorConstructionOptions` over the same `DEFAULT_MONACO_OPTIONS` baseline — e.g. `renderSideBySide: false` for the inline view.
|
|
83
|
+
- **`<CodeMirrorDiffEditor>`** — `@codemirror/merge`'s `MergeView` behind `<CodeMirrorEditor>`'s create-once discipline, with the catppuccin flavors extended to the diff chrome (red deletions / green insertions, themed collapse bars and revert controls). `revertControls` adds per-chunk arrows copying the original chunk into the modified document (flows through `onChange`); `language`, `extensions`, and `dynamicExtensions` apply to both sides; `onMount` hands you the `MergeView`.
|
|
62
84
|
|
|
63
85
|
## License
|
|
64
86
|
|
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { n as useControlledReconcileSignal, r as CODE_FONT_FAMILY, t as useLatest } from "./use-latest-BhihMdOp.js";
|
|
2
|
+
import { flavors } from "@catppuccin/palette";
|
|
3
|
+
import { EditorState, Prec } from "@codemirror/state";
|
|
4
|
+
import { EditorView, lineNumbers } from "@codemirror/view";
|
|
5
|
+
import { ControlledEditorHost, ControlledMergeHost, codeMirrorLanguages, documentExtensions, loadLanguage, normalizeContext, normalizeContext as normalizeContext$1, searchPhrasesZhCn, searchPhrasesZhCn as searchPhrasesZhCn$1, setMinijinjaContext, setSqlSchema } from "@coldsmirk/inkstone-codemirror";
|
|
6
|
+
import { useEffect, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { catppuccinLatte, catppuccinMacchiato } from "@catppuccin/codemirror";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
import { indentUnit } from "@codemirror/language";
|
|
10
|
+
//#region src/codemirror-theme.ts
|
|
11
|
+
function toCssSize(value) {
|
|
12
|
+
if (value === void 0) return;
|
|
13
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
14
|
+
}
|
|
15
|
+
function alphaOf(color, alphaValue) {
|
|
16
|
+
return `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${alphaValue})`;
|
|
17
|
+
}
|
|
18
|
+
function completionIconMask(body) {
|
|
19
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="#000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
|
|
20
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
21
|
+
}
|
|
22
|
+
const COMPLETION_ICON_FALLBACK = completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"2.5\"/>");
|
|
23
|
+
const COMPLETION_ICONS = {
|
|
24
|
+
keyword: completionIconMask("<path d=\"M5.5 5 2.5 8l3 3\"/><path d=\"m10.5 5 3 3-3 3\"/>"),
|
|
25
|
+
function: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
|
|
26
|
+
method: completionIconMask("<path d=\"M10.8 3.4c-2-.4-2.9.7-3.2 2.5L6.4 12c-.3 1.7-1.2 2.2-2.6 1.8\"/><path d=\"M4.6 7.5h5\"/>"),
|
|
27
|
+
variable: completionIconMask("<path d=\"M4 5c2.2 0 5.3 6 8 6\"/><path d=\"M12 5c-2.7 0-5.8 6-8 6\"/>"),
|
|
28
|
+
constant: completionIconMask("<path d=\"M3.5 5.2h9\"/><path d=\"M6.2 5.2v6.3\"/><path d=\"M10.2 5.2v4.4c0 1.3.6 1.9 1.7 1.9\"/>"),
|
|
29
|
+
property: completionIconMask("<circle cx=\"4.9\" cy=\"11.1\" r=\"1.8\"/><circle cx=\"11.1\" cy=\"4.9\" r=\"1.8\"/><path d=\"M6.4 9.6l3.2-3.2\"/>"),
|
|
30
|
+
class: completionIconMask("<rect x=\"3.5\" y=\"3.5\" width=\"9\" height=\"9\" rx=\"2.2\"/>"),
|
|
31
|
+
interface: completionIconMask("<circle cx=\"8\" cy=\"8\" r=\"4.5\"/>"),
|
|
32
|
+
type: completionIconMask("<path d=\"M4 4.5h8\"/><path d=\"M8 4.5V12\"/>"),
|
|
33
|
+
enum: completionIconMask("<path d=\"M6.5 4.5h6\"/><path d=\"M6.5 8h6\"/><path d=\"M6.5 11.5h6\"/><path d=\"M3.4 4.5h.01\"/><path d=\"M3.4 8h.01\"/><path d=\"M3.4 11.5h.01\"/>"),
|
|
34
|
+
namespace: completionIconMask("<path d=\"M6.2 3.5c-1.3 0-1.9.6-1.9 1.9v1.2c0 .9-.4 1.4-1.3 1.4.9 0 1.3.5 1.3 1.4v1.2c0 1.3.6 1.9 1.9 1.9\"/><path d=\"M9.8 3.5c1.3 0 1.9.6 1.9 1.9v1.2c0 .9.4 1.4 1.3 1.4-.9 0-1.3.5-1.3 1.4v1.2c0 1.3-.6 1.9-1.9 1.9\"/>"),
|
|
35
|
+
text: completionIconMask("<path d=\"M3.5 5h9\"/><path d=\"M3.5 8h9\"/><path d=\"M3.5 11h5.5\"/>")
|
|
36
|
+
};
|
|
37
|
+
function completionIconRules() {
|
|
38
|
+
const rules = {};
|
|
39
|
+
for (const [kind, mask] of Object.entries(COMPLETION_ICONS)) rules[`.cm-completionIcon-${kind}:after`] = {
|
|
40
|
+
maskImage: mask,
|
|
41
|
+
WebkitMaskImage: mask
|
|
42
|
+
};
|
|
43
|
+
return rules;
|
|
44
|
+
}
|
|
45
|
+
function chromeTheme(dark) {
|
|
46
|
+
const { colors } = dark ? flavors.macchiato : flavors.latte;
|
|
47
|
+
const hairline = `1px solid ${alphaOf(colors.overlay0, .35)}`;
|
|
48
|
+
const frame = {
|
|
49
|
+
border: hairline,
|
|
50
|
+
borderRadius: "8px",
|
|
51
|
+
boxShadow: dark ? "0 8px 24px rgba(0, 0, 0, 0.5)" : "0 8px 24px rgba(0, 0, 0, 0.12)",
|
|
52
|
+
color: colors.text.hex
|
|
53
|
+
};
|
|
54
|
+
return EditorView.theme({
|
|
55
|
+
"&": { colorScheme: dark ? "dark" : "light" },
|
|
56
|
+
".cm-scroller": { fontFamily: CODE_FONT_FAMILY },
|
|
57
|
+
".cm-tooltip": frame,
|
|
58
|
+
".cm-tooltip.cm-tooltip-autocomplete": {
|
|
59
|
+
...frame,
|
|
60
|
+
"& > ul": {
|
|
61
|
+
borderRadius: "7px",
|
|
62
|
+
padding: "4px",
|
|
63
|
+
maxHeight: "16em"
|
|
64
|
+
},
|
|
65
|
+
"& > ul > li": {
|
|
66
|
+
display: "flex",
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
padding: "3px 8px",
|
|
69
|
+
borderRadius: "5px",
|
|
70
|
+
lineHeight: "1.45"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
".cm-completionLabel": {
|
|
74
|
+
minWidth: "0",
|
|
75
|
+
overflow: "hidden",
|
|
76
|
+
textOverflow: "ellipsis"
|
|
77
|
+
},
|
|
78
|
+
".cm-completionMatchedText": {
|
|
79
|
+
textDecoration: "none",
|
|
80
|
+
fontWeight: "600",
|
|
81
|
+
color: colors.blue.hex
|
|
82
|
+
},
|
|
83
|
+
".cm-completionDetail": {
|
|
84
|
+
marginLeft: "auto",
|
|
85
|
+
paddingLeft: "12px",
|
|
86
|
+
fontStyle: "normal",
|
|
87
|
+
fontSize: "85%",
|
|
88
|
+
color: colors.overlay1.hex
|
|
89
|
+
},
|
|
90
|
+
".cm-completionIcon": {
|
|
91
|
+
width: "14px",
|
|
92
|
+
paddingRight: "8px",
|
|
93
|
+
opacity: "1",
|
|
94
|
+
color: colors.overlay1.hex
|
|
95
|
+
},
|
|
96
|
+
".cm-completionIcon:after": {
|
|
97
|
+
content: "''",
|
|
98
|
+
display: "block",
|
|
99
|
+
width: "12px",
|
|
100
|
+
height: "12px",
|
|
101
|
+
backgroundColor: "currentColor",
|
|
102
|
+
maskImage: COMPLETION_ICON_FALLBACK,
|
|
103
|
+
WebkitMaskImage: COMPLETION_ICON_FALLBACK,
|
|
104
|
+
maskRepeat: "no-repeat",
|
|
105
|
+
WebkitMaskRepeat: "no-repeat",
|
|
106
|
+
maskPosition: "center",
|
|
107
|
+
WebkitMaskPosition: "center",
|
|
108
|
+
maskSize: "contain",
|
|
109
|
+
WebkitMaskSize: "contain"
|
|
110
|
+
},
|
|
111
|
+
...completionIconRules(),
|
|
112
|
+
".cm-completionIcon-keyword": { color: colors.mauve.hex },
|
|
113
|
+
".cm-completionIcon-function, .cm-completionIcon-method": { color: colors.blue.hex },
|
|
114
|
+
".cm-completionIcon-variable": { color: colors.peach.hex },
|
|
115
|
+
".cm-completionIcon-constant": { color: colors.sky.hex },
|
|
116
|
+
".cm-completionIcon-property": { color: colors.teal.hex },
|
|
117
|
+
".cm-completionIcon-namespace": { color: colors.sapphire.hex },
|
|
118
|
+
".cm-completionIcon-text": { color: colors.overlay1.hex },
|
|
119
|
+
".cm-completionIcon-class, .cm-completionIcon-type, .cm-completionIcon-interface, .cm-completionIcon-enum": { color: colors.yellow.hex },
|
|
120
|
+
".cm-tooltip.cm-completionInfo": {
|
|
121
|
+
...frame,
|
|
122
|
+
padding: "8px 10px",
|
|
123
|
+
maxWidth: "26em"
|
|
124
|
+
},
|
|
125
|
+
".cm-tooltip-lint": {
|
|
126
|
+
padding: "4px",
|
|
127
|
+
maxWidth: "26em",
|
|
128
|
+
overflowWrap: "anywhere"
|
|
129
|
+
},
|
|
130
|
+
".cm-diagnostic": {
|
|
131
|
+
margin: "0",
|
|
132
|
+
padding: "3px 8px",
|
|
133
|
+
borderLeftWidth: "3px",
|
|
134
|
+
borderRadius: "5px",
|
|
135
|
+
lineHeight: "1.45"
|
|
136
|
+
},
|
|
137
|
+
".cm-diagnostic-error": { borderLeftColor: colors.red.hex },
|
|
138
|
+
".cm-diagnostic-warning": { borderLeftColor: colors.yellow.hex },
|
|
139
|
+
".cm-diagnostic-info": { borderLeftColor: colors.overlay1.hex },
|
|
140
|
+
".cm-diagnostic-hint": { borderLeftColor: colors.blue.hex },
|
|
141
|
+
".cm-diagnosticAction": {
|
|
142
|
+
padding: "2px 6px",
|
|
143
|
+
backgroundColor: colors.surface1.hex,
|
|
144
|
+
color: colors.text.hex,
|
|
145
|
+
borderRadius: "4px"
|
|
146
|
+
},
|
|
147
|
+
".cm-diagnosticSource": {
|
|
148
|
+
fontSize: "85%",
|
|
149
|
+
color: colors.overlay1.hex,
|
|
150
|
+
opacity: "1"
|
|
151
|
+
},
|
|
152
|
+
"&.cm-focused": { outline: "none" },
|
|
153
|
+
".cm-activeLine": { backgroundColor: alphaOf(colors.overlay0, .12) },
|
|
154
|
+
".cm-activeLineGutter": { backgroundColor: alphaOf(colors.overlay0, .12) },
|
|
155
|
+
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
|
|
156
|
+
".cm-selectionBackground": { backgroundColor: alphaOf(colors.blue, dark ? .2 : .14) },
|
|
157
|
+
".cm-content ::selection": { backgroundColor: alphaOf(colors.blue, dark ? .35 : .25) },
|
|
158
|
+
".cm-scroller, .cm-tooltip-autocomplete > ul, .cm-tooltip.cm-completionInfo": {
|
|
159
|
+
scrollbarWidth: "thin",
|
|
160
|
+
scrollbarColor: `${alphaOf(colors.overlay0, .55)} transparent`
|
|
161
|
+
},
|
|
162
|
+
".cm-panels.cm-panels-top": { borderBottom: hairline },
|
|
163
|
+
".cm-panels.cm-panels-bottom": { borderTop: hairline },
|
|
164
|
+
".cm-panel.cm-search, .cm-panel.cm-gotoLine": {
|
|
165
|
+
position: "relative",
|
|
166
|
+
display: "flex",
|
|
167
|
+
flexWrap: "wrap",
|
|
168
|
+
alignItems: "center",
|
|
169
|
+
columnGap: "8px",
|
|
170
|
+
rowGap: "8px",
|
|
171
|
+
margin: "0",
|
|
172
|
+
padding: "10px 44px 10px 14px"
|
|
173
|
+
},
|
|
174
|
+
".cm-panel.cm-search br": {
|
|
175
|
+
flexBasis: "100%",
|
|
176
|
+
height: "0",
|
|
177
|
+
margin: "0",
|
|
178
|
+
border: "none"
|
|
179
|
+
},
|
|
180
|
+
".cm-panel.cm-search label, .cm-panel.cm-gotoLine label": {
|
|
181
|
+
display: "inline-flex",
|
|
182
|
+
alignItems: "center",
|
|
183
|
+
gap: "6px",
|
|
184
|
+
margin: "0",
|
|
185
|
+
fontSize: "12.5px",
|
|
186
|
+
color: colors.subtext0.hex,
|
|
187
|
+
cursor: "pointer",
|
|
188
|
+
userSelect: "none"
|
|
189
|
+
},
|
|
190
|
+
".cm-panel.cm-search input[type=checkbox]": {
|
|
191
|
+
margin: "0",
|
|
192
|
+
width: "14px",
|
|
193
|
+
height: "14px",
|
|
194
|
+
accentColor: colors.blue.hex
|
|
195
|
+
},
|
|
196
|
+
".cm-panel.cm-search button[name=close]": {
|
|
197
|
+
position: "absolute",
|
|
198
|
+
top: "10px",
|
|
199
|
+
right: "10px",
|
|
200
|
+
boxSizing: "border-box",
|
|
201
|
+
width: "28px",
|
|
202
|
+
height: "28px",
|
|
203
|
+
padding: "0",
|
|
204
|
+
display: "inline-flex",
|
|
205
|
+
alignItems: "center",
|
|
206
|
+
justifyContent: "center",
|
|
207
|
+
fontSize: "16px",
|
|
208
|
+
lineHeight: "1",
|
|
209
|
+
color: colors.overlay1.hex,
|
|
210
|
+
background: "transparent",
|
|
211
|
+
border: "none",
|
|
212
|
+
borderRadius: "6px",
|
|
213
|
+
cursor: "pointer"
|
|
214
|
+
},
|
|
215
|
+
".cm-panel.cm-search button[name=close]:hover": {
|
|
216
|
+
backgroundColor: colors.surface0.hex,
|
|
217
|
+
color: colors.text.hex
|
|
218
|
+
},
|
|
219
|
+
".cm-panel .cm-textfield": {
|
|
220
|
+
boxSizing: "border-box",
|
|
221
|
+
height: "28px",
|
|
222
|
+
width: "240px",
|
|
223
|
+
maxWidth: "100%",
|
|
224
|
+
margin: "0",
|
|
225
|
+
padding: "0 10px",
|
|
226
|
+
fontSize: "12.5px",
|
|
227
|
+
color: colors.text.hex,
|
|
228
|
+
backgroundColor: colors.base.hex,
|
|
229
|
+
border: hairline,
|
|
230
|
+
borderRadius: "6px"
|
|
231
|
+
},
|
|
232
|
+
".cm-panel .cm-textfield:focus": {
|
|
233
|
+
outline: "none",
|
|
234
|
+
borderColor: alphaOf(colors.blue, .6),
|
|
235
|
+
boxShadow: `0 0 0 2px ${alphaOf(colors.blue, .2)}`
|
|
236
|
+
},
|
|
237
|
+
".cm-panel .cm-button": {
|
|
238
|
+
boxSizing: "border-box",
|
|
239
|
+
height: "28px",
|
|
240
|
+
margin: "0",
|
|
241
|
+
padding: "0 12px",
|
|
242
|
+
fontSize: "12.5px",
|
|
243
|
+
color: colors.text.hex,
|
|
244
|
+
backgroundImage: "none",
|
|
245
|
+
backgroundColor: colors.surface0.hex,
|
|
246
|
+
border: hairline,
|
|
247
|
+
borderRadius: "6px",
|
|
248
|
+
cursor: "pointer"
|
|
249
|
+
},
|
|
250
|
+
".cm-panel .cm-button:hover": { backgroundColor: colors.surface1.hex },
|
|
251
|
+
".cm-panel .cm-button:active": {
|
|
252
|
+
backgroundImage: "none",
|
|
253
|
+
backgroundColor: colors.surface1.hex
|
|
254
|
+
}
|
|
255
|
+
}, { dark });
|
|
256
|
+
}
|
|
257
|
+
function defaultThemeExtensions(dark, height, width, fontSize, lineHeight) {
|
|
258
|
+
const extensions = [Prec.low(chromeTheme(dark)), Prec.low(dark ? catppuccinMacchiato : catppuccinLatte)];
|
|
259
|
+
const cssHeight = toCssSize(height);
|
|
260
|
+
const cssWidth = toCssSize(width);
|
|
261
|
+
if (cssHeight !== void 0 || cssWidth !== void 0 || fontSize !== void 0 || lineHeight !== void 0) {
|
|
262
|
+
const root = {};
|
|
263
|
+
if (cssHeight !== void 0) root.height = cssHeight;
|
|
264
|
+
if (cssWidth !== void 0) root.width = cssWidth;
|
|
265
|
+
if (fontSize !== void 0) root.fontSize = `${fontSize}px`;
|
|
266
|
+
const spec = { "&": root };
|
|
267
|
+
const scroller = {};
|
|
268
|
+
if (cssHeight !== void 0) scroller.overflow = "auto";
|
|
269
|
+
if (lineHeight !== void 0) scroller.lineHeight = lineHeight < 8 ? String(lineHeight) : `${lineHeight}px`;
|
|
270
|
+
if (Object.keys(scroller).length > 0) spec[".cm-scroller"] = scroller;
|
|
271
|
+
extensions.push(EditorView.theme(spec));
|
|
272
|
+
}
|
|
273
|
+
return extensions;
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/codemirror-diff-editor.tsx
|
|
277
|
+
function mergeTheme(dark) {
|
|
278
|
+
const { colors } = dark ? flavors.macchiato : flavors.latte;
|
|
279
|
+
const removed = colors.red;
|
|
280
|
+
const added = colors.green;
|
|
281
|
+
const underline = (color) => `linear-gradient(${alphaOf(color, .5)}, ${alphaOf(color, .5)}) bottom/100% 2px no-repeat`;
|
|
282
|
+
return EditorView.theme({
|
|
283
|
+
"&.cm-merge-a .cm-changedLine": { backgroundColor: alphaOf(removed, dark ? .14 : .09) },
|
|
284
|
+
"&.cm-merge-b .cm-changedLine": { backgroundColor: alphaOf(added, dark ? .14 : .09) },
|
|
285
|
+
"&.cm-merge-a .cm-changedText": { background: underline(removed) },
|
|
286
|
+
"&.cm-merge-b .cm-changedText": { background: underline(added) },
|
|
287
|
+
"&.cm-merge-a .cm-changedLineGutter": { background: removed.hex },
|
|
288
|
+
"&.cm-merge-b .cm-changedLineGutter": { background: added.hex },
|
|
289
|
+
".cm-collapsedLines": {
|
|
290
|
+
color: colors.subtext0.hex,
|
|
291
|
+
background: `linear-gradient(to bottom, transparent 0, ${colors.surface0.hex} 30%, ${colors.surface0.hex} 70%, transparent 100%)`
|
|
292
|
+
}
|
|
293
|
+
}, { dark });
|
|
294
|
+
}
|
|
295
|
+
const DIFF_CHROME_STYLE_SELECTOR = "style[data-inkstone=\"codemirror-diff\"]";
|
|
296
|
+
function installDiffChrome(root) {
|
|
297
|
+
if (root.querySelector(DIFF_CHROME_STYLE_SELECTOR)) return;
|
|
298
|
+
const style = (root.nodeType === 9 ? root : root.host.ownerDocument).createElement("style");
|
|
299
|
+
style.dataset.inkstone = "codemirror-diff";
|
|
300
|
+
style.textContent = [
|
|
301
|
+
"[data-inkstone-diff] > .cm-mergeView { height: 100%; }",
|
|
302
|
+
"[data-inkstone-diff] .cm-merge-revert button { color: inherit; border-radius: 4px; }",
|
|
303
|
+
"[data-inkstone-diff] .cm-merge-revert button:hover { background-color: color-mix(in srgb, currentColor 15%, transparent); }"
|
|
304
|
+
].join("\n");
|
|
305
|
+
if (root.nodeType === 9) root.head.append(style);
|
|
306
|
+
else root.append(style);
|
|
307
|
+
}
|
|
308
|
+
function editorRoot(container) {
|
|
309
|
+
const root = container.getRootNode();
|
|
310
|
+
if (root.nodeType === 9 || root.nodeType === 11 && "host" in root) return root;
|
|
311
|
+
return container.ownerDocument;
|
|
312
|
+
}
|
|
313
|
+
function eventElement(event) {
|
|
314
|
+
const target = event.target;
|
|
315
|
+
return target?.nodeType === 1 ? target : target?.parentElement ?? null;
|
|
316
|
+
}
|
|
317
|
+
function installDiffControlKeyboardSupport(container) {
|
|
318
|
+
const enhanceControls = () => {
|
|
319
|
+
for (const control of container.querySelectorAll(":scope .cm-collapsedLines")) {
|
|
320
|
+
control.setAttribute("role", "button");
|
|
321
|
+
control.tabIndex = 0;
|
|
322
|
+
}
|
|
323
|
+
for (const button of container.querySelectorAll(":scope .cm-merge-revert button")) button.type = "button";
|
|
324
|
+
};
|
|
325
|
+
const handleClick = (event) => {
|
|
326
|
+
if (event.detail !== 0) return;
|
|
327
|
+
const button = eventElement(event)?.closest(".cm-merge-revert button");
|
|
328
|
+
if (!button || !container.contains(button)) return;
|
|
329
|
+
event.preventDefault();
|
|
330
|
+
const MouseEventConstructor = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
|
|
331
|
+
button.dispatchEvent(new MouseEventConstructor("mousedown", {
|
|
332
|
+
bubbles: true,
|
|
333
|
+
button: 0,
|
|
334
|
+
cancelable: true
|
|
335
|
+
}));
|
|
336
|
+
};
|
|
337
|
+
const handleKeyDown = (event) => {
|
|
338
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
339
|
+
const control = eventElement(event)?.closest(".cm-collapsedLines");
|
|
340
|
+
if (!control || !container.contains(control)) return;
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
event.stopPropagation();
|
|
343
|
+
control.click();
|
|
344
|
+
};
|
|
345
|
+
enhanceControls();
|
|
346
|
+
const observer = new MutationObserver(enhanceControls);
|
|
347
|
+
observer.observe(container, {
|
|
348
|
+
childList: true,
|
|
349
|
+
subtree: true
|
|
350
|
+
});
|
|
351
|
+
container.addEventListener("click", handleClick);
|
|
352
|
+
container.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
353
|
+
return () => {
|
|
354
|
+
observer.disconnect();
|
|
355
|
+
container.removeEventListener("click", handleClick);
|
|
356
|
+
container.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function CodeMirrorDiffEditor({ original, modified, onChange, language, readOnly = false, revertControls = false, collapseUnchanged = false, lineWrapping = false, showLineNumbers = true, extensions, dynamicExtensions, dark = false, height, width, fontSize = 14, lineHeight = 1.6, className, originalAriaLabel = "Original content", modifiedAriaLabel = "Modified content", onMount }) {
|
|
360
|
+
const containerRef = useRef(null);
|
|
361
|
+
const hostRef = useRef(null);
|
|
362
|
+
const modifiedTransitionRef = useRef(false);
|
|
363
|
+
const [reconcileRevision, requestControlledReconcile] = useControlledReconcileSignal();
|
|
364
|
+
const onChangeRef = useLatest(onChange);
|
|
365
|
+
const onMountRef = useLatest(onMount);
|
|
366
|
+
const [loadedLanguage, setLoadedLanguage] = useState(null);
|
|
367
|
+
const languageExtension = useMemo(() => loadedLanguage && loadedLanguage.id === language ? [loadedLanguage.extension] : [], [language, loadedLanguage]);
|
|
368
|
+
const activeRevertControls = revertControls && !readOnly;
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
if (!language) return;
|
|
371
|
+
let cancelled = false;
|
|
372
|
+
loadLanguage(language).then((extension) => {
|
|
373
|
+
if (!cancelled) setLoadedLanguage({
|
|
374
|
+
id: language,
|
|
375
|
+
extension
|
|
376
|
+
});
|
|
377
|
+
}).catch((error) => {
|
|
378
|
+
console.error("inkstone: failed to load CodeMirror language", language, error);
|
|
379
|
+
if (!cancelled) setLoadedLanguage(null);
|
|
380
|
+
});
|
|
381
|
+
return () => {
|
|
382
|
+
cancelled = true;
|
|
383
|
+
};
|
|
384
|
+
}, [language]);
|
|
385
|
+
const dynamic = useMemo(() => [
|
|
386
|
+
...defaultThemeExtensions(dark, void 0, void 0, fontSize, lineHeight),
|
|
387
|
+
mergeTheme(dark),
|
|
388
|
+
...showLineNumbers ? [lineNumbers()] : [],
|
|
389
|
+
...lineWrapping ? [EditorView.lineWrapping] : [],
|
|
390
|
+
...readOnly ? [EditorState.readOnly.of(true), EditorView.editable.of(false)] : [],
|
|
391
|
+
...languageExtension,
|
|
392
|
+
...dynamicExtensions ?? []
|
|
393
|
+
], [
|
|
394
|
+
dark,
|
|
395
|
+
fontSize,
|
|
396
|
+
lineHeight,
|
|
397
|
+
showLineNumbers,
|
|
398
|
+
lineWrapping,
|
|
399
|
+
readOnly,
|
|
400
|
+
languageExtension,
|
|
401
|
+
dynamicExtensions
|
|
402
|
+
]);
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
const container = containerRef.current;
|
|
405
|
+
if (!container) return;
|
|
406
|
+
const root = editorRoot(container);
|
|
407
|
+
installDiffChrome(root);
|
|
408
|
+
const host = new ControlledMergeHost({
|
|
409
|
+
parent: container,
|
|
410
|
+
root,
|
|
411
|
+
original,
|
|
412
|
+
modified,
|
|
413
|
+
onModifiedChange: (next) => {
|
|
414
|
+
if (modifiedTransitionRef.current) return;
|
|
415
|
+
onChangeRef.current?.(next);
|
|
416
|
+
requestControlledReconcile();
|
|
417
|
+
},
|
|
418
|
+
extensions: [...documentExtensions(), ...extensions ?? []],
|
|
419
|
+
originalExtensions: [
|
|
420
|
+
EditorState.readOnly.of(true),
|
|
421
|
+
EditorView.editable.of(false),
|
|
422
|
+
EditorView.contentAttributes.of({ "aria-label": originalAriaLabel })
|
|
423
|
+
],
|
|
424
|
+
modifiedExtensions: [EditorView.contentAttributes.of({ "aria-label": modifiedAriaLabel })],
|
|
425
|
+
dynamicExtensions: dynamic,
|
|
426
|
+
revertControls: activeRevertControls ? "a-to-b" : void 0,
|
|
427
|
+
collapseUnchanged: collapseUnchanged ? {} : void 0
|
|
428
|
+
});
|
|
429
|
+
hostRef.current = host;
|
|
430
|
+
const removeDiffControlKeyboardSupport = installDiffControlKeyboardSupport(container);
|
|
431
|
+
onMountRef.current?.(host.view);
|
|
432
|
+
return () => {
|
|
433
|
+
removeDiffControlKeyboardSupport();
|
|
434
|
+
host.destroy();
|
|
435
|
+
hostRef.current = null;
|
|
436
|
+
};
|
|
437
|
+
}, []);
|
|
438
|
+
useInsertionEffect(() => {
|
|
439
|
+
const host = hostRef.current;
|
|
440
|
+
modifiedTransitionRef.current = host !== null && host.view.b.state.doc.toString() !== modified;
|
|
441
|
+
}, [modified, reconcileRevision]);
|
|
442
|
+
useLayoutEffect(() => {
|
|
443
|
+
hostRef.current?.setOriginal(original);
|
|
444
|
+
}, [original]);
|
|
445
|
+
useLayoutEffect(() => {
|
|
446
|
+
try {
|
|
447
|
+
hostRef.current?.setModified(modified);
|
|
448
|
+
} finally {
|
|
449
|
+
modifiedTransitionRef.current = false;
|
|
450
|
+
}
|
|
451
|
+
}, [modified, reconcileRevision]);
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
hostRef.current?.reconfigure(dynamic);
|
|
454
|
+
}, [dynamic]);
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
hostRef.current?.view.reconfigure({
|
|
457
|
+
revertControls: activeRevertControls ? "a-to-b" : void 0,
|
|
458
|
+
collapseUnchanged: collapseUnchanged ? {} : void 0
|
|
459
|
+
});
|
|
460
|
+
}, [activeRevertControls, collapseUnchanged]);
|
|
461
|
+
return /* @__PURE__ */ jsx("div", {
|
|
462
|
+
ref: containerRef,
|
|
463
|
+
className,
|
|
464
|
+
"data-inkstone-diff": "",
|
|
465
|
+
style: {
|
|
466
|
+
height: toCssSize(height),
|
|
467
|
+
width: toCssSize(width),
|
|
468
|
+
color: (dark ? flavors.macchiato : flavors.latte).colors.overlay1.hex
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/codemirror-editor.tsx
|
|
474
|
+
function CodeMirrorEditor({ value, onChange, language, context, sqlSchema, placeholder, lineWrapping = false, showLineNumbers = false, folding = false, spellcheck = false, search = false, extensions, dynamicExtensions, dark = false, locale = "en", height, width, fontSize = 14, lineHeight = 1.6, tabSize, className, onMount, onFocus, onBlur, ariaLabel = "Editor content" }) {
|
|
475
|
+
const containerRef = useRef(null);
|
|
476
|
+
const hostRef = useRef(null);
|
|
477
|
+
const valueTransitionRef = useRef(false);
|
|
478
|
+
const [reconcileRevision, requestControlledReconcile] = useControlledReconcileSignal();
|
|
479
|
+
const onChangeRef = useLatest(onChange);
|
|
480
|
+
const onMountRef = useLatest(onMount);
|
|
481
|
+
const onFocusRef = useLatest(onFocus);
|
|
482
|
+
const onBlurRef = useLatest(onBlur);
|
|
483
|
+
const lastContextKeyRef = useRef(null);
|
|
484
|
+
const lastSqlSchemaKeyRef = useRef(null);
|
|
485
|
+
const [loadedLanguage, setLoadedLanguage] = useState(null);
|
|
486
|
+
const languageExtension = useMemo(() => loadedLanguage && loadedLanguage.id === language ? [loadedLanguage.extension] : [], [language, loadedLanguage]);
|
|
487
|
+
useEffect(() => {
|
|
488
|
+
if (!language) return;
|
|
489
|
+
let cancelled = false;
|
|
490
|
+
loadLanguage(language).then((extension) => {
|
|
491
|
+
if (!cancelled) setLoadedLanguage({
|
|
492
|
+
id: language,
|
|
493
|
+
extension
|
|
494
|
+
});
|
|
495
|
+
}).catch((error) => {
|
|
496
|
+
console.error("inkstone: failed to load CodeMirror language", language, error);
|
|
497
|
+
if (!cancelled) setLoadedLanguage(null);
|
|
498
|
+
});
|
|
499
|
+
return () => {
|
|
500
|
+
cancelled = true;
|
|
501
|
+
};
|
|
502
|
+
}, [language]);
|
|
503
|
+
const dynamic = useMemo(() => [
|
|
504
|
+
...defaultThemeExtensions(dark, height, width, fontSize, lineHeight),
|
|
505
|
+
...tabSize === void 0 ? [] : [EditorState.tabSize.of(tabSize), indentUnit.of(" ".repeat(tabSize))],
|
|
506
|
+
...locale === "zh-cn" ? [searchPhrasesZhCn] : [],
|
|
507
|
+
...languageExtension,
|
|
508
|
+
EditorView.contentAttributes.of({ "aria-label": ariaLabel }),
|
|
509
|
+
...dynamicExtensions ?? []
|
|
510
|
+
], [
|
|
511
|
+
dark,
|
|
512
|
+
height,
|
|
513
|
+
width,
|
|
514
|
+
fontSize,
|
|
515
|
+
lineHeight,
|
|
516
|
+
tabSize,
|
|
517
|
+
locale,
|
|
518
|
+
languageExtension,
|
|
519
|
+
ariaLabel,
|
|
520
|
+
dynamicExtensions
|
|
521
|
+
]);
|
|
522
|
+
const contextSchema = useMemo(() => context ? normalizeContext(context) : null, [context]);
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const container = containerRef.current;
|
|
525
|
+
if (!container) return;
|
|
526
|
+
const host = new ControlledEditorHost({
|
|
527
|
+
parent: container,
|
|
528
|
+
doc: value,
|
|
529
|
+
onChange: (next) => {
|
|
530
|
+
if (valueTransitionRef.current) return;
|
|
531
|
+
onChangeRef.current?.(next);
|
|
532
|
+
requestControlledReconcile();
|
|
533
|
+
},
|
|
534
|
+
extensions: [
|
|
535
|
+
...documentExtensions({
|
|
536
|
+
placeholder,
|
|
537
|
+
lineWrapping,
|
|
538
|
+
showLineNumbers,
|
|
539
|
+
folding,
|
|
540
|
+
spellcheck,
|
|
541
|
+
search
|
|
542
|
+
}),
|
|
543
|
+
EditorView.updateListener.of((update) => {
|
|
544
|
+
if (update.focusChanged) if (update.view.hasFocus) onFocusRef.current?.();
|
|
545
|
+
else onBlurRef.current?.();
|
|
546
|
+
}),
|
|
547
|
+
...extensions ?? []
|
|
548
|
+
],
|
|
549
|
+
dynamicExtensions: dynamic
|
|
550
|
+
});
|
|
551
|
+
hostRef.current = host;
|
|
552
|
+
onMountRef.current?.(host.view);
|
|
553
|
+
return () => {
|
|
554
|
+
host.destroy();
|
|
555
|
+
hostRef.current = null;
|
|
556
|
+
};
|
|
557
|
+
}, []);
|
|
558
|
+
useInsertionEffect(() => {
|
|
559
|
+
const host = hostRef.current;
|
|
560
|
+
valueTransitionRef.current = host !== null && host.view.state.doc.toString() !== value;
|
|
561
|
+
}, [reconcileRevision, value]);
|
|
562
|
+
useLayoutEffect(() => {
|
|
563
|
+
try {
|
|
564
|
+
hostRef.current?.setValue(value);
|
|
565
|
+
} finally {
|
|
566
|
+
valueTransitionRef.current = false;
|
|
567
|
+
}
|
|
568
|
+
}, [reconcileRevision, value]);
|
|
569
|
+
useEffect(() => {
|
|
570
|
+
hostRef.current?.reconfigure(dynamic);
|
|
571
|
+
}, [dynamic]);
|
|
572
|
+
useEffect(() => {
|
|
573
|
+
const host = hostRef.current;
|
|
574
|
+
if (!host) return;
|
|
575
|
+
const key = contextSchema ? JSON.stringify(contextSchema) : "";
|
|
576
|
+
const last = lastContextKeyRef.current;
|
|
577
|
+
if (last?.host === host && last.key === key) return;
|
|
578
|
+
lastContextKeyRef.current = {
|
|
579
|
+
host,
|
|
580
|
+
key
|
|
581
|
+
};
|
|
582
|
+
if (key === "" && last?.host !== host) return;
|
|
583
|
+
host.view.dispatch({ effects: setMinijinjaContext.of(contextSchema) });
|
|
584
|
+
}, [contextSchema]);
|
|
585
|
+
useEffect(() => {
|
|
586
|
+
const host = hostRef.current;
|
|
587
|
+
if (!host) return;
|
|
588
|
+
const key = sqlSchema ? JSON.stringify(sqlSchema) : "";
|
|
589
|
+
const last = lastSqlSchemaKeyRef.current;
|
|
590
|
+
if (last?.host === host && last.key === key) return;
|
|
591
|
+
lastSqlSchemaKeyRef.current = {
|
|
592
|
+
host,
|
|
593
|
+
key
|
|
594
|
+
};
|
|
595
|
+
if (key === "" && last?.host !== host) return;
|
|
596
|
+
host.view.dispatch({ effects: setSqlSchema.of(sqlSchema ?? null) });
|
|
597
|
+
}, [sqlSchema]);
|
|
598
|
+
return /* @__PURE__ */ jsx("div", {
|
|
599
|
+
ref: containerRef,
|
|
600
|
+
className
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
//#endregion
|
|
604
|
+
export { CodeMirrorDiffEditor as a, CodeMirrorEditor as i, normalizeContext$1 as n, searchPhrasesZhCn$1 as r, codeMirrorLanguages as t };
|