@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
package/src/pages/BlocksList.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// packages/studio/src/pages/BlocksList.tsx
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { RestrictToVerticalAxis } from "@dnd-kit/abstract/modifiers";
|
|
4
4
|
import { DragDropProvider } from "@dnd-kit/react";
|
|
5
5
|
import { isSortable, useSortable } from "@dnd-kit/react/sortable";
|
|
@@ -92,6 +92,7 @@ function BlockItem({
|
|
|
92
92
|
<div
|
|
93
93
|
ref={ref}
|
|
94
94
|
data-block-item=""
|
|
95
|
+
data-block-id={block.id}
|
|
95
96
|
{...stylex.props(styles.item, isDragging && styles.itemDragging)}
|
|
96
97
|
>
|
|
97
98
|
<div {...stylex.props(styles.itemBar)}>
|
|
@@ -185,14 +186,28 @@ export function BlocksList({
|
|
|
185
186
|
blocks,
|
|
186
187
|
catalog,
|
|
187
188
|
readOnly,
|
|
189
|
+
focusBlock,
|
|
188
190
|
onChange,
|
|
189
191
|
}: {
|
|
190
192
|
blocks: BlockInstance[];
|
|
191
193
|
catalog: BlockSnapshot[];
|
|
192
194
|
readOnly?: boolean;
|
|
195
|
+
focusBlock?: { id: string; nonce: number } | null;
|
|
193
196
|
onChange: (next: BlockInstance[]) => void;
|
|
194
197
|
}) {
|
|
195
198
|
const [openId, setOpenId] = useState<string | null>(null);
|
|
199
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (!focusBlock) return;
|
|
203
|
+
setOpenId(focusBlock.id);
|
|
204
|
+
const id = focusBlock.id;
|
|
205
|
+
const frame = requestAnimationFrame(() => {
|
|
206
|
+
const row = listRef.current?.querySelector(`[data-block-id="${CSS.escape(id)}"]`);
|
|
207
|
+
row?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
208
|
+
});
|
|
209
|
+
return () => cancelAnimationFrame(frame);
|
|
210
|
+
}, [focusBlock]);
|
|
196
211
|
|
|
197
212
|
function add(type: string) {
|
|
198
213
|
const id = crypto.randomUUID();
|
|
@@ -219,7 +234,7 @@ export function BlocksList({
|
|
|
219
234
|
onChange(reorderBlock(blocks, String(source.id), source.index));
|
|
220
235
|
}}
|
|
221
236
|
>
|
|
222
|
-
<div {...stylex.props(styles.list)}>
|
|
237
|
+
<div {...stylex.props(styles.list)} ref={listRef}>
|
|
223
238
|
{blocks.map((block, index) => (
|
|
224
239
|
<BlockItem
|
|
225
240
|
key={block.id}
|
|
@@ -49,9 +49,10 @@ import {
|
|
|
49
49
|
import {
|
|
50
50
|
editorPreviewPath,
|
|
51
51
|
modelForType,
|
|
52
|
-
previewSiteUrl,
|
|
53
|
-
primarySiteUrl,
|
|
54
52
|
onSitePreviewUrl,
|
|
53
|
+
previewEnableUrl,
|
|
54
|
+
previewFrameIdentity,
|
|
55
|
+
primarySiteUrl,
|
|
55
56
|
studioIsOnSite,
|
|
56
57
|
productionDocUrl,
|
|
57
58
|
} from "./preview-url";
|
|
@@ -61,7 +62,16 @@ import { blocksFromFrontmatter } from "./blocks-list";
|
|
|
61
62
|
import type { WorkspaceOutletContext } from "../workspace/types";
|
|
62
63
|
import { contentDocPath, defaultLocaleOf, settingsPath } from "../workspace-path";
|
|
63
64
|
import { bindPreviewEditor, previewFrameTransport } from "./preview-bridge";
|
|
64
|
-
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";
|
|
65
75
|
|
|
66
76
|
const AUTOSAVE_MS = 400;
|
|
67
77
|
const hover = "@media (hover: hover)";
|
|
@@ -214,6 +224,9 @@ export function DocEditorPage() {
|
|
|
214
224
|
const splitRef = useRef<HTMLDivElement>(null);
|
|
215
225
|
const previewFrameRef = useRef<HTMLIFrameElement>(null);
|
|
216
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);
|
|
217
230
|
const navigate = useNavigate();
|
|
218
231
|
const wide = useWideLayout();
|
|
219
232
|
const { setStatus: setEditorStatus, clear: clearEditorStatus } =
|
|
@@ -253,33 +266,39 @@ export function DocEditorPage() {
|
|
|
253
266
|
const detailNames = fieldNames.filter((name) => fieldKinds[name] !== "blocks");
|
|
254
267
|
const blocksField =
|
|
255
268
|
fieldNames.find((name) => fieldKinds[name] === "blocks") ?? "blocks";
|
|
269
|
+
const blocksFieldRef = useRef(blocksField);
|
|
270
|
+
blocksFieldRef.current = blocksField;
|
|
256
271
|
const siteUrl = primarySiteUrl(workspace.siteUrls ?? []);
|
|
257
272
|
const liveUrl = productionDocUrl(siteUrl, modelForType(models, type), locale, slug);
|
|
273
|
+
const previewPath = editorPreviewPath(
|
|
274
|
+
modelForType(models, type),
|
|
275
|
+
slug,
|
|
276
|
+
locale,
|
|
277
|
+
branch,
|
|
278
|
+
tree,
|
|
279
|
+
);
|
|
280
|
+
// Strings only. A replica refresh builds new models and siteUrls arrays
|
|
281
|
+
// with the same contents; that must not mint a ticket and reload the frame.
|
|
282
|
+
const frameIdentity = previewFrameIdentity({
|
|
283
|
+
siteUrl,
|
|
284
|
+
path: showPreview ? previewPath : null,
|
|
285
|
+
branch,
|
|
286
|
+
tree,
|
|
287
|
+
});
|
|
258
288
|
|
|
259
289
|
useEffect(() => {
|
|
260
|
-
if (!
|
|
261
|
-
setPreviewUrl(null);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const redirect = editorPreviewPath(
|
|
265
|
-
modelForType(models, type),
|
|
266
|
-
slug,
|
|
267
|
-
locale,
|
|
268
|
-
branch,
|
|
269
|
-
tree,
|
|
270
|
-
);
|
|
271
|
-
if (!redirect || !workspaceId || !siteUrl) {
|
|
290
|
+
if (!frameIdentity || !previewPath || !workspaceId || !siteUrl) {
|
|
272
291
|
setPreviewUrl(null);
|
|
273
292
|
return;
|
|
274
293
|
}
|
|
275
294
|
if (studioIsOnSite(siteUrl, window.location.origin)) {
|
|
276
|
-
setPreviewUrl(onSitePreviewUrl(siteUrl,
|
|
295
|
+
setPreviewUrl(onSitePreviewUrl(siteUrl, previewPath));
|
|
277
296
|
return;
|
|
278
297
|
}
|
|
279
298
|
let cancelled = false;
|
|
280
|
-
void createPreviewTicket(workspaceId, { redirect, branch, tree })
|
|
299
|
+
void createPreviewTicket(workspaceId, { redirect: previewPath, branch, tree })
|
|
281
300
|
.then((res) => {
|
|
282
|
-
if (!cancelled) setPreviewUrl(
|
|
301
|
+
if (!cancelled) setPreviewUrl(previewEnableUrl(siteUrl, res.ticket));
|
|
283
302
|
})
|
|
284
303
|
.catch(() => {
|
|
285
304
|
if (!cancelled) setPreviewUrl(null);
|
|
@@ -287,7 +306,7 @@ export function DocEditorPage() {
|
|
|
287
306
|
return () => {
|
|
288
307
|
cancelled = true;
|
|
289
308
|
};
|
|
290
|
-
}, [
|
|
309
|
+
}, [frameIdentity, previewPath, workspaceId, siteUrl, branch, tree]);
|
|
291
310
|
|
|
292
311
|
useEffect(() => {
|
|
293
312
|
setPreviewLoading(true);
|
|
@@ -302,6 +321,57 @@ export function DocEditorPage() {
|
|
|
302
321
|
setPreviewLive(null);
|
|
303
322
|
}, [previewUrl]);
|
|
304
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
|
+
|
|
305
375
|
useEffect(() => {
|
|
306
376
|
const frame = previewFrameRef.current;
|
|
307
377
|
if (!frame || !siteUrl) return;
|
|
@@ -318,6 +388,9 @@ export function DocEditorPage() {
|
|
|
318
388
|
ref: () => channelRef.current,
|
|
319
389
|
onOffer: setPreviewOffer,
|
|
320
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),
|
|
321
394
|
});
|
|
322
395
|
previewSession.current = session;
|
|
323
396
|
if (editEpochRef.current > 0) session.setDoc(latest.current);
|
|
@@ -347,6 +420,9 @@ export function DocEditorPage() {
|
|
|
347
420
|
ref: () => channelRef.current,
|
|
348
421
|
onOffer: setPreviewOffer,
|
|
349
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),
|
|
350
426
|
});
|
|
351
427
|
previewSession.current = session;
|
|
352
428
|
if (editEpochRef.current > 0) session.setDoc(latest.current);
|
|
@@ -366,6 +442,28 @@ export function DocEditorPage() {
|
|
|
366
442
|
};
|
|
367
443
|
}, [embed]);
|
|
368
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
|
+
|
|
369
467
|
useEffect(() => {
|
|
370
468
|
if (editEpoch === 0) return;
|
|
371
469
|
previewSession.current?.setDoc(latest.current);
|
|
@@ -539,13 +637,12 @@ export function DocEditorPage() {
|
|
|
539
637
|
setDocStatus(detail.status);
|
|
540
638
|
setSource(detail.status === "clean" ? "head" : "tree");
|
|
541
639
|
}
|
|
542
|
-
// Branch HEAD moved (teammate push / pull / commit). A clean doc reloads
|
|
543
|
-
//
|
|
544
|
-
//
|
|
640
|
+
// Branch HEAD moved (teammate push / pull / commit). A clean doc reloads its
|
|
641
|
+
// fields. The preview frame stays mounted: the page follows the same move
|
|
642
|
+
// on its SSE stream and refreshes in place.
|
|
545
643
|
function onHeadChanged() {
|
|
546
644
|
if (docStatusRef.current === "clean") {
|
|
547
645
|
onChanged();
|
|
548
|
-
remountPreview();
|
|
549
646
|
return;
|
|
550
647
|
}
|
|
551
648
|
void fetchDoc(workspaceId, locale, type, slug)
|
|
@@ -1062,6 +1159,7 @@ export function DocEditorPage() {
|
|
|
1062
1159
|
blocks={blocksFromFrontmatter(frontmatter[blocksField])}
|
|
1063
1160
|
catalog={blocks}
|
|
1064
1161
|
readOnly={aiEditing || parseFailed}
|
|
1162
|
+
focusBlock={blockFocus}
|
|
1065
1163
|
onChange={(next) => {
|
|
1066
1164
|
markEdited();
|
|
1067
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",
|