@backmatter/studio 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/UiKitApp-b8qFsmA-.js +2401 -0
- package/dist/chunks/WorkspaceRoutes-BKKEkAq7.js +90126 -0
- package/dist/chunks/{dashboard-DCHRI0WD.js → dashboard-BNUcbYU0.js} +380 -382
- package/dist/chunks/{index-BS0-Bvbk.js → index-laWAz5_p.js} +111 -112
- package/dist/dashboard.js +2 -2
- package/dist/index.js +287 -146
- package/dist/site-widget.css +1615 -0
- package/dist/site-widget.js +30896 -630
- package/dist/studio.css +303 -98
- package/package.json +4 -4
- package/src/App.tsx +12 -29
- package/src/api.ts +55 -0
- package/src/chat/ChangeCard.tsx +2 -0
- package/src/chat/ChatMessage.tsx +73 -62
- package/src/chat/ChatPanel.tsx +110 -15
- package/src/chat/DocListCard.tsx +2 -0
- package/src/chat/LiveStatus.tsx +78 -8
- package/src/chat/Reveal.tsx +34 -0
- package/src/chat/Shimmer.tsx +65 -23
- package/src/chat/chat-helpers.ts +35 -0
- package/src/chat/motion.ts +73 -0
- package/src/chat/useStickToBottom.ts +129 -14
- package/src/chrome/AppShell.tsx +47 -10
- package/src/chrome/AppSidebar.tsx +9 -34
- package/src/chrome/ContentExplorerSidebar.tsx +149 -96
- package/src/chrome/UserMenu.tsx +3 -1
- package/src/chrome/content-tree-visibility.ts +55 -0
- package/src/chrome/explorer-icons.tsx +2 -4
- package/src/devbar-state.ts +125 -0
- package/src/index.css +3 -131
- package/src/pages/CliAuthorizePage.tsx +61 -28
- package/src/pages/MediaGalleryPage.tsx +144 -85
- package/src/pages/MediaLightbox.tsx +195 -96
- package/src/pages/SelectRepositoriesLink.tsx +28 -4
- package/src/pages/SettingsGeneralPage.tsx +25 -28
- package/src/pages/SettingsGithubPage.tsx +413 -124
- package/src/pages/SettingsUsagePage.tsx +23 -1
- package/src/pages/media-grid.ts +14 -1
- package/src/pages/ui-kit/UiKitApp.tsx +164 -0
- package/src/pages/ui-kit/buttons.tsx +30 -0
- package/src/pages/ui-kit/catalog.tsx +41 -0
- package/src/pages/ui-kit/colors.tsx +95 -0
- package/src/pages/ui-kit/devbar.tsx +160 -0
- package/src/pages/ui-kit/feedback.tsx +39 -0
- package/src/pages/ui-kit/fields.tsx +47 -0
- package/src/pages/ui-kit/lists.tsx +19 -0
- package/src/pages/ui-kit/overlays.tsx +39 -0
- package/src/pages/ui-kit/path.ts +9 -0
- package/src/pages/ui-kit/sections.tsx +32 -0
- package/src/pages/ui-kit/surfaces.tsx +121 -0
- package/src/pages/ui-kit/topic.tsx +112 -0
- package/src/pages/ui-kit/topics.ts +71 -0
- package/src/pages/ui-kit/type.tsx +55 -0
- package/src/preview-frame-radius.ts +51 -0
- package/src/preview-frame.tsx +93 -49
- package/src/shell-resets.css +155 -0
- package/src/site-bar.tsx +1791 -425
- package/src/site-dashboard-href.ts +21 -0
- package/src/site-devbar-overlay-inject.ts +15 -0
- package/src/site-devbar-overlay.css +217 -0
- package/src/site-pointer.ts +1 -1
- package/src/site-publish-menu.tsx +33 -0
- package/src/site-widget-styles.ts +7 -0
- package/src/site-widget.tsx +61 -21
- package/src/studio-prose-ui.css +39 -0
- package/src/theme/ThemeProvider.tsx +4 -1
- package/src/workspace-path.ts +18 -1
- package/dist/chunks/EmbedPublishPage-88VsmOhi.js +0 -149
- package/dist/chunks/UiKitPage-X4hkFL2d.js +0 -173
- package/dist/chunks/WorkspaceRoutes-DXzdWqN3.js +0 -59523
- package/dist/chunks/status-bar-action-wj1KZ51m.js +0 -30612
- package/src/pages/UiKitPage.tsx +0 -331
package/src/chrome/AppShell.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// packages/studio/src/chrome/AppShell.tsx
|
|
2
|
-
import { useEffect } from "react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
3
|
import { useLocation, useSearchParams } from "react-router";
|
|
4
4
|
import * as stylex from "@stylexjs/stylex";
|
|
5
5
|
import { ChatBar } from "../chat/ChatBar";
|
|
@@ -9,12 +9,13 @@ import { colors } from "../styles/tokens.stylex";
|
|
|
9
9
|
import { TooltipProvider } from "../ui/Tooltip";
|
|
10
10
|
import { cmsWorkspaceSlug, isOrgPickerPath } from "../workspace-path";
|
|
11
11
|
import { AppSidebar } from "./AppSidebar";
|
|
12
|
+
import { WorkspaceRail } from "./ContentExplorerSidebar";
|
|
12
13
|
import { CommandPalette } from "./CommandPalette";
|
|
13
14
|
import { FloatingUserMenu } from "./UserMenu";
|
|
14
15
|
import { EditorActionsProvider } from "./EditorActionsContext";
|
|
15
16
|
import { EditorFocusProvider } from "./EditorFocusContext";
|
|
16
17
|
import { EditorStatusProvider } from "./EditorStatusContext";
|
|
17
|
-
import { WorkspaceNavProvider } from "./WorkspaceNavContext";
|
|
18
|
+
import { WorkspaceNavProvider, useWorkspaceNav } from "./WorkspaceNavContext";
|
|
18
19
|
import { installParentChatHotkey } from "./hotkeys";
|
|
19
20
|
import { WorkspaceStatusBar } from "./WorkspaceStatusBar";
|
|
20
21
|
|
|
@@ -40,28 +41,43 @@ function AppShellInner({ children }: { children: React.ReactNode }) {
|
|
|
40
41
|
// Mount from the URL, not from nav. Waiting on nav remounts this tree when
|
|
41
42
|
// WorkspaceLayout publishes (and later clears) nav, which loops loading.
|
|
42
43
|
const workspaceKey = cmsWorkspaceSlug(location.pathname);
|
|
44
|
+
const workspaceNav = useWorkspaceNav();
|
|
43
45
|
const orgPicker = isOrgPickerPath(location.pathname);
|
|
44
46
|
const embed = searchParams.get("embed") === "1";
|
|
47
|
+
const [treeHost, setTreeHost] = useState<HTMLDivElement | null>(null);
|
|
48
|
+
const inWorkspace = Boolean(workspaceKey && !embed && !orgPicker);
|
|
45
49
|
|
|
46
50
|
useEffect(() => {
|
|
47
51
|
if (!embed) return;
|
|
48
52
|
return installParentChatHotkey();
|
|
49
53
|
}, [embed]);
|
|
50
54
|
|
|
55
|
+
const chat =
|
|
56
|
+
workspaceKey && !embed ? (
|
|
57
|
+
<ChatSessionScope>
|
|
58
|
+
<ChatBar />
|
|
59
|
+
<ChatPanel />
|
|
60
|
+
</ChatSessionScope>
|
|
61
|
+
) : null;
|
|
62
|
+
|
|
51
63
|
const shell = (
|
|
52
64
|
<div {...stylex.props(styles.shell)}>
|
|
53
65
|
{workspaceKey && !embed ? <WorkspaceStatusBar workspaceId={workspaceKey} /> : null}
|
|
54
66
|
<div {...stylex.props(styles.body)}>
|
|
55
|
-
{orgPicker || embed ? null :
|
|
56
|
-
|
|
67
|
+
{orgPicker || embed ? null : inWorkspace ? (
|
|
68
|
+
<WorkspaceRail workspace={workspaceNav} treeHost={treeHost} />
|
|
69
|
+
) : (
|
|
70
|
+
<AppSidebar />
|
|
71
|
+
)}
|
|
72
|
+
<div {...stylex.props(styles.stage)}>
|
|
73
|
+
<div {...stylex.props(styles.stageBody)}>
|
|
74
|
+
<div ref={setTreeHost} {...stylex.props(styles.treeHost)} />
|
|
75
|
+
<main {...stylex.props(styles.main)}>{children}</main>
|
|
76
|
+
</div>
|
|
77
|
+
{chat}
|
|
78
|
+
</div>
|
|
57
79
|
</div>
|
|
58
80
|
{orgPicker ? <FloatingUserMenu /> : null}
|
|
59
|
-
{workspaceKey && !embed ? (
|
|
60
|
-
<ChatSessionScope>
|
|
61
|
-
<ChatBar />
|
|
62
|
-
<ChatPanel />
|
|
63
|
-
</ChatSessionScope>
|
|
64
|
-
) : null}
|
|
65
81
|
{embed ? null : <CommandPalette />}
|
|
66
82
|
</div>
|
|
67
83
|
);
|
|
@@ -89,6 +105,27 @@ const styles = stylex.create({
|
|
|
89
105
|
flex: 1,
|
|
90
106
|
overflow: "hidden",
|
|
91
107
|
},
|
|
108
|
+
stage: {
|
|
109
|
+
display: "flex",
|
|
110
|
+
minWidth: 0,
|
|
111
|
+
minHeight: 0,
|
|
112
|
+
flex: 1,
|
|
113
|
+
flexDirection: "column",
|
|
114
|
+
overflow: "hidden",
|
|
115
|
+
},
|
|
116
|
+
stageBody: {
|
|
117
|
+
display: "flex",
|
|
118
|
+
minWidth: 0,
|
|
119
|
+
minHeight: 0,
|
|
120
|
+
flex: 1,
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
},
|
|
123
|
+
treeHost: {
|
|
124
|
+
display: "flex",
|
|
125
|
+
minWidth: 0,
|
|
126
|
+
minHeight: 0,
|
|
127
|
+
flexShrink: 0,
|
|
128
|
+
},
|
|
92
129
|
main: {
|
|
93
130
|
display: "flex",
|
|
94
131
|
minWidth: 0,
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
// packages/studio/src/chrome/AppSidebar.tsx
|
|
2
|
-
import { useLocation } from "react-router";
|
|
3
2
|
import * as stylex from "@stylexjs/stylex";
|
|
4
3
|
import { useReplicaState } from "../replica/useReplica";
|
|
5
4
|
import { colors, space, type } from "../styles/tokens.stylex";
|
|
6
|
-
import { useWorkspaceNav } from "./WorkspaceNavContext";
|
|
7
5
|
import { NavItem } from "./NavItem";
|
|
8
6
|
import { UserMenu } from "./UserMenu";
|
|
9
|
-
import {
|
|
7
|
+
import { workspaceHomePath } from "../workspace-path";
|
|
10
8
|
import { DocIcon } from "./icons";
|
|
11
|
-
import { ContentExplorerSidebar } from "./ContentExplorerSidebar";
|
|
12
|
-
export function AppSidebar() {
|
|
13
|
-
const location = useLocation();
|
|
14
|
-
const workspaceNav = useWorkspaceNav();
|
|
15
|
-
const inWorkspace = Boolean(cmsWorkspaceSlug(location.pathname));
|
|
16
|
-
const railOnly = inWorkspace && isWorkspaceMediaPath(location.pathname);
|
|
17
9
|
|
|
10
|
+
/** Workspace list. Inside a workspace the explorer frame owns the rail. */
|
|
11
|
+
export function AppSidebar() {
|
|
18
12
|
return (
|
|
19
|
-
<aside {...stylex.props(styles.aside
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<ContentExplorerSidebar workspace={workspaceNav} />
|
|
23
|
-
</div>
|
|
24
|
-
) : (
|
|
25
|
-
<WorkspaceListNav />
|
|
26
|
-
)}
|
|
27
|
-
<SidebarFooter compact={railOnly} />
|
|
13
|
+
<aside {...stylex.props(styles.aside)}>
|
|
14
|
+
<WorkspaceListNav />
|
|
15
|
+
<SidebarFooter />
|
|
28
16
|
</aside>
|
|
29
17
|
);
|
|
30
18
|
}
|
|
@@ -68,21 +56,15 @@ function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
|
68
56
|
return <p {...stylex.props(styles.section)}>{children}</p>;
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
function SidebarFooter(
|
|
59
|
+
function SidebarFooter() {
|
|
72
60
|
return (
|
|
73
|
-
<div {...stylex.props(styles.footer
|
|
74
|
-
<UserMenu
|
|
61
|
+
<div {...stylex.props(styles.footer)}>
|
|
62
|
+
<UserMenu />
|
|
75
63
|
</div>
|
|
76
64
|
);
|
|
77
65
|
}
|
|
78
66
|
|
|
79
67
|
const styles = stylex.create({
|
|
80
|
-
workspaceNav: {
|
|
81
|
-
display: "flex",
|
|
82
|
-
minHeight: 0,
|
|
83
|
-
flex: 1,
|
|
84
|
-
flexDirection: "column",
|
|
85
|
-
},
|
|
86
68
|
aside: {
|
|
87
69
|
display: "flex",
|
|
88
70
|
height: "100%",
|
|
@@ -95,10 +77,6 @@ const styles = stylex.create({
|
|
|
95
77
|
borderRightColor: colors.border,
|
|
96
78
|
backgroundColor: colors.bgSurface,
|
|
97
79
|
},
|
|
98
|
-
asideRail: {
|
|
99
|
-
width: "48px",
|
|
100
|
-
minWidth: "48px",
|
|
101
|
-
},
|
|
102
80
|
brand: {
|
|
103
81
|
flexShrink: 0,
|
|
104
82
|
borderBottomWidth: 1,
|
|
@@ -174,7 +152,4 @@ const styles = stylex.create({
|
|
|
174
152
|
borderTopColor: colors.border,
|
|
175
153
|
padding: space.md,
|
|
176
154
|
},
|
|
177
|
-
footerCompact: {
|
|
178
|
-
padding: space.sm,
|
|
179
|
-
},
|
|
180
155
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// packages/studio/src/chrome/ContentExplorerSidebar.tsx
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
3
4
|
import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
|
|
4
5
|
import * as stylex from "@stylexjs/stylex";
|
|
5
6
|
import { notifyTreeChanged, saveDoc, type DocListItem, type DocStatus } from "../api";
|
|
@@ -8,14 +9,15 @@ import { colors, radius, space, type } from "../styles/tokens.stylex";
|
|
|
8
9
|
import { Tooltip } from "../ui/Tooltip";
|
|
9
10
|
import {
|
|
10
11
|
contentDocPath,
|
|
11
|
-
|
|
12
|
+
contentRailHref,
|
|
12
13
|
isWorkspaceContentPath,
|
|
14
|
+
isWorkspaceEditorPath,
|
|
13
15
|
isWorkspaceMediaPath,
|
|
14
16
|
isWorkspaceSettingsPath,
|
|
15
17
|
mediaPath,
|
|
16
18
|
settingsPath,
|
|
17
|
-
workspaceHomePath,
|
|
18
19
|
} from "../workspace-path";
|
|
20
|
+
import { UserMenu } from "./UserMenu";
|
|
19
21
|
import {
|
|
20
22
|
groupContentTypes,
|
|
21
23
|
type ContentTypeNav,
|
|
@@ -53,6 +55,11 @@ import {
|
|
|
53
55
|
type FolderCreateContext,
|
|
54
56
|
} from "./folder-create-context";
|
|
55
57
|
import { canWriteContent } from "../workspace/permissions";
|
|
58
|
+
import {
|
|
59
|
+
contentTreeVisible,
|
|
60
|
+
toggleContentTree,
|
|
61
|
+
useContentTreeOpen,
|
|
62
|
+
} from "./content-tree-visibility";
|
|
56
63
|
|
|
57
64
|
const hover = "@media (hover: hover)";
|
|
58
65
|
const statusDot = "#ea580c";
|
|
@@ -62,7 +69,36 @@ const TREE_PAD_PX = 8;
|
|
|
62
69
|
const INDENT_PX = 8;
|
|
63
70
|
const TWISTIE_PX = 16;
|
|
64
71
|
|
|
65
|
-
export function
|
|
72
|
+
export function WorkspaceRail({
|
|
73
|
+
workspace,
|
|
74
|
+
treeHost,
|
|
75
|
+
}: {
|
|
76
|
+
workspace: WorkspaceNav | null;
|
|
77
|
+
treeHost: HTMLElement | null;
|
|
78
|
+
}) {
|
|
79
|
+
if (!workspace) {
|
|
80
|
+
return (
|
|
81
|
+
<RailColumn>
|
|
82
|
+
<div {...stylex.props(styles.rail, styles.railAccountOnly)}>
|
|
83
|
+
<UserMenu compact />
|
|
84
|
+
</div>
|
|
85
|
+
</RailColumn>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return <ContentExplorerSidebar workspace={workspace} treeHost={treeHost} />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function RailColumn({ children }: { children?: React.ReactNode }) {
|
|
92
|
+
return <aside {...stylex.props(styles.railColumn)}>{children}</aside>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ContentExplorerSidebar({
|
|
96
|
+
workspace,
|
|
97
|
+
treeHost,
|
|
98
|
+
}: {
|
|
99
|
+
workspace: WorkspaceNav;
|
|
100
|
+
treeHost: HTMLElement | null;
|
|
101
|
+
}) {
|
|
66
102
|
const location = useLocation();
|
|
67
103
|
const [searchParams] = useSearchParams();
|
|
68
104
|
const replica = useReplicaState();
|
|
@@ -128,80 +164,85 @@ export function ContentExplorerSidebar({ workspace }: { workspace: WorkspaceNav
|
|
|
128
164
|
|
|
129
165
|
const empty = workspace.contentTypes.length === 0;
|
|
130
166
|
const settingsActive = isWorkspaceSettingsPath(location.pathname);
|
|
131
|
-
const
|
|
167
|
+
const treeOpen = useContentTreeOpen();
|
|
168
|
+
const showTree = contentTreeVisible(location.pathname, treeOpen);
|
|
132
169
|
const canWrite = canWriteContent(workspace.role);
|
|
133
170
|
|
|
171
|
+
const tree = showTree ? (
|
|
172
|
+
<div {...stylex.props(styles.panel)}>
|
|
173
|
+
<header {...stylex.props(styles.panelHeader)}>
|
|
174
|
+
<span {...stylex.props(styles.panelTitle)}>Content</span>
|
|
175
|
+
<div {...stylex.props(styles.panelActions)}>
|
|
176
|
+
<IconButton label="New file (soon)" disabled>
|
|
177
|
+
<ExplorerHeaderNewFileIcon />
|
|
178
|
+
</IconButton>
|
|
179
|
+
<IconButton label="View options (soon)" disabled>
|
|
180
|
+
<ExplorerHeaderListIcon />
|
|
181
|
+
</IconButton>
|
|
182
|
+
</div>
|
|
183
|
+
</header>
|
|
184
|
+
<div {...stylex.props(styles.treeScroll)}>
|
|
185
|
+
{empty ? (
|
|
186
|
+
<p {...stylex.props(styles.emptyHint)}>
|
|
187
|
+
No content types yet. Pull from GitHub to load models.
|
|
188
|
+
</p>
|
|
189
|
+
) : (
|
|
190
|
+
<>
|
|
191
|
+
{grouped.pages.length > 0 ? (
|
|
192
|
+
<ExplorerSection
|
|
193
|
+
section="pages"
|
|
194
|
+
models={grouped.pages}
|
|
195
|
+
workspace={workspace}
|
|
196
|
+
docsByType={docsByType}
|
|
197
|
+
route={route}
|
|
198
|
+
expanded={expanded}
|
|
199
|
+
onToggleFolder={toggleFolder}
|
|
200
|
+
onExpandFolderPath={expandFolderPath}
|
|
201
|
+
canWrite={canWrite}
|
|
202
|
+
/>
|
|
203
|
+
) : null}
|
|
204
|
+
{grouped.data.length > 0 ? (
|
|
205
|
+
<ExplorerSection
|
|
206
|
+
section="data"
|
|
207
|
+
models={grouped.data}
|
|
208
|
+
workspace={workspace}
|
|
209
|
+
docsByType={docsByType}
|
|
210
|
+
route={route}
|
|
211
|
+
expanded={expanded}
|
|
212
|
+
onToggleFolder={toggleFolder}
|
|
213
|
+
onExpandFolderPath={expandFolderPath}
|
|
214
|
+
canWrite={canWrite}
|
|
215
|
+
/>
|
|
216
|
+
) : null}
|
|
217
|
+
{grouped.globals.length > 0 ? (
|
|
218
|
+
<ExplorerSection
|
|
219
|
+
section="globals"
|
|
220
|
+
models={grouped.globals}
|
|
221
|
+
workspace={workspace}
|
|
222
|
+
docsByType={docsByType}
|
|
223
|
+
route={route}
|
|
224
|
+
expanded={expanded}
|
|
225
|
+
onToggleFolder={toggleFolder}
|
|
226
|
+
onExpandFolderPath={expandFolderPath}
|
|
227
|
+
canWrite={canWrite}
|
|
228
|
+
/>
|
|
229
|
+
) : null}
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
) : null;
|
|
235
|
+
|
|
134
236
|
return (
|
|
135
|
-
<
|
|
237
|
+
<RailColumn>
|
|
136
238
|
<ActivityRail
|
|
137
239
|
pendingCount={pendingCount}
|
|
138
240
|
settingsActive={settingsActive}
|
|
139
241
|
workspaceSlug={workspace.workspaceSlug}
|
|
140
|
-
|
|
242
|
+
treeOpen={treeOpen}
|
|
141
243
|
/>
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
<span {...stylex.props(styles.panelTitle)}>Content</span>
|
|
145
|
-
<div {...stylex.props(styles.panelActions)}>
|
|
146
|
-
<IconButton label="New file (soon)" disabled>
|
|
147
|
-
<ExplorerHeaderNewFileIcon />
|
|
148
|
-
</IconButton>
|
|
149
|
-
<IconButton label="View options (soon)" disabled>
|
|
150
|
-
<ExplorerHeaderListIcon />
|
|
151
|
-
</IconButton>
|
|
152
|
-
</div>
|
|
153
|
-
</header>
|
|
154
|
-
<div {...stylex.props(styles.treeScroll)}>
|
|
155
|
-
{empty ? (
|
|
156
|
-
<p {...stylex.props(styles.emptyHint)}>
|
|
157
|
-
No content types yet. Pull from GitHub to load models.
|
|
158
|
-
</p>
|
|
159
|
-
) : (
|
|
160
|
-
<>
|
|
161
|
-
{grouped.pages.length > 0 ? (
|
|
162
|
-
<ExplorerSection
|
|
163
|
-
section="pages"
|
|
164
|
-
models={grouped.pages}
|
|
165
|
-
workspace={workspace}
|
|
166
|
-
docsByType={docsByType}
|
|
167
|
-
route={route}
|
|
168
|
-
expanded={expanded}
|
|
169
|
-
onToggleFolder={toggleFolder}
|
|
170
|
-
onExpandFolderPath={expandFolderPath}
|
|
171
|
-
canWrite={canWrite}
|
|
172
|
-
/>
|
|
173
|
-
) : null}
|
|
174
|
-
{grouped.data.length > 0 ? (
|
|
175
|
-
<ExplorerSection
|
|
176
|
-
section="data"
|
|
177
|
-
models={grouped.data}
|
|
178
|
-
workspace={workspace}
|
|
179
|
-
docsByType={docsByType}
|
|
180
|
-
route={route}
|
|
181
|
-
expanded={expanded}
|
|
182
|
-
onToggleFolder={toggleFolder}
|
|
183
|
-
onExpandFolderPath={expandFolderPath}
|
|
184
|
-
canWrite={canWrite}
|
|
185
|
-
/>
|
|
186
|
-
) : null}
|
|
187
|
-
{grouped.globals.length > 0 ? (
|
|
188
|
-
<ExplorerSection
|
|
189
|
-
section="globals"
|
|
190
|
-
models={grouped.globals}
|
|
191
|
-
workspace={workspace}
|
|
192
|
-
docsByType={docsByType}
|
|
193
|
-
route={route}
|
|
194
|
-
expanded={expanded}
|
|
195
|
-
onToggleFolder={toggleFolder}
|
|
196
|
-
onExpandFolderPath={expandFolderPath}
|
|
197
|
-
canWrite={canWrite}
|
|
198
|
-
/>
|
|
199
|
-
) : null}
|
|
200
|
-
</>
|
|
201
|
-
)}
|
|
202
|
-
</div>
|
|
203
|
-
</div> : null}
|
|
204
|
-
</div>
|
|
244
|
+
{tree && treeHost ? createPortal(tree, treeHost) : null}
|
|
245
|
+
</RailColumn>
|
|
205
246
|
);
|
|
206
247
|
}
|
|
207
248
|
|
|
@@ -209,40 +250,40 @@ function ActivityRail({
|
|
|
209
250
|
pendingCount,
|
|
210
251
|
settingsActive,
|
|
211
252
|
workspaceSlug,
|
|
212
|
-
|
|
253
|
+
treeOpen,
|
|
213
254
|
}: {
|
|
214
255
|
pendingCount: number;
|
|
215
256
|
settingsActive: boolean;
|
|
216
257
|
workspaceSlug: string;
|
|
217
|
-
|
|
258
|
+
treeOpen: boolean;
|
|
218
259
|
}) {
|
|
219
260
|
const navigate = useNavigate();
|
|
220
261
|
const location = useLocation();
|
|
262
|
+
const onDocument = isWorkspaceEditorPath(location.pathname);
|
|
263
|
+
const filesLabel = onDocument ? (treeOpen ? "Hide files" : "Show files") : "Content";
|
|
264
|
+
const filesActive = isWorkspaceContentPath(location.pathname) && (!onDocument || treeOpen);
|
|
221
265
|
|
|
222
266
|
return (
|
|
223
267
|
<nav
|
|
224
268
|
aria-label="Studio"
|
|
225
|
-
{...stylex.props(styles.rail
|
|
269
|
+
{...stylex.props(styles.rail)}
|
|
226
270
|
>
|
|
227
271
|
<div {...stylex.props(styles.railTop)}>
|
|
228
|
-
<Tooltip label=
|
|
272
|
+
<Tooltip label={filesLabel} side="right">
|
|
229
273
|
<button
|
|
230
274
|
type="button"
|
|
231
|
-
aria-label=
|
|
232
|
-
aria-
|
|
275
|
+
aria-label={filesLabel}
|
|
276
|
+
aria-pressed={onDocument ? treeOpen : undefined}
|
|
277
|
+
aria-current={!onDocument && filesActive ? "page" : undefined}
|
|
233
278
|
onClick={() => {
|
|
234
|
-
if (
|
|
235
|
-
|
|
279
|
+
if (onDocument) {
|
|
280
|
+
toggleContentTree();
|
|
236
281
|
return;
|
|
237
282
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
283
|
+
const href = contentRailHref(location.pathname, workspaceSlug);
|
|
284
|
+
if (href) navigate(href);
|
|
241
285
|
}}
|
|
242
|
-
{...stylex.props(
|
|
243
|
-
styles.railBtn,
|
|
244
|
-
isWorkspaceContentPath(location.pathname) && styles.railBtnActive,
|
|
245
|
-
)}
|
|
286
|
+
{...stylex.props(styles.railBtn, filesActive && styles.railBtnActive)}
|
|
246
287
|
>
|
|
247
288
|
<ExplorerRailFilesIcon />
|
|
248
289
|
</button>
|
|
@@ -297,6 +338,7 @@ function ActivityRail({
|
|
|
297
338
|
<ExplorerRailSettingsIcon />
|
|
298
339
|
</Link>
|
|
299
340
|
</Tooltip>
|
|
341
|
+
<UserMenu compact />
|
|
300
342
|
</div>
|
|
301
343
|
</nav>
|
|
302
344
|
);
|
|
@@ -637,7 +679,7 @@ function FolderTreeRow({
|
|
|
637
679
|
<ExplorerTwistie open={open} />
|
|
638
680
|
</span>
|
|
639
681
|
<span {...stylex.props(styles.treeIcon)}>
|
|
640
|
-
<ExplorerFolderIcon
|
|
682
|
+
<ExplorerFolderIcon />
|
|
641
683
|
</span>
|
|
642
684
|
<span {...stylex.props(styles.treeLabel)}>{node.name}</span>
|
|
643
685
|
</button>
|
|
@@ -833,27 +875,32 @@ function sectionTitle(section: ContentTypeSection) {
|
|
|
833
875
|
}
|
|
834
876
|
|
|
835
877
|
const styles = stylex.create({
|
|
836
|
-
|
|
878
|
+
railColumn: {
|
|
837
879
|
display: "flex",
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
880
|
+
height: "100%",
|
|
881
|
+
width: "48px",
|
|
882
|
+
minWidth: "48px",
|
|
883
|
+
flexShrink: 0,
|
|
884
|
+
flexDirection: "column",
|
|
885
|
+
borderRightWidth: 1,
|
|
886
|
+
borderRightStyle: "solid",
|
|
887
|
+
borderRightColor: colors.border,
|
|
888
|
+
backgroundColor: colors.bgSurface,
|
|
841
889
|
},
|
|
842
890
|
rail: {
|
|
843
891
|
display: "flex",
|
|
844
892
|
width: "48px",
|
|
893
|
+
minHeight: 0,
|
|
894
|
+
flex: 1,
|
|
845
895
|
flexShrink: 0,
|
|
846
896
|
flexDirection: "column",
|
|
847
897
|
justifyContent: "space-between",
|
|
848
898
|
alignItems: "center",
|
|
849
|
-
borderRightWidth: 1,
|
|
850
|
-
borderRightStyle: "solid",
|
|
851
|
-
borderRightColor: colors.border,
|
|
852
899
|
backgroundColor: colors.bgSidebar,
|
|
853
900
|
paddingBlock: space.md,
|
|
854
901
|
},
|
|
855
|
-
|
|
856
|
-
|
|
902
|
+
railAccountOnly: {
|
|
903
|
+
justifyContent: "flex-end",
|
|
857
904
|
},
|
|
858
905
|
railTop: {
|
|
859
906
|
display: "flex",
|
|
@@ -865,6 +912,7 @@ const styles = stylex.create({
|
|
|
865
912
|
display: "flex",
|
|
866
913
|
flexDirection: "column",
|
|
867
914
|
alignItems: "center",
|
|
915
|
+
gap: 2,
|
|
868
916
|
},
|
|
869
917
|
railBtn: {
|
|
870
918
|
position: "relative",
|
|
@@ -921,10 +969,15 @@ const styles = stylex.create({
|
|
|
921
969
|
},
|
|
922
970
|
panel: {
|
|
923
971
|
display: "flex",
|
|
924
|
-
|
|
972
|
+
width: "calc(min(360px, 38vw) - 48px)",
|
|
973
|
+
minWidth: "252px",
|
|
925
974
|
minHeight: 0,
|
|
926
|
-
|
|
975
|
+
flexGrow: 0,
|
|
976
|
+
flexShrink: 0,
|
|
927
977
|
flexDirection: "column",
|
|
978
|
+
borderRightWidth: 1,
|
|
979
|
+
borderRightStyle: "solid",
|
|
980
|
+
borderRightColor: colors.border,
|
|
928
981
|
backgroundColor: colors.bgSurface,
|
|
929
982
|
},
|
|
930
983
|
panelHeader: {
|
package/src/chrome/UserMenu.tsx
CHANGED
|
@@ -110,10 +110,12 @@ const styles = stylex.create({
|
|
|
110
110
|
},
|
|
111
111
|
},
|
|
112
112
|
triggerCompact: {
|
|
113
|
+
width: "36px",
|
|
114
|
+
height: "36px",
|
|
115
|
+
minHeight: "36px",
|
|
113
116
|
justifyContent: "center",
|
|
114
117
|
paddingInline: 0,
|
|
115
118
|
paddingBlock: 0,
|
|
116
|
-
minHeight: "32px",
|
|
117
119
|
},
|
|
118
120
|
initial: {
|
|
119
121
|
fontSize: type.sm,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// packages/studio/src/chrome/content-tree-visibility.ts
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import type { KeyValueStore } from "../last-workspace";
|
|
4
|
+
import { isWorkspaceContentPath, isWorkspaceEditorPath } from "../workspace-path";
|
|
5
|
+
|
|
6
|
+
export const CONTENT_TREE_OPEN_KEY = "backmatter-content-tree-open";
|
|
7
|
+
const CONTENT_TREE_EVENT = "backmatter-content-tree";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The type index is the file tree, so it stays open.
|
|
11
|
+
* A document follows the editor toggle. Other routes have no tree.
|
|
12
|
+
*/
|
|
13
|
+
export function contentTreeVisible(pathname: string, open: boolean) {
|
|
14
|
+
if (!isWorkspaceContentPath(pathname)) return false;
|
|
15
|
+
if (isWorkspaceEditorPath(pathname)) return open;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readContentTreeOpen(storage: KeyValueStore = localStorage) {
|
|
20
|
+
try {
|
|
21
|
+
return storage.getItem(CONTENT_TREE_OPEN_KEY) !== "0";
|
|
22
|
+
} catch {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeContentTreeOpen(
|
|
28
|
+
open: boolean,
|
|
29
|
+
storage: KeyValueStore = localStorage,
|
|
30
|
+
) {
|
|
31
|
+
try {
|
|
32
|
+
storage.setItem(CONTENT_TREE_OPEN_KEY, open ? "1" : "0");
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore quota / private mode
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toggleContentTree() {
|
|
39
|
+
writeContentTreeOpen(!readContentTreeOpen());
|
|
40
|
+
window.dispatchEvent(new Event(CONTENT_TREE_EVENT));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useContentTreeOpen() {
|
|
44
|
+
const [open, setOpen] = useState(() => readContentTreeOpen());
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const sync = () => setOpen(readContentTreeOpen());
|
|
47
|
+
window.addEventListener(CONTENT_TREE_EVENT, sync);
|
|
48
|
+
window.addEventListener("storage", sync);
|
|
49
|
+
return () => {
|
|
50
|
+
window.removeEventListener(CONTENT_TREE_EVENT, sync);
|
|
51
|
+
window.removeEventListener("storage", sync);
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
return open;
|
|
55
|
+
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
IconFileText,
|
|
10
10
|
IconFiles,
|
|
11
11
|
IconFolder1,
|
|
12
|
-
IconFolderOpen,
|
|
13
12
|
IconGit,
|
|
14
13
|
IconHistory,
|
|
15
14
|
IconImages1,
|
|
@@ -42,9 +41,8 @@ export function ExplorerTwistie({ open }: { open: boolean }) {
|
|
|
42
41
|
return <Central icon={Icon} size={TREE} />;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
export function ExplorerFolderIcon(
|
|
46
|
-
|
|
47
|
-
return <Central icon={Icon} size={TREE} />;
|
|
44
|
+
export function ExplorerFolderIcon() {
|
|
45
|
+
return <Central icon={IconFolder1} size={TREE} />;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
export function ExplorerFileIcon({ ext }: { ext?: string }) {
|