camox 0.63.0 → 0.65.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 +28 -28
- package/dist/core/createLayout.d.ts +1 -1
- package/dist/core/derivedRoutes.js +3 -30
- package/dist/core/editing/createEditableBlock.d.ts +604 -604
- package/dist/features/canvas/canvasCamera.js +11 -2
- package/dist/features/canvas/useCanvasCamera.js +14 -10
- package/dist/features/page/CuratedPageContent.js +2 -1
- package/dist/features/page/UnplacedBlockContent.js +52 -0
- package/dist/features/preview/components/PreviewPanel.js +40 -12
- package/dist/features/preview/components/SidebarStack.js +78 -9
- package/dist/features/preview/strayCaretGuard.js +34 -0
- package/dist/features/provider/CoreCamoxProvider.js +5 -3
- package/dist/features/routes/pageRuntime.d.ts +2 -0
- package/dist/features/routes/pageRuntime.js +8 -5
- package/dist/features/runtime/pageApp.js +111 -74
- package/dist/features/runtime/pageClient.js +22 -2
- package/dist/features/runtime/runtime.d.ts +10 -0
- package/dist/features/runtime/runtime.js +314 -4
- package/dist/features/runtime/runtimeRoutes.d.ts +5 -0
- package/dist/features/runtime/runtimeRoutes.js +5 -0
- package/dist/features/studio/components/ChatWidget.js +88 -62
- package/dist/features/studio/components/ConnectAgent.js +313 -0
- package/dist/features/studio/components/Patrol.js +455 -205
- package/dist/features/studio/components/ReviewDraftSheet.js +431 -334
- package/dist/features/studio/components/Squareling.js +74 -41
- package/dist/features/studio/components/ThreadComposer.js +130 -107
- package/dist/features/studio/components/ThreadMessages.js +82 -13
- package/dist/features/studio/components/clientLogoPaths.js +10 -0
- package/dist/features/studio/findingSentence.js +37 -0
- package/dist/features/studio/reportSentence.js +15 -0
- package/dist/features/studio/studioStore.js +23 -1
- package/dist/features/studio/workingSentence.js +8 -0
- package/dist/lib/api-client.d.ts +1 -0
- package/dist/lib/auth.js +34 -32
- package/dist/lib/queries.js +9 -1
- package/dist/lib/server-auth-cookie.js +17 -4
- package/dist/lib/studio-reads.d.ts +1 -1
- package/dist/lib/studio-reads.js +5 -1
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +15 -4
|
@@ -23,6 +23,11 @@ const MAX_CANVAS_ZOOM = 2;
|
|
|
23
23
|
* camera, so painting and panning slow down with the size of the site.
|
|
24
24
|
*/
|
|
25
25
|
const MIN_CANVAS_ZOOM = .1;
|
|
26
|
+
/**
|
|
27
|
+
* The overview is at most this fraction of the page fit's zoom, so it always
|
|
28
|
+
* shows a Page as an item on the Canvas, even when the site is one short Page.
|
|
29
|
+
*/
|
|
30
|
+
const CANVAS_OVERVIEW_PAGE_ZOOM = .6;
|
|
26
31
|
/** Log-scale zoom per pixel of Ctrl/⌘ + wheel, including trackpad pinches. */
|
|
27
32
|
const CANVAS_WHEEL_ZOOM_RATE = .0139392;
|
|
28
33
|
/** Relative (log-scale) bands make the detent feel the same at every page width. */
|
|
@@ -178,7 +183,11 @@ function zoomCanvasAt(camera, point, scale, minimumScale = Number.EPSILON, maxim
|
|
|
178
183
|
}
|
|
179
184
|
function fitCanvas(viewport, content) {
|
|
180
185
|
const padding = canvasInsets(viewport);
|
|
181
|
-
const
|
|
186
|
+
const pageZoom = content.pageWidth === void 0 ? Infinity : CANVAS_OVERVIEW_PAGE_ZOOM * fitCanvasPage(viewport, {
|
|
187
|
+
left: 0,
|
|
188
|
+
width: content.pageWidth
|
|
189
|
+
}).scale;
|
|
190
|
+
const scale = Math.max(Number.EPSILON, Math.min(1, pageZoom, (viewport.width - padding.x * 2) / Math.max(1, content.width), (viewport.height - padding.top - padding.bottom) / Math.max(1, content.height)));
|
|
182
191
|
return {
|
|
183
192
|
x: (viewport.width - content.width * scale) / 2,
|
|
184
193
|
y: (viewport.height + padding.top - padding.bottom - content.height * scale) / 2,
|
|
@@ -241,4 +250,4 @@ function canvasWheelDelta(delta, mode, viewportSize) {
|
|
|
241
250
|
}
|
|
242
251
|
|
|
243
252
|
//#endregion
|
|
244
|
-
export { CANVAS_OVERSCROLL, CANVAS_WHEEL_ZOOM_RATE, MIN_CANVAS_ZOOM, PAN_SNAP_CAPTURE, PAN_SNAP_RELEASE, ZOOM_SNAP_CAPTURE, ZOOM_SNAP_RELEASE, ZOOM_STRETCH, canvasInsets, canvasMaximumScale, canvasMinimumScale, canvasOverview, canvasTargetOffset, canvasVerticalRail, canvasWheelDelta, constrainCanvasCamera, findCanvasSnapTarget, fitCanvas, fitCanvasPage, isCanvasCameraSnapped, isCanvasPageScale, isInCanvasSnapWindow, placeCanvasPageAnchor, snapCanvasZoom, stretchCanvasZoom, unstretchCanvasZoom, zoomCanvasAt };
|
|
253
|
+
export { CANVAS_OVERSCROLL, CANVAS_OVERVIEW_PAGE_ZOOM, CANVAS_WHEEL_ZOOM_RATE, MIN_CANVAS_ZOOM, PAN_SNAP_CAPTURE, PAN_SNAP_RELEASE, ZOOM_SNAP_CAPTURE, ZOOM_SNAP_RELEASE, ZOOM_STRETCH, canvasInsets, canvasMaximumScale, canvasMinimumScale, canvasOverview, canvasTargetOffset, canvasVerticalRail, canvasWheelDelta, constrainCanvasCamera, findCanvasSnapTarget, fitCanvas, fitCanvasPage, isCanvasCameraSnapped, isCanvasPageScale, isInCanvasSnapWindow, placeCanvasPageAnchor, snapCanvasZoom, stretchCanvasZoom, unstretchCanvasZoom, zoomCanvasAt };
|
|
@@ -117,10 +117,19 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
117
117
|
};
|
|
118
118
|
};
|
|
119
119
|
let viewportSize = measureViewport();
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
const page = initialTargetRef.current;
|
|
121
|
+
const targets = () => rowRef.current?.targets ?? (page ? [{
|
|
122
|
+
...page,
|
|
123
|
+
key: "initial"
|
|
124
|
+
}] : []);
|
|
125
|
+
const measureContent = () => {
|
|
126
|
+
const widths = targets().map((target) => target.width);
|
|
127
|
+
return {
|
|
128
|
+
width: rowRef.current?.width ?? content.offsetWidth,
|
|
129
|
+
height: content.offsetHeight,
|
|
130
|
+
pageWidth: widths.length ? Math.min(...widths) : void 0
|
|
131
|
+
};
|
|
132
|
+
};
|
|
124
133
|
let contentSize = measureContent();
|
|
125
134
|
let minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
126
135
|
const updateGeometry = (nextViewport = measureViewport(), nextContent = measureContent()) => {
|
|
@@ -128,11 +137,6 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
128
137
|
contentSize = nextContent;
|
|
129
138
|
minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
130
139
|
};
|
|
131
|
-
const page = initialTargetRef.current;
|
|
132
|
-
const targets = () => rowRef.current?.targets ?? (page ? [{
|
|
133
|
-
...page,
|
|
134
|
-
key: "initial"
|
|
135
|
-
}] : []);
|
|
136
140
|
const targetOf = (key) => key == null ? void 0 : targets().find((candidate) => candidate.key === key);
|
|
137
141
|
let stretch;
|
|
138
142
|
let zoomEnd = 0;
|
|
@@ -511,7 +515,7 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
511
515
|
const previousInsets = insets;
|
|
512
516
|
const nextViewport = measureViewport();
|
|
513
517
|
const nextContent = measureContent();
|
|
514
|
-
if (insets.left === previousInsets.left && insets.top === previousInsets.top && nextViewport.width === viewportSize.width && nextViewport.height === viewportSize.height && nextContent.width === contentSize.width && nextContent.height === contentSize.height) return;
|
|
518
|
+
if (insets.left === previousInsets.left && insets.top === previousInsets.top && nextViewport.width === viewportSize.width && nextViewport.height === viewportSize.height && nextContent.width === contentSize.width && nextContent.height === contentSize.height && nextContent.pageWidth === contentSize.pageWidth) return;
|
|
515
519
|
const wasFitted = isOverview(target);
|
|
516
520
|
gesture = void 0;
|
|
517
521
|
const held = nextViewport.width !== viewportSize.width && !flight && !fittingInitialPage ? targetOf(selection) : void 0;
|
|
@@ -12,6 +12,7 @@ import * as React from "react";
|
|
|
12
12
|
import { jsx } from "react/jsx-runtime";
|
|
13
13
|
|
|
14
14
|
//#region src/features/page/CuratedPageContent.tsx
|
|
15
|
+
/** A block read with its bundle, rendered by its definition's code as a page shows it. */
|
|
15
16
|
function PageBlock(t0) {
|
|
16
17
|
const $ = c(19);
|
|
17
18
|
const { blockId, mode } = t0;
|
|
@@ -177,4 +178,4 @@ function _temp(block) {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
//#endregion
|
|
180
|
-
export { CuratedPageContent };
|
|
181
|
+
export { CuratedPageContent, PageBlock };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { reads, useRead } from "../../lib/studio-reads.js";
|
|
2
|
+
import { BlockErrorBoundary } from "../preview/components/BlockErrorBoundary.js";
|
|
3
|
+
import { PageBlock } from "./CuratedPageContent.js";
|
|
4
|
+
import { c } from "react/compiler-runtime";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
//#region src/features/page/UnplacedBlockContent.tsx
|
|
8
|
+
/**
|
|
9
|
+
* A block placed nowhere, alone, as a page would show it: the look document's Block Definition
|
|
10
|
+
* mode (ADR 0032). Its bundle is read like a page's block, so it renders and hydrates as one.
|
|
11
|
+
* Thumbnails render a Block Definition their own way (`BlockPeek`); only the block render is
|
|
12
|
+
* shared.
|
|
13
|
+
*/
|
|
14
|
+
function UnplacedBlockContent(t0) {
|
|
15
|
+
const $ = c(8);
|
|
16
|
+
const { blockId } = t0;
|
|
17
|
+
let t1;
|
|
18
|
+
if ($[0] !== blockId) {
|
|
19
|
+
t1 = reads.blockBundle(blockId);
|
|
20
|
+
$[0] = blockId;
|
|
21
|
+
$[1] = t1;
|
|
22
|
+
} else t1 = $[1];
|
|
23
|
+
const { block } = useRead(t1);
|
|
24
|
+
let t2;
|
|
25
|
+
if ($[2] !== blockId) {
|
|
26
|
+
t2 = /* @__PURE__ */ jsx(PageBlock, {
|
|
27
|
+
blockId,
|
|
28
|
+
mode: "site"
|
|
29
|
+
});
|
|
30
|
+
$[2] = blockId;
|
|
31
|
+
$[3] = t2;
|
|
32
|
+
} else t2 = $[3];
|
|
33
|
+
let t3;
|
|
34
|
+
if ($[4] !== block.type || $[5] !== blockId || $[6] !== t2) {
|
|
35
|
+
t3 = /* @__PURE__ */ jsx("main", {
|
|
36
|
+
className: "flex flex-col",
|
|
37
|
+
children: /* @__PURE__ */ jsx(BlockErrorBoundary, {
|
|
38
|
+
blockId,
|
|
39
|
+
blockType: block.type,
|
|
40
|
+
children: t2
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
$[4] = block.type;
|
|
44
|
+
$[5] = blockId;
|
|
45
|
+
$[6] = t2;
|
|
46
|
+
$[7] = t3;
|
|
47
|
+
} else t3 = $[7];
|
|
48
|
+
return t3;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { UnplacedBlockContent };
|
|
@@ -11,6 +11,7 @@ import { previewPageIframeProps, usePreviewPageFrame } from "../previewPageFrame
|
|
|
11
11
|
import { adoptCanvasDocument, pauseCanvasDocument } from "../../canvas/canvasFrameDocument.js";
|
|
12
12
|
import { forwardCanvasWheel, useCanvasFrameInput } from "../../canvas/useCanvasFrameInput.js";
|
|
13
13
|
import { useActionShortcuts } from "../../provider/useAdminShortcuts.js";
|
|
14
|
+
import { guardStrayCaret } from "../strayCaretGuard.js";
|
|
14
15
|
import { useBlockActionsShortcuts } from "./BlockActionsPopover.js";
|
|
15
16
|
import { FieldOverlayStyles } from "./FieldOverlayStyles.js";
|
|
16
17
|
import { MobilePreviewDrawer } from "./MobilePreviewDrawer.js";
|
|
@@ -60,6 +61,7 @@ const PreviewFrameEffects = () => {
|
|
|
60
61
|
t0 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61
62
|
/* @__PURE__ */ jsx(FieldOverlayStyles, {}),
|
|
62
63
|
/* @__PURE__ */ jsx(KeyDownForwarder, {}),
|
|
64
|
+
/* @__PURE__ */ jsx(StrayCaretGuard, {}),
|
|
63
65
|
/* @__PURE__ */ jsx(OverlayTracker, {})
|
|
64
66
|
] });
|
|
65
67
|
$[0] = t0;
|
|
@@ -72,6 +74,27 @@ const KeyDownForwarder = () => {
|
|
|
72
74
|
useActionShortcuts(inIframe ? iframeWindow : null);
|
|
73
75
|
return null;
|
|
74
76
|
};
|
|
77
|
+
const StrayCaretGuard = () => {
|
|
78
|
+
const $ = c(3);
|
|
79
|
+
const { window: iframeWindow } = useFrame();
|
|
80
|
+
let t0;
|
|
81
|
+
let t1;
|
|
82
|
+
if ($[0] !== iframeWindow) {
|
|
83
|
+
t0 = () => {
|
|
84
|
+
if (!iframeWindow) return;
|
|
85
|
+
return guardStrayCaret(iframeWindow.document);
|
|
86
|
+
};
|
|
87
|
+
t1 = [iframeWindow];
|
|
88
|
+
$[0] = iframeWindow;
|
|
89
|
+
$[1] = t0;
|
|
90
|
+
$[2] = t1;
|
|
91
|
+
} else {
|
|
92
|
+
t0 = $[1];
|
|
93
|
+
t1 = $[2];
|
|
94
|
+
}
|
|
95
|
+
React.useEffect(t0, t1);
|
|
96
|
+
return null;
|
|
97
|
+
};
|
|
75
98
|
/** The preview's read-only tree is never editable, whether or not the Canvas shows. */
|
|
76
99
|
const READ_ONLY_ARTIFACT_STUDIO = {
|
|
77
100
|
editing: false,
|
|
@@ -91,7 +114,7 @@ function CuratedBlockShortcuts() {
|
|
|
91
114
|
* when used, pauses with the camera, and draws its editing overlays.
|
|
92
115
|
*/
|
|
93
116
|
function PreviewPageCanvasEffects(t0) {
|
|
94
|
-
const $ = c(
|
|
117
|
+
const $ = c(31);
|
|
95
118
|
const { owner } = t0;
|
|
96
119
|
const { window: frameWindow, iframeElement } = useFrame();
|
|
97
120
|
const { frame, slot, inCanvas } = usePreviewPageFrame();
|
|
@@ -225,33 +248,38 @@ function PreviewPageCanvasEffects(t0) {
|
|
|
225
248
|
if (!placed) return null;
|
|
226
249
|
let t11;
|
|
227
250
|
let t12;
|
|
251
|
+
let t13;
|
|
228
252
|
if ($[24] === Symbol.for("react.memo_cache_sentinel")) {
|
|
229
253
|
t11 = /* @__PURE__ */ jsx(FieldOverlayStyles, {});
|
|
230
|
-
t12 = /* @__PURE__ */ jsx(
|
|
254
|
+
t12 = /* @__PURE__ */ jsx(StrayCaretGuard, {});
|
|
255
|
+
t13 = /* @__PURE__ */ jsx(OverlayTracker, {});
|
|
231
256
|
$[24] = t11;
|
|
232
257
|
$[25] = t12;
|
|
258
|
+
$[26] = t13;
|
|
233
259
|
} else {
|
|
234
260
|
t11 = $[24];
|
|
235
261
|
t12 = $[25];
|
|
262
|
+
t13 = $[26];
|
|
236
263
|
}
|
|
237
|
-
const
|
|
238
|
-
let
|
|
239
|
-
if ($[
|
|
240
|
-
|
|
264
|
+
const t14 = owner?.kind === "page";
|
|
265
|
+
let t15;
|
|
266
|
+
if ($[27] !== iframeElement || $[28] !== owner || $[29] !== t14) {
|
|
267
|
+
t15 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
241
268
|
t11,
|
|
242
269
|
t12,
|
|
270
|
+
t13,
|
|
243
271
|
/* @__PURE__ */ jsx(Overlays, {
|
|
244
272
|
frame: iframeElement,
|
|
245
273
|
owner,
|
|
246
|
-
canAddBlocks:
|
|
274
|
+
canAddBlocks: t14
|
|
247
275
|
})
|
|
248
276
|
] });
|
|
249
|
-
$[
|
|
250
|
-
$[
|
|
251
|
-
$[28] = t13;
|
|
277
|
+
$[27] = iframeElement;
|
|
278
|
+
$[28] = owner;
|
|
252
279
|
$[29] = t14;
|
|
253
|
-
|
|
254
|
-
|
|
280
|
+
$[30] = t15;
|
|
281
|
+
} else t15 = $[30];
|
|
282
|
+
return t15;
|
|
255
283
|
}
|
|
256
284
|
function _temp(event) {
|
|
257
285
|
if (event.target?.closest?.("a")) event.preventDefault();
|
|
@@ -143,6 +143,8 @@ function SidebarStack(t0) {
|
|
|
143
143
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
144
144
|
t1 = {
|
|
145
145
|
ready: false,
|
|
146
|
+
boxes: /* @__PURE__ */ new Map(),
|
|
147
|
+
observer: null,
|
|
146
148
|
front: null,
|
|
147
149
|
behind: null,
|
|
148
150
|
inFront: null
|
|
@@ -156,8 +158,16 @@ function SidebarStack(t0) {
|
|
|
156
158
|
t2 = () => {
|
|
157
159
|
const current = motion.current;
|
|
158
160
|
current.ready = true;
|
|
161
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
162
|
+
const observer = new ResizeObserver(() => measureCards(current));
|
|
163
|
+
current.observer = observer;
|
|
164
|
+
for (const card of current.boxes.keys()) observer.observe(card);
|
|
165
|
+
if (current.inFront?.parentElement) observer.observe(current.inFront.parentElement);
|
|
166
|
+
}
|
|
159
167
|
return () => {
|
|
160
168
|
current.ready = false;
|
|
169
|
+
current.observer?.disconnect();
|
|
170
|
+
current.observer = null;
|
|
161
171
|
};
|
|
162
172
|
};
|
|
163
173
|
t3 = [];
|
|
@@ -233,20 +243,57 @@ function SidebarStack(t0) {
|
|
|
233
243
|
} else t9 = $[12];
|
|
234
244
|
return t9;
|
|
235
245
|
}
|
|
236
|
-
/**
|
|
237
|
-
|
|
238
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Where a card is laid out relative to `stack`, whose box its layers fill. Offsets leave out the
|
|
248
|
+
* transforms that move cards as they come.
|
|
249
|
+
*/
|
|
250
|
+
function layoutBox(card, stack) {
|
|
251
|
+
let left = 0;
|
|
252
|
+
let top = 0;
|
|
253
|
+
let node = card;
|
|
254
|
+
while (node && node !== stack) {
|
|
255
|
+
left += node.offsetLeft;
|
|
256
|
+
top += node.offsetTop;
|
|
257
|
+
const parent = node.offsetParent;
|
|
258
|
+
if (parent && parent !== stack) {
|
|
259
|
+
left += parent.clientLeft;
|
|
260
|
+
top += parent.clientTop;
|
|
261
|
+
}
|
|
262
|
+
node = parent;
|
|
263
|
+
}
|
|
264
|
+
if (node === stack) return {
|
|
265
|
+
left,
|
|
266
|
+
top,
|
|
267
|
+
width: card.offsetWidth,
|
|
268
|
+
height: card.offsetHeight
|
|
269
|
+
};
|
|
270
|
+
const stackRect = stack.getBoundingClientRect();
|
|
239
271
|
const rect = card.getBoundingClientRect();
|
|
272
|
+
return {
|
|
273
|
+
left: rect.left - stackRect.left,
|
|
274
|
+
top: rect.top - stackRect.top,
|
|
275
|
+
width: rect.width,
|
|
276
|
+
height: rect.height
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** Notes where each card of the stack is laid out now. */
|
|
280
|
+
function measureCards(motion) {
|
|
281
|
+
const stack = motion.inFront?.parentElement;
|
|
282
|
+
if (!stack) return;
|
|
283
|
+
for (const card of motion.boxes.keys()) if (card.isConnected) motion.boxes.set(card, layoutBox(card, stack));
|
|
284
|
+
}
|
|
285
|
+
/** A still copy of a card, at `box`, so it can move away once the card is gone. */
|
|
286
|
+
function copyCard(card, box) {
|
|
240
287
|
const copy = card.cloneNode(true);
|
|
241
288
|
copy.setAttribute("aria-hidden", "true");
|
|
242
289
|
copy.setAttribute("inert", "");
|
|
243
290
|
Object.assign(copy.style, {
|
|
244
291
|
position: "absolute",
|
|
245
292
|
margin: "0",
|
|
246
|
-
left: `${
|
|
247
|
-
top: `${
|
|
248
|
-
width: `${
|
|
249
|
-
height: `${
|
|
293
|
+
left: `${box.left}px`,
|
|
294
|
+
top: `${box.top}px`,
|
|
295
|
+
width: `${box.width}px`,
|
|
296
|
+
height: `${box.height}px`
|
|
250
297
|
});
|
|
251
298
|
const scrolled = [];
|
|
252
299
|
const walk = (node, path) => {
|
|
@@ -276,6 +323,13 @@ function useStackCard(ref, depth, isView) {
|
|
|
276
323
|
const card = ref.current;
|
|
277
324
|
const motion = stack?.current;
|
|
278
325
|
if (!card || !motion || depth == null) return;
|
|
326
|
+
if (!motion.boxes.has(card)) motion.boxes.set(card, {
|
|
327
|
+
left: 0,
|
|
328
|
+
top: 0,
|
|
329
|
+
width: 0,
|
|
330
|
+
height: 0
|
|
331
|
+
});
|
|
332
|
+
motion.observer?.observe(card);
|
|
279
333
|
const previousFront = motion.front;
|
|
280
334
|
if (isView) motion.front = depth;
|
|
281
335
|
if (motion.ready && previousFront != null && prefersMotion()) {
|
|
@@ -289,10 +343,21 @@ function useStackCard(ref, depth, isView) {
|
|
|
289
343
|
}
|
|
290
344
|
return () => {
|
|
291
345
|
const { behind, inFront } = motion;
|
|
292
|
-
|
|
293
|
-
const
|
|
346
|
+
const box = motion.boxes.get(card);
|
|
347
|
+
const forget = () => {
|
|
348
|
+
motion.boxes.delete(card);
|
|
349
|
+
motion.observer?.unobserve(card);
|
|
350
|
+
};
|
|
351
|
+
if (!motion.ready || !behind || !inFront || !box || !prefersMotion()) {
|
|
352
|
+
queueMicrotask(() => {
|
|
353
|
+
if (!card.isConnected) forget();
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const { copy, restoreScroll } = copyCard(card, box);
|
|
294
358
|
queueMicrotask(() => {
|
|
295
359
|
if (card.isConnected) return;
|
|
360
|
+
forget();
|
|
296
361
|
const how = motionFor(depth, motion.front ?? depth);
|
|
297
362
|
(how === "front" ? inFront : behind).append(copy);
|
|
298
363
|
restoreScroll();
|
|
@@ -303,6 +368,10 @@ function useStackCard(ref, depth, isView) {
|
|
|
303
368
|
});
|
|
304
369
|
};
|
|
305
370
|
}, []);
|
|
371
|
+
React.useLayoutEffect(() => {
|
|
372
|
+
const motion = stack?.current;
|
|
373
|
+
if (motion) measureCards(motion);
|
|
374
|
+
});
|
|
306
375
|
}
|
|
307
376
|
/**
|
|
308
377
|
* Eases a header's card and content from where they were laid out before `shape` changed to
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/features/preview/strayCaretGuard.ts
|
|
2
|
+
/** Where the browser would put the caret for a click at this point. */
|
|
3
|
+
function caretNodeAt(doc, x, y) {
|
|
4
|
+
if (typeof doc.caretPositionFromPoint === "function") return doc.caretPositionFromPoint(x, y)?.offsetNode ?? null;
|
|
5
|
+
return doc.caretRangeFromPoint?.(x, y)?.startContainer ?? null;
|
|
6
|
+
}
|
|
7
|
+
function isEditable(node) {
|
|
8
|
+
return (node?.nodeType === 1 ? node : node?.parentElement ?? null)?.closest("[contenteditable=\"\"], [contenteditable=\"true\"]") != null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Keep a click beside an editable field from landing in it. On a click over
|
|
12
|
+
* non-editable content, browsers put the caret at the nearest text, and when that
|
|
13
|
+
* text is in a field, they focus it: clicking a block's padding next to a heading
|
|
14
|
+
* starts editing the heading instead of selecting the block. Such a click now only
|
|
15
|
+
* leaves the field being edited, as a click on content without text would.
|
|
16
|
+
*/
|
|
17
|
+
function guardStrayCaret(doc) {
|
|
18
|
+
const onMouseDown = (event) => {
|
|
19
|
+
if (event.button !== 0) return;
|
|
20
|
+
const target = event.target;
|
|
21
|
+
if (!target || isEditable(target)) return;
|
|
22
|
+
if (target.closest?.("input, textarea, select, button")) return;
|
|
23
|
+
if (!isEditable(caretNodeAt(doc, event.clientX, event.clientY))) return;
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
const active = doc.activeElement;
|
|
26
|
+
if (active?.isContentEditable) active.blur();
|
|
27
|
+
doc.getSelection()?.removeAllRanges();
|
|
28
|
+
};
|
|
29
|
+
doc.addEventListener("mousedown", onMouseDown, true);
|
|
30
|
+
return () => doc.removeEventListener("mousedown", onMouseDown, true);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { guardStrayCaret };
|
|
@@ -9,7 +9,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
9
9
|
import { replaceEqualDeep, useQueryClient } from "@tanstack/react-query";
|
|
10
10
|
|
|
11
11
|
//#region src/features/provider/CoreCamoxProvider.tsx
|
|
12
|
-
function CoreCamoxProvider({ apiUrl, authenticationUrl, camoxApp, children, projectUuid, initialAuthenticated = false, initialProject }) {
|
|
12
|
+
function CoreCamoxProvider({ apiUrl, authenticationUrl, camoxApp, children, projectUuid, initialAuthenticated = false, initialProject, sessionless = false }) {
|
|
13
13
|
const queryClient = useQueryClient();
|
|
14
14
|
const authClient = React.useMemo(() => createCamoxAuthClient(apiUrl), [apiUrl]);
|
|
15
15
|
const providedScope = React.useContext(DraftScopeContext);
|
|
@@ -48,14 +48,16 @@ function CoreCamoxProvider({ apiUrl, authenticationUrl, camoxApp, children, proj
|
|
|
48
48
|
apiUrl,
|
|
49
49
|
projectUuid,
|
|
50
50
|
initialAuthenticated,
|
|
51
|
-
exchangingToken
|
|
51
|
+
exchangingToken,
|
|
52
|
+
sessionless
|
|
52
53
|
}), [
|
|
53
54
|
authClient,
|
|
54
55
|
authenticationUrl,
|
|
55
56
|
apiUrl,
|
|
56
57
|
projectUuid,
|
|
57
58
|
initialAuthenticated,
|
|
58
|
-
exchangingToken
|
|
59
|
+
exchangingToken,
|
|
60
|
+
sessionless
|
|
59
61
|
]);
|
|
60
62
|
const [isPreviewHandoff, setIsPreviewHandoff] = React.useState(false);
|
|
61
63
|
React.useEffect(() => {
|
|
@@ -46,6 +46,8 @@ export interface DraftRead {
|
|
|
46
46
|
}
|
|
47
47
|
export declare function createServerApiClient(apiUrl: string, options?: {
|
|
48
48
|
authCookieHeader?: string;
|
|
49
|
+
/** A look token, which reads the draft it was signed for without a session (ADR 0032). */
|
|
50
|
+
lookToken?: string;
|
|
49
51
|
draftId?: string;
|
|
50
52
|
locale?: LocaleView;
|
|
51
53
|
}): RouterClient<Router>;
|
|
@@ -6,7 +6,7 @@ import { PAGE_IMAGE_SIZE, pageImageUrl } from "../og/pageImage.js";
|
|
|
6
6
|
import { curatedLayoutContext } from "../../core/layoutContext.js";
|
|
7
7
|
import { isArticle } from "../../core/structuredData.js";
|
|
8
8
|
import { publishingDraft } from "../runtime/publishingDraft.js";
|
|
9
|
-
import { DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, PUBLISHING_DRAFT_HEADER } from "@camox/api-contract/drafts";
|
|
9
|
+
import { DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, LOOK_TOKEN_HEADER, PUBLISHING_DRAFT_HEADER } from "@camox/api-contract/drafts";
|
|
10
10
|
import { createORPCClient } from "@orpc/client";
|
|
11
11
|
import { RPCLink } from "@orpc/client/fetch";
|
|
12
12
|
|
|
@@ -38,7 +38,8 @@ function parseMarkdownPath(pathname) {
|
|
|
38
38
|
}
|
|
39
39
|
function createServerApiClient(apiUrl, options) {
|
|
40
40
|
const headers = localeHeaders(options?.locale);
|
|
41
|
-
if (options?.
|
|
41
|
+
if (options?.lookToken) headers[LOOK_TOKEN_HEADER] = options.lookToken;
|
|
42
|
+
if (options?.draftId && (options.authCookieHeader || options.lookToken)) headers[DRAFT_HEADER] = options.draftId;
|
|
42
43
|
else {
|
|
43
44
|
const publishing = publishingDraft();
|
|
44
45
|
if (publishing) headers[PUBLISHING_DRAFT_HEADER] = publishing;
|
|
@@ -115,13 +116,14 @@ async function readDraftOrLive(draftId, member, read) {
|
|
|
115
116
|
function renderedHref(url, read) {
|
|
116
117
|
return read.refusedDraft ? withoutDraft(url) : url;
|
|
117
118
|
}
|
|
118
|
-
async function loadCamoxPage({ apiUrl, authCookieHeader, draftId, locale, pathname, projectUuid, queryClient }) {
|
|
119
|
+
async function loadCamoxPage({ apiUrl, authCookieHeader, lookToken, draftId, locale, pathname, projectUuid, queryClient }) {
|
|
119
120
|
const studioCache = createServerStudioCache(queryClient, {
|
|
120
121
|
apiUrl,
|
|
121
122
|
authCookieHeader,
|
|
123
|
+
lookToken,
|
|
122
124
|
locale
|
|
123
125
|
});
|
|
124
|
-
const inDraft = { in: draftId && authCookieHeader ? draftId : null };
|
|
126
|
+
const inDraft = { in: draftId && (authCookieHeader || lookToken) ? draftId : null };
|
|
125
127
|
const fallback = locale && linkFallbackLocale(locale);
|
|
126
128
|
const [page] = await Promise.all([
|
|
127
129
|
studioCache.ensure(reads.pageStructure({
|
|
@@ -140,12 +142,13 @@ async function loadCamoxPage({ apiUrl, authCookieHeader, draftId, locale, pathna
|
|
|
140
142
|
* The studio's reads for one server request, read with its session. Each operation names the
|
|
141
143
|
* draft it acts in, once the request settled which one it reads.
|
|
142
144
|
*/
|
|
143
|
-
function createServerStudioCache(queryClient, { apiUrl, authCookieHeader, locale }) {
|
|
145
|
+
function createServerStudioCache(queryClient, { apiUrl, authCookieHeader, lookToken, locale }) {
|
|
144
146
|
return createStudioCache({
|
|
145
147
|
queryClient,
|
|
146
148
|
scope: createDraftScope({ queryClient }),
|
|
147
149
|
api: (draft, inLocale) => createServerApiClient(apiUrl, {
|
|
148
150
|
authCookieHeader,
|
|
151
|
+
lookToken,
|
|
149
152
|
draftId: draft ?? void 0,
|
|
150
153
|
locale: inLocale
|
|
151
154
|
}),
|