@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
package/src/site-bar.tsx
CHANGED
|
@@ -1,70 +1,78 @@
|
|
|
1
1
|
// packages/studio/src/site-bar.tsx
|
|
2
|
-
// On-site bar
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// On-site bar. One mode button, the page, then Agent and Publish.
|
|
3
|
+
// Preview fills the bar from the button that was pressed and draws a frame
|
|
4
|
+
// from the bottom-right corner. The bar mounts in a shadow root.
|
|
5
|
+
import {
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useLayoutEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
type MouseEvent,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
type Ref,
|
|
16
|
+
} from "react";
|
|
17
|
+
import { createPortal } from "react-dom";
|
|
5
18
|
import {
|
|
6
19
|
checkoutBranch,
|
|
7
20
|
createPreviewTicket,
|
|
8
|
-
|
|
21
|
+
fetchDocNav,
|
|
9
22
|
fetchGit,
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
fetchMe,
|
|
24
|
+
fetchWorkspace,
|
|
25
|
+
type PageNavItem,
|
|
12
26
|
} from "./api";
|
|
13
|
-
import {
|
|
27
|
+
import { resolveDashboardOpenHref } from "./site-dashboard-href";
|
|
28
|
+
import { GearIcon, GitBranchIcon } from "./chrome/icons";
|
|
14
29
|
import { matchAppHotkey, modifierSymbol } from "./chrome/hotkeys";
|
|
15
30
|
import { PublishIcon, SparkIcon } from "./chat/icons";
|
|
16
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
DEVBAR_EASE_DRAWER,
|
|
33
|
+
DEVBAR_EASE_IN,
|
|
34
|
+
DEVBAR_EASE_OUT,
|
|
35
|
+
DEVBAR_FLOOD_ENTER_MS,
|
|
36
|
+
DEVBAR_FLOOD_EXIT_MS,
|
|
37
|
+
HIGHLIGHT_BLOCKS_KEY,
|
|
38
|
+
PREVIEW_WITH_AGENT_KEY,
|
|
39
|
+
devbarChrome,
|
|
40
|
+
originWithin,
|
|
41
|
+
prefersReducedMotion,
|
|
42
|
+
readSiteTheme,
|
|
43
|
+
readStoredFlag,
|
|
44
|
+
readStoredMode,
|
|
45
|
+
rememberMode,
|
|
46
|
+
rememberStoredFlag,
|
|
47
|
+
resolveSiteTheme,
|
|
48
|
+
shouldAnimateWidth,
|
|
49
|
+
type DevbarActivity,
|
|
50
|
+
type DevbarMode,
|
|
51
|
+
type DevbarStep,
|
|
52
|
+
type SiteThemePreference,
|
|
53
|
+
} from "./devbar-state";
|
|
54
|
+
import { PREVIEW_CHROME_BORDER, PreviewFrame } from "./preview-frame";
|
|
17
55
|
|
|
18
56
|
export const DEVBAR_CLASS = "backmatter-devbar";
|
|
19
57
|
|
|
20
58
|
const DEFAULT_PREVIEW_ENTRY = "/api/backmatter/preview";
|
|
21
59
|
|
|
60
|
+
export type { DevbarActivity, DevbarStep };
|
|
61
|
+
|
|
22
62
|
export type PageMenuDoc = {
|
|
23
63
|
locale: string;
|
|
24
64
|
type: string;
|
|
25
65
|
slug: string;
|
|
26
66
|
};
|
|
27
67
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
label: string;
|
|
31
|
-
href: string;
|
|
32
|
-
current: boolean;
|
|
33
|
-
};
|
|
68
|
+
const PAGE_MENU_SIZE = 10;
|
|
69
|
+
const PAGE_MENU_WIDTH = 360;
|
|
34
70
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
docs: readonly PageMenuDoc[];
|
|
38
|
-
models: readonly RouteModel[];
|
|
39
|
-
current: PageMenuDoc | null;
|
|
40
|
-
}): PageMenuItem[] {
|
|
41
|
-
const byType = new Map(input.models.map((model) => [model.type, model]));
|
|
42
|
-
const locales = new Set(input.docs.map((doc) => doc.locale));
|
|
43
|
-
const showLocale = locales.size > 1;
|
|
44
|
-
const items: PageMenuItem[] = [];
|
|
45
|
-
for (const doc of input.docs) {
|
|
46
|
-
const model = byType.get(doc.type);
|
|
47
|
-
if (!model || !isRoutable(model)) continue;
|
|
48
|
-
const href = routeFor(model, doc.locale, doc.slug);
|
|
49
|
-
if (!href) continue;
|
|
50
|
-
const name = `${doc.type}/${doc.slug}`;
|
|
51
|
-
items.push({
|
|
52
|
-
key: `${doc.locale}/${doc.type}/${doc.slug}`,
|
|
53
|
-
label: showLocale ? `${name} · ${doc.locale}` : name,
|
|
54
|
-
href,
|
|
55
|
-
current: Boolean(
|
|
56
|
-
input.current &&
|
|
57
|
-
input.current.locale === doc.locale &&
|
|
58
|
-
input.current.type === doc.type &&
|
|
59
|
-
input.current.slug === doc.slug,
|
|
60
|
-
),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
items.sort((a, b) => a.label.localeCompare(b.label));
|
|
64
|
-
return items;
|
|
71
|
+
function pageMenuCacheKey(workspaceId: string, query: string) {
|
|
72
|
+
return `${workspaceId}:${query.trim().toLowerCase()}`;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
type MenuState = "page" | "branch" | null;
|
|
75
|
+
type MenuState = "page" | "branch" | "settings" | null;
|
|
68
76
|
|
|
69
77
|
export function SiteBar({
|
|
70
78
|
label,
|
|
@@ -75,111 +83,607 @@ export function SiteBar({
|
|
|
75
83
|
chatOpen,
|
|
76
84
|
publishOpen,
|
|
77
85
|
changeCount,
|
|
86
|
+
lineDiff,
|
|
87
|
+
activity = null,
|
|
78
88
|
previewEntryPath = DEFAULT_PREVIEW_ENTRY,
|
|
79
89
|
siteMode = "preview",
|
|
90
|
+
frame = false,
|
|
91
|
+
frameRadius,
|
|
92
|
+
entrance = "auto",
|
|
93
|
+
dashboardHref,
|
|
80
94
|
onExit,
|
|
81
95
|
onEnterPreview,
|
|
82
96
|
onToggleChat,
|
|
83
97
|
onTogglePublish,
|
|
98
|
+
onStop,
|
|
99
|
+
onViewLive,
|
|
100
|
+
onPublishedDone,
|
|
101
|
+
onSignOut,
|
|
102
|
+
onPublishAnchor,
|
|
84
103
|
}: {
|
|
85
104
|
label: string;
|
|
86
105
|
branch?: string;
|
|
87
106
|
tree?: string;
|
|
88
107
|
doc: (PageMenuDoc & { workspaceId: string }) | null;
|
|
89
|
-
siteMode?:
|
|
108
|
+
siteMode?: DevbarMode;
|
|
90
109
|
exiting?: boolean;
|
|
91
110
|
chatOpen: boolean;
|
|
92
111
|
publishOpen: boolean;
|
|
93
112
|
changeCount: number;
|
|
113
|
+
/** Insertions and deletions behind the preview changes row. */
|
|
114
|
+
lineDiff?: { added: number; removed: number };
|
|
115
|
+
activity?: DevbarActivity | null;
|
|
94
116
|
previewEntryPath?: string;
|
|
117
|
+
/** Viewport frame. Hosts opt in with `devBar.previewFrame`. */
|
|
118
|
+
frame?: boolean;
|
|
119
|
+
/** Bottom corner radius for the preview ring (`devBar.previewFrameRadius`). */
|
|
120
|
+
frameRadius?: number;
|
|
121
|
+
/** `play` runs the rise on every mount. `auto` skips it when this tab was already in this mode. */
|
|
122
|
+
entrance?: "auto" | "play";
|
|
123
|
+
dashboardHref?: string;
|
|
95
124
|
onExit: () => void;
|
|
96
125
|
onEnterPreview?: () => void;
|
|
97
126
|
onToggleChat: () => void;
|
|
98
127
|
onTogglePublish: () => void;
|
|
128
|
+
onStop?: () => void;
|
|
129
|
+
onViewLive?: () => void;
|
|
130
|
+
/** Called 3.2s after the published row appears. */
|
|
131
|
+
onPublishedDone?: () => void;
|
|
132
|
+
/** Drops the site session. The settings menu shows Sign out when this is set. */
|
|
133
|
+
onSignOut?: () => void;
|
|
134
|
+
/** Light-DOM publish panel positions from the Deploy button. */
|
|
135
|
+
onPublishAnchor?: (rect: DOMRect | null) => void;
|
|
99
136
|
}) {
|
|
100
|
-
const
|
|
137
|
+
const hostRef = useRef<HTMLDivElement>(null);
|
|
138
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
139
|
+
const barRef = useRef<HTMLElement | null>(null);
|
|
140
|
+
const rowRef = useRef<HTMLDivElement>(null);
|
|
141
|
+
const modeBtnRef = useRef<HTMLButtonElement>(null);
|
|
142
|
+
const publishBtnRef = useRef<HTMLButtonElement>(null);
|
|
143
|
+
const settingsBtnRef = useRef<HTMLButtonElement>(null);
|
|
144
|
+
const checkRef = useRef<HTMLSpanElement>(null);
|
|
145
|
+
const [shadow, setShadow] = useState<ShadowRoot | null>(null);
|
|
146
|
+
const attachHost = useCallback((host: HTMLDivElement | null) => {
|
|
147
|
+
hostRef.current = host;
|
|
148
|
+
if (!host) {
|
|
149
|
+
setShadow(null);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const root = host.shadowRoot ?? host.attachShadow({ mode: "open" });
|
|
153
|
+
setShadow((prev) => (prev === root ? prev : root));
|
|
154
|
+
}, []);
|
|
155
|
+
const [barNode, setBarNode] = useState<HTMLElement | null>(null);
|
|
101
156
|
const [menu, setMenu] = useState<MenuState>(null);
|
|
157
|
+
const [mode, setMode] = useState<DevbarMode>(siteMode);
|
|
158
|
+
const [hidden, setHidden] = useState(false);
|
|
159
|
+
const hiddenRef = useRef(false);
|
|
160
|
+
const pending = useRef<DevbarMode | null>(null);
|
|
161
|
+
const widthRef = useRef(0);
|
|
162
|
+
const widthReady = useRef(false);
|
|
163
|
+
const rose = useRef(false);
|
|
164
|
+
const handlers = useRef({
|
|
165
|
+
toggleHidden: () => {},
|
|
166
|
+
onModeKey: (_origin: HTMLElement) => {},
|
|
167
|
+
onEscape: () => {},
|
|
168
|
+
openDashboard: () => false,
|
|
169
|
+
openShortcuts: () => {},
|
|
170
|
+
});
|
|
171
|
+
const previewWithAgent = useRef(true);
|
|
172
|
+
const [theme, setTheme] = useState<SiteThemePreference>("system");
|
|
173
|
+
const [highlightBlocks, setHighlightBlocks] = useState(true);
|
|
174
|
+
const [previewAgent, setPreviewAgent] = useState(true);
|
|
175
|
+
const [settingsPane, setSettingsPane] = useState<"main" | "shortcuts">("main");
|
|
176
|
+
const [hostName, setHostName] = useState("");
|
|
177
|
+
const [workspaceSlug, setWorkspaceSlug] = useState<string | null>(null);
|
|
178
|
+
const [workspaceDefaultLocale, setWorkspaceDefaultLocale] = useState("en");
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const id = doc?.workspaceId;
|
|
182
|
+
if (!id) {
|
|
183
|
+
setWorkspaceSlug(null);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let cancelled = false;
|
|
187
|
+
void fetchWorkspace(id)
|
|
188
|
+
.then(({ workspace }) => {
|
|
189
|
+
if (cancelled) return;
|
|
190
|
+
setWorkspaceSlug(workspace.slug);
|
|
191
|
+
setWorkspaceDefaultLocale(workspace.locales[0] ?? "en");
|
|
192
|
+
})
|
|
193
|
+
.catch(() => {
|
|
194
|
+
if (!cancelled) setWorkspaceSlug(null);
|
|
195
|
+
});
|
|
196
|
+
return () => {
|
|
197
|
+
cancelled = true;
|
|
198
|
+
};
|
|
199
|
+
}, [doc?.workspaceId]);
|
|
200
|
+
|
|
201
|
+
const dashboardOpenHref = useMemo(
|
|
202
|
+
() => resolveDashboardOpenHref(dashboardHref, doc, workspaceSlug, workspaceDefaultLocale),
|
|
203
|
+
[dashboardHref, doc, workspaceSlug, workspaceDefaultLocale],
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const setBar = useCallback((node: HTMLElement | null) => {
|
|
207
|
+
barRef.current = node;
|
|
208
|
+
if (node && !(node.getRootNode() instanceof ShadowRoot)) return;
|
|
209
|
+
setBarNode(node);
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const chrome = devbarChrome({ siteMode: mode, changeCount, activity });
|
|
213
|
+
const visualMode = chrome.mode;
|
|
214
|
+
const activityKey =
|
|
215
|
+
activity?.type === "editing"
|
|
216
|
+
? `editing:${activity.field}`
|
|
217
|
+
: activity?.type === "publishing"
|
|
218
|
+
? `publishing:${activity.steps.map((step) => `${step.state}:${step.label}`).join("|")}`
|
|
219
|
+
: activity?.type === "published"
|
|
220
|
+
? `published:${activity.sha}`
|
|
221
|
+
: "";
|
|
222
|
+
const signature = `${mode}|${chrome.row}|${changeCount}|${lineDiff?.added ?? ""}|${lineDiff?.removed ?? ""}|${label}|${activityKey}`;
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
setHostName(location.host);
|
|
226
|
+
const storage = browserStorage();
|
|
227
|
+
const nextTheme = readSiteTheme(storage);
|
|
228
|
+
setPreviewAgent(readStoredFlag(storage, PREVIEW_WITH_AGENT_KEY, true));
|
|
229
|
+
setTheme(nextTheme);
|
|
230
|
+
setHighlightBlocks(readStoredFlag(storage, HIGHLIGHT_BLOCKS_KEY, true));
|
|
231
|
+
}, []);
|
|
232
|
+
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
applySiteTheme(theme);
|
|
235
|
+
if (theme !== "system" || typeof matchMedia !== "function") return;
|
|
236
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
237
|
+
const onChange = () => applySiteTheme("system");
|
|
238
|
+
media.addEventListener("change", onChange);
|
|
239
|
+
return () => media.removeEventListener("change", onChange);
|
|
240
|
+
}, [theme]);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
document.documentElement.classList.toggle("backmatter-blocks", highlightBlocks);
|
|
244
|
+
return () => document.documentElement.classList.remove("backmatter-blocks");
|
|
245
|
+
}, [highlightBlocks]);
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
const host = hostRef.current;
|
|
249
|
+
if (!host) return;
|
|
250
|
+
const root = document.documentElement;
|
|
251
|
+
const name = `${DEVBAR_CLASS}-pointing`;
|
|
252
|
+
const sync = () => host.classList.toggle(name, root.classList.contains("backmatter-pointing"));
|
|
253
|
+
sync();
|
|
254
|
+
const observer = new MutationObserver(sync);
|
|
255
|
+
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
|
|
256
|
+
return () => observer.disconnect();
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (pending.current && pending.current !== siteMode && !exiting) {
|
|
261
|
+
pending.current = null;
|
|
262
|
+
rememberMode(siteMode);
|
|
263
|
+
setMode(siteMode);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!pending.current) setMode(siteMode);
|
|
267
|
+
if (pending.current === siteMode) pending.current = null;
|
|
268
|
+
}, [siteMode, exiting]);
|
|
102
269
|
|
|
103
270
|
useEffect(() => {
|
|
104
271
|
if (!menu) return;
|
|
105
272
|
function onPointer(event: PointerEvent) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
if (rootRef.current?.contains(target)) return;
|
|
109
|
-
setMenu(null);
|
|
110
|
-
}
|
|
111
|
-
function onKey(event: KeyboardEvent) {
|
|
112
|
-
if (event.key !== "Escape") return;
|
|
273
|
+
const root = rootRef.current;
|
|
274
|
+
if (root && event.composedPath().includes(root)) return;
|
|
113
275
|
setMenu(null);
|
|
114
276
|
}
|
|
115
277
|
document.addEventListener("pointerdown", onPointer);
|
|
116
|
-
document.
|
|
278
|
+
return () => document.removeEventListener("pointerdown", onPointer);
|
|
279
|
+
}, [menu]);
|
|
280
|
+
|
|
281
|
+
useLayoutEffect(() => {
|
|
282
|
+
const bar = barRef.current;
|
|
283
|
+
const button = settingsBtnRef.current;
|
|
284
|
+
if (!bar || !button || menu !== "settings") return;
|
|
285
|
+
placeMenuRight(bar, button);
|
|
286
|
+
}, [menu, barNode, signature]);
|
|
287
|
+
|
|
288
|
+
useLayoutEffect(() => {
|
|
289
|
+
if (!onPublishAnchor) return;
|
|
290
|
+
const button = publishBtnRef.current;
|
|
291
|
+
if (!publishOpen || !button) {
|
|
292
|
+
onPublishAnchor(null);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const report = () => onPublishAnchor(button.getBoundingClientRect());
|
|
296
|
+
report();
|
|
297
|
+
window.addEventListener("resize", report);
|
|
298
|
+
window.addEventListener("scroll", report, true);
|
|
117
299
|
return () => {
|
|
118
|
-
|
|
119
|
-
|
|
300
|
+
window.removeEventListener("resize", report);
|
|
301
|
+
window.removeEventListener("scroll", report, true);
|
|
120
302
|
};
|
|
121
|
-
}, [
|
|
303
|
+
}, [publishOpen, onPublishAnchor, signature, barNode]);
|
|
122
304
|
|
|
123
305
|
useEffect(() => {
|
|
124
306
|
function onKey(event: KeyboardEvent) {
|
|
125
|
-
if (
|
|
126
|
-
|
|
307
|
+
if (isTypingTarget(event.composedPath()[0] ?? null)) return;
|
|
308
|
+
if ((event.metaKey || event.ctrlKey) && event.key === ".") {
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
handlers.current.toggleHidden();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === "p") {
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
const button = modeBtnRef.current;
|
|
316
|
+
if (!button || button.disabled) return;
|
|
317
|
+
handlers.current.onModeKey(button);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key.toLowerCase() === "d") {
|
|
321
|
+
if (!handlers.current.openDashboard()) return;
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (event.key === "?" && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
326
|
+
event.preventDefault();
|
|
327
|
+
handlers.current.openShortcuts();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (event.key === "Escape") {
|
|
331
|
+
handlers.current.onEscape();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (matchAppHotkey(event) === "chat") setMenu(null);
|
|
127
335
|
}
|
|
128
336
|
document.addEventListener("keydown", onKey, true);
|
|
129
337
|
return () => document.removeEventListener("keydown", onKey, true);
|
|
130
338
|
}, []);
|
|
131
339
|
|
|
132
|
-
|
|
340
|
+
useLayoutEffect(() => {
|
|
341
|
+
const bar = barRef.current;
|
|
342
|
+
if (!shadow || !bar || !(bar.getRootNode() instanceof ShadowRoot)) return;
|
|
343
|
+
|
|
344
|
+
if (!rose.current) {
|
|
345
|
+
rose.current = true;
|
|
346
|
+
const settled = entrance !== "play" && readStoredMode() === siteMode;
|
|
347
|
+
if (entrance !== "play") rememberMode(siteMode);
|
|
348
|
+
if (settled) {
|
|
349
|
+
rootRef.current?.setAttribute("data-settled", "true");
|
|
350
|
+
requestAnimationFrame(() => {
|
|
351
|
+
requestAnimationFrame(() => rootRef.current?.removeAttribute("data-settled"));
|
|
352
|
+
});
|
|
353
|
+
} else if (prefersReducedMotion()) {
|
|
354
|
+
bar.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200, easing: "ease", fill: "backwards" });
|
|
355
|
+
} else {
|
|
356
|
+
const rise = bar.animate(
|
|
357
|
+
[{ transform: "translateY(100%)" }, { transform: "none" }],
|
|
358
|
+
{ duration: 460, easing: DEVBAR_EASE_OUT, delay: 600, fill: "backwards" },
|
|
359
|
+
);
|
|
360
|
+
rise.id = "rise";
|
|
361
|
+
if (rowRef.current) staggerChildren(rowRef.current, { delay: 740 });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const first = !widthReady.current;
|
|
366
|
+
widthReady.current = true;
|
|
367
|
+
const running = bar.getAnimations().some((animation) => animation.id === "w");
|
|
368
|
+
const before = running ? bar.offsetWidth : widthRef.current;
|
|
369
|
+
bar.getAnimations().filter((animation) => animation.id === "w").forEach((animation) => animation.cancel());
|
|
370
|
+
const after = bar.offsetWidth;
|
|
371
|
+
widthRef.current = after;
|
|
372
|
+
if (first) return;
|
|
373
|
+
const row = rowRef.current;
|
|
374
|
+
if (row) {
|
|
375
|
+
if (prefersReducedMotion()) {
|
|
376
|
+
row.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150, easing: "ease", fill: "backwards" });
|
|
377
|
+
} else {
|
|
378
|
+
staggerChildren(row, { delay: 40, blur: true });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (prefersReducedMotion() || !shouldAnimateWidth(before, after)) return;
|
|
382
|
+
const width = bar.animate(
|
|
383
|
+
[{ width: `${before}px` }, { width: `${after}px` }],
|
|
384
|
+
{ duration: 380, easing: DEVBAR_EASE_DRAWER },
|
|
385
|
+
);
|
|
386
|
+
width.id = "w";
|
|
387
|
+
}, [shadow, signature, entrance, siteMode]);
|
|
388
|
+
|
|
389
|
+
const publishedKey = activity?.type === "published" ? activity.sha : "";
|
|
390
|
+
useLayoutEffect(() => {
|
|
391
|
+
const check = checkRef.current;
|
|
392
|
+
if (!publishedKey || !check || prefersReducedMotion()) return;
|
|
393
|
+
check.animate(
|
|
394
|
+
[
|
|
395
|
+
{ transform: "scale(0.5)", opacity: 0 },
|
|
396
|
+
{ transform: "none", opacity: 1 },
|
|
397
|
+
],
|
|
398
|
+
{ duration: 420, delay: 140, easing: "cubic-bezier(.34, 1.56, .64, 1)", fill: "backwards" },
|
|
399
|
+
);
|
|
400
|
+
}, [publishedKey]);
|
|
401
|
+
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (!publishedKey || !onPublishedDone) return;
|
|
404
|
+
const id = window.setTimeout(onPublishedDone, 3200);
|
|
405
|
+
return () => window.clearTimeout(id);
|
|
406
|
+
}, [publishedKey, onPublishedDone]);
|
|
407
|
+
|
|
408
|
+
function changeMode(next: DevbarMode, origin: HTMLElement) {
|
|
409
|
+
if (exiting || next === mode) return;
|
|
410
|
+
if (next === "preview" && !onEnterPreview) return;
|
|
411
|
+
const bar = barRef.current;
|
|
412
|
+
if (bar) {
|
|
413
|
+
const point = originWithin(bar.getBoundingClientRect(), origin.getBoundingClientRect());
|
|
414
|
+
bar.style.setProperty("--ox", `${point.x}px`);
|
|
415
|
+
bar.style.setProperty("--oy", `${point.y}px`);
|
|
416
|
+
}
|
|
417
|
+
if (next === "live") {
|
|
418
|
+
setMenu(null);
|
|
419
|
+
if (chatOpen) onToggleChat();
|
|
420
|
+
if (publishOpen) onTogglePublish();
|
|
421
|
+
}
|
|
422
|
+
pending.current = next;
|
|
423
|
+
rememberMode(next);
|
|
424
|
+
setMode(next);
|
|
425
|
+
if (next === "preview") onEnterPreview?.();
|
|
426
|
+
else onExit();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function setToolbarHidden(next: boolean) {
|
|
430
|
+
if (hiddenRef.current === next) return;
|
|
431
|
+
hiddenRef.current = next;
|
|
432
|
+
setMenu(null);
|
|
433
|
+
setHidden(next);
|
|
434
|
+
const bar = barRef.current;
|
|
435
|
+
if (!bar || !(bar.getRootNode() instanceof ShadowRoot)) return;
|
|
436
|
+
const activeRoot = bar.getRootNode();
|
|
437
|
+
const active = activeRoot instanceof ShadowRoot ? activeRoot.activeElement : null;
|
|
438
|
+
if (next && active instanceof HTMLElement) active.blur();
|
|
439
|
+
bar.getAnimations().filter((animation) => animation.id === "hide" || animation.id === "rise").forEach((animation) => animation.cancel());
|
|
440
|
+
if (prefersReducedMotion()) {
|
|
441
|
+
const fade = bar.animate(
|
|
442
|
+
[{ opacity: next ? 1 : 0 }, { opacity: next ? 0 : 1 }],
|
|
443
|
+
{ duration: 200, easing: "ease", fill: "forwards" },
|
|
444
|
+
);
|
|
445
|
+
fade.id = next ? "hide" : "rise";
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (next) {
|
|
449
|
+
const hide = bar.animate(
|
|
450
|
+
[{ transform: "translateY(0)" }, { transform: "translateY(100%)" }],
|
|
451
|
+
{ duration: 220, easing: DEVBAR_EASE_IN, fill: "forwards" },
|
|
452
|
+
);
|
|
453
|
+
hide.id = "hide";
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const rise = bar.animate(
|
|
457
|
+
[{ transform: "translateY(100%)" }, { transform: "none" }],
|
|
458
|
+
{ duration: 460, easing: DEVBAR_EASE_OUT, fill: "backwards" },
|
|
459
|
+
);
|
|
460
|
+
rise.id = "rise";
|
|
461
|
+
if (rowRef.current) staggerChildren(rowRef.current, { delay: 140 });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
handlers.current.openDashboard = () => {
|
|
465
|
+
if (!dashboardHref) return false;
|
|
466
|
+
void (async () => {
|
|
467
|
+
let href = dashboardOpenHref ?? dashboardHref;
|
|
468
|
+
if (doc?.workspaceId && doc.type && doc.slug && !workspaceSlug) {
|
|
469
|
+
try {
|
|
470
|
+
const { workspace } = await fetchWorkspace(doc.workspaceId);
|
|
471
|
+
href =
|
|
472
|
+
resolveDashboardOpenHref(dashboardHref, doc, workspace.slug, workspace.locales[0] ?? "en") ??
|
|
473
|
+
href;
|
|
474
|
+
} catch {
|
|
475
|
+
// Keep the dashboard origin.
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
location.assign(href);
|
|
479
|
+
})();
|
|
480
|
+
return true;
|
|
481
|
+
};
|
|
482
|
+
handlers.current.openShortcuts = () => {
|
|
483
|
+
setSettingsPane("shortcuts");
|
|
484
|
+
setMenu("settings");
|
|
485
|
+
};
|
|
486
|
+
handlers.current.toggleHidden = () => setToolbarHidden(!hiddenRef.current);
|
|
487
|
+
handlers.current.onModeKey = (origin) => changeMode(visualMode === "preview" ? "live" : "preview", origin);
|
|
488
|
+
handlers.current.onEscape = () => {
|
|
489
|
+
if (menu) {
|
|
490
|
+
setMenu(null);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (publishOpen) {
|
|
494
|
+
onTogglePublish();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (activity?.type === "editing") onStop?.();
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
function openAgent(origin: HTMLElement) {
|
|
501
|
+
if (exiting) return;
|
|
502
|
+
setMenu(null);
|
|
503
|
+
if (publishOpen) onTogglePublish();
|
|
504
|
+
if (mode === "live" && previewWithAgent.current) changeMode("preview", origin);
|
|
505
|
+
onToggleChat();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function openPublish() {
|
|
509
|
+
if (exiting) return;
|
|
510
|
+
setMenu(null);
|
|
133
511
|
if (chatOpen) onToggleChat();
|
|
512
|
+
onTogglePublish();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
previewWithAgent.current = previewAgent;
|
|
516
|
+
|
|
517
|
+
function toggleSettings(pane: "main" | "shortcuts") {
|
|
134
518
|
if (publishOpen) onTogglePublish();
|
|
519
|
+
setSettingsPane(pane);
|
|
520
|
+
setMenu((open) => (open === "settings" && pane === "main" ? null : "settings"));
|
|
135
521
|
}
|
|
136
522
|
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
dismissPanels();
|
|
141
|
-
return next;
|
|
142
|
-
});
|
|
523
|
+
function togglePageMenu() {
|
|
524
|
+
if (publishOpen) onTogglePublish();
|
|
525
|
+
setMenu((open) => (open === "page" ? null : "page"));
|
|
143
526
|
}
|
|
144
527
|
|
|
145
|
-
const
|
|
528
|
+
const modeDisabled = Boolean(exiting) || (visualMode === "live" && !onEnterPreview);
|
|
529
|
+
const publishDisabled = !doc || changeCount <= 0;
|
|
530
|
+
const modeButton = (
|
|
531
|
+
<ModeButton
|
|
532
|
+
mode={visualMode}
|
|
533
|
+
disabled={modeDisabled}
|
|
534
|
+
buttonRef={modeBtnRef}
|
|
535
|
+
onClick={(event) => changeMode(visualMode === "preview" ? "live" : "preview", event.currentTarget)}
|
|
536
|
+
/>
|
|
537
|
+
);
|
|
146
538
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
539
|
+
let row: ReactNode;
|
|
540
|
+
if (activity?.type === "editing") {
|
|
541
|
+
row = (
|
|
542
|
+
<>
|
|
543
|
+
{modeButton}
|
|
544
|
+
<span className={`${DEVBAR_CLASS}-spin`} aria-hidden />
|
|
545
|
+
<span className={`${DEVBAR_CLASS}-shimmer`}>Agent is editing</span>
|
|
546
|
+
<code className={`${DEVBAR_CLASS}-field`}>{activity.field}</code>
|
|
547
|
+
<button type="button" className={`${DEVBAR_CLASS}-btn`} onClick={() => onStop?.()}>
|
|
548
|
+
Stop
|
|
549
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>esc</kbd>
|
|
550
|
+
</button>
|
|
551
|
+
</>
|
|
552
|
+
);
|
|
553
|
+
} else if (activity?.type === "publishing") {
|
|
554
|
+
row = (
|
|
555
|
+
<>
|
|
556
|
+
{modeButton}
|
|
557
|
+
<span className={`${DEVBAR_CLASS}-steps`}>
|
|
558
|
+
{activity.steps.map((step, index) => (
|
|
559
|
+
<span key={`${step.label}:${index}`} className={`${DEVBAR_CLASS}-step`}>
|
|
560
|
+
{index > 0 ? <span className={`${DEVBAR_CLASS}-dash`} aria-hidden>—</span> : null}
|
|
561
|
+
<StepMark state={step.state} />
|
|
562
|
+
<span>{step.label}</span>
|
|
563
|
+
</span>
|
|
564
|
+
))}
|
|
565
|
+
</span>
|
|
566
|
+
</>
|
|
567
|
+
);
|
|
568
|
+
} else if (activity?.type === "published") {
|
|
569
|
+
row = (
|
|
570
|
+
<>
|
|
571
|
+
{modeButton}
|
|
572
|
+
<span ref={checkRef} className={`${DEVBAR_CLASS}-check`} aria-hidden>
|
|
573
|
+
✓
|
|
574
|
+
</span>
|
|
575
|
+
<span className={`${DEVBAR_CLASS}-published`}>
|
|
576
|
+
Published <code className={`${DEVBAR_CLASS}-field`}>{activity.sha}</code>
|
|
577
|
+
<span className={`${DEVBAR_CLASS}-when`}> · {activity.when}</span>
|
|
578
|
+
</span>
|
|
579
|
+
<button type="button" className={`${DEVBAR_CLASS}-btn`} onClick={() => (onViewLive ?? onExit)()}>
|
|
580
|
+
View live
|
|
581
|
+
</button>
|
|
582
|
+
<AgentButton
|
|
583
|
+
pressed={chatOpen}
|
|
584
|
+
disabled={!doc}
|
|
585
|
+
shortcut={modifierSymbol()}
|
|
586
|
+
onClick={(event) => openAgent(event.currentTarget)}
|
|
587
|
+
/>
|
|
588
|
+
</>
|
|
589
|
+
);
|
|
590
|
+
} else {
|
|
591
|
+
row = (
|
|
592
|
+
<>
|
|
593
|
+
{modeButton}
|
|
178
594
|
<PageMenu
|
|
179
595
|
label={label}
|
|
180
596
|
doc={doc}
|
|
597
|
+
dashboardHref={dashboardOpenHref}
|
|
598
|
+
bar={barNode}
|
|
181
599
|
open={menu === "page"}
|
|
182
|
-
|
|
600
|
+
expanded={menu === "page"}
|
|
601
|
+
onToggle={togglePageMenu}
|
|
602
|
+
/>
|
|
603
|
+
<span className={`${DEVBAR_CLASS}-rule`} aria-hidden />
|
|
604
|
+
<AgentButton
|
|
605
|
+
pressed={chatOpen}
|
|
606
|
+
disabled={!doc}
|
|
607
|
+
shortcut={modifierSymbol()}
|
|
608
|
+
onClick={(event) => openAgent(event.currentTarget)}
|
|
609
|
+
/>
|
|
610
|
+
<PublishButton
|
|
611
|
+
buttonRef={publishBtnRef}
|
|
612
|
+
open={publishOpen}
|
|
613
|
+
count={changeCount}
|
|
614
|
+
disabled={publishDisabled}
|
|
615
|
+
onClick={openPublish}
|
|
616
|
+
/>
|
|
617
|
+
<SettingsButton
|
|
618
|
+
buttonRef={settingsBtnRef}
|
|
619
|
+
open={menu === "settings"}
|
|
620
|
+
onClick={() => toggleSettings("main")}
|
|
621
|
+
/>
|
|
622
|
+
</>
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const body = (
|
|
627
|
+
<>
|
|
628
|
+
<style>{BAR_CSS}</style>
|
|
629
|
+
<div
|
|
630
|
+
ref={rootRef}
|
|
631
|
+
className={`${DEVBAR_CLASS}-root`}
|
|
632
|
+
data-mode={visualMode}
|
|
633
|
+
data-branch={branch || undefined}
|
|
634
|
+
>
|
|
635
|
+
{frame ? <PreviewFrame active={visualMode === "preview"} radius={frameRadius} /> : null}
|
|
636
|
+
<footer
|
|
637
|
+
ref={setBar}
|
|
638
|
+
aria-label="Backmatter"
|
|
639
|
+
aria-hidden={hidden || undefined}
|
|
640
|
+
inert={hidden ? true : undefined}
|
|
641
|
+
data-hidden={hidden ? "true" : "false"}
|
|
642
|
+
data-branch={branch || undefined}
|
|
643
|
+
className={DEVBAR_CLASS}
|
|
644
|
+
>
|
|
645
|
+
<div className={`${DEVBAR_CLASS}-clip`}>
|
|
646
|
+
<span className={`${DEVBAR_CLASS}-flood`} aria-hidden />
|
|
647
|
+
<div ref={rowRef} className={`${DEVBAR_CLASS}-row`}>
|
|
648
|
+
{row}
|
|
649
|
+
</div>
|
|
650
|
+
{activity?.type === "publishing" ? (
|
|
651
|
+
<span
|
|
652
|
+
className={`${DEVBAR_CLASS}-progress`}
|
|
653
|
+
style={{ width: `${Math.max(0, Math.min(1, activity.progress)) * 100}%` }}
|
|
654
|
+
aria-hidden
|
|
655
|
+
/>
|
|
656
|
+
) : null}
|
|
657
|
+
</div>
|
|
658
|
+
<div className={`${DEVBAR_CLASS}-sr`} aria-live="polite">
|
|
659
|
+
{chrome.announce}
|
|
660
|
+
</div>
|
|
661
|
+
</footer>
|
|
662
|
+
<SettingsMenu
|
|
663
|
+
open={menu === "settings"}
|
|
664
|
+
pane={settingsPane}
|
|
665
|
+
hostName={hostName}
|
|
666
|
+
branch={branch}
|
|
667
|
+
bar={barNode}
|
|
668
|
+
highlightBlocks={highlightBlocks}
|
|
669
|
+
previewAgent={previewAgent}
|
|
670
|
+
dashboardHref={dashboardOpenHref}
|
|
671
|
+
onHighlight={(on) => {
|
|
672
|
+
setHighlightBlocks(on);
|
|
673
|
+
rememberStoredFlag(browserStorage(), HIGHLIGHT_BLOCKS_KEY, on);
|
|
674
|
+
}}
|
|
675
|
+
onPreviewAgent={(on) => {
|
|
676
|
+
setPreviewAgent(on);
|
|
677
|
+
rememberStoredFlag(browserStorage(), PREVIEW_WITH_AGENT_KEY, on);
|
|
678
|
+
}}
|
|
679
|
+
onShortcuts={() => setSettingsPane("shortcuts")}
|
|
680
|
+
onBack={() => setSettingsPane("main")}
|
|
681
|
+
onDashboard={() => {
|
|
682
|
+
handlers.current.openDashboard();
|
|
683
|
+
}}
|
|
684
|
+
onHide={() => setToolbarHidden(true)}
|
|
685
|
+
onSwitchBranch={branch ? () => setMenu("branch") : undefined}
|
|
686
|
+
onSignOut={onSignOut}
|
|
183
687
|
/>
|
|
184
688
|
{branch ? (
|
|
185
689
|
<BranchMenu
|
|
@@ -187,161 +691,619 @@ export function SiteBar({
|
|
|
187
691
|
tree={tree}
|
|
188
692
|
workspaceId={doc?.workspaceId ?? null}
|
|
189
693
|
previewEntryPath={previewEntryPath}
|
|
694
|
+
bar={barNode}
|
|
190
695
|
open={menu === "branch"}
|
|
191
|
-
|
|
696
|
+
onClose={() => setMenu(null)}
|
|
192
697
|
/>
|
|
193
698
|
) : null}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
699
|
+
</div>
|
|
700
|
+
</>
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
return (
|
|
704
|
+
<div ref={attachHost} className={`${DEVBAR_CLASS}-host`} data-backmatter-devbar="">
|
|
705
|
+
<style>{LIGHT_CSS}</style>
|
|
706
|
+
{shadow ? createPortal(body, shadow) : <template shadowrootmode="open">{body}</template>}
|
|
707
|
+
</div>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function ModeButton({
|
|
712
|
+
mode,
|
|
713
|
+
disabled,
|
|
714
|
+
buttonRef,
|
|
715
|
+
onClick,
|
|
716
|
+
}: {
|
|
717
|
+
mode: DevbarMode;
|
|
718
|
+
disabled: boolean;
|
|
719
|
+
buttonRef: Ref<HTMLButtonElement>;
|
|
720
|
+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
721
|
+
}) {
|
|
722
|
+
const preview = mode === "preview";
|
|
723
|
+
return (
|
|
724
|
+
<button
|
|
725
|
+
ref={buttonRef}
|
|
726
|
+
type="button"
|
|
727
|
+
data-mode-button=""
|
|
728
|
+
className={`${DEVBAR_CLASS}-mode`}
|
|
729
|
+
aria-pressed={preview}
|
|
730
|
+
aria-keyshortcuts="Meta+Shift+P Control+Shift+P"
|
|
731
|
+
title={preview ? "View the published site" : "View draft content"}
|
|
732
|
+
disabled={disabled}
|
|
733
|
+
onClick={onClick}
|
|
734
|
+
>
|
|
735
|
+
<ModeMark exit={preview} />
|
|
736
|
+
{preview ? "Exit preview" : "Enter preview"}
|
|
737
|
+
</button>
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function AgentButton({
|
|
742
|
+
pressed,
|
|
743
|
+
disabled,
|
|
744
|
+
shortcut,
|
|
745
|
+
onClick,
|
|
746
|
+
}: {
|
|
747
|
+
pressed: boolean;
|
|
748
|
+
disabled: boolean;
|
|
749
|
+
shortcut: string;
|
|
750
|
+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
751
|
+
}) {
|
|
752
|
+
return (
|
|
753
|
+
<button
|
|
754
|
+
type="button"
|
|
755
|
+
className={`${DEVBAR_CLASS}-btn ${DEVBAR_CLASS}-agent${pressed ? ` ${DEVBAR_CLASS}-on` : ""}`}
|
|
756
|
+
aria-pressed={pressed}
|
|
757
|
+
aria-keyshortcuts="Meta+J Control+J"
|
|
758
|
+
disabled={disabled}
|
|
759
|
+
onClick={onClick}
|
|
760
|
+
>
|
|
761
|
+
<SparkIcon />
|
|
762
|
+
<span>Agent</span>
|
|
763
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>{shortcut}J</kbd>
|
|
764
|
+
</button>
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function PublishButton({
|
|
769
|
+
buttonRef,
|
|
770
|
+
open,
|
|
771
|
+
count,
|
|
772
|
+
disabled,
|
|
773
|
+
onClick,
|
|
774
|
+
}: {
|
|
775
|
+
buttonRef?: Ref<HTMLButtonElement>;
|
|
776
|
+
open?: boolean;
|
|
777
|
+
count: number;
|
|
778
|
+
disabled: boolean;
|
|
779
|
+
onClick: () => void;
|
|
780
|
+
}) {
|
|
781
|
+
return (
|
|
782
|
+
<button
|
|
783
|
+
ref={buttonRef}
|
|
784
|
+
type="button"
|
|
785
|
+
className={`${DEVBAR_CLASS}-publish${open ? ` ${DEVBAR_CLASS}-publish-on` : ""}`}
|
|
786
|
+
aria-haspopup="dialog"
|
|
787
|
+
aria-expanded={open ?? false}
|
|
788
|
+
disabled={disabled}
|
|
789
|
+
onClick={onClick}
|
|
790
|
+
>
|
|
791
|
+
<PublishIcon />
|
|
792
|
+
<span>
|
|
793
|
+
Publish
|
|
794
|
+
{count > 0 ? <span className={`${DEVBAR_CLASS}-count`}> {count}</span> : null}
|
|
795
|
+
</span>
|
|
796
|
+
</button>
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function ModeMark({ exit }: { exit: boolean }) {
|
|
801
|
+
if (exit) {
|
|
802
|
+
return (
|
|
803
|
+
<svg className={`${DEVBAR_CLASS}-mark`} width="12" height="12" viewBox="0 0 12 12" aria-hidden>
|
|
804
|
+
<path d="M3 3l6 6M9 3 3 9" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
|
|
805
|
+
</svg>
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return (
|
|
809
|
+
<svg className={`${DEVBAR_CLASS}-mark`} width="12" height="12" viewBox="0 0 12 12" aria-hidden>
|
|
810
|
+
<circle cx="6" cy="6" r="3.25" stroke="currentColor" strokeWidth="1.25" />
|
|
811
|
+
<circle cx="6" cy="6" r="1.35" fill="currentColor" />
|
|
812
|
+
</svg>
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function StepMark({ state }: { state: DevbarStep["state"] }) {
|
|
817
|
+
if (state === "done") return <span className={`${DEVBAR_CLASS}-done`} aria-hidden>✓</span>;
|
|
818
|
+
if (state === "active") return <span className={`${DEVBAR_CLASS}-spin`} aria-hidden />;
|
|
819
|
+
return <span className={`${DEVBAR_CLASS}-ring`} aria-hidden />;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function staggerChildren(row: HTMLElement, options: { delay?: number; blur?: boolean } = {}) {
|
|
823
|
+
const delay = options.delay ?? 0;
|
|
824
|
+
if (prefersReducedMotion()) {
|
|
825
|
+
row.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 150, easing: "ease", fill: "backwards" });
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
[...row.children].forEach((el, index) => {
|
|
829
|
+
(el as HTMLElement).animate(
|
|
830
|
+
[
|
|
831
|
+
{ opacity: 0, transform: "translateY(4px)", filter: options.blur ? "blur(2px)" : "none" },
|
|
832
|
+
{ opacity: 1, transform: "none", filter: "none" },
|
|
833
|
+
],
|
|
834
|
+
{ duration: 300, delay: delay + index * 28, easing: DEVBAR_EASE_OUT, fill: "backwards" },
|
|
835
|
+
);
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function isTypingTarget(target: EventTarget | null) {
|
|
840
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
841
|
+
const tag = target.tagName;
|
|
842
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
843
|
+
return target.isContentEditable;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function SettingsButton({
|
|
847
|
+
buttonRef,
|
|
848
|
+
open,
|
|
849
|
+
onClick,
|
|
850
|
+
}: {
|
|
851
|
+
buttonRef?: Ref<HTMLButtonElement>;
|
|
852
|
+
open: boolean;
|
|
853
|
+
onClick: () => void;
|
|
854
|
+
}) {
|
|
855
|
+
return (
|
|
856
|
+
<button
|
|
857
|
+
ref={buttonRef}
|
|
858
|
+
type="button"
|
|
859
|
+
className={`${DEVBAR_CLASS}-gear${open ? ` ${DEVBAR_CLASS}-gear-on` : ""}`}
|
|
860
|
+
aria-label="Settings"
|
|
861
|
+
aria-haspopup="dialog"
|
|
862
|
+
aria-expanded={open}
|
|
863
|
+
onClick={onClick}
|
|
864
|
+
>
|
|
865
|
+
<GearIcon />
|
|
866
|
+
</button>
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function SettingsMenu({
|
|
871
|
+
open,
|
|
872
|
+
pane,
|
|
873
|
+
hostName,
|
|
874
|
+
branch,
|
|
875
|
+
bar,
|
|
876
|
+
highlightBlocks,
|
|
877
|
+
previewAgent,
|
|
878
|
+
dashboardHref,
|
|
879
|
+
onHighlight,
|
|
880
|
+
onPreviewAgent,
|
|
881
|
+
onShortcuts,
|
|
882
|
+
onBack,
|
|
883
|
+
onDashboard,
|
|
884
|
+
onHide,
|
|
885
|
+
onSwitchBranch,
|
|
886
|
+
onSignOut,
|
|
887
|
+
}: {
|
|
888
|
+
open: boolean;
|
|
889
|
+
pane: "main" | "shortcuts";
|
|
890
|
+
hostName: string;
|
|
891
|
+
branch?: string;
|
|
892
|
+
bar: HTMLElement | null;
|
|
893
|
+
highlightBlocks: boolean;
|
|
894
|
+
previewAgent: boolean;
|
|
895
|
+
dashboardHref?: string;
|
|
896
|
+
onHighlight: (on: boolean) => void;
|
|
897
|
+
onPreviewAgent: (on: boolean) => void;
|
|
898
|
+
onShortcuts: () => void;
|
|
899
|
+
onBack: () => void;
|
|
900
|
+
onDashboard: () => void;
|
|
901
|
+
onHide: () => void;
|
|
902
|
+
onSwitchBranch?: () => void;
|
|
903
|
+
onSignOut?: () => void;
|
|
904
|
+
}) {
|
|
905
|
+
const titleId = useId();
|
|
906
|
+
const [email, setEmail] = useState<string | null>(null);
|
|
907
|
+
const shortcut = modifierSymbol();
|
|
908
|
+
|
|
909
|
+
useEffect(() => {
|
|
910
|
+
if (!open) return;
|
|
911
|
+
let cancelled = false;
|
|
912
|
+
void fetchMe()
|
|
913
|
+
.then((me) => {
|
|
914
|
+
if (!cancelled) setEmail(me?.user.email ?? null);
|
|
915
|
+
})
|
|
916
|
+
.catch(() => {
|
|
917
|
+
if (!cancelled) setEmail(null);
|
|
918
|
+
});
|
|
919
|
+
return () => {
|
|
920
|
+
cancelled = true;
|
|
921
|
+
};
|
|
922
|
+
}, [open]);
|
|
923
|
+
|
|
924
|
+
if (!open || !bar) return null;
|
|
925
|
+
const place = hostName && branch ? `${hostName} · ${branch}` : hostName || branch || "";
|
|
926
|
+
const panel =
|
|
927
|
+
pane === "shortcuts" ? (
|
|
928
|
+
<div role="dialog" aria-labelledby={titleId} className={`${DEVBAR_CLASS}-menu ${DEVBAR_CLASS}-settings`}>
|
|
929
|
+
<div className={`${DEVBAR_CLASS}-settings-head`}>
|
|
930
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-back`} onClick={onBack}>
|
|
931
|
+
<Chevron />
|
|
932
|
+
<span id={titleId}>Shortcuts</span>
|
|
211
933
|
</button>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
>
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
934
|
+
</div>
|
|
935
|
+
<ShortcutRow label="Toggle preview" keys={`${shortcut}⇧P`} />
|
|
936
|
+
<ShortcutRow label="Agent" keys={`${shortcut}J`} />
|
|
937
|
+
<ShortcutRow label="Open in dashboard" keys={`${shortcut}D`} />
|
|
938
|
+
<ShortcutRow label="Hide toolbar" keys={`${shortcut}.`} />
|
|
939
|
+
<ShortcutRow label="Keyboard shortcuts" keys="?" />
|
|
940
|
+
</div>
|
|
941
|
+
) : (
|
|
942
|
+
<div role="dialog" aria-labelledby={titleId} className={`${DEVBAR_CLASS}-menu ${DEVBAR_CLASS}-settings`}>
|
|
943
|
+
<div className={`${DEVBAR_CLASS}-settings-head`}>
|
|
944
|
+
<span id={titleId} className={`${DEVBAR_CLASS}-settings-kicker`}>
|
|
945
|
+
Settings
|
|
946
|
+
</span>
|
|
947
|
+
{place ? <span className={`${DEVBAR_CLASS}-settings-place`}>{place}</span> : null}
|
|
948
|
+
</div>
|
|
949
|
+
<div className={`${DEVBAR_CLASS}-settings-row`}>
|
|
950
|
+
<BlocksIcon />
|
|
951
|
+
<span>Highlight editable blocks</span>
|
|
952
|
+
<Switch on={highlightBlocks} label="Highlight editable blocks" onClick={() => onHighlight(!highlightBlocks)} />
|
|
953
|
+
</div>
|
|
954
|
+
<div className={`${DEVBAR_CLASS}-settings-row`}>
|
|
955
|
+
<EyeIcon />
|
|
956
|
+
<span>Preview when Agent edits</span>
|
|
957
|
+
<Switch on={previewAgent} label="Preview when Agent edits" onClick={() => onPreviewAgent(!previewAgent)} />
|
|
958
|
+
</div>
|
|
959
|
+
<div className={`${DEVBAR_CLASS}-settings-rule`} />
|
|
960
|
+
{dashboardHref ? (
|
|
961
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-action`} onClick={onDashboard}>
|
|
962
|
+
<GridIcon />
|
|
963
|
+
<span>Open in dashboard</span>
|
|
964
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>{shortcut}D</kbd>
|
|
965
|
+
</button>
|
|
966
|
+
) : null}
|
|
967
|
+
{onSwitchBranch ? (
|
|
968
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-action`} onClick={onSwitchBranch}>
|
|
969
|
+
<span className={`${DEVBAR_CLASS}-settings-icon`} aria-hidden>
|
|
970
|
+
<GitBranchIcon />
|
|
227
971
|
</span>
|
|
972
|
+
<span>Switch branch</span>
|
|
973
|
+
{branch ? <span className={`${DEVBAR_CLASS}-settings-email`}>{branch}</span> : null}
|
|
228
974
|
</button>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
975
|
+
) : null}
|
|
976
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-action`} onClick={onShortcuts}>
|
|
977
|
+
<KeyboardIcon />
|
|
978
|
+
<span>Keyboard shortcuts</span>
|
|
979
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>?</kbd>
|
|
980
|
+
</button>
|
|
981
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-action`} aria-keyshortcuts="Meta+. Control+." onClick={onHide}>
|
|
982
|
+
<HideIcon />
|
|
983
|
+
<span>Hide toolbar</span>
|
|
984
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>{shortcut}.</kbd>
|
|
985
|
+
</button>
|
|
986
|
+
{onSignOut ? (
|
|
987
|
+
<>
|
|
988
|
+
<div className={`${DEVBAR_CLASS}-settings-rule`} />
|
|
989
|
+
<button type="button" className={`${DEVBAR_CLASS}-settings-action`} onClick={onSignOut}>
|
|
990
|
+
<LeaveIcon />
|
|
991
|
+
<span>Sign out</span>
|
|
992
|
+
{email ? <span className={`${DEVBAR_CLASS}-settings-email`}>{email}</span> : null}
|
|
993
|
+
</button>
|
|
994
|
+
</>
|
|
995
|
+
) : null}
|
|
996
|
+
</div>
|
|
997
|
+
);
|
|
998
|
+
return createPortal(panel, bar);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function ShortcutRow({ label, keys }: { label: string; keys: string }) {
|
|
1002
|
+
return (
|
|
1003
|
+
<div className={`${DEVBAR_CLASS}-settings-action ${DEVBAR_CLASS}-settings-static`}>
|
|
1004
|
+
<span>{label}</span>
|
|
1005
|
+
<kbd className={`${DEVBAR_CLASS}-kbd`}>{keys}</kbd>
|
|
1006
|
+
</div>
|
|
232
1007
|
);
|
|
233
1008
|
}
|
|
234
1009
|
|
|
235
|
-
function
|
|
236
|
-
return
|
|
1010
|
+
function Switch({ on, label, onClick }: { on: boolean; label: string; onClick: () => void }) {
|
|
1011
|
+
return (
|
|
1012
|
+
<button
|
|
1013
|
+
type="button"
|
|
1014
|
+
role="switch"
|
|
1015
|
+
className={`${DEVBAR_CLASS}-switch${on ? ` ${DEVBAR_CLASS}-switch-on` : ""}`}
|
|
1016
|
+
aria-checked={on}
|
|
1017
|
+
aria-label={label}
|
|
1018
|
+
onClick={onClick}
|
|
1019
|
+
>
|
|
1020
|
+
<span className={`${DEVBAR_CLASS}-knob`} />
|
|
1021
|
+
</button>
|
|
1022
|
+
);
|
|
237
1023
|
}
|
|
238
1024
|
|
|
239
|
-
function
|
|
1025
|
+
function placeMenuRight(bar: HTMLElement, button: HTMLElement) {
|
|
1026
|
+
const barRect = bar.getBoundingClientRect();
|
|
1027
|
+
const btnRect = button.getBoundingClientRect();
|
|
1028
|
+
bar.style.setProperty("--menu-right", `${Math.max(0, barRect.right - btnRect.right)}px`);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function placeMenuCenter(bar: HTMLElement, button: HTMLElement) {
|
|
1032
|
+
const barRect = bar.getBoundingClientRect();
|
|
1033
|
+
const btnRect = button.getBoundingClientRect();
|
|
1034
|
+
const menuWidth = Math.min(PAGE_MENU_WIDTH, window.innerWidth - 32);
|
|
1035
|
+
const anchor = btnRect.left + btnRect.width / 2 - barRect.left;
|
|
1036
|
+
let left = anchor - menuWidth / 2;
|
|
1037
|
+
const inset = 16;
|
|
1038
|
+
const minLeft = inset - barRect.left;
|
|
1039
|
+
const maxLeft = window.innerWidth - inset - menuWidth - barRect.left;
|
|
1040
|
+
bar.style.setProperty("--menu-x", `${Math.max(minLeft, Math.min(left, maxLeft))}px`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function BlocksIcon() {
|
|
240
1044
|
return (
|
|
241
|
-
<svg className={`${DEVBAR_CLASS}-
|
|
242
|
-
<path d="
|
|
1045
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1046
|
+
<path d="M3 3h4.2v4.2H3zM8.8 3H13v4.2H8.8zM3 8.8h4.2V13H3z" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1047
|
+
<path d="M8.8 10.2h4.2" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeDasharray="1.4 1.5" />
|
|
1048
|
+
</svg>
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function EyeIcon() {
|
|
1053
|
+
return (
|
|
1054
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1055
|
+
<path d="M1.6 8S4 3.8 8 3.8 14.4 8 14.4 8 12 12.2 8 12.2 1.6 8 1.6 8Z" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1056
|
+
<circle cx="8" cy="8" r="1.8" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1057
|
+
</svg>
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function GridIcon() {
|
|
1062
|
+
return (
|
|
1063
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1064
|
+
<path d="M2.5 2.5h4.2v4.2H2.5zM9.3 2.5h4.2v4.2H9.3zM2.5 9.3h4.2v4.2H2.5zM9.3 9.3h4.2v4.2H9.3z" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1065
|
+
</svg>
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function KeyboardIcon() {
|
|
1070
|
+
return (
|
|
1071
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1072
|
+
<rect x="1.8" y="3.6" width="12.4" height="8.8" rx="1.4" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1073
|
+
<path d="M4 6.2h.1M6.4 6.2h.1M8.8 6.2h.1M11.2 6.2h.1M4.8 8.4h6.4" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
|
|
1074
|
+
</svg>
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function HideIcon() {
|
|
1079
|
+
return (
|
|
1080
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1081
|
+
<path d="M2 8s2.2-3.6 6-3.6S14 8 14 8s-2.2 3.6-6 3.6S2 8 2 8Z" fill="none" stroke="currentColor" strokeWidth="1.3" />
|
|
1082
|
+
<path d="M3 13 13 3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
|
|
243
1083
|
</svg>
|
|
244
1084
|
);
|
|
245
1085
|
}
|
|
246
1086
|
|
|
1087
|
+
function LeaveIcon() {
|
|
1088
|
+
return (
|
|
1089
|
+
<svg className={`${DEVBAR_CLASS}-settings-icon`} width="16" height="16" viewBox="0 0 16 16" aria-hidden>
|
|
1090
|
+
<path d="M6.5 3.2H3.8v9.6h2.7" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
|
|
1091
|
+
<path d="M7 8h6.2M11 5.4 13.6 8 11 10.6" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
|
|
1092
|
+
</svg>
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function browserStorage(): Storage | null {
|
|
1097
|
+
try {
|
|
1098
|
+
return typeof localStorage === "undefined" ? null : localStorage;
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function applySiteTheme(preference: SiteThemePreference) {
|
|
1105
|
+
if (typeof document === "undefined") return;
|
|
1106
|
+
const prefersDark = typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1107
|
+
const dark = resolveSiteTheme(preference, prefersDark) === "dark";
|
|
1108
|
+
const root = document.documentElement;
|
|
1109
|
+
root.dataset.theme = dark ? "dark" : "light";
|
|
1110
|
+
root.style.colorScheme = preference === "system" ? "light dark" : dark ? "dark" : "light";
|
|
1111
|
+
root.classList.toggle("dark", dark);
|
|
1112
|
+
root.classList.toggle("light", !dark);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
247
1115
|
function PageMenu({
|
|
248
1116
|
label,
|
|
249
1117
|
doc,
|
|
1118
|
+
dashboardHref,
|
|
1119
|
+
bar,
|
|
250
1120
|
open,
|
|
1121
|
+
expanded,
|
|
251
1122
|
onToggle,
|
|
252
1123
|
}: {
|
|
253
1124
|
label: string;
|
|
254
1125
|
doc: (PageMenuDoc & { workspaceId: string }) | null;
|
|
1126
|
+
dashboardHref?: string;
|
|
1127
|
+
bar: HTMLElement | null;
|
|
255
1128
|
open: boolean;
|
|
1129
|
+
expanded: boolean;
|
|
256
1130
|
onToggle: () => void;
|
|
257
1131
|
}) {
|
|
258
|
-
const [
|
|
259
|
-
const [
|
|
1132
|
+
const [rows, setRows] = useState<PageNavItem[]>([]);
|
|
1133
|
+
const [total, setTotal] = useState(0);
|
|
1134
|
+
const [loading, setLoading] = useState(false);
|
|
1135
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
260
1136
|
const [error, setError] = useState<string | null>(null);
|
|
261
1137
|
const [query, setQuery] = useState("");
|
|
262
1138
|
const listId = useId();
|
|
1139
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
1140
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
1141
|
+
const filterRef = useRef<HTMLInputElement>(null);
|
|
1142
|
+
const requestRef = useRef(0);
|
|
1143
|
+
const cacheRef = useRef(new Map<string, { rows: PageNavItem[]; total: number }>());
|
|
263
1144
|
|
|
264
1145
|
useEffect(() => {
|
|
265
|
-
if (
|
|
1146
|
+
if (open) return;
|
|
1147
|
+
setQuery("");
|
|
266
1148
|
}, [open]);
|
|
267
1149
|
|
|
268
|
-
|
|
269
|
-
if (!open
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
1150
|
+
useLayoutEffect(() => {
|
|
1151
|
+
if (!open) return;
|
|
1152
|
+
filterRef.current?.focus({ preventScroll: true });
|
|
1153
|
+
}, [open]);
|
|
1154
|
+
|
|
1155
|
+
useLayoutEffect(() => {
|
|
1156
|
+
const button = buttonRef.current;
|
|
1157
|
+
if (!open || !bar || !button) return;
|
|
1158
|
+
const place = () => placeMenuCenter(bar, button);
|
|
1159
|
+
place();
|
|
1160
|
+
window.addEventListener("resize", place);
|
|
1161
|
+
return () => window.removeEventListener("resize", place);
|
|
1162
|
+
}, [open, bar]);
|
|
1163
|
+
|
|
1164
|
+
const loadPage = useCallback(
|
|
1165
|
+
(offset: number, append: boolean, options?: { silent?: boolean }) => {
|
|
1166
|
+
if (!doc) return;
|
|
1167
|
+
const requestId = ++requestRef.current;
|
|
1168
|
+
const q = query.trim();
|
|
1169
|
+
const cacheKey = pageMenuCacheKey(doc.workspaceId, q);
|
|
1170
|
+
if (append) setLoadingMore(true);
|
|
1171
|
+
else if (!options?.silent) setLoading(true);
|
|
1172
|
+
void fetchDocNav(doc.workspaceId, {
|
|
1173
|
+
q: q || undefined,
|
|
1174
|
+
limit: PAGE_MENU_SIZE,
|
|
1175
|
+
offset,
|
|
1176
|
+
current: doc,
|
|
282
1177
|
})
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1178
|
+
.then((page) => {
|
|
1179
|
+
if (requestId !== requestRef.current) return;
|
|
1180
|
+
setTotal(page.total);
|
|
1181
|
+
setRows((prev) => {
|
|
1182
|
+
const next = append ? [...prev, ...page.items] : page.items;
|
|
1183
|
+
cacheRef.current.set(cacheKey, { rows: next, total: page.total });
|
|
1184
|
+
return next;
|
|
1185
|
+
});
|
|
1186
|
+
setError(null);
|
|
1187
|
+
})
|
|
1188
|
+
.catch(() => {
|
|
1189
|
+
if (requestId !== requestRef.current) return;
|
|
1190
|
+
if (!append) {
|
|
1191
|
+
const hit = cacheRef.current.get(cacheKey);
|
|
1192
|
+
if (!hit) setRows([]);
|
|
1193
|
+
}
|
|
1194
|
+
setError("Couldn't load pages");
|
|
1195
|
+
})
|
|
1196
|
+
.finally(() => {
|
|
1197
|
+
if (requestId !== requestRef.current) return;
|
|
1198
|
+
setLoading(false);
|
|
1199
|
+
setLoadingMore(false);
|
|
1200
|
+
});
|
|
1201
|
+
},
|
|
1202
|
+
[doc, query],
|
|
1203
|
+
);
|
|
290
1204
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
1205
|
+
useEffect(() => {
|
|
1206
|
+
if (!open || !doc) return;
|
|
1207
|
+
const q = query.trim();
|
|
1208
|
+
const cacheKey = pageMenuCacheKey(doc.workspaceId, q);
|
|
1209
|
+
const hit = cacheRef.current.get(cacheKey);
|
|
1210
|
+
if (hit) {
|
|
1211
|
+
setRows(hit.rows);
|
|
1212
|
+
setTotal(hit.total);
|
|
1213
|
+
setError(null);
|
|
1214
|
+
}
|
|
1215
|
+
const timer = window.setTimeout(
|
|
1216
|
+
() => loadPage(0, false, { silent: Boolean(hit) }),
|
|
1217
|
+
q ? 200 : 0,
|
|
1218
|
+
);
|
|
1219
|
+
return () => window.clearTimeout(timer);
|
|
1220
|
+
}, [open, doc, query, loadPage]);
|
|
294
1221
|
|
|
295
|
-
|
|
296
|
-
|
|
1222
|
+
function onMenuScroll() {
|
|
1223
|
+
const menu = menuRef.current;
|
|
1224
|
+
if (!menu || loading || loadingMore || rows.length >= total) return;
|
|
1225
|
+
if (menu.scrollTop + menu.clientHeight < menu.scrollHeight - 48) return;
|
|
1226
|
+
loadPage(rows.length, true);
|
|
297
1227
|
}
|
|
298
1228
|
|
|
1229
|
+
if (!doc) return <span className={`${DEVBAR_CLASS}-crumb`}>{label}</span>;
|
|
1230
|
+
|
|
1231
|
+
const empty = !loading && !error && rows.length === 0;
|
|
1232
|
+
const menuNode = open ? (
|
|
1233
|
+
<div
|
|
1234
|
+
id={listId}
|
|
1235
|
+
role="menu"
|
|
1236
|
+
aria-label="Pages"
|
|
1237
|
+
className={`${DEVBAR_CLASS}-menu ${DEVBAR_CLASS}-page-menu`}
|
|
1238
|
+
>
|
|
1239
|
+
<MenuBody
|
|
1240
|
+
query={query}
|
|
1241
|
+
onQuery={setQuery}
|
|
1242
|
+
inputRef={filterRef}
|
|
1243
|
+
searchClassName={`${DEVBAR_CLASS}-page-search`}
|
|
1244
|
+
showFilter
|
|
1245
|
+
empty={empty}
|
|
1246
|
+
emptyLabel={query.trim() ? "Nothing matches" : "No pages"}
|
|
1247
|
+
error={error}
|
|
1248
|
+
loading={loading && rows.length === 0}
|
|
1249
|
+
/>
|
|
1250
|
+
<div ref={menuRef} className={`${DEVBAR_CLASS}-page-menu-list`} onScroll={onMenuScroll}>
|
|
1251
|
+
{rows.map((item) => (
|
|
1252
|
+
<button
|
|
1253
|
+
key={item.key}
|
|
1254
|
+
type="button"
|
|
1255
|
+
role="menuitem"
|
|
1256
|
+
className={`${DEVBAR_CLASS}-option${item.current ? ` ${DEVBAR_CLASS}-current` : ""}`}
|
|
1257
|
+
aria-current={item.current ? "page" : undefined}
|
|
1258
|
+
onClick={() => {
|
|
1259
|
+
if (item.current || item.href === location.pathname) {
|
|
1260
|
+
onToggle();
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
location.assign(item.href);
|
|
1264
|
+
}}
|
|
1265
|
+
>
|
|
1266
|
+
<span>{item.label}</span>
|
|
1267
|
+
</button>
|
|
1268
|
+
))}
|
|
1269
|
+
{loadingMore ? <p className={`${DEVBAR_CLASS}-note`}>Loading more…</p> : null}
|
|
1270
|
+
{empty && !error ? <p className={`${DEVBAR_CLASS}-note`}>{query.trim() ? "Nothing matches" : "No pages"}</p> : null}
|
|
1271
|
+
{error ? <p className={`${DEVBAR_CLASS}-note ${DEVBAR_CLASS}-note-error`}>{error}</p> : null}
|
|
1272
|
+
{loading && rows.length === 0 ? (
|
|
1273
|
+
<p className={`${DEVBAR_CLASS}-note ${DEVBAR_CLASS}-note-muted`}>Loading pages…</p>
|
|
1274
|
+
) : null}
|
|
1275
|
+
</div>
|
|
1276
|
+
{dashboardHref ? (
|
|
1277
|
+
<div className={`${DEVBAR_CLASS}-page-menu-foot`}>
|
|
1278
|
+
<button
|
|
1279
|
+
type="button"
|
|
1280
|
+
role="menuitem"
|
|
1281
|
+
className={`${DEVBAR_CLASS}-option`}
|
|
1282
|
+
onClick={() => location.assign(dashboardHref)}
|
|
1283
|
+
>
|
|
1284
|
+
<span>Open in dashboard</span>
|
|
1285
|
+
</button>
|
|
1286
|
+
</div>
|
|
1287
|
+
) : null}
|
|
1288
|
+
</div>
|
|
1289
|
+
) : null;
|
|
1290
|
+
|
|
299
1291
|
return (
|
|
300
|
-
|
|
1292
|
+
<>
|
|
301
1293
|
<button
|
|
1294
|
+
ref={buttonRef}
|
|
302
1295
|
type="button"
|
|
303
|
-
className={`${DEVBAR_CLASS}-
|
|
1296
|
+
className={`${DEVBAR_CLASS}-btn ${DEVBAR_CLASS}-page${expanded ? ` ${DEVBAR_CLASS}-page-on` : ""}`}
|
|
304
1297
|
aria-haspopup="menu"
|
|
305
|
-
aria-expanded={
|
|
1298
|
+
aria-expanded={expanded}
|
|
306
1299
|
aria-controls={listId}
|
|
307
1300
|
onClick={onToggle}
|
|
308
1301
|
>
|
|
309
1302
|
<span>{label}</span>
|
|
310
|
-
<Chevron />
|
|
311
|
-
</button>
|
|
312
|
-
{
|
|
313
|
-
|
|
314
|
-
<MenuBody
|
|
315
|
-
query={query}
|
|
316
|
-
onQuery={setQuery}
|
|
317
|
-
showFilter={(items?.length ?? 0) > 7}
|
|
318
|
-
empty={shown?.length === 0}
|
|
319
|
-
emptyLabel={query.trim() ? "Nothing matches" : "No pages"}
|
|
320
|
-
error={error}
|
|
321
|
-
loading={shown === null && !error}
|
|
322
|
-
>
|
|
323
|
-
{shown?.map((item) => (
|
|
324
|
-
<button
|
|
325
|
-
key={item.key}
|
|
326
|
-
type="button"
|
|
327
|
-
role="menuitem"
|
|
328
|
-
className={`${DEVBAR_CLASS}-option${item.current ? ` ${DEVBAR_CLASS}-current` : ""}`}
|
|
329
|
-
aria-current={item.current ? "page" : undefined}
|
|
330
|
-
onClick={() => {
|
|
331
|
-
if (item.current || item.href === location.pathname) {
|
|
332
|
-
onToggle();
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
location.assign(item.href);
|
|
336
|
-
}}
|
|
337
|
-
>
|
|
338
|
-
<span>{item.label}</span>
|
|
339
|
-
</button>
|
|
340
|
-
))}
|
|
341
|
-
</MenuBody>
|
|
342
|
-
</div>
|
|
343
|
-
) : null}
|
|
344
|
-
</div>
|
|
1303
|
+
<Chevron />
|
|
1304
|
+
</button>
|
|
1305
|
+
{menuNode && bar ? createPortal(menuNode, bar) : null}
|
|
1306
|
+
</>
|
|
345
1307
|
);
|
|
346
1308
|
}
|
|
347
1309
|
|
|
@@ -350,15 +1312,17 @@ function BranchMenu({
|
|
|
350
1312
|
tree,
|
|
351
1313
|
workspaceId,
|
|
352
1314
|
previewEntryPath,
|
|
1315
|
+
bar,
|
|
353
1316
|
open,
|
|
354
|
-
|
|
1317
|
+
onClose,
|
|
355
1318
|
}: {
|
|
356
1319
|
branch: string;
|
|
357
1320
|
tree?: string;
|
|
358
1321
|
workspaceId: string | null;
|
|
359
1322
|
previewEntryPath: string;
|
|
1323
|
+
bar: HTMLElement | null;
|
|
360
1324
|
open: boolean;
|
|
361
|
-
|
|
1325
|
+
onClose: () => void;
|
|
362
1326
|
}) {
|
|
363
1327
|
const [names, setNames] = useState<string[] | null>(null);
|
|
364
1328
|
const [defaultBranch, setDefaultBranch] = useState<string | null>(null);
|
|
@@ -390,87 +1354,66 @@ function BranchMenu({
|
|
|
390
1354
|
};
|
|
391
1355
|
}, [open, workspaceId, names, branch]);
|
|
392
1356
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (!workspaceId) {
|
|
396
|
-
return (
|
|
397
|
-
<span className={`${DEVBAR_CLASS}-crumb ${DEVBAR_CLASS}-branch`}>
|
|
398
|
-
<GitBranchIcon />
|
|
399
|
-
<span>{branch}</span>
|
|
400
|
-
</span>
|
|
401
|
-
);
|
|
402
|
-
}
|
|
1357
|
+
if (!open || !bar) return null;
|
|
403
1358
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.catch((err: unknown) => {
|
|
455
|
-
setBusy(false);
|
|
456
|
-
setError(err instanceof Error ? err.message : "Couldn't switch branch");
|
|
457
|
-
});
|
|
458
|
-
}}
|
|
459
|
-
>
|
|
460
|
-
<span>{item.label}</span>
|
|
461
|
-
{item.label === defaultBranch ? <span className={`${DEVBAR_CLASS}-meta`}>default</span> : null}
|
|
462
|
-
</button>
|
|
463
|
-
))}
|
|
464
|
-
</MenuBody>
|
|
465
|
-
</div>
|
|
466
|
-
) : null}
|
|
467
|
-
</div>
|
|
1359
|
+
const shown = names ? filterItems(names.map((name) => ({ key: name, label: name })), query) : null;
|
|
1360
|
+
return createPortal(
|
|
1361
|
+
<div id={listId} role="menu" aria-label="Branches" className={`${DEVBAR_CLASS}-menu ${DEVBAR_CLASS}-page-menu`}>
|
|
1362
|
+
{workspaceId ? (
|
|
1363
|
+
<MenuBody
|
|
1364
|
+
query={query}
|
|
1365
|
+
onQuery={setQuery}
|
|
1366
|
+
showFilter={(names?.length ?? 0) > 7}
|
|
1367
|
+
empty={shown?.length === 0}
|
|
1368
|
+
emptyLabel={query.trim() ? "Nothing matches" : "No branches"}
|
|
1369
|
+
error={error}
|
|
1370
|
+
loading={shown === null && !error}
|
|
1371
|
+
>
|
|
1372
|
+
{shown?.map((item) => (
|
|
1373
|
+
<button
|
|
1374
|
+
key={item.key}
|
|
1375
|
+
type="button"
|
|
1376
|
+
role="menuitem"
|
|
1377
|
+
className={`${DEVBAR_CLASS}-option${item.label === branch ? ` ${DEVBAR_CLASS}-current` : ""}`}
|
|
1378
|
+
aria-current={item.label === branch ? "true" : undefined}
|
|
1379
|
+
disabled={busy}
|
|
1380
|
+
onClick={() => {
|
|
1381
|
+
if (item.label === branch) {
|
|
1382
|
+
onClose();
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
setBusy(true);
|
|
1386
|
+
setError(null);
|
|
1387
|
+
void switchPreviewBranch({
|
|
1388
|
+
workspaceId,
|
|
1389
|
+
branch: item.label,
|
|
1390
|
+
tree,
|
|
1391
|
+
redirect: location.pathname + location.search + location.hash,
|
|
1392
|
+
previewEntryPath,
|
|
1393
|
+
}).catch((err: unknown) => {
|
|
1394
|
+
setBusy(false);
|
|
1395
|
+
setError(err instanceof Error ? err.message : "Couldn't switch branch");
|
|
1396
|
+
});
|
|
1397
|
+
}}
|
|
1398
|
+
>
|
|
1399
|
+
<span>{item.label}</span>
|
|
1400
|
+
{item.label === defaultBranch ? <span className={`${DEVBAR_CLASS}-meta`}>default</span> : null}
|
|
1401
|
+
</button>
|
|
1402
|
+
))}
|
|
1403
|
+
</MenuBody>
|
|
1404
|
+
) : (
|
|
1405
|
+
<p className={`${DEVBAR_CLASS}-note`}>{branch}</p>
|
|
1406
|
+
)}
|
|
1407
|
+
</div>,
|
|
1408
|
+
bar,
|
|
468
1409
|
);
|
|
469
1410
|
}
|
|
470
1411
|
|
|
471
1412
|
function MenuBody({
|
|
472
1413
|
query,
|
|
473
1414
|
onQuery,
|
|
1415
|
+
inputRef,
|
|
1416
|
+
searchClassName,
|
|
474
1417
|
showFilter,
|
|
475
1418
|
empty,
|
|
476
1419
|
emptyLabel,
|
|
@@ -480,33 +1423,51 @@ function MenuBody({
|
|
|
480
1423
|
}: {
|
|
481
1424
|
query: string;
|
|
482
1425
|
onQuery: (value: string) => void;
|
|
1426
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
1427
|
+
searchClassName?: string;
|
|
483
1428
|
showFilter: boolean;
|
|
484
1429
|
empty: boolean;
|
|
485
1430
|
emptyLabel: string;
|
|
486
1431
|
error: string | null;
|
|
487
1432
|
loading: boolean;
|
|
488
|
-
children
|
|
1433
|
+
children?: ReactNode;
|
|
489
1434
|
}) {
|
|
490
1435
|
return (
|
|
491
1436
|
<>
|
|
492
1437
|
{showFilter ? (
|
|
493
|
-
<
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1438
|
+
<div className={`${DEVBAR_CLASS}-page-menu-search`}>
|
|
1439
|
+
<input
|
|
1440
|
+
ref={inputRef}
|
|
1441
|
+
value={query}
|
|
1442
|
+
onChange={(event) => onQuery(event.target.value)}
|
|
1443
|
+
placeholder="Search files"
|
|
1444
|
+
aria-label="Search files"
|
|
1445
|
+
autoComplete="off"
|
|
1446
|
+
spellCheck={false}
|
|
1447
|
+
className={searchClassName}
|
|
1448
|
+
/>
|
|
1449
|
+
</div>
|
|
1450
|
+
) : null}
|
|
1451
|
+
{children ? (
|
|
1452
|
+
<>
|
|
1453
|
+
{error ? <p className={`${DEVBAR_CLASS}-note ${DEVBAR_CLASS}-note-error`}>{error}</p> : null}
|
|
1454
|
+
{loading ? <p className={`${DEVBAR_CLASS}-note`}>Loading…</p> : null}
|
|
1455
|
+
{empty && !error ? <p className={`${DEVBAR_CLASS}-note`}>{emptyLabel}</p> : null}
|
|
1456
|
+
{children}
|
|
1457
|
+
</>
|
|
501
1458
|
) : null}
|
|
502
|
-
{error ? <p className={`${DEVBAR_CLASS}-note ${DEVBAR_CLASS}-note-error`}>{error}</p> : null}
|
|
503
|
-
{loading ? <p className={`${DEVBAR_CLASS}-note`}>Loading…</p> : null}
|
|
504
|
-
{empty && !error ? <p className={`${DEVBAR_CLASS}-note`}>{emptyLabel}</p> : null}
|
|
505
|
-
{children}
|
|
506
1459
|
</>
|
|
507
1460
|
);
|
|
508
1461
|
}
|
|
509
1462
|
|
|
1463
|
+
function Chevron() {
|
|
1464
|
+
return (
|
|
1465
|
+
<svg className={`${DEVBAR_CLASS}-chevron`} width="10" height="10" viewBox="0 0 12 12" fill="none" aria-hidden>
|
|
1466
|
+
<path d="M2.5 4.5 6 8l3.5-3.5" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
1467
|
+
</svg>
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
510
1471
|
function filterItems<T extends { label: string }>(items: readonly T[], query: string): T[] {
|
|
511
1472
|
const needle = query.trim().toLowerCase();
|
|
512
1473
|
if (!needle) return [...items];
|
|
@@ -542,169 +1503,399 @@ export async function switchPreviewBranch(input: {
|
|
|
542
1503
|
assign(`${input.previewEntryPath}${joiner}ticket=${encodeURIComponent(minted.ticket)}`);
|
|
543
1504
|
}
|
|
544
1505
|
|
|
1506
|
+
const LIGHT_CSS = `
|
|
1507
|
+
.${DEVBAR_CLASS}-host {
|
|
1508
|
+
position: absolute;
|
|
1509
|
+
width: 0;
|
|
1510
|
+
height: 0;
|
|
1511
|
+
overflow: visible;
|
|
1512
|
+
border: 0;
|
|
1513
|
+
padding: 0;
|
|
1514
|
+
text-decoration: none;
|
|
1515
|
+
color: inherit;
|
|
1516
|
+
}
|
|
1517
|
+
html.backmatter-pointing, html.backmatter-pointing * { cursor: crosshair !important; }
|
|
1518
|
+
html.backmatter-pointing [data-backmatter-panel],
|
|
1519
|
+
html.backmatter-pointing [data-backmatter-panel] *,
|
|
1520
|
+
html.backmatter-pointing [data-backmatter-devbar] { cursor: pointer !important; }
|
|
1521
|
+
html.backmatter-blocks [data-backmatter-block] {
|
|
1522
|
+
outline: 1.5px solid oklch(0.56 0.21 295);
|
|
1523
|
+
outline-offset: 2px;
|
|
1524
|
+
}
|
|
1525
|
+
`;
|
|
1526
|
+
|
|
545
1527
|
const BAR_CSS = `
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1528
|
+
.${DEVBAR_CLASS}-root {
|
|
1529
|
+
--preview: ${PREVIEW_CHROME_BORDER};
|
|
1530
|
+
--preview-hover: oklch(0.50 0.20 295);
|
|
1531
|
+
--preview-deep: oklch(0.48 0.20 295);
|
|
1532
|
+
--preview-line: oklch(0.66 0.16 295);
|
|
1533
|
+
--preview-outline: oklch(0.72 0.13 295);
|
|
1534
|
+
--preview-ink: oklch(0.98 0.01 295);
|
|
1535
|
+
--preview-ink-soft: oklch(0.88 0.05 295);
|
|
1536
|
+
--preview-on-white: oklch(0.36 0.16 295);
|
|
1537
|
+
--panel: #ffffff;
|
|
1538
|
+
--line: #e7e7e4;
|
|
1539
|
+
--line2: #d8d8d4;
|
|
1540
|
+
--fg: #17171a;
|
|
1541
|
+
--fg2: #5d5d62;
|
|
1542
|
+
--fg3: #8e8e93;
|
|
1543
|
+
--hover: #f3f3f1;
|
|
1544
|
+
--live: oklch(0.66 0.13 150);
|
|
1545
|
+
--shadow-live: 0 1px 2px rgba(0, 0, 0, 0.05), 0 10px 28px -8px rgba(0, 0, 0, 0.18);
|
|
1546
|
+
--shadow-preview: 0 1px 2px rgba(0, 0, 0, 0.08), 0 10px 28px -8px oklch(0.45 0.2 295 / 0.5);
|
|
1547
|
+
--ease-out: ${DEVBAR_EASE_OUT};
|
|
1548
|
+
--ease-in: ${DEVBAR_EASE_IN};
|
|
1549
|
+
--ox: 28px;
|
|
1550
|
+
--oy: 20px;
|
|
1551
|
+
color-scheme: light;
|
|
1552
|
+
text-decoration: none;
|
|
549
1553
|
}
|
|
550
1554
|
.${DEVBAR_CLASS} {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1555
|
+
all: initial;
|
|
1556
|
+
position: var(--bm-devbar-position, fixed);
|
|
1557
|
+
right: var(--bm-devbar-right, max(16px, env(safe-area-inset-right, 0px)));
|
|
1558
|
+
left: var(--bm-devbar-left, auto);
|
|
1559
|
+
bottom: var(--bm-devbar-bottom, 0px);
|
|
554
1560
|
z-index: 2147483000;
|
|
555
|
-
|
|
556
|
-
display: flex;
|
|
557
|
-
align-items: center;
|
|
558
|
-
gap: 8px;
|
|
1561
|
+
display: block;
|
|
559
1562
|
width: max-content;
|
|
560
1563
|
max-width: calc(100vw - 32px);
|
|
561
|
-
|
|
562
|
-
padding: 4px 6px calc(4px + env(safe-area-inset-bottom, 0px)) 4px;
|
|
1564
|
+
height: 40px;
|
|
563
1565
|
box-sizing: border-box;
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
font:
|
|
571
|
-
-
|
|
1566
|
+
transform-origin: 100% 100%;
|
|
1567
|
+
pointer-events: auto;
|
|
1568
|
+
color: var(--fg);
|
|
1569
|
+
font-family: "Geist Sans", Geist, ui-sans-serif, system-ui, sans-serif;
|
|
1570
|
+
font-size: 12.5px;
|
|
1571
|
+
font-weight: 500;
|
|
1572
|
+
font-style: normal;
|
|
1573
|
+
line-height: 1;
|
|
1574
|
+
letter-spacing: normal;
|
|
572
1575
|
font-variant-numeric: tabular-nums;
|
|
573
|
-
|
|
1576
|
+
-webkit-font-smoothing: antialiased;
|
|
1577
|
+
text-align: left;
|
|
1578
|
+
cursor: default;
|
|
1579
|
+
user-select: none;
|
|
1580
|
+
transition: color 250ms ease;
|
|
574
1581
|
}
|
|
575
|
-
|
|
576
|
-
|
|
1582
|
+
.${DEVBAR_CLASS}[data-hidden="true"] { pointer-events: none; }
|
|
1583
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS} { color: var(--preview-ink); }
|
|
1584
|
+
.${DEVBAR_CLASS} button, .${DEVBAR_CLASS} input {
|
|
1585
|
+
all: unset;
|
|
1586
|
+
box-sizing: border-box;
|
|
1587
|
+
font: inherit;
|
|
1588
|
+
color: inherit;
|
|
1589
|
+
cursor: pointer;
|
|
1590
|
+
text-decoration: none;
|
|
1591
|
+
-webkit-appearance: none;
|
|
1592
|
+
appearance: none;
|
|
577
1593
|
}
|
|
578
|
-
.${DEVBAR_CLASS}
|
|
579
|
-
|
|
580
|
-
|
|
1594
|
+
.${DEVBAR_CLASS} button {
|
|
1595
|
+
transition: transform 160ms var(--ease-out), background-color 150ms ease, color 250ms ease, border-color 300ms ease;
|
|
1596
|
+
}
|
|
1597
|
+
.${DEVBAR_CLASS} button:active:not(:disabled) { transform: scale(0.97); }
|
|
1598
|
+
.${DEVBAR_CLASS} button:disabled { cursor: default; }
|
|
1599
|
+
.${DEVBAR_CLASS} button:focus-visible, .${DEVBAR_CLASS}-menu input:focus-visible {
|
|
1600
|
+
outline: none;
|
|
1601
|
+
box-shadow: inset 0 0 0 1.5px currentColor;
|
|
1602
|
+
}
|
|
1603
|
+
.${DEVBAR_CLASS} input.${DEVBAR_CLASS}-page-search:focus,
|
|
1604
|
+
.${DEVBAR_CLASS} input.${DEVBAR_CLASS}-page-search:focus-visible {
|
|
1605
|
+
outline: none;
|
|
1606
|
+
box-shadow: none;
|
|
1607
|
+
}
|
|
1608
|
+
.${DEVBAR_CLASS}-clip {
|
|
1609
|
+
position: relative;
|
|
1610
|
+
height: 40px;
|
|
1611
|
+
overflow: hidden;
|
|
1612
|
+
box-sizing: border-box;
|
|
1613
|
+
border: 1px solid var(--line);
|
|
1614
|
+
border-bottom: 0;
|
|
1615
|
+
border-radius: 12px 12px 0 0;
|
|
1616
|
+
background: var(--panel);
|
|
1617
|
+
box-shadow: var(--shadow-live);
|
|
1618
|
+
transition: border-color 300ms ease;
|
|
1619
|
+
}
|
|
1620
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-clip {
|
|
1621
|
+
border-color: transparent;
|
|
1622
|
+
background: transparent;
|
|
1623
|
+
box-shadow: none;
|
|
1624
|
+
}
|
|
1625
|
+
.${DEVBAR_CLASS}-flood {
|
|
1626
|
+
position: absolute;
|
|
1627
|
+
z-index: 0;
|
|
1628
|
+
inset: -1px -1px 0 -1px;
|
|
1629
|
+
border-radius: 12px 12px 0 0;
|
|
1630
|
+
background: var(--preview);
|
|
1631
|
+
pointer-events: none;
|
|
1632
|
+
clip-path: circle(0px at var(--ox) var(--oy));
|
|
1633
|
+
transition: clip-path ${DEVBAR_FLOOD_EXIT_MS}ms var(--ease-in);
|
|
1634
|
+
}
|
|
1635
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-flood {
|
|
1636
|
+
clip-path: circle(150% at var(--ox) var(--oy));
|
|
1637
|
+
transition: clip-path ${DEVBAR_FLOOD_ENTER_MS}ms var(--ease-out);
|
|
1638
|
+
}
|
|
1639
|
+
.${DEVBAR_CLASS}-root[data-settled] .${DEVBAR_CLASS}-flood { transition: none; }
|
|
1640
|
+
.${DEVBAR_CLASS}-row {
|
|
1641
|
+
position: relative;
|
|
1642
|
+
z-index: 1;
|
|
1643
|
+
display: flex;
|
|
581
1644
|
align-items: center;
|
|
582
1645
|
gap: 2px;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
1646
|
+
height: 39px;
|
|
1647
|
+
padding: 0 8px 0 12px;
|
|
1648
|
+
white-space: nowrap;
|
|
586
1649
|
}
|
|
587
|
-
.${DEVBAR_CLASS}-mode
|
|
1650
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-mode {
|
|
588
1651
|
display: inline-flex;
|
|
1652
|
+
flex-shrink: 0;
|
|
589
1653
|
align-items: center;
|
|
590
1654
|
gap: 6px;
|
|
591
|
-
height:
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
border
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
cursor: pointer;
|
|
599
|
-
}
|
|
600
|
-
.${DEVBAR_CLASS}-mode button[aria-pressed="true"] {
|
|
601
|
-
background: #f4efe4;
|
|
602
|
-
color: #18181b;
|
|
1655
|
+
height: 28px;
|
|
1656
|
+
margin-right: 10px;
|
|
1657
|
+
padding: 0 10px;
|
|
1658
|
+
border: 1px solid transparent;
|
|
1659
|
+
border-radius: 8px;
|
|
1660
|
+
background: var(--preview);
|
|
1661
|
+
color: var(--preview-ink);
|
|
603
1662
|
}
|
|
604
|
-
.${DEVBAR_CLASS}-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
border-radius: 999px;
|
|
608
|
-
background: #c4841d;
|
|
609
|
-
flex-shrink: 0;
|
|
1663
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-mode {
|
|
1664
|
+
background: transparent;
|
|
1665
|
+
border-color: var(--preview-outline);
|
|
610
1666
|
}
|
|
611
|
-
.${DEVBAR_CLASS}-
|
|
1667
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-mode:active:not(:disabled) { background: var(--preview-deep); }
|
|
1668
|
+
.${DEVBAR_CLASS}-mark { flex-shrink: 0; }
|
|
612
1669
|
.${DEVBAR_CLASS}-crumb {
|
|
613
1670
|
display: inline-flex;
|
|
614
1671
|
align-items: center;
|
|
615
|
-
gap: 6px;
|
|
616
1672
|
min-width: 0;
|
|
617
|
-
max-width: min(180px,
|
|
1673
|
+
max-width: min(180px, 28vw);
|
|
618
1674
|
height: 28px;
|
|
619
|
-
padding: 0
|
|
620
|
-
border:
|
|
621
|
-
|
|
622
|
-
background: transparent;
|
|
623
|
-
color: #18181b;
|
|
624
|
-
font: inherit;
|
|
1675
|
+
padding: 0 8px;
|
|
1676
|
+
border-radius: 8px;
|
|
1677
|
+
color: var(--fg2);
|
|
625
1678
|
}
|
|
626
|
-
|
|
627
|
-
.${DEVBAR_CLASS}-
|
|
1679
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-crumb { color: var(--preview-ink-soft); }
|
|
1680
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-page {
|
|
1681
|
+
min-width: 0;
|
|
1682
|
+
max-width: min(200px, 34vw);
|
|
1683
|
+
}
|
|
1684
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-page > span {
|
|
628
1685
|
min-width: 0;
|
|
629
1686
|
overflow: hidden;
|
|
630
1687
|
text-overflow: ellipsis;
|
|
631
1688
|
white-space: nowrap;
|
|
632
1689
|
}
|
|
633
|
-
.${DEVBAR_CLASS}-
|
|
634
|
-
.${DEVBAR_CLASS}-
|
|
1690
|
+
.${DEVBAR_CLASS}-page-on { background: var(--hover); }
|
|
1691
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-page-on {
|
|
1692
|
+
background: rgba(255, 255, 255, 0.18);
|
|
1693
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
|
|
1694
|
+
}
|
|
1695
|
+
.${DEVBAR_CLASS}-chevron { flex-shrink: 0; color: var(--fg3); }
|
|
1696
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-chevron { color: var(--preview-ink-soft); }
|
|
635
1697
|
.${DEVBAR_CLASS}-rule {
|
|
636
1698
|
width: 1px;
|
|
637
1699
|
height: 16px;
|
|
638
1700
|
flex-shrink: 0;
|
|
639
|
-
|
|
1701
|
+
margin: 0 6px;
|
|
1702
|
+
background: var(--line2);
|
|
640
1703
|
}
|
|
641
|
-
.${DEVBAR_CLASS}-
|
|
1704
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-rule { background: var(--preview-line); }
|
|
1705
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-btn,
|
|
1706
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-publish {
|
|
642
1707
|
display: inline-flex;
|
|
643
1708
|
flex-shrink: 0;
|
|
644
1709
|
align-items: center;
|
|
645
1710
|
gap: 6px;
|
|
646
|
-
}
|
|
647
|
-
.${DEVBAR_CLASS}-btn {
|
|
648
|
-
display: inline-flex;
|
|
649
|
-
align-items: center;
|
|
650
|
-
gap: 6px;
|
|
651
1711
|
height: 28px;
|
|
652
|
-
padding: 0
|
|
653
|
-
border: 1px solid #e4e4e7;
|
|
1712
|
+
padding: 0 8px;
|
|
654
1713
|
border-radius: 8px;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
font: inherit;
|
|
658
|
-
font-size: 13px;
|
|
659
|
-
cursor: pointer;
|
|
1714
|
+
border: 0;
|
|
1715
|
+
text-decoration: none;
|
|
660
1716
|
}
|
|
661
|
-
.${DEVBAR_CLASS}-
|
|
662
|
-
|
|
663
|
-
|
|
1717
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-btn span,
|
|
1718
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-publish span {
|
|
1719
|
+
text-decoration: none;
|
|
664
1720
|
}
|
|
665
|
-
.${DEVBAR_CLASS}-
|
|
666
|
-
font-family: Geist Mono, ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1721
|
+
.${DEVBAR_CLASS}-kbd, .${DEVBAR_CLASS}-field, .${DEVBAR_CLASS}-when {
|
|
1722
|
+
font-family: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
667
1723
|
font-size: 11px;
|
|
668
|
-
font-weight:
|
|
669
|
-
color: #71717a;
|
|
1724
|
+
font-weight: 500;
|
|
670
1725
|
}
|
|
1726
|
+
.${DEVBAR_CLASS}-kbd, .${DEVBAR_CLASS}-when, .${DEVBAR_CLASS}-field { color: var(--fg3); }
|
|
1727
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-kbd,
|
|
1728
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-field,
|
|
1729
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-when { color: var(--preview-ink-soft); }
|
|
671
1730
|
.${DEVBAR_CLASS}-count { font-variant-numeric: tabular-nums; }
|
|
672
|
-
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-
|
|
673
|
-
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-
|
|
674
|
-
background: #
|
|
675
|
-
|
|
676
|
-
color: #fafafa;
|
|
1731
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-publish:not(:disabled) { background: #17171a; color: #fff; }
|
|
1732
|
+
.${DEVBAR_CLASS}-root[data-mode="live"] button.${DEVBAR_CLASS}-publish:disabled {
|
|
1733
|
+
background: #f0f0ee;
|
|
1734
|
+
color: var(--fg3);
|
|
677
1735
|
}
|
|
678
|
-
.${DEVBAR_CLASS}-
|
|
679
|
-
|
|
1736
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-publish:not(:disabled) {
|
|
1737
|
+
background: #fff;
|
|
1738
|
+
color: var(--preview-on-white);
|
|
1739
|
+
}
|
|
1740
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-publish:disabled {
|
|
1741
|
+
background: rgba(255, 255, 255, 0.14);
|
|
1742
|
+
color: var(--preview-ink-soft);
|
|
1743
|
+
}
|
|
1744
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-btn:disabled { opacity: 0.45; }
|
|
1745
|
+
.${DEVBAR_CLASS}-changes, .${DEVBAR_CLASS}-steps, .${DEVBAR_CLASS}-published, .${DEVBAR_CLASS}-step {
|
|
1746
|
+
display: inline-flex;
|
|
1747
|
+
align-items: center;
|
|
1748
|
+
gap: 6px;
|
|
1749
|
+
min-width: 0;
|
|
1750
|
+
}
|
|
1751
|
+
.${DEVBAR_CLASS}-diff { display: inline-flex; gap: 4px; }
|
|
1752
|
+
.${DEVBAR_CLASS}-add, .${DEVBAR_CLASS}-del { font-variant-numeric: tabular-nums; }
|
|
1753
|
+
.${DEVBAR_CLASS}-dash { color: var(--preview-ink-soft); }
|
|
1754
|
+
.${DEVBAR_CLASS}-spin {
|
|
1755
|
+
width: 12px;
|
|
1756
|
+
height: 12px;
|
|
1757
|
+
flex-shrink: 0;
|
|
1758
|
+
box-sizing: border-box;
|
|
1759
|
+
border: 1.5px solid var(--preview-line);
|
|
1760
|
+
border-top-color: #fff;
|
|
1761
|
+
border-radius: 999px;
|
|
1762
|
+
animation: ${DEVBAR_CLASS}-spin 800ms linear infinite;
|
|
1763
|
+
}
|
|
1764
|
+
.${DEVBAR_CLASS}-ring {
|
|
1765
|
+
width: 12px;
|
|
1766
|
+
height: 12px;
|
|
1767
|
+
flex-shrink: 0;
|
|
1768
|
+
box-sizing: border-box;
|
|
1769
|
+
border: 1.5px solid var(--preview-line);
|
|
1770
|
+
border-radius: 999px;
|
|
1771
|
+
}
|
|
1772
|
+
.${DEVBAR_CLASS}-done, .${DEVBAR_CLASS}-check { color: var(--live); }
|
|
1773
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-done { color: var(--preview-ink); }
|
|
1774
|
+
.${DEVBAR_CLASS}-shimmer {
|
|
1775
|
+
display: inline-block;
|
|
1776
|
+
background-image: linear-gradient(90deg, var(--preview-ink-soft) 0%, #fff 45%, var(--preview-ink-soft) 90%);
|
|
1777
|
+
background-size: 200% 100%;
|
|
1778
|
+
-webkit-background-clip: text;
|
|
1779
|
+
background-clip: text;
|
|
1780
|
+
color: transparent;
|
|
1781
|
+
animation: ${DEVBAR_CLASS}-shimmer 1.4s linear infinite;
|
|
1782
|
+
}
|
|
1783
|
+
.${DEVBAR_CLASS}-progress {
|
|
680
1784
|
position: absolute;
|
|
681
|
-
bottom: calc(100% + 8px);
|
|
682
1785
|
left: 0;
|
|
1786
|
+
bottom: 0;
|
|
683
1787
|
z-index: 2;
|
|
1788
|
+
height: 2px;
|
|
1789
|
+
background: #fff;
|
|
1790
|
+
pointer-events: none;
|
|
1791
|
+
transition: width 300ms linear;
|
|
1792
|
+
}
|
|
1793
|
+
.${DEVBAR_CLASS}-sr {
|
|
1794
|
+
position: absolute;
|
|
1795
|
+
width: 1px;
|
|
1796
|
+
height: 1px;
|
|
1797
|
+
padding: 0;
|
|
1798
|
+
margin: -1px;
|
|
1799
|
+
overflow: hidden;
|
|
1800
|
+
clip: rect(0 0 0 0);
|
|
1801
|
+
white-space: nowrap;
|
|
1802
|
+
border: 0;
|
|
1803
|
+
}
|
|
1804
|
+
.${DEVBAR_CLASS}-menu {
|
|
1805
|
+
position: absolute;
|
|
1806
|
+
bottom: calc(100% + 8px);
|
|
1807
|
+
left: var(--menu-x, 12px);
|
|
1808
|
+
z-index: 3;
|
|
684
1809
|
display: flex;
|
|
685
1810
|
flex-direction: column;
|
|
686
1811
|
width: 240px;
|
|
687
1812
|
max-height: 280px;
|
|
688
1813
|
overflow: auto;
|
|
689
1814
|
padding: 4px;
|
|
690
|
-
border: 1px solid
|
|
1815
|
+
border: 1px solid var(--line);
|
|
691
1816
|
border-radius: 10px;
|
|
692
|
-
background:
|
|
1817
|
+
background: var(--panel);
|
|
1818
|
+
color: var(--fg);
|
|
1819
|
+
box-shadow: var(--shadow-live);
|
|
1820
|
+
box-sizing: border-box;
|
|
1821
|
+
}
|
|
1822
|
+
.${DEVBAR_CLASS}-page-menu {
|
|
1823
|
+
display: flex;
|
|
1824
|
+
flex-direction: column;
|
|
1825
|
+
width: min(360px, calc(100vw - 32px));
|
|
1826
|
+
max-height: min(420px, calc(100vh - 96px));
|
|
1827
|
+
padding: 0;
|
|
1828
|
+
border-radius: 16px;
|
|
1829
|
+
overflow: hidden;
|
|
1830
|
+
}
|
|
1831
|
+
.${DEVBAR_CLASS}-page-menu-search {
|
|
1832
|
+
flex-shrink: 0;
|
|
1833
|
+
padding: 8px 12px 0;
|
|
1834
|
+
border-bottom: 1px solid var(--line);
|
|
1835
|
+
}
|
|
1836
|
+
.${DEVBAR_CLASS}-page-menu-list {
|
|
1837
|
+
display: flex;
|
|
1838
|
+
flex-direction: column;
|
|
1839
|
+
flex: 1 1 auto;
|
|
1840
|
+
gap: 1px;
|
|
1841
|
+
min-height: 240px;
|
|
1842
|
+
max-height: 320px;
|
|
1843
|
+
overflow: auto;
|
|
1844
|
+
padding: 4px 6px 6px;
|
|
1845
|
+
box-sizing: border-box;
|
|
1846
|
+
}
|
|
1847
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-page-menu {
|
|
1848
|
+
border-color: var(--preview-line);
|
|
1849
|
+
background: #fff;
|
|
1850
|
+
color: var(--fg);
|
|
693
1851
|
box-shadow: 0 16px 40px rgba(24, 24, 27, 0.14), 0 2px 8px rgba(24, 24, 27, 0.06);
|
|
694
1852
|
}
|
|
695
|
-
.${DEVBAR_CLASS}-menu-end { left: auto; right: 0; }
|
|
696
1853
|
.${DEVBAR_CLASS}-menu input {
|
|
697
1854
|
width: 100%;
|
|
698
|
-
box-sizing: border-box;
|
|
699
1855
|
height: 32px;
|
|
700
1856
|
margin: 2px 0 4px;
|
|
701
1857
|
padding: 0 8px;
|
|
702
|
-
border: 1px solid
|
|
1858
|
+
border: 1px solid var(--line);
|
|
703
1859
|
border-radius: 7px;
|
|
704
|
-
background: #
|
|
705
|
-
color:
|
|
706
|
-
font: inherit;
|
|
1860
|
+
background: #fff;
|
|
1861
|
+
color: var(--fg);
|
|
707
1862
|
font-size: 16px;
|
|
1863
|
+
cursor: text;
|
|
1864
|
+
user-select: text;
|
|
1865
|
+
}
|
|
1866
|
+
.${DEVBAR_CLASS} input.${DEVBAR_CLASS}-page-search {
|
|
1867
|
+
display: block;
|
|
1868
|
+
width: 100%;
|
|
1869
|
+
height: 40px;
|
|
1870
|
+
margin: 0;
|
|
1871
|
+
padding: 0 4px 10px;
|
|
1872
|
+
border: 0;
|
|
1873
|
+
border-radius: 0;
|
|
1874
|
+
background: transparent;
|
|
1875
|
+
color: var(--fg);
|
|
1876
|
+
font-size: 13px;
|
|
1877
|
+
font-weight: 500;
|
|
1878
|
+
line-height: 1.3;
|
|
1879
|
+
cursor: text;
|
|
1880
|
+
user-select: text;
|
|
1881
|
+
box-sizing: border-box;
|
|
1882
|
+
}
|
|
1883
|
+
.${DEVBAR_CLASS} input.${DEVBAR_CLASS}-page-search::placeholder {
|
|
1884
|
+
color: var(--fg3);
|
|
1885
|
+
font-weight: 400;
|
|
1886
|
+
}
|
|
1887
|
+
.${DEVBAR_CLASS}-page-menu-foot {
|
|
1888
|
+
display: flex;
|
|
1889
|
+
flex-direction: column;
|
|
1890
|
+
flex-shrink: 0;
|
|
1891
|
+
padding: 0 8px 8px;
|
|
1892
|
+
}
|
|
1893
|
+
.${DEVBAR_CLASS}-page-menu-foot::before {
|
|
1894
|
+
content: "";
|
|
1895
|
+
display: block;
|
|
1896
|
+
height: 1px;
|
|
1897
|
+
margin: 0 4px 6px;
|
|
1898
|
+
background: var(--line);
|
|
708
1899
|
}
|
|
709
1900
|
.${DEVBAR_CLASS}-option {
|
|
710
1901
|
display: flex;
|
|
@@ -713,14 +1904,17 @@ button.${DEVBAR_CLASS}-crumb { cursor: pointer; }
|
|
|
713
1904
|
width: 100%;
|
|
714
1905
|
min-height: 32px;
|
|
715
1906
|
padding: 0 8px;
|
|
716
|
-
border: 0;
|
|
717
1907
|
border-radius: 7px;
|
|
718
|
-
background: transparent;
|
|
719
|
-
color: #18181b;
|
|
720
|
-
font: inherit;
|
|
721
|
-
font-size: 13px;
|
|
722
1908
|
text-align: left;
|
|
723
|
-
|
|
1909
|
+
box-sizing: border-box;
|
|
1910
|
+
}
|
|
1911
|
+
.${DEVBAR_CLASS}-page-menu .${DEVBAR_CLASS}-option {
|
|
1912
|
+
flex-shrink: 0;
|
|
1913
|
+
min-height: 30px;
|
|
1914
|
+
padding: 5px 12px;
|
|
1915
|
+
border-radius: 7px;
|
|
1916
|
+
font-size: 12.5px;
|
|
1917
|
+
line-height: 1.25;
|
|
724
1918
|
}
|
|
725
1919
|
.${DEVBAR_CLASS}-option > span:first-child {
|
|
726
1920
|
min-width: 0;
|
|
@@ -728,42 +1922,214 @@ button.${DEVBAR_CLASS}-crumb { cursor: pointer; }
|
|
|
728
1922
|
text-overflow: ellipsis;
|
|
729
1923
|
white-space: nowrap;
|
|
730
1924
|
}
|
|
731
|
-
.${DEVBAR_CLASS}-current { background:
|
|
1925
|
+
.${DEVBAR_CLASS}-current { background: var(--hover); }
|
|
732
1926
|
.${DEVBAR_CLASS}-meta {
|
|
733
1927
|
margin-left: auto;
|
|
734
1928
|
flex-shrink: 0;
|
|
735
|
-
color:
|
|
1929
|
+
color: var(--fg3);
|
|
736
1930
|
font-size: 11px;
|
|
737
|
-
font-weight:
|
|
1931
|
+
font-weight: 500;
|
|
1932
|
+
}
|
|
1933
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-gear {
|
|
1934
|
+
display: inline-flex;
|
|
1935
|
+
align-items: center;
|
|
1936
|
+
justify-content: center;
|
|
1937
|
+
width: 28px;
|
|
1938
|
+
height: 28px;
|
|
1939
|
+
margin-left: 4px;
|
|
1940
|
+
border-radius: 8px;
|
|
1941
|
+
background: var(--hover);
|
|
1942
|
+
color: var(--fg2);
|
|
1943
|
+
}
|
|
1944
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-gear svg {
|
|
1945
|
+
display: block;
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
}
|
|
1948
|
+
.${DEVBAR_CLASS}-gear-on { background: var(--line); color: var(--fg); }
|
|
1949
|
+
.${DEVBAR_CLASS}-publish-on { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35); }
|
|
1950
|
+
.${DEVBAR_CLASS}-root[data-mode="live"] button.${DEVBAR_CLASS}-publish-on {
|
|
1951
|
+
box-shadow: inset 0 0 0 1px var(--line2);
|
|
1952
|
+
}
|
|
1953
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-gear {
|
|
1954
|
+
background: rgba(255, 255, 255, 0.14);
|
|
1955
|
+
color: var(--preview-ink);
|
|
1956
|
+
}
|
|
1957
|
+
.${DEVBAR_CLASS}-settings {
|
|
1958
|
+
right: var(--menu-right, 8px);
|
|
1959
|
+
left: auto;
|
|
1960
|
+
width: 328px;
|
|
1961
|
+
max-height: min(480px, calc(100vh - 80px));
|
|
1962
|
+
padding: 8px;
|
|
1963
|
+
border-radius: 16px;
|
|
1964
|
+
overflow: auto;
|
|
1965
|
+
box-sizing: border-box;
|
|
1966
|
+
}
|
|
1967
|
+
.${DEVBAR_CLASS}-settings-head {
|
|
1968
|
+
display: flex;
|
|
1969
|
+
align-items: center;
|
|
1970
|
+
justify-content: space-between;
|
|
1971
|
+
gap: 12px;
|
|
1972
|
+
min-height: 28px;
|
|
1973
|
+
padding: 4px 12px 8px;
|
|
1974
|
+
}
|
|
1975
|
+
.${DEVBAR_CLASS}-settings-kicker {
|
|
1976
|
+
color: var(--fg3);
|
|
1977
|
+
font-size: 11px;
|
|
1978
|
+
font-weight: 600;
|
|
1979
|
+
letter-spacing: 0.08em;
|
|
1980
|
+
text-transform: uppercase;
|
|
1981
|
+
}
|
|
1982
|
+
.${DEVBAR_CLASS}-settings-place {
|
|
1983
|
+
min-width: 0;
|
|
1984
|
+
overflow: hidden;
|
|
1985
|
+
color: var(--fg3);
|
|
1986
|
+
font-size: 12px;
|
|
1987
|
+
font-weight: 500;
|
|
1988
|
+
text-overflow: ellipsis;
|
|
1989
|
+
white-space: nowrap;
|
|
1990
|
+
}
|
|
1991
|
+
.${DEVBAR_CLASS}-settings-row,
|
|
1992
|
+
.${DEVBAR_CLASS}-settings-action,
|
|
1993
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-settings-action {
|
|
1994
|
+
display: flex;
|
|
1995
|
+
align-items: center;
|
|
1996
|
+
gap: 10px;
|
|
1997
|
+
width: 100%;
|
|
1998
|
+
min-height: 40px;
|
|
1999
|
+
padding: 0 12px;
|
|
2000
|
+
border-radius: 8px;
|
|
2001
|
+
color: var(--fg);
|
|
2002
|
+
text-align: left;
|
|
2003
|
+
box-sizing: border-box;
|
|
2004
|
+
}
|
|
2005
|
+
.${DEVBAR_CLASS}-settings-row.${DEVBAR_CLASS}-settings-theme {
|
|
2006
|
+
flex-wrap: wrap;
|
|
2007
|
+
row-gap: 8px;
|
|
2008
|
+
padding-bottom: 4px;
|
|
2009
|
+
}
|
|
2010
|
+
.${DEVBAR_CLASS}-settings-theme > span:nth-child(2) {
|
|
2011
|
+
flex: 1 1 auto;
|
|
2012
|
+
}
|
|
2013
|
+
.${DEVBAR_CLASS}-settings-row > span:nth-child(2),
|
|
2014
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-settings-action > span:nth-child(2) {
|
|
2015
|
+
min-width: 0;
|
|
2016
|
+
flex: 1;
|
|
2017
|
+
}
|
|
2018
|
+
.${DEVBAR_CLASS}-settings-icon { flex-shrink: 0; color: var(--fg2); }
|
|
2019
|
+
.${DEVBAR_CLASS}-settings-rule { height: 1px; margin: 6px 12px; background: var(--line); }
|
|
2020
|
+
.${DEVBAR_CLASS}-segment {
|
|
2021
|
+
display: inline-flex;
|
|
2022
|
+
flex-shrink: 0;
|
|
2023
|
+
padding: 2px;
|
|
2024
|
+
border-radius: 9px;
|
|
2025
|
+
background: var(--hover);
|
|
2026
|
+
}
|
|
2027
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-segment-btn {
|
|
2028
|
+
height: 26px;
|
|
2029
|
+
padding: 0 10px;
|
|
2030
|
+
border-radius: 7px;
|
|
2031
|
+
color: var(--fg2);
|
|
2032
|
+
font-size: 12.5px;
|
|
2033
|
+
}
|
|
2034
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-segment-on {
|
|
2035
|
+
background: #fff;
|
|
2036
|
+
color: var(--fg);
|
|
2037
|
+
font-weight: 600;
|
|
2038
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
2039
|
+
}
|
|
2040
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-switch {
|
|
2041
|
+
position: relative;
|
|
2042
|
+
flex-shrink: 0;
|
|
2043
|
+
width: 36px;
|
|
2044
|
+
height: 22px;
|
|
2045
|
+
margin-left: auto;
|
|
2046
|
+
border-radius: 999px;
|
|
2047
|
+
background: #e4e4e1;
|
|
2048
|
+
}
|
|
2049
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-switch-on { background: var(--preview); }
|
|
2050
|
+
.${DEVBAR_CLASS}-knob {
|
|
2051
|
+
position: absolute;
|
|
2052
|
+
top: 2px;
|
|
2053
|
+
left: 2px;
|
|
2054
|
+
width: 18px;
|
|
2055
|
+
height: 18px;
|
|
2056
|
+
border-radius: 999px;
|
|
2057
|
+
background: #fff;
|
|
2058
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.16);
|
|
2059
|
+
transition: transform 160ms var(--ease-out);
|
|
2060
|
+
}
|
|
2061
|
+
.${DEVBAR_CLASS}-switch-on .${DEVBAR_CLASS}-knob { transform: translateX(14px); }
|
|
2062
|
+
.${DEVBAR_CLASS}-settings .${DEVBAR_CLASS}-kbd { margin-left: auto; color: var(--fg3); }
|
|
2063
|
+
.${DEVBAR_CLASS}-settings-email {
|
|
2064
|
+
margin-left: auto;
|
|
2065
|
+
max-width: 46%;
|
|
2066
|
+
overflow: hidden;
|
|
2067
|
+
color: var(--fg3);
|
|
2068
|
+
font-size: 12px;
|
|
2069
|
+
font-weight: 500;
|
|
2070
|
+
text-align: right;
|
|
2071
|
+
text-overflow: ellipsis;
|
|
2072
|
+
white-space: nowrap;
|
|
2073
|
+
}
|
|
2074
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-settings-back {
|
|
2075
|
+
display: inline-flex;
|
|
2076
|
+
align-items: center;
|
|
2077
|
+
gap: 6px;
|
|
2078
|
+
min-height: 28px;
|
|
2079
|
+
color: var(--fg);
|
|
2080
|
+
font-weight: 600;
|
|
2081
|
+
}
|
|
2082
|
+
.${DEVBAR_CLASS}-settings-back svg { transform: rotate(90deg); }
|
|
2083
|
+
.${DEVBAR_CLASS}-settings-static { cursor: default; }
|
|
2084
|
+
.${DEVBAR_CLASS}-menu-split {
|
|
2085
|
+
display: flex;
|
|
2086
|
+
flex-direction: column;
|
|
2087
|
+
margin-top: 4px;
|
|
2088
|
+
padding-top: 4px;
|
|
2089
|
+
border-top: 1px solid var(--line);
|
|
738
2090
|
}
|
|
739
2091
|
.${DEVBAR_CLASS}-note {
|
|
740
2092
|
margin: 0;
|
|
741
2093
|
padding: 8px;
|
|
742
|
-
color:
|
|
2094
|
+
color: var(--fg2);
|
|
743
2095
|
font-size: 12px;
|
|
744
|
-
font-weight:
|
|
2096
|
+
font-weight: 500;
|
|
2097
|
+
}
|
|
2098
|
+
.${DEVBAR_CLASS}-page-menu .${DEVBAR_CLASS}-note {
|
|
2099
|
+
padding: 8px 12px;
|
|
2100
|
+
font-size: 12.5px;
|
|
745
2101
|
}
|
|
746
2102
|
.${DEVBAR_CLASS}-note-error { color: #b91c1c; }
|
|
2103
|
+
.${DEVBAR_CLASS}-note-muted { color: var(--fg3); }
|
|
747
2104
|
@media (hover: hover) {
|
|
748
|
-
.${DEVBAR_CLASS}-mode
|
|
749
|
-
.${DEVBAR_CLASS}-
|
|
750
|
-
.${DEVBAR_CLASS}-btn:hover:not(:disabled)
|
|
751
|
-
.${DEVBAR_CLASS}-
|
|
752
|
-
.${DEVBAR_CLASS}-
|
|
753
|
-
}
|
|
754
|
-
.${DEVBAR_CLASS} button:
|
|
755
|
-
|
|
756
|
-
|
|
2105
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-mode:hover:not(:disabled) { background: var(--preview-hover); }
|
|
2106
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-mode:hover:not(:disabled) { background: var(--preview-hover); }
|
|
2107
|
+
.${DEVBAR_CLASS}-root[data-mode="live"] button.${DEVBAR_CLASS}-btn:hover:not(:disabled),
|
|
2108
|
+
.${DEVBAR_CLASS}-root[data-mode="live"] button.${DEVBAR_CLASS}-page:hover:not(:disabled),
|
|
2109
|
+
.${DEVBAR_CLASS}-root[data-mode="live"] button.${DEVBAR_CLASS}-gear:hover,
|
|
2110
|
+
.${DEVBAR_CLASS}-option:hover:not(:disabled),
|
|
2111
|
+
.${DEVBAR_CLASS} button.${DEVBAR_CLASS}-settings-action:hover { background: var(--hover); }
|
|
2112
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-btn:hover:not(:disabled),
|
|
2113
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-page:hover:not(:disabled) { background: var(--preview-hover); }
|
|
2114
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-publish:hover:not(:disabled),
|
|
2115
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-publish:active:not(:disabled) { background: #fff; }
|
|
2116
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] button.${DEVBAR_CLASS}-btn:active:not(:disabled) { background: var(--preview-deep); }
|
|
757
2117
|
}
|
|
758
|
-
|
|
759
|
-
.${DEVBAR_CLASS}-
|
|
760
|
-
|
|
761
|
-
|
|
2118
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2119
|
+
.${DEVBAR_CLASS}-flood,
|
|
2120
|
+
.${DEVBAR_CLASS} button,
|
|
2121
|
+
.${DEVBAR_CLASS}-clip,
|
|
2122
|
+
.${DEVBAR_CLASS}-progress,
|
|
2123
|
+
.${DEVBAR_CLASS}-knob { transition: none !important; }
|
|
2124
|
+
.${DEVBAR_CLASS}-flood { clip-path: none; opacity: 0; }
|
|
2125
|
+
.${DEVBAR_CLASS}-root[data-mode="preview"] .${DEVBAR_CLASS}-flood { opacity: 1; }
|
|
762
2126
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
2127
|
+
@keyframes ${DEVBAR_CLASS}-spin { to { transform: rotate(360deg); } }
|
|
2128
|
+
@keyframes ${DEVBAR_CLASS}-shimmer {
|
|
2129
|
+
from { background-position: 200% 0; }
|
|
2130
|
+
to { background-position: -200% 0; }
|
|
2131
|
+
}
|
|
2132
|
+
:host(.${DEVBAR_CLASS}-pointing) .${DEVBAR_CLASS},
|
|
2133
|
+
:host(.${DEVBAR_CLASS}-pointing) .${DEVBAR_CLASS} * { cursor: pointer !important; }
|
|
2134
|
+
:host(.${DEVBAR_CLASS}-pointing) .${DEVBAR_CLASS} button:disabled { cursor: default !important; }
|
|
769
2135
|
`;
|