@backmatter/studio 0.0.8 → 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-bNp6AbKE.js → dashboard-BNUcbYU0.js} +380 -382
- package/dist/chunks/{index-DoDE-hwB.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 +268 -67
- 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/ContentExplorerSidebar.tsx +23 -8
- package/src/chrome/content-tree-visibility.ts +55 -0
- 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 +74 -11
- package/src/pages/MediaLightbox.tsx +93 -1
- package/src/pages/SettingsUsagePage.tsx +23 -1
- package/src/pages/media-grid.ts +13 -0
- 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 +10 -6
- package/dist/chunks/EmbedPublishPage-CtIlaeGO.js +0 -149
- package/dist/chunks/UiKitPage-DcnSQ-Hf.js +0 -173
- package/dist/chunks/WorkspaceRoutes-DVpUOaMV.js +0 -59550
- package/dist/chunks/status-bar-action-C0fsAWvD.js +0 -30618
- package/src/pages/UiKitPage.tsx +0 -331
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// packages/studio/src/site-dashboard-href.ts
|
|
2
|
+
import { contentDocPath } from "./workspace-path";
|
|
3
|
+
|
|
4
|
+
export type DashboardDocRef = {
|
|
5
|
+
locale: string;
|
|
6
|
+
type: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Dashboard URL for the current site document, or the dashboard origin when unknown. */
|
|
11
|
+
export function resolveDashboardOpenHref(
|
|
12
|
+
dashboardOrigin: string | undefined,
|
|
13
|
+
doc: DashboardDocRef | null,
|
|
14
|
+
workspaceSlug: string | null,
|
|
15
|
+
defaultLocale = "en",
|
|
16
|
+
): string | undefined {
|
|
17
|
+
if (!dashboardOrigin?.trim()) return undefined;
|
|
18
|
+
const base = dashboardOrigin.replace(/\/+$/, "");
|
|
19
|
+
if (!doc || !workspaceSlug) return base;
|
|
20
|
+
return `${base}${contentDocPath(workspaceSlug, doc.type, doc.slug, doc.locale, defaultLocale)}`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// packages/studio/src/site-devbar-overlay-inject.ts
|
|
2
|
+
// Injected with the widget JS so page/publish menus stay styled when the host
|
|
3
|
+
// loads only `site-widget.js` or the CSS chunk is stale.
|
|
4
|
+
import overlayCss from "./site-devbar-overlay.css?inline";
|
|
5
|
+
|
|
6
|
+
const STYLE_ID = "backmatter-devbar-overlay-styles";
|
|
7
|
+
|
|
8
|
+
export function ensureDevbarOverlayStyles(): void {
|
|
9
|
+
if (typeof document === "undefined") return;
|
|
10
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
11
|
+
const style = document.createElement("style");
|
|
12
|
+
style.id = STYLE_ID;
|
|
13
|
+
style.textContent = overlayCss;
|
|
14
|
+
document.head.appendChild(style);
|
|
15
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/* packages/studio/src/site-devbar-overlay.css
|
|
2
|
+
Floating devbar menus (page, publish, settings) in the light DOM. */
|
|
3
|
+
|
|
4
|
+
.backmatter-devbar-overlay {
|
|
5
|
+
--panel: #ffffff;
|
|
6
|
+
--line: #e7e7e4;
|
|
7
|
+
--line2: #d8d8d4;
|
|
8
|
+
--hover: #f3f3f1;
|
|
9
|
+
--fg: #17171a;
|
|
10
|
+
--fg2: #5d5d62;
|
|
11
|
+
--fg3: #8e8e93;
|
|
12
|
+
--shadow-live: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 28px -8px rgba(0, 0, 0, 0.18);
|
|
13
|
+
position: fixed;
|
|
14
|
+
z-index: 2147483002;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
border: 1px solid var(--line);
|
|
19
|
+
border-radius: 12px;
|
|
20
|
+
background: var(--panel);
|
|
21
|
+
color: var(--fg);
|
|
22
|
+
box-shadow: var(--shadow-live);
|
|
23
|
+
font-family: "Geist Sans", Geist, ui-sans-serif, system-ui, sans-serif;
|
|
24
|
+
font-size: 12.5px;
|
|
25
|
+
font-weight: 500;
|
|
26
|
+
line-height: 1.3;
|
|
27
|
+
-webkit-font-smoothing: antialiased;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.backmatter-devbar-overlay.backmatter-devbar-menu {
|
|
31
|
+
width: 240px;
|
|
32
|
+
max-height: min(320px, calc(100dvh - 96px));
|
|
33
|
+
overflow: auto;
|
|
34
|
+
padding: 6px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.backmatter-devbar-overlay.backmatter-devbar-menu-pages {
|
|
38
|
+
width: min(280px, calc(100vw - 24px));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.backmatter-devbar-overlay.backmatter-devbar-publish-panel {
|
|
42
|
+
width: min(320px, calc(100vw - 24px));
|
|
43
|
+
padding: 0;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
max-height: min(420px, calc(100dvh - 96px));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.backmatter-devbar-overlay.backmatter-devbar-settings {
|
|
49
|
+
width: min(328px, calc(100vw - 24px));
|
|
50
|
+
max-height: min(480px, calc(100dvh - 96px));
|
|
51
|
+
padding: 8px;
|
|
52
|
+
border-radius: 16px;
|
|
53
|
+
overflow: auto;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.backmatter-devbar-overlay input {
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 32px;
|
|
59
|
+
margin: 2px 0 6px;
|
|
60
|
+
padding: 0 10px;
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
border: 1px solid var(--line);
|
|
63
|
+
border-radius: 7px;
|
|
64
|
+
background: #fff;
|
|
65
|
+
color: var(--fg);
|
|
66
|
+
font: inherit;
|
|
67
|
+
font-size: 16px;
|
|
68
|
+
cursor: text;
|
|
69
|
+
user-select: text;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.backmatter-devbar-overlay button {
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: 0;
|
|
75
|
+
border: 0;
|
|
76
|
+
background: transparent;
|
|
77
|
+
color: inherit;
|
|
78
|
+
font: inherit;
|
|
79
|
+
text-align: left;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
-webkit-appearance: none;
|
|
82
|
+
appearance: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.backmatter-devbar-overlay .backmatter-devbar-option {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 8px;
|
|
89
|
+
width: 100%;
|
|
90
|
+
min-height: 32px;
|
|
91
|
+
padding: 0 10px;
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.backmatter-devbar-overlay .backmatter-devbar-option > span:first-child {
|
|
97
|
+
min-width: 0;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.backmatter-devbar-overlay .backmatter-devbar-current {
|
|
104
|
+
background: var(--hover);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.backmatter-devbar-overlay .backmatter-devbar-meta {
|
|
108
|
+
margin-left: auto;
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
color: var(--fg3);
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.backmatter-devbar-overlay .backmatter-devbar-note {
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding: 8px;
|
|
118
|
+
color: var(--fg2);
|
|
119
|
+
font-size: 12px;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.backmatter-devbar-overlay .backmatter-devbar-note-error {
|
|
124
|
+
color: #b91c1c;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.backmatter-devbar-overlay .backmatter-devbar-menu-split {
|
|
128
|
+
margin-top: 4px;
|
|
129
|
+
padding-top: 4px;
|
|
130
|
+
border-top: 1px solid var(--line);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-head {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
gap: 12px;
|
|
138
|
+
min-height: 28px;
|
|
139
|
+
padding: 4px 12px 8px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-kicker {
|
|
143
|
+
color: var(--fg3);
|
|
144
|
+
font-size: 11px;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
letter-spacing: 0.08em;
|
|
147
|
+
text-transform: uppercase;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-place {
|
|
151
|
+
min-width: 0;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
color: var(--fg3);
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
font-weight: 500;
|
|
156
|
+
text-overflow: ellipsis;
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-row,
|
|
161
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-action,
|
|
162
|
+
.backmatter-devbar-overlay button.backmatter-devbar-settings-action {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: 10px;
|
|
166
|
+
width: 100%;
|
|
167
|
+
min-height: 40px;
|
|
168
|
+
padding: 0 12px;
|
|
169
|
+
border-radius: 8px;
|
|
170
|
+
box-sizing: border-box;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.backmatter-devbar-overlay .backmatter-devbar-settings-rule {
|
|
174
|
+
height: 1px;
|
|
175
|
+
margin: 6px 12px;
|
|
176
|
+
background: var(--line);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.backmatter-devbar-overlay .backmatter-devbar-segment {
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
flex-shrink: 0;
|
|
182
|
+
padding: 2px;
|
|
183
|
+
border-radius: 9px;
|
|
184
|
+
background: var(--hover);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.backmatter-devbar-overlay button.backmatter-devbar-segment-btn {
|
|
188
|
+
height: 26px;
|
|
189
|
+
padding: 0 10px;
|
|
190
|
+
border-radius: 7px;
|
|
191
|
+
color: var(--fg2);
|
|
192
|
+
font-size: 12.5px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.backmatter-devbar-overlay button.backmatter-devbar-segment-on {
|
|
196
|
+
background: #fff;
|
|
197
|
+
color: var(--fg);
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.backmatter-devbar-overlay .backmatter-devbar-kbd {
|
|
203
|
+
margin-left: auto;
|
|
204
|
+
color: var(--fg3);
|
|
205
|
+
font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
206
|
+
font-size: 11px;
|
|
207
|
+
font-weight: 500;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@media (hover: hover) {
|
|
211
|
+
.backmatter-devbar-overlay .backmatter-devbar-option:hover:not(:disabled) {
|
|
212
|
+
background: var(--hover);
|
|
213
|
+
}
|
|
214
|
+
.backmatter-devbar-overlay button.backmatter-devbar-settings-action:hover {
|
|
215
|
+
background: var(--hover);
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/site-pointer.ts
CHANGED
|
@@ -205,7 +205,7 @@ function positionPointerOutline(outline: HTMLDivElement, target: Element) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
function isSiteChrome(el: Element): boolean {
|
|
208
|
-
return el.closest("[data-backmatter-panel], .backmatter-devbar, [data-backmatter-pointer]") !== null;
|
|
208
|
+
return el.closest("[data-backmatter-panel], .backmatter-devbar, [data-backmatter-devbar], [data-backmatter-pointer]") !== null;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** The page element a stored pointer refers to. Chat chrome is ignored. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// packages/studio/src/site-publish-menu.tsx
|
|
2
|
+
// Publish popover for the on-site bar (StyleX). Loaded on demand so site-bar tests stay StyleX-free.
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import type { Ref } from "react";
|
|
5
|
+
import { EmbedPublishPage } from "./pages/EmbedPublishPage";
|
|
6
|
+
import { DEVBAR_CLASS, DEVBAR_OVERLAY_CLASS, devbarOverlayRoot } from "./site-bar";
|
|
7
|
+
|
|
8
|
+
export function PublishMenu({
|
|
9
|
+
menuRef,
|
|
10
|
+
workspaceId,
|
|
11
|
+
onStatus,
|
|
12
|
+
onPublished,
|
|
13
|
+
}: {
|
|
14
|
+
menuRef: Ref<HTMLDivElement | null>;
|
|
15
|
+
workspaceId: string;
|
|
16
|
+
onStatus?: (status: { changeCount: number; conflictCount: number }) => void;
|
|
17
|
+
onPublished?: () => void;
|
|
18
|
+
}) {
|
|
19
|
+
const root = devbarOverlayRoot();
|
|
20
|
+
if (!root) return null;
|
|
21
|
+
return createPortal(
|
|
22
|
+
<div
|
|
23
|
+
ref={menuRef}
|
|
24
|
+
role="dialog"
|
|
25
|
+
aria-label="Publish"
|
|
26
|
+
className={`backmatter-studio ${DEVBAR_OVERLAY_CLASS} ${DEVBAR_CLASS}-menu ${DEVBAR_CLASS}-publish-panel`}
|
|
27
|
+
data-backmatter-panel=""
|
|
28
|
+
>
|
|
29
|
+
<EmbedPublishPage workspaceId={workspaceId} onStatus={onStatus} onPublished={onPublished} />
|
|
30
|
+
</div>,
|
|
31
|
+
root,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// packages/studio/src/site-widget-styles.ts
|
|
2
|
+
// On-site widget stylesheet entry: scoped resets only. Full studio chrome
|
|
3
|
+
// (fonts, prose-ui, katex) lives in styles.ts → dist/studio.css.
|
|
4
|
+
import "@fontsource/geist-sans/500.css";
|
|
5
|
+
import "@fontsource/geist-mono/500.css";
|
|
6
|
+
import "./shell-resets.css";
|
|
7
|
+
import "./site-devbar-overlay.css";
|
package/src/site-widget.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// packages/studio/src/site-widget.tsx
|
|
2
|
-
|
|
2
|
+
import "./site-widget-styles";
|
|
3
|
+
// The on-site bar: Enter/Exit preview, the current page, then Agent and Publish. The host page mounts this
|
|
3
4
|
// after the site session verifies. Calls go through the host's same-origin
|
|
4
5
|
// CMS proxy.
|
|
5
|
-
import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from "react";
|
|
6
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from "react";
|
|
6
7
|
import * as stylex from "@stylexjs/stylex";
|
|
7
8
|
import { ChatPanel } from "./chat/ChatPanel";
|
|
8
9
|
import { chatPointerFromGrab } from "./chat/chat-context";
|
|
@@ -12,8 +13,7 @@ import { isPanelVisible } from "./chat/chat-state";
|
|
|
12
13
|
import { DOC_CHANGED_EVENT, EditorFocusProvider, useEditorFocus } from "./chrome/EditorFocusContext";
|
|
13
14
|
import { matchAppHotkey } from "./chrome/hotkeys";
|
|
14
15
|
import { EmbedPublishPage } from "./pages/EmbedPublishPage";
|
|
15
|
-
import {
|
|
16
|
-
import { DEVBAR_CLASS, SiteBar } from "./site-bar";
|
|
16
|
+
import { SiteBar } from "./site-bar";
|
|
17
17
|
import { installSitePointer } from "./site-pointer";
|
|
18
18
|
import { lightTheme } from "./styles/themes";
|
|
19
19
|
import { configureStudio } from "./studio-config";
|
|
@@ -45,7 +45,11 @@ export type SiteWidgetProps = {
|
|
|
45
45
|
previewEntryPath?: string;
|
|
46
46
|
/** Full-viewport preview ring and top pill (see `devBar.previewFrame` in host config). */
|
|
47
47
|
previewFrame?: boolean;
|
|
48
|
+
previewFrameRadius?: number;
|
|
48
49
|
version?: string;
|
|
50
|
+
/** Studio origin. The settings menu opens this in the current tab. */
|
|
51
|
+
dashboardHref?: string;
|
|
52
|
+
onSignOut?: () => void;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
export function SiteWidget({
|
|
@@ -61,7 +65,10 @@ export function SiteWidget({
|
|
|
61
65
|
proxyPath = "/api/backmatter/cms",
|
|
62
66
|
previewEntryPath,
|
|
63
67
|
previewFrame,
|
|
68
|
+
previewFrameRadius,
|
|
64
69
|
version,
|
|
70
|
+
dashboardHref,
|
|
71
|
+
onSignOut,
|
|
65
72
|
}: SiteWidgetProps) {
|
|
66
73
|
const expiredRef = useRef(onSessionExpired);
|
|
67
74
|
expiredRef.current = onSessionExpired;
|
|
@@ -85,7 +92,11 @@ export function SiteWidget({
|
|
|
85
92
|
const [chatOpen, setChatOpen] = useState(false);
|
|
86
93
|
const [publishOpen, setPublishOpen] = useState(false);
|
|
87
94
|
const [changeCount, setChangeCount] = useState(0);
|
|
95
|
+
const [publishAnchor, setPublishAnchor] = useState<DOMRect | null>(null);
|
|
88
96
|
const label = doc ? `${doc.type}/${doc.slug}` : "No document";
|
|
97
|
+
const onPublishAnchor = useCallback((rect: DOMRect | null) => {
|
|
98
|
+
setPublishAnchor(rect);
|
|
99
|
+
}, []);
|
|
89
100
|
|
|
90
101
|
function wrote() {
|
|
91
102
|
onDocChanged?.();
|
|
@@ -102,6 +113,23 @@ export function SiteWidget({
|
|
|
102
113
|
return () => document.removeEventListener("keydown", onKey, true);
|
|
103
114
|
}, []);
|
|
104
115
|
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!publishOpen) return;
|
|
118
|
+
function onPointer(event: PointerEvent) {
|
|
119
|
+
for (const node of event.composedPath()) {
|
|
120
|
+
if (node instanceof HTMLElement) {
|
|
121
|
+
if (node.matches("[data-backmatter-panel], [data-backmatter-devbar]")) return;
|
|
122
|
+
}
|
|
123
|
+
if (node instanceof ShadowRoot && node.host instanceof HTMLElement) {
|
|
124
|
+
if (node.host.hasAttribute("data-backmatter-devbar")) return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
setPublishOpen(false);
|
|
128
|
+
}
|
|
129
|
+
document.addEventListener("pointerdown", onPointer);
|
|
130
|
+
return () => document.removeEventListener("pointerdown", onPointer);
|
|
131
|
+
}, [publishOpen]);
|
|
132
|
+
|
|
105
133
|
const bar = (
|
|
106
134
|
<SiteBar
|
|
107
135
|
label={label}
|
|
@@ -116,14 +144,18 @@ export function SiteWidget({
|
|
|
116
144
|
previewEntryPath={previewEntryPath}
|
|
117
145
|
onExit={onExit}
|
|
118
146
|
onEnterPreview={onEnterPreview}
|
|
147
|
+
frame={Boolean(previewFrame)}
|
|
148
|
+
frameRadius={previewFrameRadius}
|
|
119
149
|
onToggleChat={() => setChatOpen((open) => !open)}
|
|
120
150
|
onTogglePublish={() => setPublishOpen((open) => !open)}
|
|
151
|
+
dashboardHref={dashboardHref}
|
|
152
|
+
onSignOut={onSignOut}
|
|
153
|
+
onPublishAnchor={onPublishAnchor}
|
|
121
154
|
/>
|
|
122
155
|
);
|
|
123
156
|
|
|
124
157
|
return (
|
|
125
158
|
<>
|
|
126
|
-
{previewFrame ? <PreviewFrame /> : null}
|
|
127
159
|
{doc ? (
|
|
128
160
|
<TooltipProvider>
|
|
129
161
|
<EditorFocusProvider>
|
|
@@ -138,8 +170,8 @@ export function SiteWidget({
|
|
|
138
170
|
</EditorFocusProvider>
|
|
139
171
|
</TooltipProvider>
|
|
140
172
|
) : null}
|
|
141
|
-
{doc ? (
|
|
142
|
-
<div data-backmatter-panel=""
|
|
173
|
+
{doc && publishOpen ? (
|
|
174
|
+
<div data-backmatter-panel="" style={publishPanelStyle(publishAnchor)}>
|
|
143
175
|
<EmbedPublishPage
|
|
144
176
|
workspaceId={doc.workspaceId}
|
|
145
177
|
onStatus={({ changeCount: count }) => setChangeCount(count)}
|
|
@@ -218,7 +250,7 @@ function SitePointer({ doc }: { doc: SiteWidgetDoc }) {
|
|
|
218
250
|
|
|
219
251
|
useEffect(() => {
|
|
220
252
|
const pointer = installSitePointer(document, {
|
|
221
|
-
bar: () => document.querySelector(
|
|
253
|
+
bar: () => document.querySelector("[data-backmatter-devbar]"),
|
|
222
254
|
ignore: (target) => {
|
|
223
255
|
if (!(target instanceof Element)) return true;
|
|
224
256
|
if (target.closest("[data-backmatter-panel]")) return true;
|
|
@@ -251,16 +283,24 @@ function SitePointer({ doc }: { doc: SiteWidgetDoc }) {
|
|
|
251
283
|
return null;
|
|
252
284
|
}
|
|
253
285
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
286
|
+
function publishPanelStyle(anchor: DOMRect | null): CSSProperties {
|
|
287
|
+
const insetRight = anchor
|
|
288
|
+
? Math.max(16, window.innerWidth - anchor.right)
|
|
289
|
+
: 16;
|
|
290
|
+
const bottom = anchor
|
|
291
|
+
? Math.max(52, window.innerHeight - anchor.top + 8)
|
|
292
|
+
: 52;
|
|
293
|
+
return {
|
|
294
|
+
position: "fixed",
|
|
295
|
+
right: `max(${insetRight}px, env(safe-area-inset-right, 0px))`,
|
|
296
|
+
bottom: `calc(${bottom}px + env(safe-area-inset-bottom, 0px))`,
|
|
297
|
+
zIndex: 2147483001,
|
|
298
|
+
width: "min(320px, calc(100vw - 32px))",
|
|
299
|
+
maxHeight: "min(420px, calc(100vh - 80px))",
|
|
300
|
+
overflow: "auto",
|
|
301
|
+
border: "1px solid #e4e4e7",
|
|
302
|
+
borderRadius: 12,
|
|
303
|
+
background: "#ffffff",
|
|
304
|
+
boxShadow: "0 16px 40px rgba(24, 24, 27, 0.14), 0 2px 8px rgba(24, 24, 27, 0.06)",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* packages/studio/src/studio-prose-ui.css
|
|
2
|
+
Document editor prose surface — full studio only, not the on-site widget. */
|
|
3
|
+
|
|
4
|
+
.prose-ui {
|
|
5
|
+
--p-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
6
|
+
--p-font-family-heading: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
7
|
+
--p-font-family-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,
|
|
8
|
+
monospace;
|
|
9
|
+
--p-body-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
10
|
+
--p-h1-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
11
|
+
--p-h2-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
12
|
+
--p-h3-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
13
|
+
--p-h4-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
14
|
+
--p-h5-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
15
|
+
--p-h6-font-family: "Geist Sans", Inter, ui-sans-serif, system-ui, sans-serif;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.prose-ui img {
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
height: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.prose-ui > :first-child {
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.prose-ui > :last-child {
|
|
28
|
+
margin-bottom: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.prose-ui [data-body-block] :is(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote) {
|
|
32
|
+
margin-top: 0;
|
|
33
|
+
margin-bottom: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.prose-ui [data-body-block] :focus,
|
|
37
|
+
.prose-ui [data-body-block] :focus-visible {
|
|
38
|
+
outline: none;
|
|
39
|
+
}
|
|
@@ -101,10 +101,13 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
|
101
101
|
[preference, resolved, setPreference, cyclePreference],
|
|
102
102
|
);
|
|
103
103
|
|
|
104
|
+
const themeProps = stylex.props(theme, styles.root);
|
|
105
|
+
|
|
104
106
|
return (
|
|
105
107
|
<ThemeContext.Provider value={value}>
|
|
106
108
|
<div
|
|
107
|
-
{...
|
|
109
|
+
{...themeProps}
|
|
110
|
+
className={[themeProps.className, "backmatter-studio"].filter(Boolean).join(" ")}
|
|
108
111
|
>
|
|
109
112
|
{children}
|
|
110
113
|
</div>
|
package/src/workspace-path.ts
CHANGED
|
@@ -97,16 +97,14 @@ export function isWorkspaceContentPath(pathname: string) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Where the content rail icon
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* Where the content rail icon navigates.
|
|
101
|
+
* On a content route it stays put: a document toggles the file tree, and the
|
|
102
|
+
* type index is already the file tree.
|
|
103
103
|
* Anywhere else (settings, media, connect): the workspace home, which opens the docs.
|
|
104
104
|
*/
|
|
105
105
|
export function contentRailHref(pathname: string, slug: string): string | null {
|
|
106
106
|
if (!isWorkspaceContentPath(pathname)) return workspaceHomePath(slug);
|
|
107
|
-
|
|
108
|
-
if (!doc) return null;
|
|
109
|
-
return contentTypePath(slug, decodeURIComponent(doc[1]!));
|
|
107
|
+
return null;
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
export function isWorkspaceSettingsPath(pathname: string) {
|
|
@@ -122,3 +120,9 @@ export function isOrgPickerPath(pathname: string) {
|
|
|
122
120
|
const path = pathname.replace(/\/$/, "") || "/";
|
|
123
121
|
return path === "/invite";
|
|
124
122
|
}
|
|
123
|
+
|
|
124
|
+
/** CLI approval is a full-screen form, not a workspace. */
|
|
125
|
+
export function isCliAuthorizePath(pathname: string) {
|
|
126
|
+
const path = pathname.replace(/\/$/, "") || "/";
|
|
127
|
+
return path === "/cli/authorize";
|
|
128
|
+
}
|