@backmatter/studio 0.0.7 → 0.0.9
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-b8qFsmA-.js +2401 -0
- package/dist/chunks/WorkspaceRoutes-BKKEkAq7.js +90126 -0
- package/dist/chunks/{dashboard-DCHRI0WD.js → dashboard-BNUcbYU0.js} +380 -382
- package/dist/chunks/{index-BS0-Bvbk.js → index-laWAz5_p.js} +111 -112
- package/dist/dashboard.js +2 -2
- package/dist/index.js +287 -146
- package/dist/site-widget.css +1615 -0
- package/dist/site-widget.js +30896 -630
- package/dist/studio.css +303 -98
- package/package.json +4 -4
- package/src/App.tsx +12 -29
- package/src/api.ts +55 -0
- package/src/chat/ChangeCard.tsx +2 -0
- package/src/chat/ChatMessage.tsx +73 -62
- package/src/chat/ChatPanel.tsx +110 -15
- package/src/chat/DocListCard.tsx +2 -0
- package/src/chat/LiveStatus.tsx +78 -8
- package/src/chat/Reveal.tsx +34 -0
- package/src/chat/Shimmer.tsx +65 -23
- package/src/chat/chat-helpers.ts +35 -0
- package/src/chat/motion.ts +73 -0
- package/src/chat/useStickToBottom.ts +129 -14
- package/src/chrome/AppShell.tsx +47 -10
- package/src/chrome/AppSidebar.tsx +9 -34
- package/src/chrome/ContentExplorerSidebar.tsx +149 -96
- package/src/chrome/UserMenu.tsx +3 -1
- package/src/chrome/content-tree-visibility.ts +55 -0
- package/src/chrome/explorer-icons.tsx +2 -4
- package/src/devbar-state.ts +125 -0
- package/src/index.css +3 -131
- package/src/pages/CliAuthorizePage.tsx +61 -28
- package/src/pages/MediaGalleryPage.tsx +144 -85
- package/src/pages/MediaLightbox.tsx +195 -96
- package/src/pages/SelectRepositoriesLink.tsx +28 -4
- package/src/pages/SettingsGeneralPage.tsx +25 -28
- package/src/pages/SettingsGithubPage.tsx +413 -124
- package/src/pages/SettingsUsagePage.tsx +23 -1
- package/src/pages/media-grid.ts +14 -1
- package/src/pages/ui-kit/UiKitApp.tsx +164 -0
- package/src/pages/ui-kit/buttons.tsx +30 -0
- package/src/pages/ui-kit/catalog.tsx +41 -0
- package/src/pages/ui-kit/colors.tsx +95 -0
- package/src/pages/ui-kit/devbar.tsx +160 -0
- package/src/pages/ui-kit/feedback.tsx +39 -0
- package/src/pages/ui-kit/fields.tsx +47 -0
- package/src/pages/ui-kit/lists.tsx +19 -0
- package/src/pages/ui-kit/overlays.tsx +39 -0
- package/src/pages/ui-kit/path.ts +9 -0
- package/src/pages/ui-kit/sections.tsx +32 -0
- package/src/pages/ui-kit/surfaces.tsx +121 -0
- package/src/pages/ui-kit/topic.tsx +112 -0
- package/src/pages/ui-kit/topics.ts +71 -0
- package/src/pages/ui-kit/type.tsx +55 -0
- package/src/preview-frame-radius.ts +51 -0
- package/src/preview-frame.tsx +93 -49
- package/src/shell-resets.css +155 -0
- package/src/site-bar.tsx +1791 -425
- package/src/site-dashboard-href.ts +21 -0
- package/src/site-devbar-overlay-inject.ts +15 -0
- package/src/site-devbar-overlay.css +217 -0
- package/src/site-pointer.ts +1 -1
- package/src/site-publish-menu.tsx +33 -0
- package/src/site-widget-styles.ts +7 -0
- package/src/site-widget.tsx +61 -21
- package/src/studio-prose-ui.css +39 -0
- package/src/theme/ThemeProvider.tsx +4 -1
- package/src/workspace-path.ts +18 -1
- package/dist/chunks/EmbedPublishPage-88VsmOhi.js +0 -149
- package/dist/chunks/UiKitPage-X4hkFL2d.js +0 -173
- package/dist/chunks/WorkspaceRoutes-DXzdWqN3.js +0 -59523
- package/dist/chunks/status-bar-action-wj1KZ51m.js +0 -30612
- package/src/pages/UiKitPage.tsx +0 -331
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
} from "@central-icons-react/round-outlined-radius-2-stroke-1.5";
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
8
|
import * as stylex from "@stylexjs/stylex";
|
|
9
|
+
import { MAX_MEDIA_TEXT } from "@backmatter/core";
|
|
9
10
|
import { colors, radius, space, type, z } from "../styles/tokens.stylex";
|
|
11
|
+
import { Field } from "../ui/Field";
|
|
12
|
+
import { Input } from "../ui/Input";
|
|
10
13
|
import { Text } from "../ui/Text";
|
|
11
14
|
import { cx, popup, popupClassName } from "../ui/popup";
|
|
12
15
|
import type { CatalogMedia } from "./media-grid";
|
|
@@ -22,6 +25,8 @@ export function MediaLightbox({
|
|
|
22
25
|
onPrev,
|
|
23
26
|
onNext,
|
|
24
27
|
onDelete,
|
|
28
|
+
canEdit = false,
|
|
29
|
+
onDescribe,
|
|
25
30
|
}: {
|
|
26
31
|
item: CatalogMedia;
|
|
27
32
|
index: number;
|
|
@@ -30,6 +35,8 @@ export function MediaLightbox({
|
|
|
30
35
|
onPrev: () => void;
|
|
31
36
|
onNext: () => void;
|
|
32
37
|
onDelete?: () => void;
|
|
38
|
+
canEdit?: boolean;
|
|
39
|
+
onDescribe?: (text: { alt?: string; caption?: string }) => Promise<void>;
|
|
33
40
|
}) {
|
|
34
41
|
const full = item.variants?.["1200"] ?? item.url;
|
|
35
42
|
const [copied, setCopied] = useState(false);
|
|
@@ -41,6 +48,10 @@ export function MediaLightbox({
|
|
|
41
48
|
useEffect(() => {
|
|
42
49
|
const onKey = (event: KeyboardEvent) => {
|
|
43
50
|
if (document.querySelector('[role="menu"], [role="alertdialog"]')) return;
|
|
51
|
+
const typing =
|
|
52
|
+
event.target instanceof HTMLElement &&
|
|
53
|
+
event.target.closest("input, textarea, select");
|
|
54
|
+
if (typing && event.key !== "Escape") return;
|
|
44
55
|
if (event.key === "Escape" || event.key === " ") {
|
|
45
56
|
event.preventDefault();
|
|
46
57
|
onClose();
|
|
@@ -66,9 +77,43 @@ export function MediaLightbox({
|
|
|
66
77
|
aria-label={item.filename}
|
|
67
78
|
>
|
|
68
79
|
<div {...stylex.props(styles.stage)} onClick={onClose}>
|
|
69
|
-
<
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
aria-label="Previous image"
|
|
83
|
+
disabled={index <= 0}
|
|
84
|
+
{...stylex.props(styles.nav, styles.navPrev)}
|
|
85
|
+
onClick={(event) => {
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
onPrev();
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
‹
|
|
91
|
+
</button>
|
|
92
|
+
<div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
|
|
93
|
+
<StageImage
|
|
94
|
+
key={item.id}
|
|
95
|
+
thumb={item.thumbUrl}
|
|
96
|
+
full={full}
|
|
97
|
+
alt={item.alt?.trim() || item.filename}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
aria-label="Next image"
|
|
103
|
+
disabled={index >= count - 1}
|
|
104
|
+
{...stylex.props(styles.nav, styles.navNext)}
|
|
105
|
+
onClick={(event) => {
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
onNext();
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
›
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
<aside {...stylex.props(styles.sidebar)}>
|
|
114
|
+
<div {...stylex.props(styles.sidebarTools)}>
|
|
70
115
|
<Menu.Root>
|
|
71
|
-
<Menu.Trigger aria-label="Image actions" className={cx(styles.
|
|
116
|
+
<Menu.Trigger aria-label="Image actions" className={cx(styles.sidebarTool)}>
|
|
72
117
|
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
73
118
|
<circle cx="3" cy="7" r="1.15" fill="currentColor" />
|
|
74
119
|
<circle cx="7" cy="7" r="1.15" fill="currentColor" />
|
|
@@ -109,12 +154,7 @@ export function MediaLightbox({
|
|
|
109
154
|
</Menu.Positioner>
|
|
110
155
|
</Menu.Portal>
|
|
111
156
|
</Menu.Root>
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
aria-label="Close"
|
|
115
|
-
{...stylex.props(styles.stageTool)}
|
|
116
|
-
onClick={onClose}
|
|
117
|
-
>
|
|
157
|
+
<button type="button" aria-label="Close" {...stylex.props(styles.sidebarTool)} onClick={onClose}>
|
|
118
158
|
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
119
159
|
<path
|
|
120
160
|
d="M3.5 3.5l7 7M10.5 3.5l-7 7"
|
|
@@ -126,81 +166,60 @@ export function MediaLightbox({
|
|
|
126
166
|
</svg>
|
|
127
167
|
</button>
|
|
128
168
|
</div>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
{...stylex.props(styles.nav, styles.navPrev)}
|
|
134
|
-
onClick={(event) => {
|
|
135
|
-
event.stopPropagation();
|
|
136
|
-
onPrev();
|
|
137
|
-
}}
|
|
138
|
-
>
|
|
139
|
-
‹
|
|
140
|
-
</button>
|
|
141
|
-
<div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
|
|
142
|
-
<StageImage key={item.id} thumb={item.thumbUrl} full={full} alt={item.filename} />
|
|
143
|
-
</div>
|
|
144
|
-
<button
|
|
145
|
-
type="button"
|
|
146
|
-
aria-label="Next image"
|
|
147
|
-
disabled={index >= count - 1}
|
|
148
|
-
{...stylex.props(styles.nav, styles.navNext)}
|
|
149
|
-
onClick={(event) => {
|
|
150
|
-
event.stopPropagation();
|
|
151
|
-
onNext();
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
›
|
|
155
|
-
</button>
|
|
156
|
-
</div>
|
|
157
|
-
<aside {...stylex.props(styles.sidebar)}>
|
|
158
|
-
<div {...stylex.props(styles.filename)}>
|
|
159
|
-
<Text as="h2" size="lg" weight="medium">
|
|
160
|
-
{item.filename}
|
|
161
|
-
</Text>
|
|
162
|
-
</div>
|
|
163
|
-
<dl {...stylex.props(styles.facts)}>
|
|
164
|
-
<Fact label="Dimensions" value={dimensions(item)} />
|
|
165
|
-
<Fact label="Size" value={item.byteSize === undefined ? "—" : formatBytes(item.byteSize)} />
|
|
166
|
-
<Fact label="Type" value={kindLabel(item.contentType)} />
|
|
167
|
-
<Fact label="Added" value={addedLabel(item.createdAt)} />
|
|
168
|
-
</dl>
|
|
169
|
-
<div {...stylex.props(styles.urlBlock)}>
|
|
170
|
-
<div {...stylex.props(styles.urlHead)}>
|
|
171
|
-
<Text size="xs" tone="muted">
|
|
172
|
-
Original URL
|
|
169
|
+
<div {...stylex.props(styles.sidebarBody)}>
|
|
170
|
+
<div {...stylex.props(styles.filename)}>
|
|
171
|
+
<Text as="h2" size="lg" weight="medium">
|
|
172
|
+
{item.filename}
|
|
173
173
|
</Text>
|
|
174
|
-
<button
|
|
175
|
-
type="button"
|
|
176
|
-
aria-label={copied ? "Copied" : "Copy URL"}
|
|
177
|
-
{...stylex.props(styles.copy)}
|
|
178
|
-
onClick={() => {
|
|
179
|
-
void navigator.clipboard.writeText(item.url).then(() => setCopied(true));
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
183
|
-
<rect
|
|
184
|
-
x="4.75"
|
|
185
|
-
y="4.75"
|
|
186
|
-
width="7"
|
|
187
|
-
height="7"
|
|
188
|
-
rx="1.25"
|
|
189
|
-
fill="none"
|
|
190
|
-
stroke="currentColor"
|
|
191
|
-
strokeWidth="1.5"
|
|
192
|
-
/>
|
|
193
|
-
<path
|
|
194
|
-
d="M9.25 4.75V3.25A1.25 1.25 0 0 0 8 2H3.25A1.25 1.25 0 0 0 2 3.25V8A1.25 1.25 0 0 0 3.25 9.25H4.75"
|
|
195
|
-
fill="none"
|
|
196
|
-
stroke="currentColor"
|
|
197
|
-
strokeWidth="1.5"
|
|
198
|
-
/>
|
|
199
|
-
</svg>
|
|
200
|
-
{copied ? "Copied" : "Copy"}
|
|
201
|
-
</button>
|
|
202
174
|
</div>
|
|
203
|
-
<
|
|
175
|
+
<dl {...stylex.props(styles.facts)}>
|
|
176
|
+
<Fact label="Dimensions" value={dimensions(item)} />
|
|
177
|
+
<Fact label="Size" value={item.byteSize === undefined ? "—" : formatBytes(item.byteSize)} />
|
|
178
|
+
<Fact label="Type" value={kindLabel(item.contentType)} />
|
|
179
|
+
<Fact label="Added" value={addedLabel(item.createdAt)} />
|
|
180
|
+
</dl>
|
|
181
|
+
<MediaText
|
|
182
|
+
key={item.id}
|
|
183
|
+
item={item}
|
|
184
|
+
canEdit={canEdit}
|
|
185
|
+
onDescribe={onDescribe}
|
|
186
|
+
/>
|
|
187
|
+
<div {...stylex.props(styles.urlBlock)}>
|
|
188
|
+
<div {...stylex.props(styles.urlHead)}>
|
|
189
|
+
<Text size="xs" tone="muted">
|
|
190
|
+
Original URL
|
|
191
|
+
</Text>
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
aria-label={copied ? "Copied" : "Copy URL"}
|
|
195
|
+
{...stylex.props(styles.copy)}
|
|
196
|
+
onClick={() => {
|
|
197
|
+
void navigator.clipboard.writeText(item.url).then(() => setCopied(true));
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
201
|
+
<rect
|
|
202
|
+
x="4.75"
|
|
203
|
+
y="4.75"
|
|
204
|
+
width="7"
|
|
205
|
+
height="7"
|
|
206
|
+
rx="1.25"
|
|
207
|
+
fill="none"
|
|
208
|
+
stroke="currentColor"
|
|
209
|
+
strokeWidth="1.5"
|
|
210
|
+
/>
|
|
211
|
+
<path
|
|
212
|
+
d="M9.25 4.75V3.25A1.25 1.25 0 0 0 8 2H3.25A1.25 1.25 0 0 0 2 3.25V8A1.25 1.25 0 0 0 3.25 9.25H4.75"
|
|
213
|
+
fill="none"
|
|
214
|
+
stroke="currentColor"
|
|
215
|
+
strokeWidth="1.5"
|
|
216
|
+
/>
|
|
217
|
+
</svg>
|
|
218
|
+
{copied ? "Copied" : "Copy"}
|
|
219
|
+
</button>
|
|
220
|
+
</div>
|
|
221
|
+
<p {...stylex.props(styles.url)}>{item.url}</p>
|
|
222
|
+
</div>
|
|
204
223
|
</div>
|
|
205
224
|
</aside>
|
|
206
225
|
</div>
|
|
@@ -222,6 +241,71 @@ function StageImage({ thumb, full, alt }: { thumb: string; full: string; alt: st
|
|
|
222
241
|
);
|
|
223
242
|
}
|
|
224
243
|
|
|
244
|
+
function MediaText({
|
|
245
|
+
item,
|
|
246
|
+
canEdit,
|
|
247
|
+
onDescribe,
|
|
248
|
+
}: {
|
|
249
|
+
item: CatalogMedia;
|
|
250
|
+
canEdit: boolean;
|
|
251
|
+
onDescribe?: (text: { alt?: string; caption?: string }) => Promise<void>;
|
|
252
|
+
}) {
|
|
253
|
+
const [alt, setAlt] = useState(item.alt ?? "");
|
|
254
|
+
const [caption, setCaption] = useState(item.caption ?? "");
|
|
255
|
+
const [error, setError] = useState<string | null>(null);
|
|
256
|
+
|
|
257
|
+
async function commit(field: "alt" | "caption", draft: string) {
|
|
258
|
+
const next = draft.trim();
|
|
259
|
+
const current = (item[field] ?? "").trim();
|
|
260
|
+
if (next === current || !onDescribe) return;
|
|
261
|
+
setError(null);
|
|
262
|
+
try {
|
|
263
|
+
await onDescribe({ [field]: next });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
const code = err instanceof Error ? err.message : "";
|
|
266
|
+
setError(
|
|
267
|
+
code === "too_long" ? `Use ${MAX_MEDIA_TEXT} characters or fewer.` : code || "Could not save",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!canEdit) {
|
|
273
|
+
if (!item.alt?.trim() && !item.caption?.trim()) return null;
|
|
274
|
+
return (
|
|
275
|
+
<dl {...stylex.props(styles.facts)}>
|
|
276
|
+
{item.alt?.trim() ? <Fact label="Alt text" value={item.alt} /> : null}
|
|
277
|
+
{item.caption?.trim() ? <Fact label="Caption" value={item.caption} /> : null}
|
|
278
|
+
</dl>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div {...stylex.props(styles.textFields)}>
|
|
284
|
+
<Field label="Alt text">
|
|
285
|
+
<Input
|
|
286
|
+
value={alt}
|
|
287
|
+
maxLength={MAX_MEDIA_TEXT}
|
|
288
|
+
onChange={(event) => setAlt(event.target.value)}
|
|
289
|
+
onBlur={() => {
|
|
290
|
+
void commit("alt", alt);
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
293
|
+
</Field>
|
|
294
|
+
<Field label="Caption">
|
|
295
|
+
<Input
|
|
296
|
+
value={caption}
|
|
297
|
+
maxLength={MAX_MEDIA_TEXT}
|
|
298
|
+
onChange={(event) => setCaption(event.target.value)}
|
|
299
|
+
onBlur={() => {
|
|
300
|
+
void commit("caption", caption);
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
</Field>
|
|
304
|
+
{error ? <Text tone="danger">{error}</Text> : null}
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
225
309
|
function Fact({ label, value }: { label: string; value: string }) {
|
|
226
310
|
return (
|
|
227
311
|
<div {...stylex.props(styles.fact)}>
|
|
@@ -324,31 +408,35 @@ const styles = stylex.create({
|
|
|
324
408
|
cursor: "default",
|
|
325
409
|
},
|
|
326
410
|
},
|
|
327
|
-
|
|
328
|
-
position: "absolute",
|
|
329
|
-
top: space.xl,
|
|
330
|
-
right: space.xl,
|
|
331
|
-
zIndex: 2,
|
|
411
|
+
sidebarTools: {
|
|
332
412
|
display: "flex",
|
|
333
413
|
alignItems: "center",
|
|
334
|
-
|
|
414
|
+
justifyContent: "flex-end",
|
|
415
|
+
gap: space.xs,
|
|
335
416
|
},
|
|
336
|
-
|
|
417
|
+
sidebarTool: {
|
|
337
418
|
width: "32px",
|
|
338
419
|
height: "32px",
|
|
339
420
|
display: "flex",
|
|
340
421
|
alignItems: "center",
|
|
341
422
|
justifyContent: "center",
|
|
342
423
|
padding: 0,
|
|
343
|
-
borderWidth:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
backgroundColor: "rgba(0, 0, 0, 0.45)",
|
|
348
|
-
color: "#fafafa",
|
|
424
|
+
borderWidth: 0,
|
|
425
|
+
borderRadius: radius.md,
|
|
426
|
+
backgroundColor: "transparent",
|
|
427
|
+
color: colors.textMuted,
|
|
349
428
|
cursor: "pointer",
|
|
350
429
|
[hover]: {
|
|
351
|
-
|
|
430
|
+
":hover": {
|
|
431
|
+
backgroundColor: colors.bgHover,
|
|
432
|
+
color: colors.text,
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
":focus-visible": {
|
|
436
|
+
outlineWidth: 2,
|
|
437
|
+
outlineStyle: "solid",
|
|
438
|
+
outlineColor: colors.focus,
|
|
439
|
+
outlineOffset: 1,
|
|
352
440
|
},
|
|
353
441
|
},
|
|
354
442
|
menu: {
|
|
@@ -371,7 +459,7 @@ const styles = stylex.create({
|
|
|
371
459
|
flexShrink: 0,
|
|
372
460
|
display: "flex",
|
|
373
461
|
flexDirection: "column",
|
|
374
|
-
justifyContent: "
|
|
462
|
+
justifyContent: "space-between",
|
|
375
463
|
gap: space.xl,
|
|
376
464
|
padding: space.xl,
|
|
377
465
|
backgroundColor: colors.bgSurface,
|
|
@@ -388,6 +476,12 @@ const styles = stylex.create({
|
|
|
388
476
|
borderTopColor: colors.border,
|
|
389
477
|
},
|
|
390
478
|
},
|
|
479
|
+
sidebarBody: {
|
|
480
|
+
display: "flex",
|
|
481
|
+
flexDirection: "column",
|
|
482
|
+
gap: space.xl,
|
|
483
|
+
minWidth: 0,
|
|
484
|
+
},
|
|
391
485
|
filename: {
|
|
392
486
|
minWidth: 0,
|
|
393
487
|
overflowWrap: "anywhere",
|
|
@@ -398,6 +492,11 @@ const styles = stylex.create({
|
|
|
398
492
|
gap: space.lg,
|
|
399
493
|
margin: 0,
|
|
400
494
|
},
|
|
495
|
+
textFields: {
|
|
496
|
+
display: "flex",
|
|
497
|
+
flexDirection: "column",
|
|
498
|
+
gap: space.lg,
|
|
499
|
+
},
|
|
401
500
|
fact: {
|
|
402
501
|
display: "flex",
|
|
403
502
|
flexDirection: "column",
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// packages/studio/src/pages/SelectRepositoriesLink.tsx
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
3
3
|
import { fetchGithubRepositoryAccess } from "../api";
|
|
4
4
|
import { ButtonLink } from "../ui/Button";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
export function SelectRepositoriesLink({ workspaceId }: { workspaceId: string }) {
|
|
6
|
+
function useRepositoryAccessUrl(workspaceId: string) {
|
|
8
7
|
const [url, setUrl] = useState<string | null>(null);
|
|
9
8
|
|
|
10
9
|
useEffect(() => {
|
|
@@ -21,10 +20,35 @@ export function SelectRepositoriesLink({ workspaceId }: { workspaceId: string })
|
|
|
21
20
|
};
|
|
22
21
|
}, [workspaceId]);
|
|
23
22
|
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Opens the GitHub page where this installation's repository access is chosen. */
|
|
27
|
+
export function SelectRepositoriesLink({
|
|
28
|
+
workspaceId,
|
|
29
|
+
label = "Select repositories on GitHub",
|
|
30
|
+
}: {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
}) {
|
|
34
|
+
const url = useRepositoryAccessUrl(workspaceId);
|
|
24
35
|
if (!url) return null;
|
|
25
36
|
return (
|
|
26
37
|
<ButtonLink href={url} variant="secondary">
|
|
27
|
-
|
|
38
|
+
{label}
|
|
28
39
|
</ButtonLink>
|
|
29
40
|
);
|
|
30
41
|
}
|
|
42
|
+
|
|
43
|
+
/** Renders nothing until the GitHub App's repository-access URL is known. */
|
|
44
|
+
export function GithubRepositoryAccess({
|
|
45
|
+
workspaceId,
|
|
46
|
+
children,
|
|
47
|
+
}: {
|
|
48
|
+
workspaceId: string;
|
|
49
|
+
children: (url: string) => ReactNode;
|
|
50
|
+
}) {
|
|
51
|
+
const url = useRepositoryAccessUrl(workspaceId);
|
|
52
|
+
if (!url) return null;
|
|
53
|
+
return children(url);
|
|
54
|
+
}
|
|
@@ -11,7 +11,6 @@ import { hostEnvSnippet } from "./host-env";
|
|
|
11
11
|
import { isStudioDev, publicApiUrl } from "../studio-config";
|
|
12
12
|
import { revalidateWebhookUrl } from "./preview-url";
|
|
13
13
|
import { Button } from "../ui/Button";
|
|
14
|
-
import { Field } from "../ui/Field";
|
|
15
14
|
import { Input } from "../ui/Input";
|
|
16
15
|
import { PageHeader, Panel, Stack } from "../ui/Page";
|
|
17
16
|
import { Text } from "../ui/Text";
|
|
@@ -147,40 +146,33 @@ export function SettingsGeneralPage() {
|
|
|
147
146
|
</Text>
|
|
148
147
|
<form onSubmit={(e) => void onSaveSiteUrl(e)}>
|
|
149
148
|
<div {...stylex.props(styles.block)}>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
placeholder="https://www.example.com"
|
|
163
|
-
autoComplete="off"
|
|
164
|
-
spellCheck={false}
|
|
165
|
-
data-lpignore="true"
|
|
166
|
-
data-1p-ignore
|
|
167
|
-
onChange={(e) => setSiteUrl(e.target.value)}
|
|
168
|
-
/>
|
|
169
|
-
</Field>
|
|
149
|
+
<Input
|
|
150
|
+
type="url"
|
|
151
|
+
aria-label="Site URL"
|
|
152
|
+
value={siteUrl}
|
|
153
|
+
readOnly={!canEdit}
|
|
154
|
+
placeholder="https://www.example.com"
|
|
155
|
+
autoComplete="off"
|
|
156
|
+
spellCheck={false}
|
|
157
|
+
data-lpignore="true"
|
|
158
|
+
data-1p-ignore
|
|
159
|
+
onChange={(e) => setSiteUrl(e.target.value)}
|
|
160
|
+
/>
|
|
170
161
|
</div>
|
|
162
|
+
<p {...stylex.props(styles.caption)}>
|
|
163
|
+
{webhookUrl
|
|
164
|
+
? `Preview origin. Pushes POST ${webhookUrl}`
|
|
165
|
+
: "Preview origin. A content push POSTs the revalidate route on this origin."}
|
|
166
|
+
</p>
|
|
167
|
+
<StudioVersions versions={workspace.studioVersions} />
|
|
171
168
|
{canEdit ? (
|
|
172
|
-
<div {...stylex.props(styles.
|
|
173
|
-
<Button
|
|
174
|
-
type="submit"
|
|
175
|
-
variant="secondary"
|
|
176
|
-
disabled={busy !== null}
|
|
177
|
-
>
|
|
169
|
+
<div {...stylex.props(styles.actions)}>
|
|
170
|
+
<Button type="submit" variant="secondary" disabled={busy !== null}>
|
|
178
171
|
{busy === "site-url" ? "Saving…" : "Save"}
|
|
179
172
|
</Button>
|
|
180
173
|
</div>
|
|
181
174
|
) : null}
|
|
182
175
|
</form>
|
|
183
|
-
<StudioVersions versions={workspace.studioVersions} />
|
|
184
176
|
</Panel>
|
|
185
177
|
<Panel>
|
|
186
178
|
<Text as="h2" size="lg" weight="medium">
|
|
@@ -370,6 +362,11 @@ const styles = stylex.create({
|
|
|
370
362
|
block: {
|
|
371
363
|
marginTop: space.md,
|
|
372
364
|
},
|
|
365
|
+
actions: {
|
|
366
|
+
display: "flex",
|
|
367
|
+
justifyContent: "flex-end",
|
|
368
|
+
marginTop: space.lg,
|
|
369
|
+
},
|
|
373
370
|
idRow: {
|
|
374
371
|
display: "flex",
|
|
375
372
|
alignItems: "center",
|