@backmatter/studio 0.0.10 → 0.0.12
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/dist/chunks/{UiKitApp-BxvTwsko.js → UiKitApp-ZZXQt3zy.js} +29 -29
- package/dist/chunks/{WorkspaceRoutes-BKKEkAq7.js → WorkspaceRoutes-CCDPtLla.js} +22712 -22498
- package/dist/chunks/{dashboard-8rjkfEXl.js → dashboard-Dw0zfTXg.js} +35 -35
- package/dist/chunks/{index-laWAz5_p.js → index-DhZGCAru.js} +1 -1
- package/dist/dashboard.js +2 -2
- package/dist/index.js +199 -193
- package/dist/site-widget.css +9 -1
- package/dist/site-widget.js +6060 -5936
- package/dist/studio.css +5 -25
- package/package.json +5 -1
- package/src/api.ts +1 -1
- package/src/chat/ChatProvider.tsx +13 -2
- package/src/chat/Composer.tsx +2 -1
- package/src/chat/PointerChip.tsx +15 -2
- package/src/chat/chat-helpers.ts +6 -0
- package/src/chat/pointer-arm.ts +3 -0
- package/src/chrome/WorkspaceStatusBar.tsx +64 -25
- package/src/chrome/branch-head.ts +14 -0
- package/src/chrome/commit-error.ts +82 -0
- package/src/chrome/commit-failure.tsx +111 -0
- package/src/install-preview-pointer.ts +116 -0
- package/src/pages/BlocksList.tsx +17 -2
- package/src/pages/DocEditorPage.tsx +121 -23
- package/src/pages/EmbedPublishPage.tsx +27 -24
- package/src/pages/RenderedBody.tsx +141 -322
- package/src/pages/document-paint.ts +79 -0
- package/src/pages/editor-grab.ts +39 -0
- package/src/pages/html-to-markdown.ts +26 -0
- package/src/pages/preview-bridge.ts +8 -1
- package/src/pages/preview-url.ts +15 -0
- package/src/pages/selection-format.ts +5 -2
- package/src/preview-highlight.ts +16 -0
- package/src/preview-pointer-host.ts +30 -0
- package/src/shell-resets.css +13 -0
- package/src/site-changes.ts +46 -0
- package/src/site-pointer.ts +39 -6
- package/src/site-widget.tsx +66 -23
|
@@ -6,28 +6,23 @@ import {
|
|
|
6
6
|
useImperativeHandle,
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
|
-
type KeyboardEvent,
|
|
10
9
|
type ReactNode,
|
|
11
|
-
type Ref,
|
|
12
10
|
} from "react";
|
|
13
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
14
11
|
import * as stylex from "@stylexjs/stylex";
|
|
15
12
|
import type { BlockSnapshot } from "../api";
|
|
16
|
-
import {
|
|
17
|
-
import { colors, radius, space
|
|
13
|
+
import { contentBlockFields, contentBlockLabel, contentBlockProps } from "../content-blocks";
|
|
14
|
+
import { colors, radius, space } from "../styles/tokens.stylex";
|
|
18
15
|
import {
|
|
19
16
|
blockFromMarkdown,
|
|
20
17
|
blocksFromSource,
|
|
21
|
-
codeParts,
|
|
22
18
|
joinBlocks,
|
|
23
19
|
jsxBlockSource,
|
|
24
20
|
jsxWithProps,
|
|
25
21
|
type BodyBlock,
|
|
26
22
|
} from "./body-blocks";
|
|
27
|
-
import {
|
|
28
|
-
import { compileBody } from "./compile-body";
|
|
23
|
+
import { documentNodes } from "./document-paint";
|
|
29
24
|
import { BlockSettingsDialog } from "./BlockSettingsDialog";
|
|
30
|
-
import {
|
|
25
|
+
import { markdownFromDocument } from "./html-to-markdown";
|
|
31
26
|
import { SelectionBar, type FormatSelection } from "./SelectionBar";
|
|
32
27
|
import {
|
|
33
28
|
applyBlockFormat,
|
|
@@ -43,6 +38,8 @@ export type RenderedBodyHandle = {
|
|
|
43
38
|
insert: (markdown: string) => void;
|
|
44
39
|
};
|
|
45
40
|
|
|
41
|
+
const BODY_HOST = "body";
|
|
42
|
+
|
|
46
43
|
export const RenderedBody = forwardRef<
|
|
47
44
|
RenderedBodyHandle,
|
|
48
45
|
{
|
|
@@ -55,29 +52,24 @@ export const RenderedBody = forwardRef<
|
|
|
55
52
|
>(function RenderedBody({ value, catalog = [], readOnly, labelledBy, onChange }, ref) {
|
|
56
53
|
const local = useRef(value);
|
|
57
54
|
const hosts = useRef(new Map<string, HTMLElement>());
|
|
58
|
-
const commits = useRef(new Map<string, () => void>());
|
|
59
55
|
const linking = useRef(false);
|
|
60
56
|
const [blocks, setBlocks] = useState(() => blocksFromSource(value));
|
|
61
|
-
const
|
|
62
|
-
const
|
|
57
|
+
const repaint = useRef(true);
|
|
58
|
+
const editing = useRef(false);
|
|
63
59
|
const [selection, setSelection] = useState<FormatSelection | null>(null);
|
|
64
60
|
const [settingsId, setSettingsId] = useState<string | null>(null);
|
|
65
|
-
const selectionBlock = useRef<string | null>(null);
|
|
66
61
|
|
|
67
|
-
const register = useCallback((id: string, host: HTMLElement
|
|
62
|
+
const register = useCallback((id: string, host: HTMLElement) => {
|
|
68
63
|
hosts.current.set(id, host);
|
|
69
|
-
commits.current.set(id, commit);
|
|
70
64
|
}, []);
|
|
71
65
|
|
|
72
66
|
const unregister = useCallback((id: string) => {
|
|
73
67
|
hosts.current.delete(id);
|
|
74
|
-
commits.current.delete(id);
|
|
75
68
|
}, []);
|
|
76
69
|
|
|
77
70
|
const syncSelection = useCallback(() => {
|
|
78
71
|
if (linking.current) return;
|
|
79
72
|
const next = readSelection(hosts.current);
|
|
80
|
-
selectionBlock.current = next?.blockId ?? null;
|
|
81
73
|
setSelection(next?.selection ?? null);
|
|
82
74
|
}, []);
|
|
83
75
|
|
|
@@ -95,87 +87,95 @@ export const RenderedBody = forwardRef<
|
|
|
95
87
|
useEffect(() => {
|
|
96
88
|
if (value === local.current) return;
|
|
97
89
|
local.current = value;
|
|
90
|
+
repaint.current = true;
|
|
98
91
|
setBlocks(blocksFromSource(value));
|
|
99
92
|
}, [value]);
|
|
100
93
|
|
|
101
94
|
function publish(next: BodyBlock[]) {
|
|
102
95
|
const source = joinBlocks(next);
|
|
103
96
|
local.current = source;
|
|
97
|
+
repaint.current = true;
|
|
104
98
|
setBlocks(next);
|
|
105
99
|
if (source !== value) onChange(source);
|
|
106
100
|
}
|
|
107
101
|
|
|
102
|
+
function bodyHost(): HTMLElement | undefined {
|
|
103
|
+
return hosts.current.get(BODY_HOST);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function commitDocument() {
|
|
107
|
+
const host = bodyHost();
|
|
108
|
+
if (!host) return;
|
|
109
|
+
const source = markdownFromDocument(host);
|
|
110
|
+
if (source === local.current) return;
|
|
111
|
+
local.current = source;
|
|
112
|
+
setBlocks(blocksFromSource(source));
|
|
113
|
+
if (source !== value) onChange(source);
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
function insert(markdown: string) {
|
|
117
|
+
const host = bodyHost();
|
|
118
|
+
const source = host ? markdownFromDocument(host) : local.current;
|
|
119
|
+
const current = blocksFromSource(source);
|
|
109
120
|
const id = crypto.randomUUID();
|
|
110
121
|
const block = blockFromMarkdown(markdown, id);
|
|
111
|
-
const index =
|
|
112
|
-
const next = [...
|
|
113
|
-
if (index
|
|
122
|
+
const index = caretIndex(host);
|
|
123
|
+
const next = [...current];
|
|
124
|
+
if (index < 0) next.push(block);
|
|
114
125
|
else next.splice(index + 1, 0, block);
|
|
115
126
|
if (block.kind === "jsx" && catalog.some((item) => item.name === block.jsxName)) {
|
|
116
127
|
setSettingsId(id);
|
|
117
|
-
} else {
|
|
118
|
-
setFocusId(id);
|
|
119
128
|
}
|
|
120
129
|
publish(next);
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
useImperativeHandle(ref, () => ({ insert }));
|
|
124
133
|
|
|
125
|
-
function commitHost(id: string) {
|
|
126
|
-
commits.current.get(id)?.();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
134
|
function onFormat(format: BlockFormat) {
|
|
130
|
-
const
|
|
131
|
-
const host = id ? hosts.current.get(id) : undefined;
|
|
135
|
+
const host = bodyHost();
|
|
132
136
|
const range = currentRange();
|
|
133
|
-
if (!
|
|
137
|
+
if (!host || !range || !host.contains(range.startContainer)) return;
|
|
134
138
|
if (blockFormatOf(host, range) === format) return;
|
|
135
|
-
applyBlockFormat(host, format, range);
|
|
136
|
-
placeCaretAtEnd(
|
|
137
|
-
|
|
139
|
+
const next = applyBlockFormat(host, format, range);
|
|
140
|
+
placeCaretAtEnd(next);
|
|
141
|
+
commitDocument();
|
|
138
142
|
}
|
|
139
143
|
|
|
140
144
|
function onInline(tag: "strong" | "em") {
|
|
141
|
-
const
|
|
142
|
-
const host = id ? hosts.current.get(id) : undefined;
|
|
145
|
+
const host = bodyHost();
|
|
143
146
|
const range = currentRange();
|
|
144
|
-
if (!
|
|
147
|
+
if (!host || !range || !host.contains(range.commonAncestorContainer)) return;
|
|
145
148
|
toggleInline(range, tag);
|
|
146
|
-
|
|
149
|
+
commitDocument();
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
function onLink(href: string, range: Range) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
if (!id || !host) return;
|
|
153
|
+
const host = bodyHost();
|
|
154
|
+
if (!host) return;
|
|
153
155
|
host.focus();
|
|
154
156
|
applyLink(range, href);
|
|
155
|
-
|
|
157
|
+
commitDocument();
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
160
|
+
function caretIndex(host: HTMLElement | undefined): number {
|
|
161
|
+
if (!host) return -1;
|
|
162
|
+
const selection = window.getSelection();
|
|
163
|
+
if (!selection || selection.rangeCount === 0) return -1;
|
|
164
|
+
let node: Node | null = selection.getRangeAt(0).startContainer;
|
|
165
|
+
while (node && node.parentElement !== host) node = node.parentNode;
|
|
166
|
+
if (!(node instanceof HTMLElement) || node.parentElement !== host) return -1;
|
|
167
|
+
return [...host.children].indexOf(node);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
next.push(blockFromMarkdown(after, nextId));
|
|
176
|
-
}
|
|
177
|
-
setFocusId(nextId);
|
|
178
|
-
publish(next);
|
|
170
|
+
function replaceAtom(id: string, markdown: string) {
|
|
171
|
+
const host = bodyHost();
|
|
172
|
+
const atom = host?.querySelector(`[data-body-atom="${id}"]`);
|
|
173
|
+
if (atom) atom.setAttribute("data-body-markdown", encodeURIComponent(markdown));
|
|
174
|
+
const source = host ? markdownFromDocument(host) : markdown;
|
|
175
|
+
local.current = source;
|
|
176
|
+
repaint.current = true;
|
|
177
|
+
setBlocks(blocksFromSource(source));
|
|
178
|
+
if (source !== value) onChange(source);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
const settingsBlock = blocks.find((block) => block.id === settingsId);
|
|
@@ -187,41 +187,18 @@ export const RenderedBody = forwardRef<
|
|
|
187
187
|
|
|
188
188
|
return (
|
|
189
189
|
<RenderedSurface labelledBy={labelledBy}>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
publish([blockFromMarkdown(markdown, crypto.randomUUID())]);
|
|
203
|
-
}}
|
|
204
|
-
onSplit={() => {}}
|
|
205
|
-
onRemove={() => {}}
|
|
206
|
-
/>
|
|
207
|
-
) : (
|
|
208
|
-
blocks.map((block) => (
|
|
209
|
-
<EditableBlock
|
|
210
|
-
key={block.id}
|
|
211
|
-
block={block}
|
|
212
|
-
readOnly={readOnly}
|
|
213
|
-
autoFocus={focusId === block.id}
|
|
214
|
-
catalog={catalog}
|
|
215
|
-
onOpenSettings={setSettingsId}
|
|
216
|
-
onRegister={register}
|
|
217
|
-
onUnregister={unregister}
|
|
218
|
-
onFocus={() => setActiveId(block.id)}
|
|
219
|
-
onCommit={(markdown) => commit(block.id, markdown)}
|
|
220
|
-
onSplit={(before, after) => split(block.id, before, after)}
|
|
221
|
-
onRemove={() => publish(blocks.filter((item) => item.id !== block.id))}
|
|
222
|
-
/>
|
|
223
|
-
))
|
|
224
|
-
)}
|
|
190
|
+
<DocumentField
|
|
191
|
+
blocks={blocks}
|
|
192
|
+
catalog={catalog}
|
|
193
|
+
readOnly={readOnly}
|
|
194
|
+
labelledBy={labelledBy}
|
|
195
|
+
repaint={repaint}
|
|
196
|
+
editing={editing}
|
|
197
|
+
onRegister={register}
|
|
198
|
+
onUnregister={unregister}
|
|
199
|
+
onCommit={commitDocument}
|
|
200
|
+
onOpenSettings={setSettingsId}
|
|
201
|
+
/>
|
|
225
202
|
{selection && !readOnly ? (
|
|
226
203
|
<SelectionBar
|
|
227
204
|
selection={selection}
|
|
@@ -255,11 +232,17 @@ export const RenderedBody = forwardRef<
|
|
|
255
232
|
props,
|
|
256
233
|
settings.def.fields,
|
|
257
234
|
);
|
|
258
|
-
|
|
235
|
+
replaceAtom(settings.block.id, markdown);
|
|
259
236
|
setSettingsId(null);
|
|
260
237
|
}}
|
|
261
238
|
onRemove={() => {
|
|
262
|
-
|
|
239
|
+
const host = bodyHost();
|
|
240
|
+
host?.querySelector(`[data-body-atom="${settings.block.id}"]`)?.remove();
|
|
241
|
+
const source = host ? markdownFromDocument(host) : "";
|
|
242
|
+
local.current = source;
|
|
243
|
+
repaint.current = true;
|
|
244
|
+
setBlocks(blocksFromSource(source));
|
|
245
|
+
if (source !== value) onChange(source);
|
|
263
246
|
setSettingsId(null);
|
|
264
247
|
}}
|
|
265
248
|
/>
|
|
@@ -287,185 +270,86 @@ function RenderedSurface({
|
|
|
287
270
|
);
|
|
288
271
|
}
|
|
289
272
|
|
|
290
|
-
function
|
|
291
|
-
|
|
273
|
+
function DocumentField({
|
|
274
|
+
blocks,
|
|
292
275
|
catalog,
|
|
293
276
|
readOnly,
|
|
294
|
-
|
|
295
|
-
|
|
277
|
+
labelledBy,
|
|
278
|
+
repaint,
|
|
279
|
+
editing,
|
|
296
280
|
onRegister,
|
|
297
281
|
onUnregister,
|
|
298
|
-
onFocus,
|
|
299
282
|
onCommit,
|
|
300
|
-
|
|
301
|
-
onRemove,
|
|
283
|
+
onOpenSettings,
|
|
302
284
|
}: {
|
|
303
|
-
|
|
285
|
+
blocks: BodyBlock[];
|
|
304
286
|
catalog: Array<BlockSnapshot & { label?: string }>;
|
|
305
287
|
readOnly?: boolean;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
288
|
+
labelledBy: string;
|
|
289
|
+
repaint: { current: boolean };
|
|
290
|
+
editing: { current: boolean };
|
|
291
|
+
onRegister: (id: string, host: HTMLElement) => void;
|
|
309
292
|
onUnregister: (id: string) => void;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
onSplit: (before: string, after: string) => void;
|
|
313
|
-
onRemove: () => void;
|
|
293
|
+
onCommit: () => void;
|
|
294
|
+
onOpenSettings: (id: string) => void;
|
|
314
295
|
}) {
|
|
315
|
-
const ref = useRef<
|
|
316
|
-
const editing = useRef(false);
|
|
317
|
-
const commitRef = useRef<() => void>(() => {});
|
|
318
|
-
commitRef.current = () => {
|
|
319
|
-
const host = ref.current;
|
|
320
|
-
if (!host || readOnly) return;
|
|
321
|
-
onCommit(markdownFromEditable(host, block));
|
|
322
|
-
};
|
|
296
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
323
297
|
|
|
324
298
|
useEffect(() => {
|
|
325
|
-
if (autoFocus) ref.current?.focus();
|
|
326
|
-
}, [autoFocus]);
|
|
327
|
-
|
|
328
|
-
useEffect(() => {
|
|
329
|
-
if (block.kind !== "text") return;
|
|
330
299
|
const host = ref.current;
|
|
331
300
|
if (!host) return;
|
|
332
|
-
onRegister(
|
|
333
|
-
return () => onUnregister(
|
|
334
|
-
}, [
|
|
301
|
+
onRegister(BODY_HOST, host);
|
|
302
|
+
return () => onUnregister(BODY_HOST);
|
|
303
|
+
}, [onRegister, onUnregister]);
|
|
335
304
|
|
|
336
305
|
useEffect(() => {
|
|
337
306
|
const host = ref.current;
|
|
338
|
-
if (!host
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
if (!block.markdown.trim()) {
|
|
344
|
-
host.innerHTML = "";
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
307
|
+
if (!host) return;
|
|
308
|
+
const forced = repaint.current;
|
|
309
|
+
repaint.current = false;
|
|
310
|
+
if (editing.current && !forced) return;
|
|
347
311
|
let cancelled = false;
|
|
348
|
-
void
|
|
349
|
-
.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
try {
|
|
356
|
-
host.innerHTML = renderToStaticMarkup(result.node).replace(/<link\b[^>]*>/g, "");
|
|
357
|
-
} catch {
|
|
358
|
-
host.textContent = block.markdown;
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
.catch(() => {
|
|
362
|
-
if (!cancelled && ref.current === host) host.textContent = block.markdown;
|
|
363
|
-
});
|
|
312
|
+
void documentNodes(blocks, catalog).then((nodes) => {
|
|
313
|
+
if (cancelled || ref.current !== host) return;
|
|
314
|
+
if (editing.current && !forced) return;
|
|
315
|
+
host.replaceChildren(...nodes);
|
|
316
|
+
});
|
|
364
317
|
return () => {
|
|
365
318
|
cancelled = true;
|
|
366
319
|
};
|
|
367
|
-
}, [
|
|
368
|
-
|
|
369
|
-
function commitFromDom() {
|
|
370
|
-
const host = ref.current;
|
|
371
|
-
if (!host || readOnly) return;
|
|
372
|
-
onCommit(markdownFromEditable(host, block));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function onKeyDown(event: KeyboardEvent<HTMLElement>) {
|
|
376
|
-
if (readOnly) return;
|
|
377
|
-
const emptyText = (ref.current?.innerText ?? "").trim() === "";
|
|
378
|
-
if (event.key === "Backspace" && block.kind === "text" && emptyText) {
|
|
379
|
-
event.preventDefault();
|
|
380
|
-
onRemove();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (event.key !== "Enter" || event.shiftKey || block.kind !== "text") return;
|
|
384
|
-
const host = ref.current;
|
|
385
|
-
if (!host) return;
|
|
386
|
-
const parts = splitAtCaret(host);
|
|
387
|
-
if (!parts) return;
|
|
388
|
-
event.preventDefault();
|
|
389
|
-
onSplit(parts.before, parts.after);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const empty = !block.markdown.trim();
|
|
393
|
-
const catalogBlock =
|
|
394
|
-
block.kind === "jsx" ? catalog.find((item) => item.name === block.jsxName) : undefined;
|
|
395
|
-
|
|
396
|
-
if (catalogBlock) {
|
|
397
|
-
return (
|
|
398
|
-
<div data-body-block="" {...stylex.props(styles.block)}>
|
|
399
|
-
<button
|
|
400
|
-
type="button"
|
|
401
|
-
aria-label={`Edit ${catalogBlock.name} settings`}
|
|
402
|
-
onClick={() => onOpenSettings(block.id)}
|
|
403
|
-
{...stylex.props(styles.card)}
|
|
404
|
-
>
|
|
405
|
-
<ContentBlockView
|
|
406
|
-
name={catalogBlock.name}
|
|
407
|
-
props={contentBlockProps(block.markdown, catalogBlock.name)}
|
|
408
|
-
/>
|
|
409
|
-
</button>
|
|
410
|
-
</div>
|
|
411
|
-
);
|
|
412
|
-
}
|
|
320
|
+
}, [blocks, catalog, editing, repaint]);
|
|
413
321
|
|
|
414
322
|
return (
|
|
415
323
|
<div
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
{
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
aria-multiline="true"
|
|
446
|
-
aria-label={block.jsxName ?? "Text"}
|
|
447
|
-
contentEditable={!readOnly}
|
|
448
|
-
suppressContentEditableWarning
|
|
449
|
-
spellCheck
|
|
450
|
-
data-text-block=""
|
|
451
|
-
data-empty={empty ? "" : undefined}
|
|
452
|
-
onFocus={() => {
|
|
453
|
-
editing.current = true;
|
|
454
|
-
onFocus();
|
|
455
|
-
}}
|
|
456
|
-
onBlur={() => {
|
|
457
|
-
editing.current = false;
|
|
458
|
-
commitFromDom();
|
|
459
|
-
}}
|
|
460
|
-
onKeyDown={onKeyDown}
|
|
461
|
-
onClick={(event) => {
|
|
462
|
-
const anchor = (event.target as HTMLElement).closest?.("a");
|
|
463
|
-
if (anchor) event.preventDefault();
|
|
464
|
-
}}
|
|
465
|
-
{...stylex.props(styles.text, empty && styles.placeholder)}
|
|
466
|
-
/>
|
|
467
|
-
)}
|
|
468
|
-
</div>
|
|
324
|
+
ref={ref}
|
|
325
|
+
role="textbox"
|
|
326
|
+
aria-multiline="true"
|
|
327
|
+
aria-labelledby={labelledBy}
|
|
328
|
+
contentEditable={readOnly ? "false" : "true"}
|
|
329
|
+
suppressContentEditableWarning
|
|
330
|
+
spellCheck
|
|
331
|
+
data-text-block=""
|
|
332
|
+
data-no-ring=""
|
|
333
|
+
onFocus={() => {
|
|
334
|
+
editing.current = true;
|
|
335
|
+
}}
|
|
336
|
+
onBlur={() => {
|
|
337
|
+
editing.current = false;
|
|
338
|
+
if (!readOnly) onCommit();
|
|
339
|
+
}}
|
|
340
|
+
onClick={(event) => {
|
|
341
|
+
const target = event.target;
|
|
342
|
+
if (!(target instanceof Element)) return;
|
|
343
|
+
const atom = target.closest("[data-body-atom]");
|
|
344
|
+
if (atom instanceof HTMLElement) {
|
|
345
|
+
event.preventDefault();
|
|
346
|
+
onOpenSettings(atom.getAttribute("data-body-atom") ?? "");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (target.closest("a")) event.preventDefault();
|
|
350
|
+
}}
|
|
351
|
+
{...stylex.props(styles.text)}
|
|
352
|
+
/>
|
|
469
353
|
);
|
|
470
354
|
}
|
|
471
355
|
|
|
@@ -509,31 +393,6 @@ function currentRange(): Range | null {
|
|
|
509
393
|
return current.getRangeAt(0);
|
|
510
394
|
}
|
|
511
395
|
|
|
512
|
-
function splitAtCaret(root: HTMLElement): { before: string; after: string } | null {
|
|
513
|
-
const selection = window.getSelection();
|
|
514
|
-
if (!selection || selection.rangeCount === 0) return null;
|
|
515
|
-
const range = selection.getRangeAt(0);
|
|
516
|
-
if (!root.contains(range.startContainer)) return null;
|
|
517
|
-
|
|
518
|
-
const beforeRange = range.cloneRange();
|
|
519
|
-
beforeRange.selectNodeContents(root);
|
|
520
|
-
beforeRange.setEnd(range.startContainer, range.startOffset);
|
|
521
|
-
const afterRange = range.cloneRange();
|
|
522
|
-
afterRange.selectNodeContents(root);
|
|
523
|
-
afterRange.setStart(range.endContainer, range.endOffset);
|
|
524
|
-
|
|
525
|
-
const beforeHost = document.createElement("div");
|
|
526
|
-
beforeHost.appendChild(beforeRange.cloneContents());
|
|
527
|
-
const afterHost = document.createElement("div");
|
|
528
|
-
afterHost.appendChild(afterRange.cloneContents());
|
|
529
|
-
return {
|
|
530
|
-
before: markdownFromEditable(beforeHost, { id: "before", kind: "text", markdown: "" }),
|
|
531
|
-
after: markdownFromEditable(afterHost, { id: "after", kind: "text", markdown: "" }),
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const hover = "@media (hover: hover)";
|
|
536
|
-
|
|
537
396
|
const styles = stylex.create({
|
|
538
397
|
surface: {
|
|
539
398
|
minHeight: "26rem",
|
|
@@ -545,54 +404,14 @@ const styles = stylex.create({
|
|
|
545
404
|
borderRadius: radius.md,
|
|
546
405
|
backgroundColor: colors.bgInput,
|
|
547
406
|
},
|
|
548
|
-
block: {
|
|
549
|
-
marginBottom: space.lg,
|
|
550
|
-
":last-child": {
|
|
551
|
-
marginBottom: 0,
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
card: {
|
|
555
|
-
display: "block",
|
|
556
|
-
width: "100%",
|
|
557
|
-
margin: 0,
|
|
558
|
-
padding: 0,
|
|
559
|
-
borderWidth: 0,
|
|
560
|
-
borderRadius: radius.md,
|
|
561
|
-
backgroundColor: "transparent",
|
|
562
|
-
color: "inherit",
|
|
563
|
-
fontFamily: "inherit",
|
|
564
|
-
fontSize: "inherit",
|
|
565
|
-
lineHeight: "inherit",
|
|
566
|
-
textAlign: "left",
|
|
567
|
-
cursor: "pointer",
|
|
568
|
-
[hover]: {
|
|
569
|
-
":hover": {
|
|
570
|
-
backgroundColor: colors.bgHover,
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
407
|
text: {
|
|
575
408
|
minHeight: "1.5em",
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
409
|
+
outline: "none",
|
|
410
|
+
":focus": {
|
|
411
|
+
outline: "none",
|
|
412
|
+
},
|
|
413
|
+
":focus-visible": {
|
|
414
|
+
outline: "none",
|
|
582
415
|
},
|
|
583
|
-
},
|
|
584
|
-
pre: {
|
|
585
|
-
margin: 0,
|
|
586
|
-
padding: space.md,
|
|
587
|
-
borderRadius: radius.md,
|
|
588
|
-
backgroundColor: colors.bgSurface,
|
|
589
|
-
overflowX: "auto",
|
|
590
|
-
},
|
|
591
|
-
codeText: {
|
|
592
|
-
fontFamily: type.mono,
|
|
593
|
-
fontSize: type.sm,
|
|
594
|
-
lineHeight: 1.5,
|
|
595
|
-
whiteSpace: "pre",
|
|
596
|
-
outlineWidth: 0,
|
|
597
416
|
},
|
|
598
417
|
});
|