camox 0.48.0 → 0.52.1
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/core/createApp.d.ts +1 -0
- package/dist/core/createApp.js +1 -0
- package/dist/core/editing/contentEdits.js +78 -0
- package/dist/core/editing/createEditableBlock.js +277 -311
- package/dist/core/layoutContext.d.ts +1 -0
- package/dist/core/lib/contentType.js +2 -2
- package/dist/core/recordItem.js +3 -3
- package/dist/features/canvas/CamoxCanvas.js +15 -15
- package/dist/features/canvas/CanvasLoadingScreen.js +15 -11
- package/dist/features/canvas/CanvasPageFrame.js +81 -87
- package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
- package/dist/features/canvas/DerivedPagePathInput.js +12 -12
- package/dist/features/canvas/canvasOverlayGeometry.js +1 -1
- package/dist/features/canvas/canvasStore.js +2 -2
- package/dist/features/canvas/sidebar/CanvasInspector.js +1 -1
- package/dist/features/canvas/sidebar/CuratedPageInspector.js +1 -1
- package/dist/features/canvas/sidebar/LayoutPageBlocks.js +23 -31
- package/dist/features/canvas/useCanvasCamera.js +1 -1
- package/dist/features/canvas/useDiscoveredPreviewPaths.js +16 -9
- package/dist/features/content/ContentAssets.js +84 -88
- package/dist/features/page/CuratedPageContent.js +1 -1
- package/dist/features/preview/CamoxPreview.d.ts +8 -4
- package/dist/features/preview/CamoxPreview.js +258 -443
- package/dist/features/preview/EditablePageContent.js +244 -2
- package/dist/features/preview/EditablePageExperience.js +19 -13
- package/dist/features/preview/components/AddBlockSheet.js +55 -15
- package/dist/features/preview/components/BlockActionsPopover.js +23 -51
- package/dist/features/preview/components/BlockPeek.js +66 -0
- package/dist/features/preview/components/BlockThumbnail.js +134 -119
- package/dist/features/preview/components/CanvasPreview.js +1 -1
- package/dist/features/preview/components/CreatePageModal.js +150 -156
- package/dist/features/preview/components/FieldToolbar.js +1 -1
- package/dist/features/preview/components/Frame.js +27 -4
- package/dist/features/preview/components/MobilePreviewDrawer.js +6 -9
- package/dist/features/preview/components/PageInfo.js +161 -126
- package/dist/features/preview/components/PagePicker.js +2 -2
- package/dist/features/preview/components/PageTree.js +174 -190
- package/dist/features/preview/components/PreviewPanel.js +13 -67
- package/dist/features/preview/components/PreviewToolbar.js +1 -1
- package/dist/features/preview/components/ReferenceFieldEditor.js +4 -4
- package/dist/features/preview/components/ReferenceListFieldEditor.js +4 -4
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +37 -70
- package/dist/features/preview/components/RepeatableItemsList.js +1 -1
- package/dist/features/preview/components/SelectionEditor.js +248 -256
- package/dist/features/preview/components/StudioEditingFallback.js +22 -26
- package/dist/features/preview/components/useFieldWriter.js +19 -19
- package/dist/features/preview/components/useRecordView.js +105 -147
- package/dist/features/preview/components/useRepeatableItemActions.js +0 -2
- package/dist/features/preview/pageRecord.js +30 -24
- package/dist/features/preview/previewConstants.js +2 -8
- package/dist/features/preview/previewStore.js +2 -0
- package/dist/features/preview/studioNotice.js +20 -0
- package/dist/features/preview/usePreviewViewportActions.js +1 -1
- package/dist/features/provider/AuthenticatedCamoxProvider.js +47 -31
- package/dist/features/provider/components/CommandPalette.js +2 -5
- package/dist/features/provider/signInShortcut.js +1 -2
- package/dist/features/provider/useAdminShortcuts.js +47 -53
- package/dist/features/renderer/RendererApp.js +542 -0
- package/dist/features/renderer/RendererFrames.js +450 -0
- package/dist/features/renderer/definitionAnswers.js +26 -0
- package/dist/features/renderer/protocol.js +35 -0
- package/dist/features/renderer/rendererHost.js +249 -0
- package/dist/features/renderer/studioConnection.js +186 -0
- package/dist/features/routes/pageRuntime.d.ts +2 -1
- package/dist/features/routes/pageRuntime.js +34 -22
- package/dist/features/runtime/RuntimeChrome.js +2 -5
- package/dist/features/runtime/assetPrefix.js +14 -0
- package/dist/features/runtime/breadcrumbs.js +1 -1
- package/dist/features/runtime/editingFallback.js +4 -8
- package/dist/features/runtime/livePages.js +1 -1
- package/dist/features/runtime/pageApp.js +57 -83
- package/dist/features/runtime/pageClient.js +1 -1
- package/dist/features/runtime/pageNavigation.js +3 -2
- package/dist/features/runtime/publicCache.js +95 -0
- package/dist/features/runtime/publishingDraft.js +23 -0
- package/dist/features/runtime/rendererClient.d.ts +8 -0
- package/dist/features/runtime/rendererClient.js +77 -0
- package/dist/features/runtime/runtime.d.ts +22 -3
- package/dist/features/runtime/runtime.js +209 -96
- package/dist/features/runtime/runtimeRoutes.d.ts +2 -0
- package/dist/features/runtime/runtimeRoutes.js +2 -0
- package/dist/features/runtime/worker.d.ts +14 -1
- package/dist/features/runtime/worker.js +34 -4
- package/dist/features/settings/AnalyticsSettings.js +622 -0
- package/dist/features/settings/CamoxSettings.js +95 -0
- package/dist/features/settings/FaviconSettings.js +254 -0
- package/dist/features/settings/ProjectNameSettings.js +145 -0
- package/dist/features/settings/SettingsPart.js +73 -0
- package/dist/features/settings/SocialProfilesSettings.js +245 -0
- package/dist/features/settings/useProjectMutation.js +61 -0
- package/dist/features/studio/FollowAgentProvider.js +10 -10
- package/dist/features/studio/components/DraftsControl.js +136 -131
- package/dist/features/studio/components/LocalePicker.js +178 -174
- package/dist/features/studio/components/Navbar.js +107 -66
- package/dist/features/studio/components/ProjectMenu.js +119 -171
- package/dist/features/studio/components/ReviewDraftSheet.js +1151 -991
- package/dist/features/studio/components/ThreadMessages.js +100 -65
- package/dist/features/studio/components/UserButton.js +2 -2
- package/dist/features/studio/routes.js +3 -1
- package/dist/features/studio/showInCanvas.js +129 -0
- package/dist/features/vite/definitionsSync.js +5 -6
- package/dist/features/vite/hostedChecks.js +127 -0
- package/dist/features/vite/runtimeDev.js +58 -17
- package/dist/features/vite/staticAssetHeaders.js +52 -0
- package/dist/features/vite/vite.d.ts +1 -1
- package/dist/features/vite/vite.js +49 -9
- package/dist/hooks/use-page-destinations.js +10 -10
- package/dist/lib/api-client-server.d.ts +1 -1
- package/dist/lib/api-client.d.ts +2 -1
- package/dist/lib/draft-scope.d.ts +1 -0
- package/dist/lib/locale-path.d.ts +3 -11
- package/dist/lib/locale-path.js +3 -27
- package/dist/lib/queries.js +7 -1
- package/dist/lib/revisions.js +20 -0
- package/dist/lib/shortcuts.js +29 -0
- package/dist/lib/studio-definitions.js +160 -0
- package/dist/lib/studio-reads.d.ts +1 -0
- package/dist/lib/studio-reads.js +65 -21
- package/dist/lib/use-project-room.js +16 -10
- package/dist/lib/utils.js +1 -104
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +9 -4
- package/skills/camox/SKILL.md +4 -0
- package/skills/camox/references/artifacts.md +1 -1
- package/dist/features/studio/components/FaviconDialog.js +0 -318
- package/dist/features/studio/components/SocialProfilesDialog.js +0 -342
|
@@ -9,12 +9,13 @@ import { MessageScroller, MessageScrollerButton, MessageScrollerContent, Message
|
|
|
9
9
|
//#region src/features/studio/components/ThreadMessages.tsx
|
|
10
10
|
/**
|
|
11
11
|
* A thread's messages, and "Working…" while a run is going or a message is still `sending`: on
|
|
12
|
-
* its way to the thread, which hasn't accepted it yet.
|
|
12
|
+
* its way to the thread, which hasn't accepted it yet. While the end of a turn builds the
|
|
13
|
+
* agent's code, or the agent fixes a build error, it says so instead. A draft's thread is shared, so it shows
|
|
13
14
|
* senders' names too (`showSenders`). A fresh Live thread links to the draft the editor's
|
|
14
15
|
* previous one continued in (`continuedIn`).
|
|
15
16
|
*/
|
|
16
17
|
function ThreadMessages(t0) {
|
|
17
|
-
const $ = c(
|
|
18
|
+
const $ = c(28);
|
|
18
19
|
const { view, sending: t1, showSenders: t2, continuedIn } = t0;
|
|
19
20
|
const sending = t1 === void 0 ? false : t1;
|
|
20
21
|
const showSenders = t2 === void 0 ? false : t2;
|
|
@@ -53,9 +54,9 @@ function ThreadMessages(t0) {
|
|
|
53
54
|
$[6] = t3;
|
|
54
55
|
} else t3 = $[6];
|
|
55
56
|
let t4;
|
|
56
|
-
if ($[7] !== showSenders || $[8] !== view.messages || $[9] !== working) {
|
|
57
|
+
if ($[7] !== showSenders || $[8] !== view.messages || $[9] !== view.status || $[10] !== working) {
|
|
57
58
|
let t5;
|
|
58
|
-
if ($[
|
|
59
|
+
if ($[12] !== showSenders || $[13] !== view.messages.length || $[14] !== view.status || $[15] !== working) {
|
|
59
60
|
t5 = (message, index) => /* @__PURE__ */ jsxs(MessageScrollerItem, {
|
|
60
61
|
messageId: message.id,
|
|
61
62
|
scrollAnchor: message.from === "editor",
|
|
@@ -63,28 +64,31 @@ function ThreadMessages(t0) {
|
|
|
63
64
|
children: [/* @__PURE__ */ jsx(ThreadMessageRow, {
|
|
64
65
|
message,
|
|
65
66
|
showSender: showSenders
|
|
66
|
-
}), working && index === view.messages.length - 1 && /* @__PURE__ */ jsx(Working, {})]
|
|
67
|
+
}), working && index === view.messages.length - 1 && /* @__PURE__ */ jsx(Working, { status: view.status })]
|
|
67
68
|
}, message.id);
|
|
68
|
-
$[
|
|
69
|
-
$[
|
|
70
|
-
$[
|
|
71
|
-
$[
|
|
72
|
-
|
|
69
|
+
$[12] = showSenders;
|
|
70
|
+
$[13] = view.messages.length;
|
|
71
|
+
$[14] = view.status;
|
|
72
|
+
$[15] = working;
|
|
73
|
+
$[16] = t5;
|
|
74
|
+
} else t5 = $[16];
|
|
73
75
|
t4 = view.messages.map(t5);
|
|
74
76
|
$[7] = showSenders;
|
|
75
77
|
$[8] = view.messages;
|
|
76
|
-
$[9] =
|
|
77
|
-
$[10] =
|
|
78
|
-
|
|
78
|
+
$[9] = view.status;
|
|
79
|
+
$[10] = working;
|
|
80
|
+
$[11] = t4;
|
|
81
|
+
} else t4 = $[11];
|
|
79
82
|
let t5;
|
|
80
|
-
if ($[
|
|
81
|
-
t5 = working && view.messages.length === 0 && /* @__PURE__ */ jsx(Working, {});
|
|
82
|
-
$[
|
|
83
|
-
$[
|
|
84
|
-
$[
|
|
85
|
-
|
|
83
|
+
if ($[17] !== view.messages.length || $[18] !== view.status || $[19] !== working) {
|
|
84
|
+
t5 = working && view.messages.length === 0 && /* @__PURE__ */ jsx(Working, { status: view.status });
|
|
85
|
+
$[17] = view.messages.length;
|
|
86
|
+
$[18] = view.status;
|
|
87
|
+
$[19] = working;
|
|
88
|
+
$[20] = t5;
|
|
89
|
+
} else t5 = $[20];
|
|
86
90
|
let t6;
|
|
87
|
-
if ($[
|
|
91
|
+
if ($[21] !== t3 || $[22] !== t4 || $[23] !== t5) {
|
|
88
92
|
t6 = /* @__PURE__ */ jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxs(MessageScrollerContent, {
|
|
89
93
|
className: "flex flex-col gap-4 px-2 py-3",
|
|
90
94
|
children: [
|
|
@@ -93,46 +97,53 @@ function ThreadMessages(t0) {
|
|
|
93
97
|
t5
|
|
94
98
|
]
|
|
95
99
|
}) });
|
|
96
|
-
$[
|
|
97
|
-
$[
|
|
98
|
-
$[
|
|
99
|
-
$[
|
|
100
|
-
} else t6 = $[
|
|
100
|
+
$[21] = t3;
|
|
101
|
+
$[22] = t4;
|
|
102
|
+
$[23] = t5;
|
|
103
|
+
$[24] = t6;
|
|
104
|
+
} else t6 = $[24];
|
|
101
105
|
let t7;
|
|
102
|
-
if ($[
|
|
106
|
+
if ($[25] === Symbol.for("react.memo_cache_sentinel")) {
|
|
103
107
|
t7 = /* @__PURE__ */ jsx(MessageScrollerButton, {});
|
|
104
|
-
$[
|
|
105
|
-
} else t7 = $[
|
|
108
|
+
$[25] = t7;
|
|
109
|
+
} else t7 = $[25];
|
|
106
110
|
let t8;
|
|
107
|
-
if ($[
|
|
111
|
+
if ($[26] !== t6) {
|
|
108
112
|
t8 = /* @__PURE__ */ jsx(MessageScrollerProvider, { children: /* @__PURE__ */ jsxs(MessageScroller, {
|
|
109
113
|
className: "min-h-0 flex-1",
|
|
110
114
|
children: [t6, t7]
|
|
111
115
|
}) });
|
|
112
|
-
$[
|
|
113
|
-
$[
|
|
114
|
-
} else t8 = $[
|
|
116
|
+
$[26] = t6;
|
|
117
|
+
$[27] = t8;
|
|
118
|
+
} else t8 = $[27];
|
|
115
119
|
return t8;
|
|
116
120
|
}
|
|
117
121
|
/**
|
|
118
122
|
* Shown in the last message, not after it: the scroller only brings a message the editor just
|
|
119
123
|
* sent into view when it's appended last, and it arrives while the agent is already working.
|
|
120
124
|
*/
|
|
121
|
-
function Working() {
|
|
122
|
-
const $ = c(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
function Working(t0) {
|
|
126
|
+
const $ = c(2);
|
|
127
|
+
const { status } = t0;
|
|
128
|
+
const t1 = status ? STATUS_LINES[status] : "Working…";
|
|
129
|
+
let t2;
|
|
130
|
+
if ($[0] !== t1) {
|
|
131
|
+
t2 = /* @__PURE__ */ jsx("p", {
|
|
126
132
|
role: "status",
|
|
127
133
|
className: "shimmer text-muted-foreground text-sm",
|
|
128
|
-
children:
|
|
134
|
+
children: t1
|
|
129
135
|
});
|
|
130
|
-
$[0] =
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
$[0] = t1;
|
|
137
|
+
$[1] = t2;
|
|
138
|
+
} else t2 = $[1];
|
|
139
|
+
return t2;
|
|
133
140
|
}
|
|
141
|
+
const STATUS_LINES = {
|
|
142
|
+
building: "Building…",
|
|
143
|
+
fixing: "Fixing a build error…"
|
|
144
|
+
};
|
|
134
145
|
function ThreadMessageRow(t0) {
|
|
135
|
-
const $ = c(
|
|
146
|
+
const $ = c(20);
|
|
136
147
|
const { message, showSender } = t0;
|
|
137
148
|
if (message.from === "stopped" || message.from === "failed") {
|
|
138
149
|
const t1 = message.from === "stopped" ? `Stopped by ${message.sender.name}` : "Camox couldn’t finish this. Send your message again to retry.";
|
|
@@ -156,24 +167,48 @@ function ThreadMessageRow(t0) {
|
|
|
156
167
|
} else t1 = $[3];
|
|
157
168
|
return t1;
|
|
158
169
|
}
|
|
170
|
+
if (message.from === "camox") {
|
|
171
|
+
let t1;
|
|
172
|
+
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
|
173
|
+
t1 = /* @__PURE__ */ jsx(MessageHeader, {
|
|
174
|
+
className: "text-muted-foreground text-xs",
|
|
175
|
+
children: "Camox"
|
|
176
|
+
});
|
|
177
|
+
$[4] = t1;
|
|
178
|
+
} else t1 = $[4];
|
|
179
|
+
let t2;
|
|
180
|
+
if ($[5] !== message.text) {
|
|
181
|
+
t2 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [t1, /* @__PURE__ */ jsx(Bubble, {
|
|
182
|
+
variant: "muted",
|
|
183
|
+
className: "max-w-full",
|
|
184
|
+
children: /* @__PURE__ */ jsx(BubbleContent, {
|
|
185
|
+
className: "whitespace-pre-wrap",
|
|
186
|
+
children: message.text
|
|
187
|
+
})
|
|
188
|
+
})] }) });
|
|
189
|
+
$[5] = message.text;
|
|
190
|
+
$[6] = t2;
|
|
191
|
+
} else t2 = $[6];
|
|
192
|
+
return t2;
|
|
193
|
+
}
|
|
159
194
|
let t1;
|
|
160
|
-
if ($[
|
|
195
|
+
if ($[7] !== message.sender) {
|
|
161
196
|
t1 = /* @__PURE__ */ jsx(MessageAvatar, { children: /* @__PURE__ */ jsx(SenderAvatar, { sender: message.sender }) });
|
|
162
|
-
$[
|
|
163
|
-
$[
|
|
164
|
-
} else t1 = $[
|
|
197
|
+
$[7] = message.sender;
|
|
198
|
+
$[8] = t1;
|
|
199
|
+
} else t1 = $[8];
|
|
165
200
|
let t2;
|
|
166
|
-
if ($[
|
|
201
|
+
if ($[9] !== message.sender || $[10] !== showSender) {
|
|
167
202
|
t2 = showSender && /* @__PURE__ */ jsx(MessageHeader, {
|
|
168
203
|
className: "text-muted-foreground text-xs",
|
|
169
204
|
children: message.sender.name
|
|
170
205
|
});
|
|
171
|
-
$[
|
|
172
|
-
$[
|
|
173
|
-
$[
|
|
174
|
-
} else t2 = $[
|
|
206
|
+
$[9] = message.sender;
|
|
207
|
+
$[10] = showSender;
|
|
208
|
+
$[11] = t2;
|
|
209
|
+
} else t2 = $[11];
|
|
175
210
|
let t3;
|
|
176
|
-
if ($[
|
|
211
|
+
if ($[12] !== message.text) {
|
|
177
212
|
t3 = /* @__PURE__ */ jsx(Bubble, {
|
|
178
213
|
align: "end",
|
|
179
214
|
variant: "muted",
|
|
@@ -183,26 +218,26 @@ function ThreadMessageRow(t0) {
|
|
|
183
218
|
children: message.text
|
|
184
219
|
})
|
|
185
220
|
});
|
|
186
|
-
$[
|
|
187
|
-
$[
|
|
188
|
-
} else t3 = $[
|
|
221
|
+
$[12] = message.text;
|
|
222
|
+
$[13] = t3;
|
|
223
|
+
} else t3 = $[13];
|
|
189
224
|
let t4;
|
|
190
|
-
if ($[
|
|
225
|
+
if ($[14] !== t2 || $[15] !== t3) {
|
|
191
226
|
t4 = /* @__PURE__ */ jsxs(MessageContent, { children: [t2, t3] });
|
|
192
|
-
$[
|
|
193
|
-
$[
|
|
194
|
-
$[
|
|
195
|
-
} else t4 = $[
|
|
227
|
+
$[14] = t2;
|
|
228
|
+
$[15] = t3;
|
|
229
|
+
$[16] = t4;
|
|
230
|
+
} else t4 = $[16];
|
|
196
231
|
let t5;
|
|
197
|
-
if ($[
|
|
232
|
+
if ($[17] !== t1 || $[18] !== t4) {
|
|
198
233
|
t5 = /* @__PURE__ */ jsxs(Message, {
|
|
199
234
|
align: "end",
|
|
200
235
|
children: [t1, t4]
|
|
201
236
|
});
|
|
202
|
-
$[
|
|
203
|
-
$[
|
|
204
|
-
$[
|
|
205
|
-
} else t5 = $[
|
|
237
|
+
$[17] = t1;
|
|
238
|
+
$[18] = t4;
|
|
239
|
+
$[19] = t5;
|
|
240
|
+
} else t5 = $[19];
|
|
206
241
|
return t5;
|
|
207
242
|
}
|
|
208
243
|
function ContinuedIn(t0) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useAuthContext, useAuthState } from "../../../lib/auth.js";
|
|
2
2
|
import { useNavigate } from "../../navigation/navigation.js";
|
|
3
|
-
import { PlatformLabel } from "../../../components/PlatformLabel.js";
|
|
4
3
|
import { useLocalePath, useLocalizedPath } from "../../../lib/view-locale.js";
|
|
4
|
+
import { PlatformLabel } from "../../../components/PlatformLabel.js";
|
|
5
|
+
import { previewStore, selectIsCanvasShown } from "../../preview/previewStore.js";
|
|
5
6
|
import { STUDIO_BASE_PATH } from "../routes.js";
|
|
6
7
|
import { studioStore } from "../studioStore.js";
|
|
7
|
-
import { previewStore, selectIsCanvasShown } from "../../preview/previewStore.js";
|
|
8
8
|
import { useApplyTheme } from "../useTheme.js";
|
|
9
9
|
import { c } from "react/compiler-runtime";
|
|
10
10
|
import { useSelector } from "@xstate/store-react";
|
|
@@ -3,6 +3,8 @@ const STUDIO_ROUTE_SEGMENT = "camox";
|
|
|
3
3
|
const STUDIO_BASE_PATH = `/${STUDIO_ROUTE_SEGMENT}`;
|
|
4
4
|
const STUDIO_CONTENT_PATH = `${STUDIO_BASE_PATH}/content`;
|
|
5
5
|
const STUDIO_ASSETS_PATH = `${STUDIO_CONTENT_PATH}/assets`;
|
|
6
|
+
/** The Settings Section, where members change Project Settings. */
|
|
7
|
+
const STUDIO_SETTINGS_PATH = `${STUDIO_BASE_PATH}/settings`;
|
|
6
8
|
const collectionContentPath = (collectionId) => `${STUDIO_CONTENT_PATH}/collections/${encodeURIComponent(collectionId)}`;
|
|
7
9
|
function matchCollectionContentPath(pathname) {
|
|
8
10
|
const match = /^\/camox\/content\/collections\/([^/]+)(?:(\/new)|\/([^/]+)\/edit)?$/.exec(pathname);
|
|
@@ -29,4 +31,4 @@ function matchCollectionContentPath(pathname) {
|
|
|
29
31
|
const STUDIO_CANVAS_PATH = `${STUDIO_BASE_PATH}/canvas`;
|
|
30
32
|
|
|
31
33
|
//#endregion
|
|
32
|
-
export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_ROUTE_SEGMENT, collectionContentPath, matchCollectionContentPath };
|
|
34
|
+
export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_ROUTE_SEGMENT, STUDIO_SETTINGS_PATH, collectionContentPath, matchCollectionContentPath };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useProjectUuid } from "../../lib/auth.js";
|
|
2
|
+
import { localizedPath } from "../../lib/locale-path.js";
|
|
3
|
+
import { useViewLocale } from "../../lib/view-locale-context.js";
|
|
4
|
+
import { useLocation, useNavigate } from "../navigation/navigation.js";
|
|
5
|
+
import { reads, useStudioCache } from "../../lib/studio-reads.js";
|
|
6
|
+
import { previewStore, sameEditingOwner } from "../preview/previewStore.js";
|
|
7
|
+
import { fieldTypesDictionary } from "../../core/lib/fieldTypes.js";
|
|
8
|
+
import { c } from "react/compiler-runtime";
|
|
9
|
+
|
|
10
|
+
//#region src/features/studio/showInCanvas.ts
|
|
11
|
+
/**
|
|
12
|
+
* How long showing something in the Canvas keeps selecting it, in milliseconds, while the Canvas
|
|
13
|
+
* mounts and its page's editing frame claims, each of which can clear the selection. A product
|
|
14
|
+
* tuning value: long enough for a page to load, short enough not to fight the editor.
|
|
15
|
+
*/
|
|
16
|
+
const SELECTING_FOR = 5e3;
|
|
17
|
+
const isFieldType = (kind) => !!kind && Object.hasOwn(fieldTypesDictionary, kind);
|
|
18
|
+
/** What to select on the target's page: its field, else its item or block, else nothing. */
|
|
19
|
+
function selectionOf({ blockId, itemId, field }) {
|
|
20
|
+
if (!blockId) return null;
|
|
21
|
+
const fieldType = field && isFieldType(field.kind) ? field.kind : null;
|
|
22
|
+
if (itemId) return field && fieldType ? {
|
|
23
|
+
type: "item-field",
|
|
24
|
+
blockId,
|
|
25
|
+
itemId,
|
|
26
|
+
fieldName: field.name,
|
|
27
|
+
fieldType
|
|
28
|
+
} : {
|
|
29
|
+
type: "item",
|
|
30
|
+
blockId,
|
|
31
|
+
itemId
|
|
32
|
+
};
|
|
33
|
+
return field && fieldType ? {
|
|
34
|
+
type: "block-field",
|
|
35
|
+
blockId,
|
|
36
|
+
fieldName: field.name,
|
|
37
|
+
fieldType
|
|
38
|
+
} : {
|
|
39
|
+
type: "block",
|
|
40
|
+
blockId
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Stops selecting what was last shown in the Canvas. */
|
|
44
|
+
let stopSelecting = null;
|
|
45
|
+
/**
|
|
46
|
+
* Selects `selection` once `owner`'s editing frame is active, and again whenever it's cleared,
|
|
47
|
+
* until the editor selects something else or the time runs out.
|
|
48
|
+
*/
|
|
49
|
+
function selectOnceShown(owner, selection) {
|
|
50
|
+
stopSelecting?.();
|
|
51
|
+
const select = () => {
|
|
52
|
+
const { editingContext } = previewStore.getSnapshot().context;
|
|
53
|
+
if (!sameEditingOwner(editingContext, owner)) return;
|
|
54
|
+
const current = editingContext?.selection ?? null;
|
|
55
|
+
if (current === null) {
|
|
56
|
+
previewStore.send({
|
|
57
|
+
type: "selectTarget",
|
|
58
|
+
...owner,
|
|
59
|
+
selection
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (JSON.stringify(current) !== JSON.stringify(selection)) stop();
|
|
64
|
+
};
|
|
65
|
+
const subscription = previewStore.subscribe(select);
|
|
66
|
+
const timer = setTimeout(() => stop(), SELECTING_FOR);
|
|
67
|
+
const stop = () => {
|
|
68
|
+
subscription.unsubscribe();
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
if (stopSelecting === stop) stopSelecting = null;
|
|
71
|
+
};
|
|
72
|
+
stopSelecting = stop;
|
|
73
|
+
const { editingContext } = previewStore.getSnapshot().context;
|
|
74
|
+
if (sameEditingOwner(editingContext, owner)) previewStore.send({
|
|
75
|
+
type: "selectTarget",
|
|
76
|
+
...owner,
|
|
77
|
+
selection
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Shows a page in the Canvas, in the target's locale, and selects the block, item or field in it
|
|
82
|
+
* once the page's editing frame is active.
|
|
83
|
+
*/
|
|
84
|
+
function useShowInCanvas() {
|
|
85
|
+
const $ = c(8);
|
|
86
|
+
const cache = useStudioCache();
|
|
87
|
+
const projectUuid = useProjectUuid();
|
|
88
|
+
const view = useViewLocale();
|
|
89
|
+
const navigate = useNavigate();
|
|
90
|
+
let t0;
|
|
91
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
92
|
+
t0 = { select: _temp };
|
|
93
|
+
$[0] = t0;
|
|
94
|
+
} else t0 = $[0];
|
|
95
|
+
const search = useLocation(t0);
|
|
96
|
+
let t1;
|
|
97
|
+
if ($[1] !== cache || $[2] !== navigate || $[3] !== projectUuid || $[4] !== search || $[5] !== view.locale || $[6] !== view.locales) {
|
|
98
|
+
t1 = async (target) => {
|
|
99
|
+
const locale = view.locales.find((each) => each.tag === target.locale) ?? view.locale;
|
|
100
|
+
const page = (await cache.ensure(reads.pageList({ projectUuid }), { locale }).catch(_temp2)).find((each_0) => each_0.id === target.pageId);
|
|
101
|
+
if (!page) return;
|
|
102
|
+
const selection = selectionOf(target);
|
|
103
|
+
if (selection) selectOnceShown({
|
|
104
|
+
kind: "page",
|
|
105
|
+
pageId: page.id
|
|
106
|
+
}, selection);
|
|
107
|
+
else stopSelecting?.();
|
|
108
|
+
previewStore.send({ type: "showCanvas" });
|
|
109
|
+
await navigate({ to: `${localizedPath(page.fullPath, locale)}${search}` });
|
|
110
|
+
};
|
|
111
|
+
$[1] = cache;
|
|
112
|
+
$[2] = navigate;
|
|
113
|
+
$[3] = projectUuid;
|
|
114
|
+
$[4] = search;
|
|
115
|
+
$[5] = view.locale;
|
|
116
|
+
$[6] = view.locales;
|
|
117
|
+
$[7] = t1;
|
|
118
|
+
} else t1 = $[7];
|
|
119
|
+
return t1;
|
|
120
|
+
}
|
|
121
|
+
function _temp2() {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
function _temp(location) {
|
|
125
|
+
return location.search;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
export { useShowInCanvas };
|
|
@@ -56,14 +56,13 @@ async function syncDefinitionsToApi(options) {
|
|
|
56
56
|
throw error;
|
|
57
57
|
}
|
|
58
58
|
if (result.draftCreated) logger.info("[camox] Created your development draft", { timestamp: true });
|
|
59
|
-
|
|
59
|
+
const definitionCount = result.blocks.count + result.collections.count + (result.layouts?.count ?? 0);
|
|
60
|
+
logger.info(`[camox] Synced ${counted(definitionCount, "Camox definition")}`, { timestamp: true });
|
|
60
61
|
const { deletedDefinitionTypes } = result.blocks;
|
|
61
62
|
if (deletedDefinitionTypes.length > 0) logger.info(`[camox] Removed ${counted(deletedDefinitionTypes.length, "block definition")} no longer in code: ${quoted(deletedDefinitionTypes)}. Their blocks are kept and render nothing until the definition comes back.`, { timestamp: true });
|
|
62
|
-
logger.info(`[camox] Synced ${result.collections.count} collection definitions`, { timestamp: true });
|
|
63
63
|
for (const id of result.collections.retired) logger.warn(`[camox] Removed collection "${id}"; its records are kept and hidden`, { timestamp: true });
|
|
64
64
|
const layoutSyncResults = result.layouts;
|
|
65
65
|
if (layoutSyncResults) {
|
|
66
|
-
logger.info(`[camox] Synced ${counted(layoutSyncResults.count, "layout")} to Camox API`, { timestamp: true });
|
|
67
66
|
for (const layout of layoutSyncResults.layouts) {
|
|
68
67
|
const { layoutId, createdBlockTypes, removedBlockTypes, skippedOrphanTypes } = layout;
|
|
69
68
|
if (layout.wasExisting && createdBlockTypes.length > 0) logger.info(`[camox] Added ${counted(createdBlockTypes.length, "block")} to existing layout "${layoutId}": ${quoted(createdBlockTypes)}`, { timestamp: true });
|
|
@@ -180,7 +179,7 @@ async function syncDefinitions(server, options) {
|
|
|
180
179
|
await sync({ initial: true });
|
|
181
180
|
synced = true;
|
|
182
181
|
} catch (error) {
|
|
183
|
-
logger.error(`[camox] Failed to sync
|
|
182
|
+
logger.error(`[camox] Failed to sync Camox definitions: ${String(error)}`, { timestamp: true });
|
|
184
183
|
}
|
|
185
184
|
let running = Promise.resolve();
|
|
186
185
|
let timer;
|
|
@@ -188,7 +187,7 @@ async function syncDefinitions(server, options) {
|
|
|
188
187
|
clearTimeout(timer);
|
|
189
188
|
timer = setTimeout(() => {
|
|
190
189
|
running = running.then(() => sync().catch((error) => {
|
|
191
|
-
logger.error(`[camox] Failed to sync
|
|
190
|
+
logger.error(`[camox] Failed to sync Camox definitions: ${String(error)}`, { timestamp: true });
|
|
192
191
|
}));
|
|
193
192
|
}, SYNC_DEBOUNCE_DELAY_MS);
|
|
194
193
|
};
|
|
@@ -208,4 +207,4 @@ async function syncDefinitions(server, options) {
|
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
//#endregion
|
|
211
|
-
export { VIRTUAL_CAMOX_APP, syncDefinitions, syncDefinitionsToApi };
|
|
210
|
+
export { VIRTUAL_CAMOX_APP, loadDefinitionSet, syncDefinitions, syncDefinitionsToApi };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Visitor, parseSync } from "vite-plus";
|
|
5
|
+
|
|
6
|
+
//#region src/features/vite/hostedChecks.ts
|
|
7
|
+
/**
|
|
8
|
+
* Set by Camox's revision and release sandboxes: a hosted project's build checks the code an
|
|
9
|
+
* agent may have written (ADR 0021). `apps/landing` and dev mode never set it.
|
|
10
|
+
*/
|
|
11
|
+
const HOSTED_BUILD_ENV = "CAMOX_INTERNAL_HOSTED";
|
|
12
|
+
const sdkRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
13
|
+
/** `camox`'s public entry points, as `camox/<path>`: its exports, less its internal ones. */
|
|
14
|
+
function camoxEntryPoints() {
|
|
15
|
+
const { exports } = JSON.parse(readFileSync(join(sdkRoot, "package.json"), "utf8"));
|
|
16
|
+
return new Set(Object.keys(exports).filter((path) => !path.startsWith("./_internal/")).map((path) => `camox${path.slice(1)}`));
|
|
17
|
+
}
|
|
18
|
+
function readHostedProject(root) {
|
|
19
|
+
const { dependencies = {} } = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
20
|
+
return {
|
|
21
|
+
root,
|
|
22
|
+
src: join(root, "src"),
|
|
23
|
+
dependencies: new Set(Object.keys(dependencies)),
|
|
24
|
+
camoxEntryPoints: camoxEntryPoints()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const withoutQuery = (path) => path.replace(/[?#].*$/, "");
|
|
28
|
+
const isInside = (dir, path) => path.startsWith(dir + sep);
|
|
29
|
+
const displayPath = (project, file) => relative(project.root, file).split(sep).join("/");
|
|
30
|
+
/** The project's own file an importer names, when it is one of `src/`'s: the code it checks. */
|
|
31
|
+
function sourceFile(project, importer) {
|
|
32
|
+
if (!importer || importer.startsWith("\0")) return;
|
|
33
|
+
const file = withoutQuery(importer);
|
|
34
|
+
return isAbsolute(file) && isInside(project.src, file) ? file : void 0;
|
|
35
|
+
}
|
|
36
|
+
function isAllowedImport(project, specifier, importer) {
|
|
37
|
+
if (specifier.startsWith("\0")) return true;
|
|
38
|
+
const path = withoutQuery(specifier);
|
|
39
|
+
let file;
|
|
40
|
+
if (path.startsWith("./") || path.startsWith("../")) file = resolve(dirname(importer), path);
|
|
41
|
+
else if (path.startsWith("@/")) file = resolve(project.src, path.slice(2));
|
|
42
|
+
else if (path.startsWith("/")) file = isInside(project.root, path) ? resolve(path) : resolve(project.root, `.${path}`);
|
|
43
|
+
if (file !== void 0) return isInside(project.src, file);
|
|
44
|
+
if (path === "camox" || path.startsWith("camox/")) return project.camoxEntryPoints.has(path);
|
|
45
|
+
const [scopeOrName, name] = path.split("/");
|
|
46
|
+
const packageName = scopeOrName.startsWith("@") ? `${scopeOrName}/${name}` : scopeOrName;
|
|
47
|
+
return project.dependencies.has(packageName);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Why a file of `src/` can't import `specifier`, or nothing when it may: files of `src/`,
|
|
51
|
+
* `camox`'s public entry points and `package.json`'s `dependencies`.
|
|
52
|
+
*/
|
|
53
|
+
function forbiddenImport(project, specifier, importer) {
|
|
54
|
+
const file = sourceFile(project, importer);
|
|
55
|
+
if (!file || isAllowedImport(project, specifier, file)) return;
|
|
56
|
+
return `${displayPath(project, file)} imports "${specifier}", which a hosted project can't import. It can import files in src/, camox's public entry points and the packages package.json lists in dependencies.`;
|
|
57
|
+
}
|
|
58
|
+
/** The modules the registry loads blocks from, as its globs name them. */
|
|
59
|
+
const BLOCK_MODULE = /^src\/(?:camox\/)?blocks\/([^/]+)\.tsx?$/;
|
|
60
|
+
/** A string an expression spells out, without running it. */
|
|
61
|
+
function literalString(node) {
|
|
62
|
+
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
|
|
63
|
+
if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0]?.value.cooked ?? void 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Whether `callee` calls `createBlock`: by its name, the names a module imports it as
|
|
67
|
+
* (`import { createBlock as defineBlock }`), or as a namespace's member.
|
|
68
|
+
*/
|
|
69
|
+
function isCreateBlock(callee, names) {
|
|
70
|
+
if (callee.type === "Identifier") return names.has(callee.name);
|
|
71
|
+
return callee.type === "MemberExpression" && callee.property.type === "Identifier" && callee.property.name === "createBlock";
|
|
72
|
+
}
|
|
73
|
+
/** The local names a module calls `createBlock` by: its own name, and its imports' aliases. */
|
|
74
|
+
function createBlockNames(program) {
|
|
75
|
+
const names = /* @__PURE__ */ new Set(["createBlock"]);
|
|
76
|
+
for (const statement of program.body) {
|
|
77
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
78
|
+
for (const specifier of statement.specifiers) {
|
|
79
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
80
|
+
const imported = specifier.imported;
|
|
81
|
+
if ((imported.type === "Identifier" ? imported.name : imported.value) === "createBlock") names.add(specifier.local.name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return names;
|
|
85
|
+
}
|
|
86
|
+
/** The `id` a block definition's object literal spells out. */
|
|
87
|
+
function definitionId(definition) {
|
|
88
|
+
if (definition?.type !== "ObjectExpression") return;
|
|
89
|
+
for (const property of definition.properties) {
|
|
90
|
+
if (property.type !== "Property" || property.computed) continue;
|
|
91
|
+
const key = property.key;
|
|
92
|
+
if (key.type === "Identifier" && key.name === "id" || key.type === "Literal" && key.value === "id") return property.value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Why a block's module breaks the registry, or nothing when it doesn't. The browser finds a block
|
|
97
|
+
* by its file name, so a block's file must be named after its id: the build reads the id from the
|
|
98
|
+
* `createBlock()` call, and needs it written as a string.
|
|
99
|
+
*/
|
|
100
|
+
function misnamedBlock(project, moduleId, code) {
|
|
101
|
+
const file = sourceFile(project, moduleId);
|
|
102
|
+
if (!file) return;
|
|
103
|
+
const path = displayPath(project, file);
|
|
104
|
+
const fileName = BLOCK_MODULE.exec(path)?.[1];
|
|
105
|
+
if (!fileName) return;
|
|
106
|
+
const { program, errors } = parseSync(file, code);
|
|
107
|
+
if (errors.length > 0) return;
|
|
108
|
+
let problem;
|
|
109
|
+
const names = createBlockNames(program);
|
|
110
|
+
new Visitor({ CallExpression(call) {
|
|
111
|
+
if (problem || !isCreateBlock(call.callee, names)) return;
|
|
112
|
+
const id = literalString(definitionId(call.arguments[0]));
|
|
113
|
+
const at = definitionId(call.arguments[0])?.start ?? call.start;
|
|
114
|
+
if (id === void 0) problem = {
|
|
115
|
+
message: `${path} calls createBlock() without its id as a string literal, which the build needs to check that the file is named after it: write \`id: "${fileName}"\`.`,
|
|
116
|
+
at
|
|
117
|
+
};
|
|
118
|
+
else if (id !== fileName) problem = {
|
|
119
|
+
message: `${path} defines the block "${id}", but its file is named "${fileName}": a block's file is named after its id. Rename the file to ${id}${file.slice(file.lastIndexOf("."))} or the id to "${fileName}".`,
|
|
120
|
+
at
|
|
121
|
+
};
|
|
122
|
+
} }).visit(program);
|
|
123
|
+
return problem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { HOSTED_BUILD_ENV, forbiddenImport, misnamedBlock, readHostedProject };
|