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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { referencePickerFocus } from "../preview/referencePickerFocus.js";
|
|
2
|
+
import { canvasStore } from "../canvas/canvasStore.js";
|
|
3
|
+
import { previewStore } from "../preview/previewStore.js";
|
|
4
|
+
import { SDK_VERSION, deferredInbox } from "./protocol.js";
|
|
5
|
+
import { createContentEditor } from "../../core/editing/contentEdits.js";
|
|
6
|
+
import { LIVE_KEY_SCOPE, scopeOfKey } from "@camox/api-contract/query-keys";
|
|
7
|
+
|
|
8
|
+
//#region src/features/renderer/rendererHost.ts
|
|
9
|
+
const mirrored = {
|
|
10
|
+
preview: previewStore,
|
|
11
|
+
canvas: canvasStore
|
|
12
|
+
};
|
|
13
|
+
const forwarded = {
|
|
14
|
+
preview: previewStore,
|
|
15
|
+
referencePickerFocus
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The host of the studio whose cache is `studioCache`, viewing the draft `draftScope` names, which
|
|
19
|
+
* the renderer shows too. Without them, the renderer hears nothing of the studio's cache.
|
|
20
|
+
* The edits the renderer makes in place are written with the studio's own mutations, through
|
|
21
|
+
* `queryClient`; without it, every one fails. What the renderer's pages tell the editor goes to
|
|
22
|
+
* `notify`.
|
|
23
|
+
*/
|
|
24
|
+
function createRendererHost({ studioCache, draftScope, queryClient, notify } = {}) {
|
|
25
|
+
const applyEdit = queryClient && studioCache ? createContentEditor({
|
|
26
|
+
queryClient,
|
|
27
|
+
studioCache
|
|
28
|
+
}) : void 0;
|
|
29
|
+
const frames = /* @__PURE__ */ new Map();
|
|
30
|
+
const thumbnails = /* @__PURE__ */ new Map();
|
|
31
|
+
/** The questions asked that no renderer answered yet. */
|
|
32
|
+
const asked = /* @__PURE__ */ new Map();
|
|
33
|
+
let lastAskId = 0;
|
|
34
|
+
const connectionListeners = /* @__PURE__ */ new Set();
|
|
35
|
+
let status = "connecting";
|
|
36
|
+
const statusListeners = /* @__PURE__ */ new Set();
|
|
37
|
+
const setStatus = (next) => {
|
|
38
|
+
if (status === next) return;
|
|
39
|
+
status = next;
|
|
40
|
+
for (const listener of statusListeners) listener();
|
|
41
|
+
};
|
|
42
|
+
let renderer = null;
|
|
43
|
+
/** Sends to the renderer once it said hello, in the protocol it speaks. */
|
|
44
|
+
let welcomed = false;
|
|
45
|
+
const post = (message) => {
|
|
46
|
+
if (welcomed) renderer?.(message);
|
|
47
|
+
};
|
|
48
|
+
const postStore = (store) => post({
|
|
49
|
+
type: "store",
|
|
50
|
+
store,
|
|
51
|
+
context: mirrored[store].getSnapshot().context
|
|
52
|
+
});
|
|
53
|
+
const subscriptions = Object.keys(mirrored).map((store) => mirrored[store].subscribe(() => postStore(store)));
|
|
54
|
+
const shownScope = () => draftScope?.current() ?? LIVE_KEY_SCOPE;
|
|
55
|
+
const shown = (queryKey) => scopeOfKey(queryKey) === shownScope();
|
|
56
|
+
/** Whether a project room invalidation reaches the reads of the draft the renderer shows. */
|
|
57
|
+
const reaches = async (message, projectId) => {
|
|
58
|
+
if (message.draftId === null) return true;
|
|
59
|
+
if (!draftScope) return false;
|
|
60
|
+
return await draftScope.keyScopeOfDraft(message.draftId, projectId) === shownScope();
|
|
61
|
+
};
|
|
62
|
+
const relayChange = (change) => {
|
|
63
|
+
switch (change.type) {
|
|
64
|
+
case "received": {
|
|
65
|
+
const { message, projectId } = change;
|
|
66
|
+
reaches(message, projectId).then((reached) => reached && post({
|
|
67
|
+
type: "invalidate",
|
|
68
|
+
message,
|
|
69
|
+
projectId
|
|
70
|
+
}));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
case "wrote":
|
|
74
|
+
if (!shown(change.queryKey)) return;
|
|
75
|
+
post({
|
|
76
|
+
type: "write",
|
|
77
|
+
queryKey: change.queryKey,
|
|
78
|
+
..."value" in change && change.value !== void 0 && { value: change.value }
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
case "outdated": {
|
|
82
|
+
const queryKeys = change.queryKeys.filter(shown);
|
|
83
|
+
if (queryKeys.length) post({
|
|
84
|
+
type: "outdated",
|
|
85
|
+
queryKeys
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const stopRelaying = studioCache?.subscribe(relayChange);
|
|
92
|
+
const stopFollowing = draftScope?.subscribe(({ draftId, move }) => {
|
|
93
|
+
if (move === "started" && draftId) post({
|
|
94
|
+
type: "draftStarted",
|
|
95
|
+
draftId
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const receive = (message) => {
|
|
99
|
+
switch (message.type) {
|
|
100
|
+
case "hello":
|
|
101
|
+
if (message.protocol !== 1) return;
|
|
102
|
+
renderer?.({
|
|
103
|
+
type: "welcome",
|
|
104
|
+
protocol: 1,
|
|
105
|
+
sdkVersion: SDK_VERSION
|
|
106
|
+
});
|
|
107
|
+
if (message.sdkVersion !== SDK_VERSION) return setStatus("updating");
|
|
108
|
+
welcomed = true;
|
|
109
|
+
setStatus("connected");
|
|
110
|
+
for (const store of Object.keys(mirrored)) postStore(store);
|
|
111
|
+
for (const [frameId, { slot, target }] of frames) post({
|
|
112
|
+
type: "frame",
|
|
113
|
+
frameId,
|
|
114
|
+
slot,
|
|
115
|
+
target
|
|
116
|
+
});
|
|
117
|
+
for (const [thumbnailId, { slot, blockType }] of thumbnails) post({
|
|
118
|
+
type: "thumbnail",
|
|
119
|
+
thumbnailId,
|
|
120
|
+
slot,
|
|
121
|
+
blockType
|
|
122
|
+
});
|
|
123
|
+
for (const [askId, { question }] of asked) post({
|
|
124
|
+
type: "ask",
|
|
125
|
+
askId,
|
|
126
|
+
question
|
|
127
|
+
});
|
|
128
|
+
for (const listener of connectionListeners) listener();
|
|
129
|
+
return;
|
|
130
|
+
case "storeEvent": {
|
|
131
|
+
const store = forwarded[message.store];
|
|
132
|
+
if (!store) return;
|
|
133
|
+
store.send(message.event);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "rendered": return frames.get(message.frameId)?.hears.rendered?.();
|
|
137
|
+
case "error": return frames.get(message.frameId)?.hears.failed?.(message.message);
|
|
138
|
+
case "navigate": return frames.get(message.frameId)?.hears.navigate?.(message.to);
|
|
139
|
+
case "reveal": return frames.get(message.frameId)?.hears.reveal?.(message.element, message.immediate);
|
|
140
|
+
case "answer": {
|
|
141
|
+
const question = asked.get(message.askId);
|
|
142
|
+
if (!question) return;
|
|
143
|
+
asked.delete(message.askId);
|
|
144
|
+
if (message.error === void 0) question.resolve(message.value ?? null);
|
|
145
|
+
else question.reject(new Error(message.error));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
case "notify": return notify?.(message.notice);
|
|
149
|
+
case "edit": {
|
|
150
|
+
const { editId, edit } = message;
|
|
151
|
+
const sender = renderer;
|
|
152
|
+
const answer = (error) => {
|
|
153
|
+
if (renderer === sender) post({
|
|
154
|
+
type: "edited",
|
|
155
|
+
editId,
|
|
156
|
+
...error && { error }
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
if (!applyEdit) return answer("The studio can't save changes made here.");
|
|
160
|
+
applyEdit(edit).then(() => answer(), (cause) => answer(cause instanceof Error ? cause.message : "Couldn't save the change."));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
handle: { connect(inbox) {
|
|
167
|
+
renderer = deferredInbox(inbox);
|
|
168
|
+
welcomed = false;
|
|
169
|
+
const connected = renderer;
|
|
170
|
+
return deferredInbox((message) => {
|
|
171
|
+
if (renderer === connected) receive(message);
|
|
172
|
+
});
|
|
173
|
+
} },
|
|
174
|
+
show(frameId, slot, target, hears = {}) {
|
|
175
|
+
frames.set(frameId, {
|
|
176
|
+
slot,
|
|
177
|
+
target,
|
|
178
|
+
hears
|
|
179
|
+
});
|
|
180
|
+
post({
|
|
181
|
+
type: "frame",
|
|
182
|
+
frameId,
|
|
183
|
+
slot,
|
|
184
|
+
target
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
hide(frameId) {
|
|
188
|
+
if (!frames.delete(frameId)) return;
|
|
189
|
+
post({
|
|
190
|
+
type: "frameEnd",
|
|
191
|
+
frameId
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
ask(question) {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const askId = ++lastAskId;
|
|
197
|
+
asked.set(askId, {
|
|
198
|
+
question,
|
|
199
|
+
resolve,
|
|
200
|
+
reject
|
|
201
|
+
});
|
|
202
|
+
post({
|
|
203
|
+
type: "ask",
|
|
204
|
+
askId,
|
|
205
|
+
question
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
showThumbnail(thumbnailId, slot, blockType) {
|
|
210
|
+
thumbnails.set(thumbnailId, {
|
|
211
|
+
slot,
|
|
212
|
+
blockType
|
|
213
|
+
});
|
|
214
|
+
post({
|
|
215
|
+
type: "thumbnail",
|
|
216
|
+
thumbnailId,
|
|
217
|
+
slot,
|
|
218
|
+
blockType
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
hideThumbnail(thumbnailId) {
|
|
222
|
+
if (!thumbnails.delete(thumbnailId)) return;
|
|
223
|
+
post({
|
|
224
|
+
type: "thumbnailEnd",
|
|
225
|
+
thumbnailId
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
subscribe(listener) {
|
|
229
|
+
connectionListeners.add(listener);
|
|
230
|
+
return () => connectionListeners.delete(listener);
|
|
231
|
+
},
|
|
232
|
+
status: () => status,
|
|
233
|
+
subscribeStatus(listener) {
|
|
234
|
+
statusListeners.add(listener);
|
|
235
|
+
return () => statusListeners.delete(listener);
|
|
236
|
+
},
|
|
237
|
+
close() {
|
|
238
|
+
connectionListeners.clear();
|
|
239
|
+
statusListeners.clear();
|
|
240
|
+
for (const subscription of subscriptions) subscription.unsubscribe();
|
|
241
|
+
stopRelaying?.();
|
|
242
|
+
stopFollowing?.();
|
|
243
|
+
renderer = null;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
export { createRendererHost };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { referencePickerFocus } from "../preview/referencePickerFocus.js";
|
|
2
|
+
import { canvasStore } from "../canvas/canvasStore.js";
|
|
3
|
+
import { previewStore } from "../preview/previewStore.js";
|
|
4
|
+
import { SDK_VERSION, deferredInbox } from "./protocol.js";
|
|
5
|
+
import { answerFromCode } from "./definitionAnswers.js";
|
|
6
|
+
|
|
7
|
+
//#region src/features/renderer/studioConnection.ts
|
|
8
|
+
const mirrors = {
|
|
9
|
+
preview: previewStore,
|
|
10
|
+
canvas: canvasStore
|
|
11
|
+
};
|
|
12
|
+
const forwards = {
|
|
13
|
+
preview: previewStore,
|
|
14
|
+
referencePickerFocus
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Connects the renderer document to the studio through its handle. This document's stores then
|
|
18
|
+
* mirror the studio's, its own events are forwarded to the studio, which owns their truth, and
|
|
19
|
+
* its cache follows the studio's (see `protocol.ts`). The studio's questions are answered from
|
|
20
|
+
* `camoxApp`, the draft's code; without it, none can be.
|
|
21
|
+
*/
|
|
22
|
+
function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxApp }) {
|
|
23
|
+
let welcomed = false;
|
|
24
|
+
let frames = [];
|
|
25
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
26
|
+
let thumbnails = [];
|
|
27
|
+
const changed = () => {
|
|
28
|
+
for (const listener of listeners) listener();
|
|
29
|
+
};
|
|
30
|
+
const setFrames = (next) => {
|
|
31
|
+
frames = next;
|
|
32
|
+
changed();
|
|
33
|
+
};
|
|
34
|
+
const setThumbnails = (next) => {
|
|
35
|
+
thumbnails = next;
|
|
36
|
+
changed();
|
|
37
|
+
};
|
|
38
|
+
/** The edits sent to the studio that it hasn't answered yet. */
|
|
39
|
+
const editsInFlight = /* @__PURE__ */ new Map();
|
|
40
|
+
let lastEditId = 0;
|
|
41
|
+
/** Whether the renderer holds the read under `queryKey`. */
|
|
42
|
+
const holds = (queryKey) => !!queryClient.getQueryCache().find({
|
|
43
|
+
queryKey,
|
|
44
|
+
exact: true
|
|
45
|
+
});
|
|
46
|
+
const receive = (message) => {
|
|
47
|
+
if (message.type === "welcome") {
|
|
48
|
+
welcomed = message.protocol === 1 && message.sdkVersion === SDK_VERSION;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!welcomed) return;
|
|
52
|
+
switch (message.type) {
|
|
53
|
+
case "frame": {
|
|
54
|
+
const frame = {
|
|
55
|
+
frameId: message.frameId,
|
|
56
|
+
slot: message.slot,
|
|
57
|
+
target: message.target
|
|
58
|
+
};
|
|
59
|
+
const index = frames.findIndex(({ frameId }) => frameId === message.frameId);
|
|
60
|
+
setFrames(index < 0 ? [...frames, frame] : frames.map((shown, at) => at === index ? frame : shown));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case "frameEnd":
|
|
64
|
+
setFrames(frames.filter(({ frameId }) => frameId !== message.frameId));
|
|
65
|
+
return;
|
|
66
|
+
case "store":
|
|
67
|
+
mirrors[message.store]?.send({
|
|
68
|
+
type: "mirror",
|
|
69
|
+
context: message.context
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
case "draftStarted":
|
|
73
|
+
draftScope.enterStarted(message.draftId);
|
|
74
|
+
return;
|
|
75
|
+
case "invalidate":
|
|
76
|
+
studioCache.receive(message.message, message.projectId);
|
|
77
|
+
return;
|
|
78
|
+
case "write":
|
|
79
|
+
if (!holds(message.queryKey)) return;
|
|
80
|
+
if ("value" in message && message.value !== void 0) queryClient.setQueryData(message.queryKey, message.value);
|
|
81
|
+
else queryClient.removeQueries({
|
|
82
|
+
queryKey: message.queryKey,
|
|
83
|
+
exact: true
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
case "outdated":
|
|
87
|
+
for (const queryKey of message.queryKeys) if (holds(queryKey)) queryClient.invalidateQueries({
|
|
88
|
+
queryKey,
|
|
89
|
+
exact: true
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
case "thumbnail": {
|
|
93
|
+
const { thumbnailId, slot, blockType } = message;
|
|
94
|
+
const thumbnail = {
|
|
95
|
+
thumbnailId,
|
|
96
|
+
slot,
|
|
97
|
+
blockType
|
|
98
|
+
};
|
|
99
|
+
const index = thumbnails.findIndex((shown) => shown.thumbnailId === thumbnailId);
|
|
100
|
+
setThumbnails(index < 0 ? [...thumbnails, thumbnail] : thumbnails.map((shown, at) => at === index ? thumbnail : shown));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
case "thumbnailEnd":
|
|
104
|
+
setThumbnails(thumbnails.filter(({ thumbnailId }) => thumbnailId !== message.thumbnailId));
|
|
105
|
+
return;
|
|
106
|
+
case "ask": {
|
|
107
|
+
const { askId, question } = message;
|
|
108
|
+
if (!camoxApp) {
|
|
109
|
+
studio({
|
|
110
|
+
type: "answer",
|
|
111
|
+
askId,
|
|
112
|
+
error: "The renderer has no code to answer with."
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
answerFromCode(camoxApp, question).then((value) => studio({
|
|
117
|
+
type: "answer",
|
|
118
|
+
askId,
|
|
119
|
+
value
|
|
120
|
+
}), (error) => studio({
|
|
121
|
+
type: "answer",
|
|
122
|
+
askId,
|
|
123
|
+
error: error instanceof Error ? error.message : String(error)
|
|
124
|
+
}));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
case "edited": {
|
|
128
|
+
const edit = editsInFlight.get(message.editId);
|
|
129
|
+
if (!edit) return;
|
|
130
|
+
editsInFlight.delete(message.editId);
|
|
131
|
+
if (message.error === void 0) edit.resolve();
|
|
132
|
+
else edit.reject(new Error(message.error));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const studio = handle.connect(deferredInbox(receive));
|
|
138
|
+
studio({
|
|
139
|
+
type: "hello",
|
|
140
|
+
protocol: 1,
|
|
141
|
+
sdkVersion: SDK_VERSION
|
|
142
|
+
});
|
|
143
|
+
const inspections = Object.keys(forwards).map((store) => forwards[store].inspect((inspection) => {
|
|
144
|
+
const event = inspection.event;
|
|
145
|
+
if (inspection.type !== "@xstate.transition" || !event) return;
|
|
146
|
+
if (event.type === "mirror" || event.type.startsWith("@xstate.")) return;
|
|
147
|
+
studio({
|
|
148
|
+
type: "storeEvent",
|
|
149
|
+
store,
|
|
150
|
+
event: { ...event }
|
|
151
|
+
});
|
|
152
|
+
}));
|
|
153
|
+
return {
|
|
154
|
+
connected: () => welcomed,
|
|
155
|
+
frames: () => frames,
|
|
156
|
+
thumbnails: () => thumbnails,
|
|
157
|
+
subscribe(listener) {
|
|
158
|
+
listeners.add(listener);
|
|
159
|
+
return () => listeners.delete(listener);
|
|
160
|
+
},
|
|
161
|
+
send: (message) => studio(message),
|
|
162
|
+
notify: (notice) => studio({
|
|
163
|
+
type: "notify",
|
|
164
|
+
notice
|
|
165
|
+
}),
|
|
166
|
+
edit: (edit) => new Promise((resolve, reject) => {
|
|
167
|
+
const editId = ++lastEditId;
|
|
168
|
+
editsInFlight.set(editId, {
|
|
169
|
+
resolve,
|
|
170
|
+
reject
|
|
171
|
+
});
|
|
172
|
+
studio({
|
|
173
|
+
type: "edit",
|
|
174
|
+
editId,
|
|
175
|
+
edit
|
|
176
|
+
});
|
|
177
|
+
}),
|
|
178
|
+
close() {
|
|
179
|
+
for (const inspection of inspections) inspection.unsubscribe();
|
|
180
|
+
listeners.clear();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { connectToStudio };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LocaleView } from "../../lib/locale-path.js";
|
|
2
|
+
import "../../core/layoutContext.js";
|
|
2
3
|
import "../../core/structuredData.js";
|
|
3
4
|
import "../../core/createLayout.js";
|
|
4
5
|
import "../../core/createApp.js";
|
|
5
6
|
import "../../lib/studio-reads.js";
|
|
6
|
-
import { Router } from "@camox/api-contract";
|
|
7
7
|
import { EndedDraft } from "@camox/api-contract/drafts";
|
|
8
8
|
import { QueryClient } from "@tanstack/react-query";
|
|
9
|
+
import { Router } from "@camox/api-contract/router";
|
|
9
10
|
import { RouterClient } from "@orpc/server";
|
|
10
11
|
//#region src/features/routes/pageRuntime.d.ts
|
|
11
12
|
type PageWithBlocks = Awaited<ReturnType<RouterClient<Router>["pages"]["getByPath"]>>;
|
|
@@ -2,10 +2,11 @@ import { UNNAMED_DEFAULT_LOCALE, localeHeaders } from "../../lib/locale-path.js"
|
|
|
2
2
|
import { createDraftScope, withoutDraft } from "../../lib/draft-scope.js";
|
|
3
3
|
import { createStudioCache, reads } from "../../lib/studio-reads.js";
|
|
4
4
|
import { linkFallbackLocale } from "../../lib/link-pages.js";
|
|
5
|
-
import { curatedLayoutContext } from "../../core/layoutContext.js";
|
|
6
5
|
import { PAGE_IMAGE_SIZE, pageImageUrl } from "../og/pageImage.js";
|
|
6
|
+
import { curatedLayoutContext } from "../../core/layoutContext.js";
|
|
7
7
|
import { isArticle } from "../../core/structuredData.js";
|
|
8
|
-
import {
|
|
8
|
+
import { publishingDraft } from "../runtime/publishingDraft.js";
|
|
9
|
+
import { DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, NO_DEVELOPMENT_DRAFT_ERROR, PUBLISHING_DRAFT_HEADER } from "@camox/api-contract/drafts";
|
|
9
10
|
import { createORPCClient } from "@orpc/client";
|
|
10
11
|
import { RPCLink } from "@orpc/client/fetch";
|
|
11
12
|
|
|
@@ -38,6 +39,10 @@ function parseMarkdownPath(pathname) {
|
|
|
38
39
|
function createServerApiClient(apiUrl, options) {
|
|
39
40
|
const headers = localeHeaders(options?.locale);
|
|
40
41
|
if (options?.draftId && options.authCookieHeader) headers[DRAFT_HEADER] = options.draftId;
|
|
42
|
+
else {
|
|
43
|
+
const publishing = publishingDraft();
|
|
44
|
+
if (publishing) headers[PUBLISHING_DRAFT_HEADER] = publishing;
|
|
45
|
+
}
|
|
41
46
|
return createORPCClient(new RPCLink({
|
|
42
47
|
url: `${apiUrl}/rpc`,
|
|
43
48
|
headers,
|
|
@@ -218,25 +223,32 @@ async function loadCamoxPageForRequest({ apiUrl, authCookieHeader, draftId, loca
|
|
|
218
223
|
shouldClearAuthCookie: member.clearAuthCookie
|
|
219
224
|
};
|
|
220
225
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Reads the Markdown Representation of the page at a runtime path, with its locale's prefix, in
|
|
228
|
+
* one API read: the API finds the locale the prefix names, the page there, its Markdown and what
|
|
229
|
+
* its front matter is built from. Markdown is a public output, so it reads Live whoever asks.
|
|
230
|
+
*/
|
|
231
|
+
function readPageMarkdown({ apiUrl, path, projectUuid }) {
|
|
232
|
+
return createServerApiClient(apiUrl).pages.getMarkdown({
|
|
233
|
+
path,
|
|
234
|
+
projectUuid
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* A curated page's Markdown Representation, opened by front matter stating its title,
|
|
239
|
+
* description and absolute `url`. Null when the path has no curated page, or the page has no
|
|
240
|
+
* Markdown.
|
|
241
|
+
*/
|
|
242
|
+
function curatedMarkdownResponse(camoxApp, read, url) {
|
|
243
|
+
if (!read.page || !read.markdown) return null;
|
|
244
|
+
const meta = buildPageMeta(curatedPageMetaInput(camoxApp, {
|
|
245
|
+
...read,
|
|
246
|
+
page: read.page
|
|
247
|
+
}, read.locale));
|
|
248
|
+
return new Response(withFrontMatter(read.markdown, {
|
|
249
|
+
...meta,
|
|
250
|
+
url
|
|
251
|
+
}), { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
|
|
240
252
|
}
|
|
241
253
|
/** A page's title and description, built by its layout's `toTitle` and `toDescription`. */
|
|
242
254
|
function buildPageMeta(input) {
|
|
@@ -337,4 +349,4 @@ function buildCamoxPageHead(camoxApp, loaderData, locale) {
|
|
|
337
349
|
}
|
|
338
350
|
|
|
339
351
|
//#endregion
|
|
340
|
-
export { buildCamoxPageHead, buildCamoxPageLoaderData, buildDefaultFaviconUrl, buildFaviconUrl, buildPageHead, buildPageMeta,
|
|
352
|
+
export { buildCamoxPageHead, buildCamoxPageLoaderData, buildDefaultFaviconUrl, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, curatedPageMetaInput, declarePage, getMarkdownPath, isAuthSessionError, isNotFoundError, loadCamoxPage, loadCamoxPageForRequest, parseMarkdownPath, parseQuality, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { previewStore, selectIsCanvasShown } from "../preview/previewStore.js";
|
|
2
2
|
import { shownRecordPage } from "../preview/pageRecord.js";
|
|
3
|
+
import { StudioEditingFallback } from "../preview/components/StudioEditingFallback.js";
|
|
3
4
|
import { Navbar } from "../studio/components/Navbar.js";
|
|
4
5
|
import { EditingFallbackContext } from "./editingFallback.js";
|
|
5
6
|
import { PreviewToolbar } from "../preview/components/PreviewToolbar.js";
|
|
6
|
-
import { StudioEditingFallback } from "../preview/components/StudioEditingFallback.js";
|
|
7
7
|
import { ChatSidebar } from "../studio/components/ChatSidebar.js";
|
|
8
8
|
import { FollowAgentProvider } from "../studio/FollowAgentProvider.js";
|
|
9
9
|
import { c } from "react/compiler-runtime";
|
|
@@ -12,10 +12,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
import studioCssUrl from "virtual:camox-studio-css";
|
|
13
13
|
|
|
14
14
|
//#region src/features/runtime/RuntimeChrome.tsx
|
|
15
|
-
const renderEditingFallback = ({ layoutPage
|
|
16
|
-
layoutPage,
|
|
17
|
-
children
|
|
18
|
-
});
|
|
15
|
+
const renderEditingFallback = ({ layoutPage }) => /* @__PURE__ */ jsx(StudioEditingFallback, { layoutPage });
|
|
19
16
|
function RuntimeChrome(t0) {
|
|
20
17
|
const $ = c(32);
|
|
21
18
|
const { input, children } = t0;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/features/runtime/assetPrefix.ts
|
|
2
|
+
/**
|
|
3
|
+
* One build serves under any URL prefix: Live at `/`, a revision's preview at
|
|
4
|
+
* `/_camox/r/<revisionId>/`. Whatever loads the server bundle sets this global first, never per
|
|
5
|
+
* request: the bundle collects its asset URLs when it loads. Unset, assets are under `/`.
|
|
6
|
+
*/
|
|
7
|
+
const ASSET_PREFIX_GLOBAL = "__CAMOX_ASSET_PREFIX__";
|
|
8
|
+
/** The prefix the server build's assets are under, ending with `/`. */
|
|
9
|
+
function assetPrefix() {
|
|
10
|
+
return globalThis["__CAMOX_ASSET_PREFIX__"] ?? "/";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ASSET_PREFIX_GLOBAL, assetPrefix };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { recordPageValues } from "../../core/recordItem.js";
|
|
2
|
-
import { curatedPageTitle } from "../../core/layoutContext.js";
|
|
3
2
|
import { getPageDestinations } from "../../core/pageDestinations.js";
|
|
4
3
|
import { matchDerivedLayout } from "../../core/derivedRoutes.js";
|
|
4
|
+
import { curatedPageTitle } from "../../core/layoutContext.js";
|
|
5
5
|
|
|
6
6
|
//#region src/features/runtime/breadcrumbs.ts
|
|
7
7
|
/** The paths above a page's, from the home page down: none for the home page itself. */
|
|
@@ -2,18 +2,14 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/features/runtime/editingFallback.tsx
|
|
4
4
|
/**
|
|
5
|
-
* Renders the
|
|
5
|
+
* Renders what holds the page's place while the editor is still loading. The Studio
|
|
6
6
|
* chrome provides it, so visitors never download it.
|
|
7
7
|
*/
|
|
8
8
|
const EditingFallbackContext = React.createContext(null);
|
|
9
|
-
/**
|
|
10
|
-
function EditingFallbackSlot({ layoutPage
|
|
9
|
+
/** What shows until the editor activates, laid out for the Canvas when it shows. */
|
|
10
|
+
function EditingFallbackSlot({ layoutPage }) {
|
|
11
11
|
const render = React.use(EditingFallbackContext);
|
|
12
|
-
|
|
13
|
-
return render({
|
|
14
|
-
layoutPage,
|
|
15
|
-
children
|
|
16
|
-
});
|
|
12
|
+
return render ? render({ layoutPage }) : null;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
//#endregion
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequestContext } from "../../core/requestContext.js";
|
|
2
|
-
import { curatedPageTitle } from "../../core/layoutContext.js";
|
|
3
2
|
import { getPageDestinations } from "../../core/pageDestinations.js";
|
|
4
3
|
import { discoverLayoutPages } from "../../core/derivedRoutes.js";
|
|
4
|
+
import { curatedPageTitle } from "../../core/layoutContext.js";
|
|
5
5
|
|
|
6
6
|
//#region src/features/runtime/livePages.ts
|
|
7
7
|
/**
|