@orz/ui-codemirror 0.0.0-stage → 0.1.0
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/LICENSE +29 -0
- package/README.md +251 -2
- package/dist/components/code-editor.d.ts +94 -0
- package/dist/components/code-editor.d.ts.map +1 -0
- package/dist/components/code-editor.js +243 -0
- package/dist/components/code-editor.js.map +1 -0
- package/dist/components/editor-shortcuts-help.d.ts +7 -0
- package/dist/components/editor-shortcuts-help.d.ts.map +1 -0
- package/dist/components/editor-shortcuts-help.js +29 -0
- package/dist/components/editor-shortcuts-help.js.map +1 -0
- package/dist/components/markdown-editor.d.ts +83 -0
- package/dist/components/markdown-editor.d.ts.map +1 -0
- package/dist/components/markdown-editor.js +149 -0
- package/dist/components/markdown-editor.js.map +1 -0
- package/dist/extensions/code-context.d.ts +15 -0
- package/dist/extensions/code-context.d.ts.map +1 -0
- package/dist/extensions/code-context.js +41 -0
- package/dist/extensions/code-context.js.map +1 -0
- package/dist/extensions/completion-space.d.ts +7 -0
- package/dist/extensions/completion-space.d.ts.map +1 -0
- package/dist/extensions/completion-space.js +76 -0
- package/dist/extensions/completion-space.js.map +1 -0
- package/dist/extensions/completion.d.ts +29 -0
- package/dist/extensions/completion.d.ts.map +1 -0
- package/dist/extensions/completion.js +85 -0
- package/dist/extensions/completion.js.map +1 -0
- package/dist/extensions/composition-safe-measure.d.ts +21 -0
- package/dist/extensions/composition-safe-measure.d.ts.map +1 -0
- package/dist/extensions/composition-safe-measure.js +56 -0
- package/dist/extensions/composition-safe-measure.js.map +1 -0
- package/dist/extensions/json.d.ts +9 -0
- package/dist/extensions/json.d.ts.map +1 -0
- package/dist/extensions/json.js +21 -0
- package/dist/extensions/json.js.map +1 -0
- package/dist/extensions/parked-tooltip.d.ts +19 -0
- package/dist/extensions/parked-tooltip.d.ts.map +1 -0
- package/dist/extensions/parked-tooltip.js +32 -0
- package/dist/extensions/parked-tooltip.js.map +1 -0
- package/dist/extensions/paste-as.d.ts +21 -0
- package/dist/extensions/paste-as.d.ts.map +1 -0
- package/dist/extensions/paste-as.js +448 -0
- package/dist/extensions/paste-as.js.map +1 -0
- package/dist/extensions/quote-toggle.d.ts +16 -0
- package/dist/extensions/quote-toggle.d.ts.map +1 -0
- package/dist/extensions/quote-toggle.js +65 -0
- package/dist/extensions/quote-toggle.js.map +1 -0
- package/dist/extensions/slash-commands.d.ts +7 -0
- package/dist/extensions/slash-commands.d.ts.map +1 -0
- package/dist/extensions/slash-commands.js +249 -0
- package/dist/extensions/slash-commands.js.map +1 -0
- package/dist/extensions/tag-completion.d.ts +24 -0
- package/dist/extensions/tag-completion.d.ts.map +1 -0
- package/dist/extensions/tag-completion.js +81 -0
- package/dist/extensions/tag-completion.js.map +1 -0
- package/dist/extensions/tooltip-space.d.ts +11 -0
- package/dist/extensions/tooltip-space.d.ts.map +1 -0
- package/dist/extensions/tooltip-space.js +41 -0
- package/dist/extensions/tooltip-space.js.map +1 -0
- package/dist/extensions/trim-trailing-space.d.ts +15 -0
- package/dist/extensions/trim-trailing-space.d.ts.map +1 -0
- package/dist/extensions/trim-trailing-space.js +56 -0
- package/dist/extensions/trim-trailing-space.js.map +1 -0
- package/dist/lib/html-to-markdown.d.ts +22 -0
- package/dist/lib/html-to-markdown.d.ts.map +1 -0
- package/dist/lib/html-to-markdown.js +327 -0
- package/dist/lib/html-to-markdown.js.map +1 -0
- package/dist/lib/paste-converter.d.ts +14 -0
- package/dist/lib/paste-converter.d.ts.map +1 -0
- package/dist/lib/paste-converter.js +27 -0
- package/dist/lib/paste-converter.js.map +1 -0
- package/dist/lib/platform.d.ts +13 -0
- package/dist/lib/platform.d.ts.map +1 -0
- package/dist/lib/platform.js +19 -0
- package/dist/lib/platform.js.map +1 -0
- package/package.json +83 -3
- package/styles.css +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 the orzui team
|
|
4
|
+
Copyright 2026 the todou team
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @orz/ui-codemirror
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CodeMirror 6 editors themed by `@orz/ui`: a prose-first `CodeEditor` shell, a GFM `MarkdownEditor`,
|
|
4
|
+
the completion extensions they are assembled from, and the slash-command panel and highlight over
|
|
5
|
+
`@orz/query`'s command grammar.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @orz/ui-codemirror @orz/ui react react-dom lucide-react sonner \
|
|
11
|
+
@codemirror/state @codemirror/view @codemirror/language @codemirror/autocomplete \
|
|
12
|
+
@codemirror/commands @codemirror/search @lezer/highlight
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
These are peer dependencies, so the app and the editors share one copy of each:
|
|
16
|
+
|
|
17
|
+
- `@orz/ui`: every editor registers itself with `useDirtySource` from `@orz/ui/lib/unsaved-guard`,
|
|
18
|
+
a module-level registry. A second copy of `@orz/ui` would hold a second registry, and the app's
|
|
19
|
+
navigation guard would never see an edited editor.
|
|
20
|
+
- CodeMirror and `@lezer/highlight`: you pass your own extensions into these editors, and
|
|
21
|
+
CodeMirror checks extensions, facets and state fields by identity. A second `@codemirror/state`
|
|
22
|
+
raises "Unrecognized extension value", a second `@codemirror/language` gives your `syntaxTree()`
|
|
23
|
+
a different language facet, and a second `@lezer/highlight` gives your `tags` a different
|
|
24
|
+
identity, so your `HighlightStyle` would silently colour nothing.
|
|
25
|
+
|
|
26
|
+
The markdown and JSON parsers (`@lezer/markdown`, `@lezer/json`) are ordinary dependencies. The
|
|
27
|
+
package does not use `@codemirror/lang-markdown`, which pulls in the HTML, JS and CSS parsers for
|
|
28
|
+
about 170 KB gzip. Neither is the HTML-to-Markdown converter a paste uses (`hast-util-from-dom`,
|
|
29
|
+
`hast-util-to-mdast`, `mdast-util-to-markdown`, `mdast-util-gfm`): it is a dynamic import, so a
|
|
30
|
+
bundler splits it into a chunk of its own, fetched once an editor is idle. `sonner` carries the
|
|
31
|
+
one message a paste can raise, through `@orz/ui`'s `Toaster`.
|
|
32
|
+
|
|
33
|
+
## CSS
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
@import "tailwindcss";
|
|
37
|
+
@import "@orz/ui/styles.css";
|
|
38
|
+
@import "@orz/ui-codemirror/styles.css";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Import it after `@orz/ui/styles.css`. The editor's host element is styled with the same Tailwind
|
|
42
|
+
utilities as `@orz/ui`'s `Textarea`, and the stylesheet registers `@source "./dist"` so Tailwind
|
|
43
|
+
finds them. Everything inside the editor is styled with `EditorView.theme` against `@orz/ui`'s CSS
|
|
44
|
+
variables. If you do not import the stylesheet, point Tailwind at the package yourself:
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
@source "../node_modules/@orz/ui-codemirror/dist";
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### A markdown editor
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import {
|
|
56
|
+
MarkdownEditor,
|
|
57
|
+
type MarkdownEditorHandle,
|
|
58
|
+
} from "@orz/ui-codemirror/components/markdown-editor";
|
|
59
|
+
|
|
60
|
+
const editor = useRef<MarkdownEditorHandle>(null);
|
|
61
|
+
|
|
62
|
+
<MarkdownEditor
|
|
63
|
+
ref={editor}
|
|
64
|
+
ariaLabel="Comment"
|
|
65
|
+
placeholder="Write a comment…"
|
|
66
|
+
onSubmit={(value) => send(value)}
|
|
67
|
+
onCancel={() => close()}
|
|
68
|
+
className="min-h-24 max-h-96"
|
|
69
|
+
/>;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The document lives in the EditorView, not in React state: read it through the handle or from
|
|
73
|
+
`onSubmit`, and keep `onChange` for cheap derived flags. Mod-Enter submits, Escape cancels, and
|
|
74
|
+
Alt-Enter inserts a blank line.
|
|
75
|
+
|
|
76
|
+
### Paste and quote
|
|
77
|
+
|
|
78
|
+
`MarkdownEditor` turns a formatted paste into Markdown, GitHub style: links, emphasis, lists,
|
|
79
|
+
tables and code survive, and a paste that is one line keeps only its inline formatting, so a copied
|
|
80
|
+
heading dropped into a sentence stays in it. Code copied out of an editor (VS Code's clipboard
|
|
81
|
+
type, or HTML that is all preformatted) goes in as plain text, with a fenced block on offer. After
|
|
82
|
+
a multi-line or formatted paste a toolbar under it switches the pasted range between Plain text
|
|
83
|
+
and Markdown and toggles Quote, one undo step per switch.
|
|
84
|
+
|
|
85
|
+
| Key | Does |
|
|
86
|
+
|---|---|
|
|
87
|
+
| Mod-V | Paste; formatting becomes Markdown |
|
|
88
|
+
| Mod-Shift-V | Paste as plain text |
|
|
89
|
+
| Mod-Alt-V | Paste as a quote |
|
|
90
|
+
| Mod-Alt-Shift-V | Paste plain text as a quote |
|
|
91
|
+
| Mod-. | Into the paste toolbar, right after a paste |
|
|
92
|
+
| Mod-Shift-. | Quote or unquote the selected lines, under every cursor |
|
|
93
|
+
|
|
94
|
+
No browser raises a paste event for Mod-Alt-V, so those keys read the clipboard themselves, and
|
|
95
|
+
say so in a toast when the browser refuses. A paste that beats the converter's chunk goes in as
|
|
96
|
+
plain text. `onPaste` sees every paste first; one it takes as files (`preventDefault`) is left
|
|
97
|
+
alone.
|
|
98
|
+
|
|
99
|
+
`EditorShortcutsHelp` is a keyboard button listing these keys, for a toolbar beside the editor,
|
|
100
|
+
with Apple's ⌘ ⌥ ⇧ on Apple platforms. Its list portals into the button's own subtree, so a box
|
|
101
|
+
that folds when focus leaves it stays open while the list is.
|
|
102
|
+
|
|
103
|
+
For an editor of your own, `pasteAs()` from `extensions/paste-as` is the toolbar and keys;
|
|
104
|
+
call its `handlePaste(view, event)` from your paste handler, and `prefetchPasteConverter()` once
|
|
105
|
+
the editor is up. `toggleQuote` and `blockquote` are in `extensions/quote-toggle`.
|
|
106
|
+
|
|
107
|
+
### Completion
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import type { CompletionSource } from "@codemirror/autocomplete";
|
|
111
|
+
import { EditorView } from "@codemirror/view";
|
|
112
|
+
import { completionWith } from "@orz/ui-codemirror/extensions/completion";
|
|
113
|
+
import { applyWithSpace } from "@orz/ui-codemirror/extensions/completion-space";
|
|
114
|
+
|
|
115
|
+
const mentions: CompletionSource = (context) => {
|
|
116
|
+
const match = context.matchBefore(/@\w*$/);
|
|
117
|
+
if (match === null) return null;
|
|
118
|
+
return {
|
|
119
|
+
from: match.from,
|
|
120
|
+
options: [{ label: "@alice", type: "mention", apply: applyWithSpace("@alice") }],
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Icons for your own completion types.
|
|
125
|
+
const icons = EditorView.theme({
|
|
126
|
+
".cm-completionIcon-mention::after": { content: "'@'", color: "var(--primary)" },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Build this once (module scope or useMemo): a new identity reconfigures the editor and closes
|
|
130
|
+
// the open panel.
|
|
131
|
+
const extensions = [completionWith([mentions]), icons];
|
|
132
|
+
|
|
133
|
+
<MarkdownEditor extensions={extensions} />;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Pass every source through one `completionWith` call. It installs the only `autocompletion()` for
|
|
137
|
+
the editor, binds Tab only while a panel is open, adds the `<details>` tag template, and keeps the
|
|
138
|
+
space after an accepted row out of the way of punctuation typed next.
|
|
139
|
+
|
|
140
|
+
### Slash commands
|
|
141
|
+
|
|
142
|
+
The command grammar lives in `@orz/query`, a dependency of this package; these extensions are the
|
|
143
|
+
panel and the highlight on top of it.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import {
|
|
147
|
+
createCommandRegistry,
|
|
148
|
+
parseCommandLines,
|
|
149
|
+
type ValueProviders,
|
|
150
|
+
} from "@orz/query";
|
|
151
|
+
import {
|
|
152
|
+
commandCompletionSource,
|
|
153
|
+
commandDecoration,
|
|
154
|
+
} from "@orz/ui-codemirror/extensions/slash-commands";
|
|
155
|
+
|
|
156
|
+
type Action =
|
|
157
|
+
| { type: "close" }
|
|
158
|
+
| { type: "label"; name: string }
|
|
159
|
+
| { type: "move"; status: string; assignee?: string };
|
|
160
|
+
|
|
161
|
+
const registry = createCommandRegistry<Action>([
|
|
162
|
+
{
|
|
163
|
+
name: "close",
|
|
164
|
+
detail: "→ Done",
|
|
165
|
+
args: [],
|
|
166
|
+
summarize: () => "close",
|
|
167
|
+
compile: () => ({ type: "close" }),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "label",
|
|
171
|
+
detail: "add a label",
|
|
172
|
+
args: [{ name: "label", value: { kind: "named", source: "label" }, rest: true }],
|
|
173
|
+
summarize: ([name]) => `label ${name}`,
|
|
174
|
+
compile: ([name]) => ({ type: "label", name: String(name) }),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "move",
|
|
178
|
+
detail: "change status",
|
|
179
|
+
args: [
|
|
180
|
+
{ name: "status", value: { kind: "named", source: "status" } },
|
|
181
|
+
{ name: "assignee", value: { kind: "named", source: "member" }, optional: true },
|
|
182
|
+
],
|
|
183
|
+
summarize: ([status]) => `move to ${status}`,
|
|
184
|
+
compile: ([status, assignee]) => ({
|
|
185
|
+
type: "move",
|
|
186
|
+
status: String(status),
|
|
187
|
+
...(assignee === undefined ? {} : { assignee: String(assignee) }),
|
|
188
|
+
}),
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
// Snapshot lists, or `{ search: (prefix, signal) => … }` for a list too long to load.
|
|
193
|
+
let providers: ValueProviders = {
|
|
194
|
+
label: { options: [{ value: "bug" }, { value: "docs" }] },
|
|
195
|
+
status: { options: [{ value: "Todo" }, { value: "In Progress" }] },
|
|
196
|
+
member: { options: undefined }, // still loading
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Getters, so a registry or providers built from data that arrives later need no reconfigure.
|
|
200
|
+
const getRegistry = () => registry;
|
|
201
|
+
const getProviders = () => providers;
|
|
202
|
+
const extensions = [
|
|
203
|
+
completionWith([commandCompletionSource(getRegistry, getProviders)]),
|
|
204
|
+
commandDecoration(getRegistry, getProviders),
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
<MarkdownEditor
|
|
208
|
+
extensions={extensions}
|
|
209
|
+
onSubmit={(value) => {
|
|
210
|
+
const { body, commands, invalid } = parseCommandLines(value, registry, providers);
|
|
211
|
+
// body: the text with every command line removed; commands: Action[].
|
|
212
|
+
}}
|
|
213
|
+
/>;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
A command is a whole line of the document. Lines inside fenced code are never commands, an unknown
|
|
217
|
+
`/word` stays prose, and a recognized line whose argument names nothing lands in `invalid` with a
|
|
218
|
+
reason. Arguments are shell words (`/move 'In Progress' alice`); see `@orz/query` for rest and
|
|
219
|
+
`multiple` arguments.
|
|
220
|
+
|
|
221
|
+
The panel offers the current argument's values, inserts a space and reopens for the next argument,
|
|
222
|
+
and leads with an `↵ end of command` row while every remaining argument is optional. A `search`
|
|
223
|
+
provider is asked per keystroke and the request is aborted when the next key arrives. The highlight
|
|
224
|
+
tints recognized lines, marks the name (`cm-command-name`) and each argument (`cm-command-arg`,
|
|
225
|
+
plus `-special` or `-invalid`), and redraws when the getters return a new registry or providers
|
|
226
|
+
object, so replace `providers` rather than mutating it.
|
|
227
|
+
|
|
228
|
+
### Provenance
|
|
229
|
+
|
|
230
|
+
`CodeEditorHandle.userChanged()` reports whether the reader changed the document since mount.
|
|
231
|
+
Mark your own writes so they do not count. `view` here is the `EditorView` that `CodeEditor`'s
|
|
232
|
+
`onView` hands out, or the one a command or `ViewPlugin` of yours receives:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { PROGRAMMATIC_USER_EVENT } from "@orz/ui-codemirror/components/code-editor";
|
|
236
|
+
|
|
237
|
+
view.dispatch({ changes, userEvent: PROGRAMMATIC_USER_EVENT });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The handle's `setValue` and `append` are not marked, so they count as the reader's changes.
|
|
241
|
+
|
|
242
|
+
## Entry points
|
|
243
|
+
|
|
244
|
+
| Import | Contents |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `@orz/ui-codemirror/components/code-editor` | `CodeEditor`, `CodeEditorHandle`, `CodeEditorProps`, `PROGRAMMATIC_USER_EVENT` |
|
|
247
|
+
| `@orz/ui-codemirror/components/editor-shortcuts-help` | `EditorShortcutsHelp` |
|
|
248
|
+
| `@orz/ui-codemirror/components/markdown-editor` | `MarkdownEditor`, `MarkdownEditorHandle`, `MarkdownEditorProps`, `FileClipboardEvent`, `FileDragEvent`, `markdownLanguage`, `markdownHighlightStyle` |
|
|
249
|
+
| `@orz/ui-codemirror/extensions/<name>` | `code-context`, `completion`, `completion-space`, `json`, `paste-as`, `quote-toggle`, `slash-commands`, `tag-completion`, `trim-trailing-space` |
|
|
250
|
+
| `@orz/ui-codemirror/styles.css` | `@source "./dist"` |
|
|
251
|
+
|
|
252
|
+
There is no root entry, so a markdown-only app never reaches `@lezer/json` through an index file.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type HighlightStyle } from "@codemirror/language";
|
|
2
|
+
import { type Extension } from "@codemirror/state";
|
|
3
|
+
import type { KeyBinding } from "@codemirror/view";
|
|
4
|
+
import { EditorView } from "@codemirror/view";
|
|
5
|
+
/**
|
|
6
|
+
* The user event that marks a write as the program's rather than the
|
|
7
|
+
* reader's. Tag a dispatch with
|
|
8
|
+
* `Transaction.userEvent.of(PROGRAMMATIC_USER_EVENT)` (or
|
|
9
|
+
* `userEvent: PROGRAMMATIC_USER_EVENT`) and it leaves `userChanged()` alone.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PROGRAMMATIC_USER_EVENT = "orz.programmatic";
|
|
12
|
+
export type CodeEditorHandle = {
|
|
13
|
+
getValue: () => string;
|
|
14
|
+
/**
|
|
15
|
+
* CAUTION: this dispatch is NOT annotated with `PROGRAMMATIC_USER_EVENT`,
|
|
16
|
+
* so it counts as a user change. An owner that judges provenance through
|
|
17
|
+
* `userChanged()` would read a `setValue` as the reader's edit; annotate
|
|
18
|
+
* the write before using it under such an owner.
|
|
19
|
+
*/
|
|
20
|
+
setValue: (value: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Add to the end of the document, caret after it and scrolled into view.
|
|
23
|
+
*
|
|
24
|
+
* Unmarked, like `setValue`, but here on purpose: the text arrives because
|
|
25
|
+
* the reader asked for it, so `userChanged()` reading true afterwards is
|
|
26
|
+
* the right answer.
|
|
27
|
+
*/
|
|
28
|
+
append: (text: string) => void;
|
|
29
|
+
focus: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* True when the document changed through a CM user event (typing, paste,
|
|
32
|
+
* drop) since mount — provenance, not byte-equality: writes tagged
|
|
33
|
+
* `PROGRAMMATIC_USER_EVENT` do not count.
|
|
34
|
+
*/
|
|
35
|
+
userChanged: () => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Close an open completion panel, reporting whether there was one. An
|
|
38
|
+
* editor inside a dismissable layer needs this because that layer listens
|
|
39
|
+
* for Escape on the document in the capture phase and calls
|
|
40
|
+
* `preventDefault` — after which CodeMirror's own handlers decline the
|
|
41
|
+
* event, so the layer's owner has to do the closing.
|
|
42
|
+
*/
|
|
43
|
+
dismissCompletion: () => boolean;
|
|
44
|
+
};
|
|
45
|
+
export type CodeEditorProps = {
|
|
46
|
+
/** Read once, at mount: the editor owns its document from then on. */
|
|
47
|
+
initialValue?: string;
|
|
48
|
+
placeholder?: string;
|
|
49
|
+
ariaLabel?: string;
|
|
50
|
+
autoFocus?: boolean;
|
|
51
|
+
readOnly?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The caller owns both the live value and its dirty baseline. This editor
|
|
54
|
+
* then acts only as a view and does not register a duplicate dirty source.
|
|
55
|
+
*/
|
|
56
|
+
ownerManagedDirty?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Caller's own key bindings, placed ahead of the base keymap. The order is
|
|
59
|
+
* load-bearing: defaultKeymap binds Mod-Enter to insertBlankLine, so a
|
|
60
|
+
* submit binding listed after it never fires.
|
|
61
|
+
*/
|
|
62
|
+
keymap?: KeyBinding[];
|
|
63
|
+
/** Language support, e.g. a markdown Language or a StreamLanguage. */
|
|
64
|
+
language?: Extension;
|
|
65
|
+
/** Token-level colouring to pair with `language`. */
|
|
66
|
+
highlightStyle?: HighlightStyle;
|
|
67
|
+
/**
|
|
68
|
+
* Fires on every document change. Only for cheap derived state (an empty
|
|
69
|
+
* draft disabling its submit button) — the document itself lives in the
|
|
70
|
+
* editor, not in React.
|
|
71
|
+
*/
|
|
72
|
+
onChange?: (value: string) => void;
|
|
73
|
+
/** Extra CodeMirror extensions, appended last so they can override. */
|
|
74
|
+
extensions?: Extension;
|
|
75
|
+
/**
|
|
76
|
+
* data-slot of the host element. Tests reach a specific editor kind
|
|
77
|
+
* through it; the default names the generic shell.
|
|
78
|
+
*/
|
|
79
|
+
slot?: string;
|
|
80
|
+
/** Height is the caller's business: pass the min-height/max-height here. */
|
|
81
|
+
className?: string;
|
|
82
|
+
/** The underlying EditorView, once mounted — for callers that drive the
|
|
83
|
+
* cursor or the document programmatically. */
|
|
84
|
+
onView?: (view: EditorView | null) => void;
|
|
85
|
+
ref?: React.Ref<CodeEditorHandle>;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* The common CodeMirror shell for every editor built on it: mounting, the
|
|
89
|
+
* placeholder/readOnly/extensions compartments, the theme, dirty tracking,
|
|
90
|
+
* and the base key bindings. Callers bring their own language and, ahead of
|
|
91
|
+
* the base keymap, their own semantics — submit keys, line editing, snippets.
|
|
92
|
+
*/
|
|
93
|
+
export declare function CodeEditor({ initialValue, placeholder, ariaLabel, autoFocus, readOnly, ownerManagedDirty, keymap: callerKeymap, language, highlightStyle, onChange, extensions, slot, onView, className, ref, }: CodeEditorProps): import("react").JSX.Element;
|
|
94
|
+
//# sourceMappingURL=code-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-editor.d.ts","sourceRoot":"","sources":["../../src/components/code-editor.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAEL,UAAU,EAGX,MAAM,kBAAkB,CAAC;AAQ1B;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,qDAAqD;IACrD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,uEAAuE;IACvE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;kDAC8C;IAC9C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;CACnC,CAAC;AAiEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAiB,EACjB,WAAgB,EAChB,SAAS,EACT,SAAiB,EACjB,QAAgB,EAChB,iBAAyB,EACzB,MAAM,EAAE,YAAY,EACpB,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,MAAM,EACN,SAAS,EACT,GAAG,GACJ,EAAE,eAAe,+BAgLjB"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { closeCompletion, completionStatus } from "@codemirror/autocomplete";
|
|
3
|
+
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
4
|
+
import { syntaxHighlighting } from "@codemirror/language";
|
|
5
|
+
import { Compartment, EditorState } from "@codemirror/state";
|
|
6
|
+
import { drawSelection, EditorView, keymap, placeholder as placeholderExt, } from "@codemirror/view";
|
|
7
|
+
import { cn } from "@orz/ui";
|
|
8
|
+
import { useDirtySource } from "@orz/ui/lib/unsaved-guard";
|
|
9
|
+
import { useEffect, useImperativeHandle, useRef } from "react";
|
|
10
|
+
import { compositionSafeMeasure } from "../extensions/composition-safe-measure.js";
|
|
11
|
+
import { parkedTooltip } from "../extensions/parked-tooltip.js";
|
|
12
|
+
import { tooltipSpace } from "../extensions/tooltip-space.js";
|
|
13
|
+
/**
|
|
14
|
+
* The user event that marks a write as the program's rather than the
|
|
15
|
+
* reader's. Tag a dispatch with
|
|
16
|
+
* `Transaction.userEvent.of(PROGRAMMATIC_USER_EVENT)` (or
|
|
17
|
+
* `userEvent: PROGRAMMATIC_USER_EVENT`) and it leaves `userChanged()` alone.
|
|
18
|
+
*/
|
|
19
|
+
export const PROGRAMMATIC_USER_EVENT = "orz.programmatic";
|
|
20
|
+
/** Mirrors `@orz/ui`'s `Textarea` look, with every colour coming from a variable. */
|
|
21
|
+
const editorTheme = EditorView.theme({
|
|
22
|
+
"&": {
|
|
23
|
+
color: "var(--foreground)",
|
|
24
|
+
backgroundColor: "transparent",
|
|
25
|
+
fontFamily: "var(--font-sans)",
|
|
26
|
+
// style-mod cannot express a media query against the generated theme
|
|
27
|
+
// class, so the breakpoint lives in the wrapper's Tailwind classes and
|
|
28
|
+
// the editor simply inherits it.
|
|
29
|
+
fontSize: "inherit",
|
|
30
|
+
// Lets the wrapper's max-height clamp the editor so .cm-scroller,
|
|
31
|
+
// not the page, does the scrolling.
|
|
32
|
+
flex: "1 1 auto",
|
|
33
|
+
minHeight: "0",
|
|
34
|
+
borderRadius: "inherit",
|
|
35
|
+
},
|
|
36
|
+
"&.cm-focused": { outline: "none" },
|
|
37
|
+
/**
|
|
38
|
+
* alignSelf and minHeight here, with flexGrow on .cm-scroller below, are what
|
|
39
|
+
* makes the blank area under a short document part of the editor rather than
|
|
40
|
+
* a dead shell. The base theme already means to do that, with
|
|
41
|
+
* `.cm-scroller { height: 100% }` and `.cm-content { min-height: 100% }` —
|
|
42
|
+
* but callers give this component a min-height, never a height, so the
|
|
43
|
+
* containing block is never definite, both percentages silently resolve to
|
|
44
|
+
* `auto`, and the contenteditable stays as short as its text. None of the
|
|
45
|
+
* three below depends on percentage resolution: the scroller takes the
|
|
46
|
+
* leftover height through flex, align-self overrides the base theme's
|
|
47
|
+
* `align-items: flex-start !important` so the content stretches down the
|
|
48
|
+
* cross axis, and min-content floors that stretch — a stretched box is
|
|
49
|
+
* clamped to the flex line, which costs a document taller than the caller's
|
|
50
|
+
* max-height its bottom padding, with no way to scroll to it.
|
|
51
|
+
*/
|
|
52
|
+
".cm-content": {
|
|
53
|
+
padding: "0.5rem 0.625rem",
|
|
54
|
+
lineHeight: "1.5",
|
|
55
|
+
caretColor: "var(--foreground)",
|
|
56
|
+
alignSelf: "stretch",
|
|
57
|
+
minHeight: "min-content",
|
|
58
|
+
},
|
|
59
|
+
// CodeMirror's own base theme puts monospace here; this shell is for prose
|
|
60
|
+
// first, so it inherits the host's font instead.
|
|
61
|
+
".cm-scroller": {
|
|
62
|
+
overflow: "auto",
|
|
63
|
+
fontFamily: "inherit",
|
|
64
|
+
fontSize: "inherit",
|
|
65
|
+
lineHeight: "inherit",
|
|
66
|
+
// Not `flex: 1`, which would zero the basis; the other two components of
|
|
67
|
+
// the shorthand are already at their defaults.
|
|
68
|
+
flexGrow: "1",
|
|
69
|
+
// The wrapper cannot clip to its rounded corners — that would clip an
|
|
70
|
+
// absolute completion popup too — so the scroller clips itself.
|
|
71
|
+
borderRadius: "inherit",
|
|
72
|
+
},
|
|
73
|
+
".cm-line": { padding: "0" },
|
|
74
|
+
".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--foreground)" },
|
|
75
|
+
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": {
|
|
76
|
+
backgroundColor: "color-mix(in oklab, var(--primary) 22%, transparent)",
|
|
77
|
+
},
|
|
78
|
+
".cm-placeholder": { color: "var(--muted-foreground)" },
|
|
79
|
+
".cm-gutters": { display: "none" },
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* The common CodeMirror shell for every editor built on it: mounting, the
|
|
83
|
+
* placeholder/readOnly/extensions compartments, the theme, dirty tracking,
|
|
84
|
+
* and the base key bindings. Callers bring their own language and, ahead of
|
|
85
|
+
* the base keymap, their own semantics — submit keys, line editing, snippets.
|
|
86
|
+
*/
|
|
87
|
+
export function CodeEditor({ initialValue = "", placeholder = "", ariaLabel, autoFocus = false, readOnly = false, ownerManagedDirty = false, keymap: callerKeymap, language, highlightStyle, onChange, extensions, slot, onView, className, ref, }) {
|
|
88
|
+
const host = useRef(null);
|
|
89
|
+
const view = useRef(null);
|
|
90
|
+
// Compared against, never written: a ref outlives the mount effect below,
|
|
91
|
+
// so the box stays "edited from what it was opened with" even after
|
|
92
|
+
// Suspense rebuilds the document — which is also why the baseline cannot
|
|
93
|
+
// come from the EditorView, whose doc may already be the rebuilt one.
|
|
94
|
+
const baseline = useRef(initialValue);
|
|
95
|
+
const latestInitialValue = useRef(initialValue);
|
|
96
|
+
latestInitialValue.current = initialValue;
|
|
97
|
+
// Keymap and DOM handlers are built once but must always call today's
|
|
98
|
+
// props, not the ones captured at mount.
|
|
99
|
+
const handlers = useRef({ onChange, ownerManagedDirty });
|
|
100
|
+
handlers.current = { onChange, ownerManagedDirty };
|
|
101
|
+
// One compartment per mutable extension, so a prop change reconfigures
|
|
102
|
+
// that slice instead of rebuilding the view (and losing undo history).
|
|
103
|
+
const placeholderSlot = useRef(new Compartment()).current;
|
|
104
|
+
const readOnlySlot = useRef(new Compartment()).current;
|
|
105
|
+
const extensionsSlot = useRef(new Compartment()).current;
|
|
106
|
+
// Read through the ref rather than the closure: the EditorView is a
|
|
107
|
+
// different instance after a rebuild, and only the baseline has to survive
|
|
108
|
+
// that. Whitespace-only differences are not worth a confirmation.
|
|
109
|
+
useDirtySource(() => !handlers.current.ownerManagedDirty &&
|
|
110
|
+
!readOnly &&
|
|
111
|
+
(view.current?.state.doc.toString() ?? baseline.current).trim() !==
|
|
112
|
+
baseline.current.trim());
|
|
113
|
+
const userChangedRef = useRef(false);
|
|
114
|
+
// Every method reads only refs, so an empty list costs nothing and keeps
|
|
115
|
+
// the handle's identity stable across renders, which callers rely on.
|
|
116
|
+
useImperativeHandle(ref, () => ({
|
|
117
|
+
getValue: () => view.current?.state.doc.toString() ?? "",
|
|
118
|
+
// CAUTION (mirrors CodeEditorHandle.setValue): unannotated, so it counts
|
|
119
|
+
// as a user change.
|
|
120
|
+
setValue: (value) => {
|
|
121
|
+
const current = view.current;
|
|
122
|
+
if (!current)
|
|
123
|
+
return;
|
|
124
|
+
current.dispatch({
|
|
125
|
+
changes: { from: 0, to: current.state.doc.length, insert: value },
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
append: (text) => {
|
|
129
|
+
const current = view.current;
|
|
130
|
+
if (!current)
|
|
131
|
+
return;
|
|
132
|
+
const at = current.state.doc.length;
|
|
133
|
+
current.dispatch({
|
|
134
|
+
changes: { from: at, insert: text },
|
|
135
|
+
selection: { anchor: at + text.length },
|
|
136
|
+
scrollIntoView: true,
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
focus: () => view.current?.focus(),
|
|
140
|
+
// Provenance as in the handle type: user-event transactions mark the
|
|
141
|
+
// document as reader-touched; tagged programmatic writes never do.
|
|
142
|
+
userChanged: () => userChangedRef.current,
|
|
143
|
+
dismissCompletion: () => {
|
|
144
|
+
const current = view.current;
|
|
145
|
+
// "pending" is a query in flight with nothing on screen yet, which is
|
|
146
|
+
// not something the reader can have meant to dismiss.
|
|
147
|
+
if (current === null || completionStatus(current.state) !== "active") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
closeCompletion(current);
|
|
151
|
+
return true;
|
|
152
|
+
},
|
|
153
|
+
}), []);
|
|
154
|
+
// Mount once. initialValue/ariaLabel changes do not rebuild the view —
|
|
155
|
+
// remount with a new `key` if a caller ever needs that.
|
|
156
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: mount-only by design; every mutable prop is reconfigured through the compartments below.
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const parent = host.current;
|
|
159
|
+
if (parent === null)
|
|
160
|
+
return;
|
|
161
|
+
const instance = new EditorView({
|
|
162
|
+
parent,
|
|
163
|
+
state: EditorState.create({
|
|
164
|
+
doc: latestInitialValue.current,
|
|
165
|
+
extensions: [
|
|
166
|
+
history(),
|
|
167
|
+
drawSelection(),
|
|
168
|
+
compositionSafeMeasure,
|
|
169
|
+
// Without this CM silently collapses every extra range, which is
|
|
170
|
+
// what makes Mod-D look like a no-op.
|
|
171
|
+
EditorState.allowMultipleSelections.of(true),
|
|
172
|
+
EditorView.lineWrapping,
|
|
173
|
+
keymap.of([
|
|
174
|
+
...(callerKeymap ?? []),
|
|
175
|
+
...defaultKeymap,
|
|
176
|
+
...historyKeymap,
|
|
177
|
+
]),
|
|
178
|
+
...(language ? [language] : []),
|
|
179
|
+
...(highlightStyle ? [syntaxHighlighting(highlightStyle)] : []),
|
|
180
|
+
editorTheme,
|
|
181
|
+
parkedTooltip,
|
|
182
|
+
tooltipSpace,
|
|
183
|
+
EditorView.contentAttributes.of({
|
|
184
|
+
// CodeMirror's contenteditable opts out of the niceties a
|
|
185
|
+
// <textarea> gets for free.
|
|
186
|
+
spellcheck: "true",
|
|
187
|
+
autocapitalize: "sentences",
|
|
188
|
+
autocorrect: "on",
|
|
189
|
+
...(ariaLabel === undefined ? {} : { "aria-label": ariaLabel }),
|
|
190
|
+
}),
|
|
191
|
+
EditorView.updateListener.of((update) => {
|
|
192
|
+
if (update.docChanged) {
|
|
193
|
+
// Default to "the reader did this": every UI path to a doc
|
|
194
|
+
// change (typing, paste, drop, delete, and the whole move.*
|
|
195
|
+
// family — defaultKeymap's line moves and transposes annotate
|
|
196
|
+
// their transactions) arrives as a user event, and a whitelist
|
|
197
|
+
// loses a family every time CM adds one. The exception is a
|
|
198
|
+
// write the caller marked with PROGRAMMATIC_USER_EVENT.
|
|
199
|
+
// Mount-time \r\n normalization rides in EditorState.create —
|
|
200
|
+
// no transaction, never judged.
|
|
201
|
+
const ours = update.transactions.every((t) => t.isUserEvent(PROGRAMMATIC_USER_EVENT));
|
|
202
|
+
if (!ours)
|
|
203
|
+
userChangedRef.current = true;
|
|
204
|
+
handlers.current.onChange?.(update.state.doc.toString());
|
|
205
|
+
}
|
|
206
|
+
}),
|
|
207
|
+
placeholderSlot.of(placeholderExt(placeholder)),
|
|
208
|
+
readOnlySlot.of(EditorState.readOnly.of(readOnly)),
|
|
209
|
+
extensionsSlot.of(extensions ?? []),
|
|
210
|
+
],
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
view.current = instance;
|
|
214
|
+
onView?.(instance);
|
|
215
|
+
if (autoFocus)
|
|
216
|
+
instance.focus();
|
|
217
|
+
return () => {
|
|
218
|
+
instance.destroy();
|
|
219
|
+
view.current = null;
|
|
220
|
+
onView?.(null);
|
|
221
|
+
};
|
|
222
|
+
}, []);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
view.current?.dispatch({
|
|
225
|
+
effects: placeholderSlot.reconfigure(placeholderExt(placeholder)),
|
|
226
|
+
});
|
|
227
|
+
}, [placeholder, placeholderSlot]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
view.current?.dispatch({
|
|
230
|
+
effects: readOnlySlot.reconfigure(EditorState.readOnly.of(readOnly)),
|
|
231
|
+
});
|
|
232
|
+
}, [readOnly, readOnlySlot]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
view.current?.dispatch({
|
|
235
|
+
effects: extensionsSlot.reconfigure(extensions ?? []),
|
|
236
|
+
});
|
|
237
|
+
}, [extensions, extensionsSlot]);
|
|
238
|
+
return (_jsx("div", { ref: host, "data-slot": slot ?? "code-editor", "data-read-only": readOnly ? "true" : undefined, className: cn(
|
|
239
|
+
// text-base below md is not cosmetic: iOS auto-zooms a focused field
|
|
240
|
+
// under 16px. Same rule `@orz/ui`'s Textarea follows.
|
|
241
|
+
"flex w-full flex-col rounded-lg border border-input bg-transparent text-base transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 md:text-sm dark:bg-input/30", readOnly && "opacity-50", className) }));
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=code-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-editor.js","sourceRoot":"","sources":["../../src/components/code-editor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MarkdownEditor`'s paste and quote keys, none of which leave a trace on
|
|
3
|
+
* screen until they are used, behind a keyboard button for a toolbar beside
|
|
4
|
+
* the editor. Apple platforms get ⌘ ⌥ ⇧.
|
|
5
|
+
*/
|
|
6
|
+
export declare function EditorShortcutsHelp(): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=editor-shortcuts-help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-shortcuts-help.d.ts","sourceRoot":"","sources":["../../src/components/editor-shortcuts-help.tsx"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH,wBAAgB,mBAAmB,gCAkDlC"}
|