@backmatter/studio 0.0.17 → 0.0.18
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-D7vllRQE.js → UiKitApp-DkuQBiQZ.js} +2 -2
- package/dist/chunks/{WorkspaceRoutes-DH1LD-2Q.js → WorkspaceRoutes-DVvb3N-V.js} +21440 -21060
- package/dist/chunks/{dashboard-Dsaeud2x.js → dashboard-B4FITg6c.js} +2 -2
- package/dist/chunks/{index-CGMqw2NH.js → index-CegtXnEI.js} +1 -1
- package/dist/dashboard.js +2 -2
- package/dist/index.js +2 -2
- package/dist/studio.css +20 -14
- package/package.json +2 -2
- package/src/api.ts +3 -0
- package/src/{pages/code-session-routes.ts → content-routes.ts} +6 -6
- package/src/fields/FieldControl.tsx +25 -2
- package/src/fields/LinkFieldInput.tsx +315 -0
- package/src/fields/choose-field-input.ts +5 -0
- package/src/fields/link-targets.ts +60 -0
- package/src/host-path.ts +13 -0
- package/src/pages/CodeSessionPage.tsx +6 -241
- package/src/pages/DocEditorPage.tsx +81 -121
- package/src/pages/MediaFieldInput.tsx +609 -26
- package/src/pages/MediaGalleryPage.tsx +14 -3
- package/src/pages/PreviewAddress.tsx +263 -0
- package/src/pages/code-session-boot.ts +0 -12
- package/src/pages/media-selection.ts +79 -0
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
// packages/studio/src/pages/CodeSessionPage.tsx
|
|
2
|
-
import { useCallback, useEffect, useRef, useState
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import type { RouteModel } from "@backmatter/core";
|
|
4
4
|
import { useOutletContext } from "react-router";
|
|
5
5
|
import * as stylex from "@stylexjs/stylex";
|
|
6
|
-
import { Combobox } from "@base-ui/react/combobox";
|
|
7
6
|
import {
|
|
8
|
-
IconArrowRotateClockwise,
|
|
9
|
-
IconArrowUpRight,
|
|
10
7
|
IconCheckmark1Small,
|
|
11
8
|
IconCrossSmall,
|
|
12
9
|
IconEyeOpen,
|
|
13
10
|
IconFiles,
|
|
14
|
-
IconGlobe,
|
|
15
11
|
} from "@central-icons-react/round-outlined-radius-2-stroke-1.5";
|
|
16
12
|
import { ChatPanel } from "../chat/ChatPanel";
|
|
17
13
|
import { ChatProvider, ChatSessionScope, useChatBusy } from "../chat/ChatProvider";
|
|
18
14
|
import { useReplicaState } from "../replica/useReplica";
|
|
15
|
+
import { routesForContentFiles } from "../content-routes";
|
|
19
16
|
import { colors, radius, space, type } from "../styles/tokens.stylex";
|
|
20
17
|
import { apiCredentials, apiHeaders, apiUrl } from "../studio-config";
|
|
21
18
|
import { Text } from "../ui/Text";
|
|
22
|
-
import { cx, popup, popupClassName } from "../ui/popup";
|
|
23
19
|
import type { WorkspaceOutletContext } from "../workspace/types";
|
|
24
|
-
import { bootSteps,
|
|
20
|
+
import { bootSteps, type BootStep, type HostPhase } from "./code-session-boot";
|
|
25
21
|
import {
|
|
26
22
|
frameAddress,
|
|
27
23
|
framedLocation,
|
|
@@ -30,7 +26,7 @@ import {
|
|
|
30
26
|
previewUrl,
|
|
31
27
|
type HostPreview,
|
|
32
28
|
} from "./code-session-frame";
|
|
33
|
-
import {
|
|
29
|
+
import { PreviewAddress } from "./PreviewAddress";
|
|
34
30
|
import { CodeSessionFiles, type FilesView } from "./CodeSessionFiles";
|
|
35
31
|
import { PreviewSizeSwitch, PreviewViewport } from "./PreviewSize";
|
|
36
32
|
import { persistPreviewSize, readPreviewSize, type PreviewSize } from "./preview-size";
|
|
@@ -194,7 +190,7 @@ export function CodeSessionScreen({
|
|
|
194
190
|
</TabButton>
|
|
195
191
|
</div>
|
|
196
192
|
|
|
197
|
-
<
|
|
193
|
+
<PreviewAddress
|
|
198
194
|
path={location}
|
|
199
195
|
enabled={frameSrc !== null && tab === "preview"}
|
|
200
196
|
routes={routes}
|
|
@@ -324,170 +320,6 @@ function TabButton({
|
|
|
324
320
|
);
|
|
325
321
|
}
|
|
326
322
|
|
|
327
|
-
function AddressBar({
|
|
328
|
-
path,
|
|
329
|
-
enabled,
|
|
330
|
-
routes,
|
|
331
|
-
routesPending,
|
|
332
|
-
onOpenRoutes,
|
|
333
|
-
onPath,
|
|
334
|
-
onReload,
|
|
335
|
-
onOpen,
|
|
336
|
-
}: {
|
|
337
|
-
path: string;
|
|
338
|
-
enabled: boolean;
|
|
339
|
-
routes: readonly string[];
|
|
340
|
-
routesPending: boolean;
|
|
341
|
-
onOpenRoutes?: () => void;
|
|
342
|
-
onPath: (path: string) => void;
|
|
343
|
-
onReload: () => void;
|
|
344
|
-
onOpen: () => void;
|
|
345
|
-
}) {
|
|
346
|
-
const [query, setQuery] = useState<string | null>(null);
|
|
347
|
-
const [open, setOpen] = useState(false);
|
|
348
|
-
const picked = useRef(false);
|
|
349
|
-
const fieldRef = useRef<HTMLButtonElement>(null);
|
|
350
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
351
|
-
const shown = query ? matchingRoutes(routes, query) : [...routes];
|
|
352
|
-
|
|
353
|
-
useEffect(() => {
|
|
354
|
-
if (!open) return;
|
|
355
|
-
const input = inputRef.current;
|
|
356
|
-
input?.focus();
|
|
357
|
-
input?.select();
|
|
358
|
-
}, [open]);
|
|
359
|
-
|
|
360
|
-
return (
|
|
361
|
-
<div {...stylex.props(styles.address)}>
|
|
362
|
-
<button
|
|
363
|
-
type="button"
|
|
364
|
-
aria-label="Reload preview"
|
|
365
|
-
disabled={!enabled}
|
|
366
|
-
onClick={onReload}
|
|
367
|
-
{...stylex.props(styles.iconButton)}
|
|
368
|
-
>
|
|
369
|
-
<IconArrowRotateClockwise size={14} ariaHidden />
|
|
370
|
-
</button>
|
|
371
|
-
<Combobox.Root
|
|
372
|
-
items={shown}
|
|
373
|
-
filteredItems={shown}
|
|
374
|
-
inputValue={query ?? ""}
|
|
375
|
-
disabled={!enabled}
|
|
376
|
-
open={open}
|
|
377
|
-
value={routes.includes(path) ? path : null}
|
|
378
|
-
onOpenChange={(next) => {
|
|
379
|
-
if (!enabled && next) return;
|
|
380
|
-
setOpen(next);
|
|
381
|
-
if (next) onOpenRoutes?.();
|
|
382
|
-
else setQuery(null);
|
|
383
|
-
}}
|
|
384
|
-
onInputValueChange={(value, details) => {
|
|
385
|
-
if (details.reason === "input-change" || details.reason === "input-clear") {
|
|
386
|
-
setQuery(value);
|
|
387
|
-
}
|
|
388
|
-
}}
|
|
389
|
-
onValueChange={(value) => {
|
|
390
|
-
if (typeof value !== "string") return;
|
|
391
|
-
picked.current = true;
|
|
392
|
-
setQuery(null);
|
|
393
|
-
onPath(hostPath(value));
|
|
394
|
-
queueMicrotask(() => {
|
|
395
|
-
picked.current = false;
|
|
396
|
-
});
|
|
397
|
-
}}
|
|
398
|
-
>
|
|
399
|
-
<Combobox.Trigger
|
|
400
|
-
ref={fieldRef}
|
|
401
|
-
aria-label="Preview path"
|
|
402
|
-
disabled={!enabled}
|
|
403
|
-
data-no-ring
|
|
404
|
-
className={cx(styles.addressTrigger)}
|
|
405
|
-
>
|
|
406
|
-
<IconGlobe size={13} ariaHidden />
|
|
407
|
-
<span {...stylex.props(styles.addressPath)}>{path}</span>
|
|
408
|
-
</Combobox.Trigger>
|
|
409
|
-
<Combobox.Portal>
|
|
410
|
-
<Combobox.Positioner
|
|
411
|
-
anchor={fieldRef}
|
|
412
|
-
side="bottom"
|
|
413
|
-
align="start"
|
|
414
|
-
sideOffset={6}
|
|
415
|
-
className={cx(popup.positioner)}
|
|
416
|
-
>
|
|
417
|
-
<Combobox.Popup
|
|
418
|
-
aria-label="Pages"
|
|
419
|
-
className={(state) => popupClassName(state, styles.routePopup)}
|
|
420
|
-
>
|
|
421
|
-
<form
|
|
422
|
-
onSubmit={(event: FormEvent<HTMLFormElement>) => {
|
|
423
|
-
event.preventDefault();
|
|
424
|
-
const fromList = picked.current;
|
|
425
|
-
picked.current = false;
|
|
426
|
-
if (fromList) return;
|
|
427
|
-
const typed = event.currentTarget.querySelector("input")?.value;
|
|
428
|
-
if (typed?.trim()) onPath(hostPath(typed));
|
|
429
|
-
setOpen(false);
|
|
430
|
-
setQuery(null);
|
|
431
|
-
}}
|
|
432
|
-
>
|
|
433
|
-
<Combobox.Input
|
|
434
|
-
ref={inputRef}
|
|
435
|
-
placeholder="Find a page"
|
|
436
|
-
aria-label="Find a page"
|
|
437
|
-
spellCheck={false}
|
|
438
|
-
autoComplete="off"
|
|
439
|
-
data-no-ring
|
|
440
|
-
className={cx(popup.quietInput, styles.routeSearch)}
|
|
441
|
-
onKeyDown={(event) => {
|
|
442
|
-
if (event.key !== "Enter") return;
|
|
443
|
-
// A highlighted row is chosen by the combobox. A bare Enter goes to the typed path.
|
|
444
|
-
if (event.currentTarget.getAttribute("aria-activedescendant")) return;
|
|
445
|
-
const typed = event.currentTarget.value.trim();
|
|
446
|
-
if (!typed) return;
|
|
447
|
-
picked.current = true;
|
|
448
|
-
onPath(hostPath(typed));
|
|
449
|
-
}}
|
|
450
|
-
/>
|
|
451
|
-
</form>
|
|
452
|
-
<div {...stylex.props(popup.separator)} />
|
|
453
|
-
<Combobox.Empty className={cx(popup.empty)}>
|
|
454
|
-
{routesPending ? "Loading pages" : "No pages"}
|
|
455
|
-
</Combobox.Empty>
|
|
456
|
-
<Combobox.List className={cx(styles.routeList)}>
|
|
457
|
-
{(route: string) => (
|
|
458
|
-
<Combobox.Item
|
|
459
|
-
key={route}
|
|
460
|
-
value={route}
|
|
461
|
-
className={(state) =>
|
|
462
|
-
cx(
|
|
463
|
-
popup.item,
|
|
464
|
-
styles.routeItem,
|
|
465
|
-
state.highlighted && popup.itemHighlighted,
|
|
466
|
-
state.selected && popup.itemSelected,
|
|
467
|
-
)
|
|
468
|
-
}
|
|
469
|
-
>
|
|
470
|
-
{route}
|
|
471
|
-
</Combobox.Item>
|
|
472
|
-
)}
|
|
473
|
-
</Combobox.List>
|
|
474
|
-
</Combobox.Popup>
|
|
475
|
-
</Combobox.Positioner>
|
|
476
|
-
</Combobox.Portal>
|
|
477
|
-
</Combobox.Root>
|
|
478
|
-
<button
|
|
479
|
-
type="button"
|
|
480
|
-
aria-label="Open preview in a new tab"
|
|
481
|
-
disabled={!enabled}
|
|
482
|
-
onClick={onOpen}
|
|
483
|
-
{...stylex.props(styles.iconButton)}
|
|
484
|
-
>
|
|
485
|
-
<IconArrowUpRight size={14} ariaHidden />
|
|
486
|
-
</button>
|
|
487
|
-
</div>
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
323
|
function BootCard({
|
|
492
324
|
title,
|
|
493
325
|
steps,
|
|
@@ -920,7 +752,7 @@ export default function CodeSessionPage() {
|
|
|
920
752
|
live={live}
|
|
921
753
|
onReload={() => navigate(location)}
|
|
922
754
|
onOpen={() => void openInNewTab()}
|
|
923
|
-
routes={
|
|
755
|
+
routes={routesForContentFiles(models, fileList ?? [])}
|
|
924
756
|
routesPending={routesPending}
|
|
925
757
|
onOpenRoutes={openRoutes}
|
|
926
758
|
files={{
|
|
@@ -1161,76 +993,9 @@ const styles = stylex.create({
|
|
|
1161
993
|
backgroundColor: colors.bgSurface,
|
|
1162
994
|
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.08)",
|
|
1163
995
|
},
|
|
1164
|
-
address: {
|
|
1165
|
-
display: "flex",
|
|
1166
|
-
alignItems: "center",
|
|
1167
|
-
gap: space.xs,
|
|
1168
|
-
minWidth: 0,
|
|
1169
|
-
},
|
|
1170
996
|
sizeSwitch: {
|
|
1171
997
|
justifySelf: "end",
|
|
1172
998
|
},
|
|
1173
|
-
addressTrigger: {
|
|
1174
|
-
display: "flex",
|
|
1175
|
-
flex: 1,
|
|
1176
|
-
alignItems: "center",
|
|
1177
|
-
gap: space.xs,
|
|
1178
|
-
minWidth: 0,
|
|
1179
|
-
height: "28px",
|
|
1180
|
-
paddingInline: space.sm,
|
|
1181
|
-
borderWidth: 0,
|
|
1182
|
-
borderRadius: "999px",
|
|
1183
|
-
font: "inherit",
|
|
1184
|
-
textAlign: "left",
|
|
1185
|
-
cursor: { default: "pointer", ":disabled": "default" },
|
|
1186
|
-
color: { default: colors.textMuted, ":disabled": colors.textMuted },
|
|
1187
|
-
backgroundColor: colors.bgPanelInset,
|
|
1188
|
-
},
|
|
1189
|
-
addressPath: {
|
|
1190
|
-
flex: 1,
|
|
1191
|
-
minWidth: 0,
|
|
1192
|
-
overflow: "hidden",
|
|
1193
|
-
textOverflow: "ellipsis",
|
|
1194
|
-
whiteSpace: "nowrap",
|
|
1195
|
-
fontFamily: type.mono,
|
|
1196
|
-
fontSize: type.xs,
|
|
1197
|
-
color: colors.text,
|
|
1198
|
-
},
|
|
1199
|
-
routeSearch: {
|
|
1200
|
-
fontFamily: type.mono,
|
|
1201
|
-
fontSize: type.xs,
|
|
1202
|
-
},
|
|
1203
|
-
routePopup: {
|
|
1204
|
-
width: "var(--anchor-width)",
|
|
1205
|
-
minWidth: "220px",
|
|
1206
|
-
maxHeight: "min(320px, var(--available-height))",
|
|
1207
|
-
display: "flex",
|
|
1208
|
-
flexDirection: "column",
|
|
1209
|
-
overflowY: "hidden",
|
|
1210
|
-
},
|
|
1211
|
-
routeList: {
|
|
1212
|
-
minHeight: 0,
|
|
1213
|
-
overflowY: "auto",
|
|
1214
|
-
},
|
|
1215
|
-
routeItem: {
|
|
1216
|
-
fontFamily: type.mono,
|
|
1217
|
-
},
|
|
1218
|
-
iconButton: {
|
|
1219
|
-
display: "inline-flex",
|
|
1220
|
-
alignItems: "center",
|
|
1221
|
-
justifyContent: "center",
|
|
1222
|
-
flexShrink: 0,
|
|
1223
|
-
width: "28px",
|
|
1224
|
-
height: "28px",
|
|
1225
|
-
borderWidth: 0,
|
|
1226
|
-
borderRadius: radius.sm,
|
|
1227
|
-
cursor: { default: "pointer", ":disabled": "default" },
|
|
1228
|
-
color: { default: colors.textMuted, ":disabled": colors.border },
|
|
1229
|
-
backgroundColor: {
|
|
1230
|
-
default: "transparent",
|
|
1231
|
-
[hover]: { default: null, ":hover:not(:disabled)": colors.bgHover },
|
|
1232
|
-
},
|
|
1233
|
-
},
|
|
1234
999
|
canvas: {
|
|
1235
1000
|
position: "relative",
|
|
1236
1001
|
display: "flex",
|
|
@@ -3,6 +3,7 @@ import { Snake } from "loading-dev";
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useOutletContext, useParams, useSearchParams, Link, useNavigate } from "react-router";
|
|
5
5
|
import * as stylex from "@stylexjs/stylex";
|
|
6
|
+
import { matchRoute, repoPathFromModel } from "@backmatter/core";
|
|
6
7
|
import {
|
|
7
8
|
fetchDoc,
|
|
8
9
|
fetchDocs,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
import { FieldControl } from "../fields/FieldControl";
|
|
22
23
|
import { loadRelationDocuments } from "../fields/load-relation-documents";
|
|
23
24
|
import { omitField } from "../fields/omit-field";
|
|
25
|
+
import { linkDocumentTypes } from "../fields/link-targets";
|
|
24
26
|
import { relationTargets } from "../fields/relation-targets";
|
|
25
27
|
import { useReplicaState } from "../replica/useReplica";
|
|
26
28
|
import { useSetEditorActions } from "../chrome/EditorActionsContext";
|
|
@@ -34,7 +36,6 @@ import {
|
|
|
34
36
|
} from "../chrome/EditorFocusContext";
|
|
35
37
|
import { useSetEditorStatus } from "../chrome/EditorStatusContext";
|
|
36
38
|
import { modifierSymbol } from "../chrome/hotkeys";
|
|
37
|
-
import { ReloadIcon } from "../chrome/icons";
|
|
38
39
|
import { colors, radius, space, type } from "../styles/tokens.stylex";
|
|
39
40
|
import { Button, ButtonLink } from "../ui/Button";
|
|
40
41
|
import { ConfirmDialog } from "../ui/ConfirmDialog";
|
|
@@ -50,6 +51,8 @@ import {
|
|
|
50
51
|
persistPreviewPct,
|
|
51
52
|
readPreviewPct,
|
|
52
53
|
} from "./preview-split";
|
|
54
|
+
import { routesForContentFiles } from "../content-routes";
|
|
55
|
+
import { PreviewAddress } from "./PreviewAddress";
|
|
53
56
|
import { PreviewSizeSwitch, PreviewViewport } from "./PreviewSize";
|
|
54
57
|
import { persistPreviewSize, readPreviewSize, type PreviewSize } from "./preview-size";
|
|
55
58
|
import {
|
|
@@ -82,10 +85,6 @@ import {
|
|
|
82
85
|
const AUTOSAVE_MS = 400;
|
|
83
86
|
const hover = "@media (hover: hover)";
|
|
84
87
|
|
|
85
|
-
const spin = stylex.keyframes({
|
|
86
|
-
to: { transform: "rotate(360deg)" },
|
|
87
|
-
});
|
|
88
|
-
|
|
89
88
|
const pulse = stylex.keyframes({
|
|
90
89
|
"0%": { opacity: 0.4 },
|
|
91
90
|
"50%": { opacity: 1 },
|
|
@@ -183,6 +182,9 @@ export function DocEditorPage() {
|
|
|
183
182
|
for (const type of relationTargets(fields, models, objects)) {
|
|
184
183
|
targets.add(type);
|
|
185
184
|
}
|
|
185
|
+
for (const type of linkDocumentTypes(fields, models, objects)) {
|
|
186
|
+
targets.add(type);
|
|
187
|
+
}
|
|
186
188
|
}
|
|
187
189
|
return [...targets].sort();
|
|
188
190
|
}, [model, blocks, richtext, models, objects]);
|
|
@@ -246,6 +248,9 @@ export function DocEditorPage() {
|
|
|
246
248
|
editEpochRef.current = editEpoch;
|
|
247
249
|
|
|
248
250
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
|
251
|
+
const docIdentity = `${type}\0${slug}`;
|
|
252
|
+
const [browse, setBrowse] = useState<{ doc: string; path: string } | null>(null);
|
|
253
|
+
const browsedPath = browse?.doc === docIdentity ? browse.path : null;
|
|
249
254
|
const isPage = docFormat === "page";
|
|
250
255
|
const detailNames = fieldNames.filter((name) => fieldKinds[name] !== "blocks");
|
|
251
256
|
const blocksField =
|
|
@@ -268,18 +273,28 @@ export function DocEditorPage() {
|
|
|
268
273
|
branch,
|
|
269
274
|
tree,
|
|
270
275
|
});
|
|
276
|
+
const framePath = browsedPath ?? previewPath;
|
|
277
|
+
// Each document is the content file its model path names. A deleted document is gone.
|
|
278
|
+
const previewRoutes = routesForContentFiles(
|
|
279
|
+
models,
|
|
280
|
+
replica.docs(workspaceId).flatMap((doc) => {
|
|
281
|
+
if (doc.status === "deleted") return [];
|
|
282
|
+
const model = models.find((item) => item.type === doc.type);
|
|
283
|
+
return model ? [repoPathFromModel(model, doc.slug)] : [];
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
271
286
|
|
|
272
287
|
useEffect(() => {
|
|
273
|
-
if (!frameIdentity || !
|
|
288
|
+
if (!frameIdentity || !framePath || !workspaceId || !siteUrl) {
|
|
274
289
|
setPreviewUrl(null);
|
|
275
290
|
return;
|
|
276
291
|
}
|
|
277
292
|
if (studioIsOnSite(siteUrl, window.location.origin)) {
|
|
278
|
-
setPreviewUrl(onSitePreviewUrl(siteUrl,
|
|
293
|
+
setPreviewUrl(onSitePreviewUrl(siteUrl, framePath));
|
|
279
294
|
return;
|
|
280
295
|
}
|
|
281
296
|
let cancelled = false;
|
|
282
|
-
void createPreviewTicket(workspaceId, { redirect:
|
|
297
|
+
void createPreviewTicket(workspaceId, { redirect: framePath, branch, tree })
|
|
283
298
|
.then((res) => {
|
|
284
299
|
if (!cancelled) setPreviewUrl(previewEnableUrl(siteUrl, res.ticket));
|
|
285
300
|
})
|
|
@@ -289,7 +304,7 @@ export function DocEditorPage() {
|
|
|
289
304
|
return () => {
|
|
290
305
|
cancelled = true;
|
|
291
306
|
};
|
|
292
|
-
}, [frameIdentity,
|
|
307
|
+
}, [frameIdentity, framePath, workspaceId, siteUrl, branch, tree]);
|
|
293
308
|
|
|
294
309
|
useEffect(() => {
|
|
295
310
|
if (relationTargetTypes.length === 0) {
|
|
@@ -488,6 +503,17 @@ export function DocEditorPage() {
|
|
|
488
503
|
setPreviewKey((k) => k + 1);
|
|
489
504
|
}
|
|
490
505
|
|
|
506
|
+
function onPreviewPath(next: string) {
|
|
507
|
+
const pathname = next.split(/[?#]/, 1)[0] || "/";
|
|
508
|
+
const doc = matchRoute(models, pathname);
|
|
509
|
+
if (doc && (doc.type !== type || doc.slug !== slug)) {
|
|
510
|
+
navigate(contentDocPath(workspace.slug, doc.type, doc.slug));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (next === framePath) remountPreview();
|
|
514
|
+
setBrowse({ doc: docIdentity, path: next });
|
|
515
|
+
}
|
|
516
|
+
|
|
491
517
|
function choosePreviewSize(size: PreviewSize) {
|
|
492
518
|
setPreviewSize(size);
|
|
493
519
|
persistPreviewSize(size);
|
|
@@ -881,32 +907,7 @@ export function DocEditorPage() {
|
|
|
881
907
|
style={{ ...previewSx.style, flexBasis: `${previewPct}%` }}
|
|
882
908
|
>
|
|
883
909
|
<div {...stylex.props(styles.previewBar)}>
|
|
884
|
-
<div {...stylex.props(styles.
|
|
885
|
-
<Tooltip label={previewLoading ? "Loading preview…" : "Reload preview"}>
|
|
886
|
-
<button
|
|
887
|
-
type="button"
|
|
888
|
-
aria-label={previewLoading ? "Preview loading" : "Reload preview"}
|
|
889
|
-
aria-busy={previewLoading || undefined}
|
|
890
|
-
onClick={remountPreview}
|
|
891
|
-
{...stylex.props(styles.reloadBtn)}
|
|
892
|
-
>
|
|
893
|
-
<span
|
|
894
|
-
{...stylex.props(
|
|
895
|
-
styles.reloadIcon,
|
|
896
|
-
previewLoading && styles.reloadSpinning,
|
|
897
|
-
)}
|
|
898
|
-
>
|
|
899
|
-
<ReloadIcon />
|
|
900
|
-
</span>
|
|
901
|
-
</button>
|
|
902
|
-
</Tooltip>
|
|
903
|
-
<span {...stylex.props(styles.url)} title={previewUrl ?? undefined}>
|
|
904
|
-
{previewUrl
|
|
905
|
-
? previewUrl.replace(/^https?:\/\//, "")
|
|
906
|
-
: !siteUrl
|
|
907
|
-
? "No site URL"
|
|
908
|
-
: "Loading checkout…"}
|
|
909
|
-
</span>
|
|
910
|
+
<div {...stylex.props(styles.previewLead)}>
|
|
910
911
|
{previewLive === "rendered" ? (
|
|
911
912
|
<span {...stylex.props(styles.previewLive)}>Updated</span>
|
|
912
913
|
) : null}
|
|
@@ -927,15 +928,20 @@ export function DocEditorPage() {
|
|
|
927
928
|
</button>
|
|
928
929
|
) : null}
|
|
929
930
|
</div>
|
|
930
|
-
<
|
|
931
|
-
|
|
932
|
-
{
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
{
|
|
938
|
-
|
|
931
|
+
<PreviewAddress
|
|
932
|
+
path={framePath ?? (!siteUrl ? "No site URL" : "Loading checkout…")}
|
|
933
|
+
enabled={previewUrl !== null}
|
|
934
|
+
routes={previewRoutes}
|
|
935
|
+
routesPending={!replica.ready}
|
|
936
|
+
onPath={onPreviewPath}
|
|
937
|
+
onReload={remountPreview}
|
|
938
|
+
onOpen={() => {
|
|
939
|
+
if (previewUrl) window.open(previewUrl, "_blank", "noopener,noreferrer");
|
|
940
|
+
}}
|
|
941
|
+
/>
|
|
942
|
+
<div {...stylex.props(styles.sizeSwitch)}>
|
|
943
|
+
<PreviewSizeSwitch size={previewSize} onSize={choosePreviewSize} />
|
|
944
|
+
</div>
|
|
939
945
|
</div>
|
|
940
946
|
{previewUrl ? (
|
|
941
947
|
<PreviewViewport size={previewSize} dragging={dragging}>
|
|
@@ -1000,7 +1006,7 @@ export function DocEditorPage() {
|
|
|
1000
1006
|
<div {...stylex.props(styles.editorColumn, !showPreview && styles.editorColumnSolo)}>
|
|
1001
1007
|
<div {...stylex.props(styles.toolbar)}>
|
|
1002
1008
|
<div {...stylex.props(styles.meta)}>
|
|
1003
|
-
<Text as="h1" size="
|
|
1009
|
+
<Text as="h1" size="sm" weight="medium" truncate>
|
|
1004
1010
|
{type}/{slug}
|
|
1005
1011
|
</Text>
|
|
1006
1012
|
</div>
|
|
@@ -1230,74 +1236,34 @@ const styles = stylex.create({
|
|
|
1230
1236
|
flexShrink: 0,
|
|
1231
1237
|
flexDirection: "column",
|
|
1232
1238
|
overflow: "hidden",
|
|
1233
|
-
backgroundColor: colors.
|
|
1239
|
+
backgroundColor: colors.bgApp,
|
|
1234
1240
|
},
|
|
1235
1241
|
previewBar: {
|
|
1236
|
-
display: "
|
|
1237
|
-
|
|
1242
|
+
display: "grid",
|
|
1243
|
+
gridTemplateColumns: "1fr minmax(0, 520px) 1fr",
|
|
1238
1244
|
alignItems: "center",
|
|
1239
|
-
|
|
1245
|
+
flexShrink: 0,
|
|
1240
1246
|
gap: space.md,
|
|
1247
|
+
height: "44px",
|
|
1248
|
+
boxSizing: "border-box",
|
|
1241
1249
|
paddingInline: space.md,
|
|
1242
|
-
paddingBlock: space.sm,
|
|
1243
1250
|
borderBottomWidth: 1,
|
|
1244
1251
|
borderBottomStyle: "solid",
|
|
1245
1252
|
borderBottomColor: colors.border,
|
|
1253
|
+
backgroundColor: colors.bgSurface,
|
|
1246
1254
|
fontSize: type.xs,
|
|
1247
1255
|
color: colors.textMuted,
|
|
1248
1256
|
},
|
|
1249
|
-
|
|
1257
|
+
previewLead: {
|
|
1250
1258
|
display: "flex",
|
|
1251
|
-
minWidth: 0,
|
|
1252
|
-
flex: 1,
|
|
1253
1259
|
alignItems: "center",
|
|
1254
1260
|
gap: space.sm,
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
display: "inline-flex",
|
|
1258
|
-
flexShrink: 0,
|
|
1259
|
-
alignItems: "center",
|
|
1260
|
-
justifyContent: "center",
|
|
1261
|
-
width: 22,
|
|
1262
|
-
height: 22,
|
|
1263
|
-
padding: 0,
|
|
1264
|
-
borderWidth: 0,
|
|
1265
|
-
borderRadius: radius.sm,
|
|
1266
|
-
backgroundColor: "transparent",
|
|
1267
|
-
color: colors.textMuted,
|
|
1268
|
-
cursor: "pointer",
|
|
1269
|
-
touchAction: "manipulation",
|
|
1270
|
-
[hover]: {
|
|
1271
|
-
":hover": {
|
|
1272
|
-
backgroundColor: colors.bgHover,
|
|
1273
|
-
color: colors.text,
|
|
1274
|
-
},
|
|
1275
|
-
},
|
|
1276
|
-
":focus-visible": {
|
|
1277
|
-
outlineWidth: 2,
|
|
1278
|
-
outlineStyle: "solid",
|
|
1279
|
-
outlineColor: colors.focus,
|
|
1280
|
-
outlineOffset: 1,
|
|
1281
|
-
},
|
|
1282
|
-
},
|
|
1283
|
-
reloadIcon: {
|
|
1284
|
-
display: "inline-flex",
|
|
1285
|
-
},
|
|
1286
|
-
reloadSpinning: {
|
|
1287
|
-
animationName: spin,
|
|
1288
|
-
animationDuration: "700ms",
|
|
1289
|
-
animationTimingFunction: "linear",
|
|
1290
|
-
animationIterationCount: "infinite",
|
|
1291
|
-
"@media (prefers-reduced-motion: reduce)": {
|
|
1292
|
-
animationName: "none",
|
|
1293
|
-
animationDuration: "0s",
|
|
1294
|
-
},
|
|
1295
|
-
},
|
|
1296
|
-
url: {
|
|
1261
|
+
minWidth: 0,
|
|
1262
|
+
justifySelf: "start",
|
|
1297
1263
|
overflow: "hidden",
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1264
|
+
},
|
|
1265
|
+
sizeSwitch: {
|
|
1266
|
+
justifySelf: "end",
|
|
1301
1267
|
},
|
|
1302
1268
|
previewLive: {
|
|
1303
1269
|
flexShrink: 0,
|
|
@@ -1310,28 +1276,21 @@ const styles = stylex.create({
|
|
|
1310
1276
|
editPage: {
|
|
1311
1277
|
flexShrink: 0,
|
|
1312
1278
|
borderWidth: 0,
|
|
1313
|
-
borderRadius:
|
|
1279
|
+
borderRadius: radius.sm,
|
|
1314
1280
|
backgroundColor: colors.bgHover,
|
|
1315
1281
|
color: colors.text,
|
|
1316
1282
|
cursor: "pointer",
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
},
|
|
1321
|
-
badge: {
|
|
1322
|
-
flexShrink: 0,
|
|
1323
|
-
borderWidth: 1,
|
|
1324
|
-
borderStyle: "solid",
|
|
1325
|
-
borderColor: colors.border,
|
|
1326
|
-
borderRadius: 4,
|
|
1283
|
+
font: "inherit",
|
|
1284
|
+
fontSize: type.xs,
|
|
1285
|
+
fontWeight: 500,
|
|
1327
1286
|
paddingInline: space.sm,
|
|
1328
|
-
paddingBlock:
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1287
|
+
paddingBlock: "2px",
|
|
1288
|
+
":focus-visible": {
|
|
1289
|
+
outlineWidth: 2,
|
|
1290
|
+
outlineStyle: "solid",
|
|
1291
|
+
outlineColor: colors.focus,
|
|
1292
|
+
outlineOffset: 1,
|
|
1293
|
+
},
|
|
1335
1294
|
},
|
|
1336
1295
|
conflictBanner: {
|
|
1337
1296
|
display: "flex",
|
|
@@ -1454,22 +1413,23 @@ const styles = stylex.create({
|
|
|
1454
1413
|
toolbar: {
|
|
1455
1414
|
display: "flex",
|
|
1456
1415
|
flexShrink: 0,
|
|
1457
|
-
flexWrap: "wrap",
|
|
1458
1416
|
alignItems: "center",
|
|
1459
1417
|
justifyContent: "space-between",
|
|
1460
|
-
gap: space.
|
|
1461
|
-
|
|
1462
|
-
|
|
1418
|
+
gap: space.md,
|
|
1419
|
+
height: "44px",
|
|
1420
|
+
boxSizing: "border-box",
|
|
1421
|
+
paddingInline: space.md,
|
|
1463
1422
|
borderBottomWidth: 1,
|
|
1464
1423
|
borderBottomStyle: "solid",
|
|
1465
1424
|
borderBottomColor: colors.border,
|
|
1425
|
+
backgroundColor: colors.bgSurface,
|
|
1466
1426
|
},
|
|
1467
1427
|
meta: {
|
|
1468
1428
|
minWidth: 0,
|
|
1469
1429
|
},
|
|
1470
1430
|
actions: {
|
|
1471
1431
|
display: "flex",
|
|
1472
|
-
|
|
1432
|
+
flexShrink: 0,
|
|
1473
1433
|
gap: space.sm,
|
|
1474
1434
|
},
|
|
1475
1435
|
formWrap: {
|