@backmatter/studio 0.0.11 → 0.0.13
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-Cu0CS1NU.js → UiKitApp-ZZXQt3zy.js} +29 -29
- package/dist/chunks/{WorkspaceRoutes-Dzpaj8ov.js → WorkspaceRoutes-CCDPtLla.js} +22396 -22185
- package/dist/chunks/{dashboard-kWmpeuAP.js → dashboard-Dw0zfTXg.js} +35 -35
- package/dist/chunks/{index-Bryt_VJQ.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 +6 -2
- 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 +57 -19
- 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 +95 -1
- 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/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
|
@@ -62,7 +62,16 @@ import { blocksFromFrontmatter } from "./blocks-list";
|
|
|
62
62
|
import type { WorkspaceOutletContext } from "../workspace/types";
|
|
63
63
|
import { contentDocPath, defaultLocaleOf, settingsPath } from "../workspace-path";
|
|
64
64
|
import { bindPreviewEditor, previewFrameTransport } from "./preview-bridge";
|
|
65
|
-
import {
|
|
65
|
+
import { editorGrabAction } from "./editor-grab";
|
|
66
|
+
import { useChatUI } from "../chat/ChatProvider";
|
|
67
|
+
import { POINTER_ARM_EVENT } from "../chat/pointer-arm";
|
|
68
|
+
import { registerPreviewHighlight } from "../preview-highlight";
|
|
69
|
+
import {
|
|
70
|
+
createWindowPreviewTransport,
|
|
71
|
+
samePreviewRef,
|
|
72
|
+
type PreviewChannelRef,
|
|
73
|
+
type PreviewGrabMessage,
|
|
74
|
+
} from "@backmatter/core/preview";
|
|
66
75
|
|
|
67
76
|
const AUTOSAVE_MS = 400;
|
|
68
77
|
const hover = "@media (hover: hover)";
|
|
@@ -215,6 +224,9 @@ export function DocEditorPage() {
|
|
|
215
224
|
const splitRef = useRef<HTMLDivElement>(null);
|
|
216
225
|
const previewFrameRef = useRef<HTMLIFrameElement>(null);
|
|
217
226
|
const previewSession = useRef<ReturnType<typeof bindPreviewEditor> | null>(null);
|
|
227
|
+
const { addPointer, setPreviewPointer, setPointerHighlight } = useChatUI();
|
|
228
|
+
const [blockFocus, setBlockFocus] = useState<{ id: string; nonce: number } | null>(null);
|
|
229
|
+
const blockFocusNonce = useRef(0);
|
|
218
230
|
const navigate = useNavigate();
|
|
219
231
|
const wide = useWideLayout();
|
|
220
232
|
const { setStatus: setEditorStatus, clear: clearEditorStatus } =
|
|
@@ -254,6 +266,8 @@ export function DocEditorPage() {
|
|
|
254
266
|
const detailNames = fieldNames.filter((name) => fieldKinds[name] !== "blocks");
|
|
255
267
|
const blocksField =
|
|
256
268
|
fieldNames.find((name) => fieldKinds[name] === "blocks") ?? "blocks";
|
|
269
|
+
const blocksFieldRef = useRef(blocksField);
|
|
270
|
+
blocksFieldRef.current = blocksField;
|
|
257
271
|
const siteUrl = primarySiteUrl(workspace.siteUrls ?? []);
|
|
258
272
|
const liveUrl = productionDocUrl(siteUrl, modelForType(models, type), locale, slug);
|
|
259
273
|
const previewPath = editorPreviewPath(
|
|
@@ -307,6 +321,57 @@ export function DocEditorPage() {
|
|
|
307
321
|
setPreviewLive(null);
|
|
308
322
|
}, [previewUrl]);
|
|
309
323
|
|
|
324
|
+
function onPreviewHello(hello: PreviewChannelRef, capabilities?: string[]) {
|
|
325
|
+
const canPoint =
|
|
326
|
+
samePreviewRef(hello, channelRef.current) && (capabilities?.includes("point") ?? false);
|
|
327
|
+
setPreviewPointer(canPoint);
|
|
328
|
+
window.dispatchEvent(new CustomEvent(POINTER_ARM_EVENT, { detail: { on: false } }));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function onPreviewGrab(grab: PreviewGrabMessage) {
|
|
332
|
+
const ids = blocksFromFrontmatter(latest.current.frontmatter[blocksFieldRef.current]).map(
|
|
333
|
+
(block) => block.id,
|
|
334
|
+
);
|
|
335
|
+
const decision = editorGrabAction({
|
|
336
|
+
grabRef: grab.ref,
|
|
337
|
+
openRef: channelRef.current,
|
|
338
|
+
blockIds: ids,
|
|
339
|
+
grab,
|
|
340
|
+
});
|
|
341
|
+
if (decision.action === "add") {
|
|
342
|
+
addPointer({
|
|
343
|
+
locale: channelRef.current.locale,
|
|
344
|
+
type: channelRef.current.type,
|
|
345
|
+
slug: channelRef.current.slug,
|
|
346
|
+
blockId: decision.blockId,
|
|
347
|
+
blockType: decision.blockType,
|
|
348
|
+
blockIndex: decision.blockIndex,
|
|
349
|
+
quote: decision.quote,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (decision.action === "open") {
|
|
353
|
+
blockFocusNonce.current += 1;
|
|
354
|
+
setBlockFocus({ id: decision.blockId, nonce: blockFocusNonce.current });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function onPreviewPoint(on: boolean) {
|
|
359
|
+
if (!on) {
|
|
360
|
+
window.dispatchEvent(new CustomEvent(POINTER_ARM_EVENT, { detail: { on: false } }));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const previewHandlers = useRef({
|
|
365
|
+
hello: onPreviewHello,
|
|
366
|
+
grab: onPreviewGrab,
|
|
367
|
+
point: onPreviewPoint,
|
|
368
|
+
});
|
|
369
|
+
previewHandlers.current = {
|
|
370
|
+
hello: onPreviewHello,
|
|
371
|
+
grab: onPreviewGrab,
|
|
372
|
+
point: onPreviewPoint,
|
|
373
|
+
};
|
|
374
|
+
|
|
310
375
|
useEffect(() => {
|
|
311
376
|
const frame = previewFrameRef.current;
|
|
312
377
|
if (!frame || !siteUrl) return;
|
|
@@ -323,6 +388,9 @@ export function DocEditorPage() {
|
|
|
323
388
|
ref: () => channelRef.current,
|
|
324
389
|
onOffer: setPreviewOffer,
|
|
325
390
|
onStatus: setPreviewLive,
|
|
391
|
+
onHello: (hello, capabilities) => previewHandlers.current.hello(hello, capabilities),
|
|
392
|
+
onGrab: (grab) => previewHandlers.current.grab(grab),
|
|
393
|
+
onPoint: (on) => previewHandlers.current.point(on),
|
|
326
394
|
});
|
|
327
395
|
previewSession.current = session;
|
|
328
396
|
if (editEpochRef.current > 0) session.setDoc(latest.current);
|
|
@@ -352,6 +420,9 @@ export function DocEditorPage() {
|
|
|
352
420
|
ref: () => channelRef.current,
|
|
353
421
|
onOffer: setPreviewOffer,
|
|
354
422
|
onStatus: setPreviewLive,
|
|
423
|
+
onHello: (hello, capabilities) => previewHandlers.current.hello(hello, capabilities),
|
|
424
|
+
onGrab: (grab) => previewHandlers.current.grab(grab),
|
|
425
|
+
onPoint: (on) => previewHandlers.current.point(on),
|
|
355
426
|
});
|
|
356
427
|
previewSession.current = session;
|
|
357
428
|
if (editEpochRef.current > 0) session.setDoc(latest.current);
|
|
@@ -371,6 +442,28 @@ export function DocEditorPage() {
|
|
|
371
442
|
};
|
|
372
443
|
}, [embed]);
|
|
373
444
|
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
function onArm(event: Event) {
|
|
447
|
+
const on = (event as CustomEvent<{ on?: boolean }>).detail?.on === true;
|
|
448
|
+
previewSession.current?.point(on);
|
|
449
|
+
}
|
|
450
|
+
window.addEventListener(POINTER_ARM_EVENT, onArm);
|
|
451
|
+
return () => window.removeEventListener(POINTER_ARM_EVENT, onArm);
|
|
452
|
+
}, []);
|
|
453
|
+
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
setPointerHighlight("channel");
|
|
456
|
+
const stop = registerPreviewHighlight((blockId) => {
|
|
457
|
+
previewSession.current?.highlight(blockId);
|
|
458
|
+
});
|
|
459
|
+
return () => {
|
|
460
|
+
stop();
|
|
461
|
+
setPointerHighlight("local");
|
|
462
|
+
setPreviewPointer(false);
|
|
463
|
+
previewSession.current?.point(false);
|
|
464
|
+
};
|
|
465
|
+
}, [setPointerHighlight, setPreviewPointer]);
|
|
466
|
+
|
|
374
467
|
useEffect(() => {
|
|
375
468
|
if (editEpoch === 0) return;
|
|
376
469
|
previewSession.current?.setDoc(latest.current);
|
|
@@ -1066,6 +1159,7 @@ export function DocEditorPage() {
|
|
|
1066
1159
|
blocks={blocksFromFrontmatter(frontmatter[blocksField])}
|
|
1067
1160
|
catalog={blocks}
|
|
1068
1161
|
readOnly={aiEditing || parseFailed}
|
|
1162
|
+
focusBlock={blockFocus}
|
|
1069
1163
|
onChange={(next) => {
|
|
1070
1164
|
markEdited();
|
|
1071
1165
|
setFrontmatter((prev) => ({ ...prev, [blocksField]: next }));
|
|
@@ -12,12 +12,17 @@ import {
|
|
|
12
12
|
notifyTreeChanged,
|
|
13
13
|
publishCommittedFiles,
|
|
14
14
|
resolveDoc,
|
|
15
|
-
type ApiError,
|
|
16
15
|
type ChangeStatus,
|
|
17
16
|
type GitChange,
|
|
18
17
|
type GitState,
|
|
19
18
|
} from "../api";
|
|
20
19
|
import { statusBarAction } from "../chrome/status-bar-action";
|
|
20
|
+
import {
|
|
21
|
+
explainCommitError,
|
|
22
|
+
problemsForRef,
|
|
23
|
+
type CommitErrorView,
|
|
24
|
+
} from "../chrome/commit-error";
|
|
25
|
+
import { CommitFailureNotice } from "../chrome/commit-failure";
|
|
21
26
|
import { colors, radius, space, type } from "../styles/tokens.stylex";
|
|
22
27
|
import { popup } from "../ui/popup";
|
|
23
28
|
import { canPublish } from "../workspace/permissions";
|
|
@@ -33,18 +38,6 @@ function parentOrigin(): string {
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
function formatCommitError(err: unknown): string {
|
|
37
|
-
if (!(err instanceof Error)) return "Commit failed";
|
|
38
|
-
const apiErr = err as ApiError;
|
|
39
|
-
if (apiErr.code === "conflict") return "Resolve conflicts before committing";
|
|
40
|
-
if (apiErr.code === "nothing_to_commit") return "Nothing to commit";
|
|
41
|
-
if (apiErr.code === "validation") {
|
|
42
|
-
return err.message.replace(/^validation:\s*/, "Validation failed: ");
|
|
43
|
-
}
|
|
44
|
-
if (apiErr.code === "github_not_bound") return "Connect GitHub first";
|
|
45
|
-
return err.message;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
41
|
function changeKey(doc: GitChange) {
|
|
49
42
|
return `${doc.locale}/${doc.type}/${doc.slug}`;
|
|
50
43
|
}
|
|
@@ -72,6 +65,7 @@ export function EmbedPublishPage({
|
|
|
72
65
|
const [role, setRole] = useState("viewer");
|
|
73
66
|
const [message, setMessage] = useState("");
|
|
74
67
|
const [error, setError] = useState<string | null>(null);
|
|
68
|
+
const [commitFailure, setCommitFailure] = useState<CommitErrorView | null>(null);
|
|
75
69
|
const [committing, setCommitting] = useState(false);
|
|
76
70
|
const [resolvingKey, setResolvingKey] = useState<string | null>(null);
|
|
77
71
|
|
|
@@ -157,6 +151,7 @@ export function EmbedPublishPage({
|
|
|
157
151
|
if (committing || action !== "commit" || changes.length === 0) return;
|
|
158
152
|
setCommitting(true);
|
|
159
153
|
setError(null);
|
|
154
|
+
setCommitFailure(null);
|
|
160
155
|
try {
|
|
161
156
|
const result = await commitWorkspace(workspaceId, message);
|
|
162
157
|
setMessage("");
|
|
@@ -168,7 +163,7 @@ export function EmbedPublishPage({
|
|
|
168
163
|
}
|
|
169
164
|
setGit(await fetchGit(workspaceId));
|
|
170
165
|
} catch (err) {
|
|
171
|
-
|
|
166
|
+
setCommitFailure(explainCommitError(err));
|
|
172
167
|
} finally {
|
|
173
168
|
setCommitting(false);
|
|
174
169
|
}
|
|
@@ -225,18 +220,23 @@ export function EmbedPublishPage({
|
|
|
225
220
|
? "Nothing to publish"
|
|
226
221
|
: `${changes.length} ${changes.length === 1 ? "file" : "files"} → ${branch}`}
|
|
227
222
|
</p>
|
|
223
|
+
{commitFailure ? <CommitFailureNotice failure={commitFailure} /> : null}
|
|
228
224
|
{changes.length > 0 ? (
|
|
229
225
|
<ul {...stylex.props(styles.files)}>
|
|
230
|
-
{changes.map((doc) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
226
|
+
{changes.map((doc) => {
|
|
227
|
+
const invalid =
|
|
228
|
+
problemsForRef(commitFailure, `${doc.type}/${doc.slug}`).length > 0;
|
|
229
|
+
return (
|
|
230
|
+
<li key={changeKey(doc)}>
|
|
231
|
+
<div {...stylex.props(styles.file)}>
|
|
232
|
+
<span {...stylex.props(styles.fileName, invalid && styles.invalidName)}>
|
|
233
|
+
{doc.type}/{doc.slug}
|
|
234
|
+
</span>
|
|
235
|
+
<span {...stylex.props(popup.itemMeta)}>{STATUS_LABEL[doc.status]}</span>
|
|
236
|
+
</div>
|
|
237
|
+
</li>
|
|
238
|
+
);
|
|
239
|
+
})}
|
|
240
240
|
</ul>
|
|
241
241
|
) : null}
|
|
242
242
|
{changes.length > 0 ? <div {...stylex.props(popup.separator)} /> : null}
|
|
@@ -311,6 +311,9 @@ const styles = stylex.create({
|
|
|
311
311
|
textOverflow: "ellipsis",
|
|
312
312
|
whiteSpace: "nowrap",
|
|
313
313
|
},
|
|
314
|
+
invalidName: {
|
|
315
|
+
color: colors.danger,
|
|
316
|
+
},
|
|
314
317
|
footer: {
|
|
315
318
|
display: "flex",
|
|
316
319
|
flexDirection: "column",
|