camox 0.42.0 → 0.44.0
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/bin/camox.mjs +9 -1
- package/dist/core/components/lexical/{GradientNode.js → HighlightNode.js} +34 -34
- package/dist/core/components/lexical/InlineLexicalEditor.js +14 -14
- package/dist/core/components/lexical/InlineStylesPlugin.js +25 -9
- package/dist/core/components/lexical/SelectionBroadcaster.js +7 -7
- package/dist/core/components/lexical/SidebarLexicalEditor.js +24 -24
- package/dist/core/components/lexical/TextLinkPopover.js +191 -208
- package/dist/core/components/lexical/editorConfig.js +2 -2
- package/dist/core/createApp.d.ts +31 -4
- package/dist/core/createApp.js +2 -1
- package/dist/core/createBlock.js +24 -10
- package/dist/core/createLayout.d.ts +16 -3
- package/dist/core/createLayout.js +15 -15
- package/dist/core/derivedRoutes.js +1 -1
- package/dist/core/dom.d.ts +17 -0
- package/dist/core/dom.js +22 -0
- package/dist/core/editing/createEditableBlock.d.ts +135 -1
- package/dist/core/editing/createEditableBlock.js +209 -74
- package/dist/core/lib/contentType.d.ts +5 -0
- package/dist/core/lib/contentType.js +11 -0
- package/dist/core/lib/fieldTypes.js +1 -0
- package/dist/core/lib/iconTypes.d.ts +13 -0
- package/dist/core/lib/icons.d.ts +6 -0
- package/dist/core/lib/icons.js +130 -0
- package/dist/core/lib/lexicalReact.js +4 -4
- package/dist/core/lib/lexicalState.js +8 -8
- package/dist/core/lib/modifierFormats.js +2 -2
- package/dist/core/lib/textLinks.js +5 -2
- package/dist/core/lib/textStyles.d.ts +3 -3
- package/dist/core/lib/textStyles.js +9 -14
- package/dist/core/pageDestinations.js +37 -0
- package/dist/features/content/components/AssetCard.js +104 -68
- package/dist/features/og/imageResponse.node.js +11 -9
- package/dist/features/preview/CamoxPreview.js +148 -146
- package/dist/features/preview/commentsEnabled.js +2 -2
- package/dist/features/preview/components/AddBlockSidebar.js +1 -1
- package/dist/features/preview/components/AssetLightbox.js +17 -6
- package/dist/features/preview/components/AttachedComments.js +224 -87
- package/dist/features/preview/components/BlockActionsPopover.js +1 -1
- package/dist/features/preview/components/CommentTargetQuote.js +22 -21
- package/dist/features/preview/components/CreatePageModal.js +4 -4
- package/dist/features/preview/components/DerivedLayoutDialog.js +36 -21
- package/dist/features/preview/components/DerivedLayoutSidebar.js +45 -26
- package/dist/features/preview/components/DerivedPageInfoSidebar.js +38 -26
- package/dist/features/preview/components/FieldOverlayStyles.js +29 -9
- package/dist/features/preview/components/FieldToolbar.js +12 -12
- package/dist/features/preview/components/IconFieldEditor.js +231 -0
- package/dist/features/preview/components/ItemFieldsEditor.js +21 -14
- package/dist/features/preview/components/LinkFieldEditor.js +212 -209
- package/dist/features/preview/components/OverlayTracker.js +1 -1
- package/dist/features/preview/components/PageEditorSidebar.js +1 -1
- package/dist/features/preview/components/PageInfoSidebar.js +28 -28
- package/dist/features/preview/components/PagePicker.js +46 -46
- package/dist/features/preview/components/PageTree.js +1 -1
- package/dist/features/preview/components/PreviewPanel.js +1 -1
- package/dist/features/preview/components/PreviewToolbar.js +224 -225
- package/dist/features/preview/components/PublicationRow.js +8 -4
- package/dist/features/preview/components/SendFeedbackDialog.js +83 -0
- package/dist/features/preview/components/useRepeatableItemActions.js +1 -1
- package/dist/features/preview/overlayHighlights.js +54 -0
- package/dist/features/preview/previewCommentsStore.js +54 -34
- package/dist/features/preview/previewSelection.js +28 -23
- package/dist/features/preview/publication.js +10 -0
- package/dist/features/preview/usePageComments.js +39 -0
- package/dist/features/provider/CoreCamoxProvider.js +38 -6
- package/dist/features/provider/LocalhostPreviewProvider.js +81 -45
- package/dist/features/provider/components/CommandPalette.js +35 -84
- package/dist/features/runtime/pageApp.js +32 -28
- package/dist/features/runtime/pageServer.js +4 -1
- package/dist/features/runtime/runtime.d.ts +2 -0
- package/dist/features/runtime/runtime.js +45 -21
- package/dist/features/studio/components/EnvironmentMenu.js +76 -63
- package/dist/features/studio/components/Navbar.js +53 -80
- package/dist/features/vite/auth.js +1 -50
- package/dist/features/vite/iconGeneration.js +16 -0
- package/dist/features/vite/layoutGeneration.js +7 -3
- package/dist/features/vite/vite.d.ts +4 -2
- package/dist/features/vite/vite.js +24 -3
- package/dist/hooks/use-page-destinations.js +53 -0
- package/dist/lib/auth.js +40 -20
- package/dist/lib/preview-handoff.js +13 -0
- package/dist/lib/queries.js +12 -1
- package/dist/lib/server-auth-cookie.js +21 -0
- package/dist/lib/use-project-room.js +5 -2
- package/dist/studio-overlays.css +19 -15
- package/dist/studio.css +1 -1
- package/package.json +9 -4
- package/skills/camox/SKILL.md +38 -9
- package/skills/camox/references/block-definitions.md +107 -286
- package/skills/camox/references/cli-common.md +16 -4
- package/skills/camox/references/cli-content.md +158 -0
- package/skills/camox/references/cli-environments.md +1 -1
- package/skills/camox/references/dom-integrations.md +93 -0
- package/skills/camox/references/field-styling.md +50 -0
- package/skills/camox/references/layout-definitions.md +33 -147
- package/skills/camox/references/routed-layouts.md +71 -0
- package/skills/camoxify/SKILL.md +66 -0
- package/skills/camox/references/cli-blocks.md +0 -78
- package/skills/camox/references/cli-pages.md +0 -53
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@camox/ui/button";
|
|
4
|
+
import { Copy, Info } from "lucide-react";
|
|
5
|
+
import { Alert, AlertDescription } from "@camox/ui/alert";
|
|
6
|
+
import { toast } from "@camox/ui/toaster";
|
|
7
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@camox/ui/dialog";
|
|
8
|
+
import { Label } from "@camox/ui/label";
|
|
9
|
+
import { Textarea } from "@camox/ui/textarea";
|
|
10
|
+
|
|
11
|
+
//#region src/features/preview/components/SendFeedbackDialog.tsx
|
|
12
|
+
function feedbackPrompt(pageId, context) {
|
|
13
|
+
const request = `Address unresolved Camox feedback for page ID ${pageId}. Load the camox skill and follow its feedback workflow: read the page's comments with the CLI, make the requested changes, and resolve each addressed comment.`;
|
|
14
|
+
const additionalContext = context.trim();
|
|
15
|
+
return additionalContext ? `${request}\n\nAdditional context:\n${additionalContext}` : request;
|
|
16
|
+
}
|
|
17
|
+
function SendFeedbackDialog({ pageId, disabled }) {
|
|
18
|
+
const contextId = React.useId();
|
|
19
|
+
const [open, setOpen] = React.useState(false);
|
|
20
|
+
const [context, setContext] = React.useState("");
|
|
21
|
+
const [copying, setCopying] = React.useState(false);
|
|
22
|
+
const copyPrompt = async () => {
|
|
23
|
+
if (copying) return;
|
|
24
|
+
setCopying(true);
|
|
25
|
+
try {
|
|
26
|
+
await navigator.clipboard.writeText(feedbackPrompt(pageId, context));
|
|
27
|
+
toast.success("Prompt copied. Paste it into your coding agent.");
|
|
28
|
+
setOpen(false);
|
|
29
|
+
} catch {
|
|
30
|
+
toast.error("Could not copy the prompt. Please try again.");
|
|
31
|
+
} finally {
|
|
32
|
+
setCopying(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange: setOpen,
|
|
38
|
+
children: [/* @__PURE__ */ jsx(DialogTrigger, {
|
|
39
|
+
disabled,
|
|
40
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
41
|
+
type: "button",
|
|
42
|
+
className: "w-full"
|
|
43
|
+
}),
|
|
44
|
+
children: "Send feedback to agent..."
|
|
45
|
+
}), /* @__PURE__ */ jsxs(DialogContent, {
|
|
46
|
+
className: "sm:max-w-lg",
|
|
47
|
+
"aria-describedby": `${contextId}-description`,
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Send feedback to agent" }) }),
|
|
50
|
+
/* @__PURE__ */ jsxs(Alert, { children: [/* @__PURE__ */ jsx(Info, {}), /* @__PURE__ */ jsx(AlertDescription, {
|
|
51
|
+
id: `${contextId}-description`,
|
|
52
|
+
children: "Copy the prompt and paste it into your own coding agent outside of Camox Studio to address your feedback. It will resolve the comments on its own."
|
|
53
|
+
})] }),
|
|
54
|
+
/* @__PURE__ */ jsxs("div", {
|
|
55
|
+
className: "space-y-2",
|
|
56
|
+
children: [/* @__PURE__ */ jsxs(Label, {
|
|
57
|
+
htmlFor: contextId,
|
|
58
|
+
children: ["Additional context ", /* @__PURE__ */ jsx("span", {
|
|
59
|
+
className: "text-muted-foreground font-normal",
|
|
60
|
+
children: "(optional)"
|
|
61
|
+
})]
|
|
62
|
+
}), /* @__PURE__ */ jsx(Textarea, {
|
|
63
|
+
id: contextId,
|
|
64
|
+
value: context,
|
|
65
|
+
onChange: (event) => setContext(event.target.value),
|
|
66
|
+
placeholder: "Add an overall note or extra instructions for your agent…",
|
|
67
|
+
rows: 2,
|
|
68
|
+
className: "min-h-16"
|
|
69
|
+
})]
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsxs(Button, {
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: copyPrompt,
|
|
74
|
+
disabled: copying,
|
|
75
|
+
children: [/* @__PURE__ */ jsx(Copy, {}), copying ? "Copying…" : "Copy agent prompt"]
|
|
76
|
+
}) })
|
|
77
|
+
]
|
|
78
|
+
})]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
export { SendFeedbackDialog };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { repeatableItemMutations } from "../../../lib/queries.js";
|
|
2
1
|
import "../../provider/components/CamoxAppContext.js";
|
|
2
|
+
import { repeatableItemMutations } from "../../../lib/queries.js";
|
|
3
3
|
import { previewStore } from "../previewStore.js";
|
|
4
4
|
import { useRequireDraftSource } from "../../../core/hooks/useRequireDraftSource.js";
|
|
5
5
|
import { c } from "react/compiler-runtime";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/features/preview/overlayHighlights.ts
|
|
2
|
+
const states = ["hovered", "focused"];
|
|
3
|
+
/** Detached borders represent their parent, not the portal div's DOM depth. */
|
|
4
|
+
function targetOf(element) {
|
|
5
|
+
return element.hasAttribute("data-camox-detached") ? element.parentElement ?? element : element;
|
|
6
|
+
}
|
|
7
|
+
function depthOf(element) {
|
|
8
|
+
let depth = 0;
|
|
9
|
+
for (let parent = element.parentElement; parent; parent = parent.parentElement) depth++;
|
|
10
|
+
return depth;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Local and remote interactions publish the same candidate attributes. Resolve
|
|
14
|
+
* them together, independently for hover and selection, before the browser paints.
|
|
15
|
+
* Equal-depth candidates (e.g. a sidebar-hovered list) use document order.
|
|
16
|
+
*/
|
|
17
|
+
function observeOverlayHighlights(document) {
|
|
18
|
+
const Observer = document.defaultView?.MutationObserver;
|
|
19
|
+
if (!Observer) return () => {};
|
|
20
|
+
const winners = /* @__PURE__ */ new Map();
|
|
21
|
+
const update = () => {
|
|
22
|
+
for (const state of states) {
|
|
23
|
+
let winner;
|
|
24
|
+
let deepest = -1;
|
|
25
|
+
for (const candidate of document.querySelectorAll(`[data-camox-${state}]`)) {
|
|
26
|
+
const depth = depthOf(targetOf(candidate));
|
|
27
|
+
if (depth <= deepest) continue;
|
|
28
|
+
deepest = depth;
|
|
29
|
+
winner = candidate;
|
|
30
|
+
}
|
|
31
|
+
const previous = winners.get(state);
|
|
32
|
+
if (previous === winner) continue;
|
|
33
|
+
previous?.removeAttribute(`data-camox-highlight-${state}`);
|
|
34
|
+
winner?.setAttribute(`data-camox-highlight-${state}`, "");
|
|
35
|
+
if (winner) winners.set(state, winner);
|
|
36
|
+
else winners.delete(state);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const observer = new Observer(update);
|
|
40
|
+
observer.observe(document, {
|
|
41
|
+
subtree: true,
|
|
42
|
+
childList: true,
|
|
43
|
+
attributes: true,
|
|
44
|
+
attributeFilter: states.map((state) => `data-camox-${state}`)
|
|
45
|
+
});
|
|
46
|
+
update();
|
|
47
|
+
return () => {
|
|
48
|
+
observer.disconnect();
|
|
49
|
+
for (const [state, winner] of winners) winner.removeAttribute(`data-camox-highlight-${state}`);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { observeOverlayHighlights };
|
|
@@ -1,55 +1,78 @@
|
|
|
1
1
|
import { previewStore } from "./previewStore.js";
|
|
2
|
+
import { fieldTypesDictionary } from "../../core/lib/fieldTypes.js";
|
|
2
3
|
import { createStore } from "@xstate/store-react";
|
|
3
4
|
|
|
4
5
|
//#region src/features/preview/previewCommentsStore.ts
|
|
5
|
-
|
|
6
|
+
/** Resolve against the current definition, including nested repeater ancestry. */
|
|
7
|
+
function getCommentTargetFieldType(target, bundle, app) {
|
|
8
|
+
if (target.kind !== "block-field" && target.kind !== "item-field") return;
|
|
9
|
+
if (bundle.block.id !== target.blockId) return;
|
|
10
|
+
let schema = app.getBlockById(bundle.block.type)?._internal.contentSchema;
|
|
11
|
+
if (target.kind === "item-field") {
|
|
12
|
+
const path = [];
|
|
13
|
+
const visited = /* @__PURE__ */ new Set();
|
|
14
|
+
let itemId = target.itemId;
|
|
15
|
+
while (itemId != null) {
|
|
16
|
+
if (visited.has(itemId)) return;
|
|
17
|
+
visited.add(itemId);
|
|
18
|
+
const item = bundle.repeatableItems.find((entry) => entry.id === itemId);
|
|
19
|
+
if (!item) return;
|
|
20
|
+
path.unshift(item.fieldName);
|
|
21
|
+
itemId = item.parentItemId;
|
|
22
|
+
}
|
|
23
|
+
for (const fieldName of path) schema = schema?.properties?.[fieldName]?.items;
|
|
24
|
+
}
|
|
25
|
+
const fieldType = schema?.properties?.[target.fieldName]?.fieldType;
|
|
26
|
+
if (fieldType && Object.hasOwn(fieldTypesDictionary, fieldType)) return fieldType;
|
|
27
|
+
}
|
|
28
|
+
/** Field types belong to the current app, never to persisted comment targets. */
|
|
29
|
+
function revealCommentTarget(target, fieldType) {
|
|
6
30
|
previewStore.send({
|
|
7
31
|
type: "setCommentMode",
|
|
8
32
|
enabled: false
|
|
9
33
|
});
|
|
10
34
|
previewStore.send({ type: "closeAddBlockSidebar" });
|
|
11
35
|
previewStore.send({ type: "clearPeekedBlock" });
|
|
12
|
-
if (target.
|
|
36
|
+
if (target.kind === "page") {
|
|
13
37
|
previewStore.send({
|
|
14
38
|
type: "setSelection",
|
|
15
39
|
selection: null
|
|
16
40
|
});
|
|
17
41
|
return;
|
|
18
42
|
}
|
|
19
|
-
if (target.
|
|
43
|
+
if ((target.kind === "block-field" || target.kind === "item-field") && fieldType) {
|
|
20
44
|
previewStore.send({
|
|
21
45
|
type: "setSelection",
|
|
22
|
-
selection: target.
|
|
23
|
-
type: "
|
|
46
|
+
selection: target.kind === "item-field" ? {
|
|
47
|
+
type: "item-field",
|
|
24
48
|
blockId: target.blockId,
|
|
49
|
+
itemId: target.itemId,
|
|
25
50
|
fieldName: target.fieldName,
|
|
26
|
-
fieldType
|
|
51
|
+
fieldType
|
|
27
52
|
} : {
|
|
28
|
-
type: "
|
|
53
|
+
type: "block-field",
|
|
29
54
|
blockId: target.blockId,
|
|
30
|
-
itemId: target.itemId,
|
|
31
55
|
fieldName: target.fieldName,
|
|
32
|
-
fieldType
|
|
56
|
+
fieldType
|
|
33
57
|
}
|
|
34
58
|
});
|
|
35
59
|
return;
|
|
36
60
|
}
|
|
37
61
|
previewStore.send({
|
|
38
62
|
type: "setSelection",
|
|
39
|
-
selection:
|
|
40
|
-
type: "block",
|
|
41
|
-
blockId: target.blockId
|
|
42
|
-
} : {
|
|
63
|
+
selection: "itemId" in target ? {
|
|
43
64
|
type: "item",
|
|
44
65
|
blockId: target.blockId,
|
|
45
66
|
itemId: target.itemId
|
|
67
|
+
} : {
|
|
68
|
+
type: "block",
|
|
69
|
+
blockId: target.blockId
|
|
46
70
|
}
|
|
47
71
|
});
|
|
48
72
|
}
|
|
49
|
-
/**
|
|
73
|
+
/** Server comments belong to the query cache; this store holds composer UI only. */
|
|
50
74
|
const previewCommentsStore = createStore({
|
|
51
75
|
context: {
|
|
52
|
-
comments: [],
|
|
53
76
|
draft: null,
|
|
54
77
|
activeId: null,
|
|
55
78
|
focusTarget: null
|
|
@@ -58,6 +81,7 @@ const previewCommentsStore = createStore({
|
|
|
58
81
|
startComment: (context, event) => ({
|
|
59
82
|
...context,
|
|
60
83
|
draft: {
|
|
84
|
+
id: crypto.randomUUID(),
|
|
61
85
|
pageId: event.pageId,
|
|
62
86
|
target: event.target,
|
|
63
87
|
message: ""
|
|
@@ -69,27 +93,23 @@ const previewCommentsStore = createStore({
|
|
|
69
93
|
...context,
|
|
70
94
|
focusTarget: null
|
|
71
95
|
}),
|
|
72
|
-
setMessage: (context, event) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
...context
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
setMessage: (context, event) => {
|
|
97
|
+
if (!context.draft || context.draft.message === event.message) return context;
|
|
98
|
+
return {
|
|
99
|
+
...context,
|
|
100
|
+
draft: {
|
|
101
|
+
...context.draft,
|
|
102
|
+
id: crypto.randomUUID(),
|
|
103
|
+
message: event.message
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
postSucceeded: (context, event) => {
|
|
108
|
+
if (context.draft?.id !== event.draft.id) return context;
|
|
82
109
|
return {
|
|
83
110
|
...context,
|
|
84
|
-
comments: [...context.comments, {
|
|
85
|
-
...draft,
|
|
86
|
-
id: event.id,
|
|
87
|
-
message: draft.message.trim(),
|
|
88
|
-
author: event.author,
|
|
89
|
-
createdAt: event.createdAt
|
|
90
|
-
}],
|
|
91
111
|
draft: null,
|
|
92
|
-
activeId: event.id,
|
|
112
|
+
activeId: event.draft.id,
|
|
93
113
|
focusTarget: null
|
|
94
114
|
};
|
|
95
115
|
},
|
|
@@ -112,4 +132,4 @@ const previewCommentsStore = createStore({
|
|
|
112
132
|
});
|
|
113
133
|
|
|
114
134
|
//#endregion
|
|
115
|
-
export { previewCommentsStore, revealCommentTarget };
|
|
135
|
+
export { getCommentTargetFieldType, previewCommentsStore, revealCommentTarget };
|
|
@@ -5,6 +5,30 @@ import { createContext, useContext } from "react";
|
|
|
5
5
|
|
|
6
6
|
//#region src/features/preview/previewSelection.ts
|
|
7
7
|
const PreviewPageContext = createContext(null);
|
|
8
|
+
function commentTarget(selection) {
|
|
9
|
+
switch (selection.type) {
|
|
10
|
+
case "block": return {
|
|
11
|
+
kind: "block",
|
|
12
|
+
blockId: selection.blockId
|
|
13
|
+
};
|
|
14
|
+
case "item": return {
|
|
15
|
+
kind: "item",
|
|
16
|
+
blockId: selection.blockId,
|
|
17
|
+
itemId: selection.itemId
|
|
18
|
+
};
|
|
19
|
+
case "block-field": return {
|
|
20
|
+
kind: "block-field",
|
|
21
|
+
blockId: selection.blockId,
|
|
22
|
+
fieldName: selection.fieldName
|
|
23
|
+
};
|
|
24
|
+
case "item-field": return {
|
|
25
|
+
kind: "item-field",
|
|
26
|
+
blockId: selection.blockId,
|
|
27
|
+
itemId: selection.itemId,
|
|
28
|
+
fieldName: selection.fieldName
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
8
32
|
/** Both editing and commenting use the target already known by the editable component. */
|
|
9
33
|
function selectPreviewTarget(selection, pageId, event) {
|
|
10
34
|
const snapshot = previewStore.getSnapshot();
|
|
@@ -27,39 +51,20 @@ function selectPreviewTarget(selection, pageId, event) {
|
|
|
27
51
|
event.preventDefault();
|
|
28
52
|
event.stopPropagation();
|
|
29
53
|
if (pageId === null) return;
|
|
30
|
-
const
|
|
31
|
-
const rect = element.getBoundingClientRect();
|
|
32
|
-
const field = selection.type === "block-field" || selection.type === "item-field";
|
|
33
|
-
const itemId = "itemId" in selection ? selection.itemId : void 0;
|
|
34
|
-
const attribute = field ? "data-camox-field-id" : selection.type === "item" ? "data-camox-repeater-item-id" : element.hasAttribute("data-camox-comment-block-id") ? "data-camox-comment-block-id" : "data-camox-block-id";
|
|
35
|
-
const id = field ? [
|
|
36
|
-
selection.blockId,
|
|
37
|
-
itemId,
|
|
38
|
-
selection.fieldName
|
|
39
|
-
].filter((part) => part != null).join("__") : String(itemId ?? selection.blockId);
|
|
40
|
-
const target = {
|
|
41
|
-
blockId: selection.blockId,
|
|
42
|
-
itemId,
|
|
43
|
-
fieldName: field ? selection.fieldName : void 0,
|
|
44
|
-
fieldType: field ? selection.fieldType : void 0,
|
|
45
|
-
selector: `[${attribute}="${CSS.escape(id)}"]`,
|
|
46
|
-
label: field ? `Field · ${selection.fieldName}` : `${selection.type === "item" ? "Item" : "Block"} · ${id}`,
|
|
47
|
-
x: event.clientX == null ? .5 : Math.max(0, Math.min(1, (event.clientX - rect.left) / (rect.width || 1))),
|
|
48
|
-
y: event.clientY == null ? .5 : Math.max(0, Math.min(1, (event.clientY - rect.top) / (rect.height || 1)))
|
|
49
|
-
};
|
|
54
|
+
const target = commentTarget(selection);
|
|
50
55
|
previewCommentsStore.send({
|
|
51
56
|
type: "startComment",
|
|
52
57
|
pageId,
|
|
53
58
|
target,
|
|
54
59
|
focusComposer: true
|
|
55
60
|
});
|
|
56
|
-
revealCommentTarget(target);
|
|
61
|
+
revealCommentTarget(target, "fieldType" in selection ? selection.fieldType : void 0);
|
|
57
62
|
}
|
|
58
63
|
function usePreviewSelection() {
|
|
59
64
|
const $ = c(3);
|
|
60
|
-
if ($[0] !== "
|
|
65
|
+
if ($[0] !== "5d794da8ed357c0619bb8d670a0c5261637b093c5e0fce67e7cdf572e265bb4e") {
|
|
61
66
|
for (let $i = 0; $i < 3; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
62
|
-
$[0] = "
|
|
67
|
+
$[0] = "5d794da8ed357c0619bb8d670a0c5261637b093c5e0fce67e7cdf572e265bb4e";
|
|
63
68
|
}
|
|
64
69
|
const pageId = useContext(PreviewPageContext);
|
|
65
70
|
let t0;
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
* not a separate publishing UI or a sequence of independent block requests.
|
|
6
6
|
*/
|
|
7
7
|
function buildPublicationPlan(target) {
|
|
8
|
+
if (target.kind === "layout" && target.layout.kind === "singleton") return {
|
|
9
|
+
title: "Publish page content",
|
|
10
|
+
description: `Publishes the editable blocks on ${target.name}. The route and application data are controlled by code, not publishing.`,
|
|
11
|
+
items: [{
|
|
12
|
+
key: `layout:${target.layout.id}`,
|
|
13
|
+
label: `${target.name} blocks`,
|
|
14
|
+
impact: "Updates this page’s before and after blocks. Synced block changes also apply wherever those blocks are used.",
|
|
15
|
+
optional: false
|
|
16
|
+
}]
|
|
17
|
+
};
|
|
8
18
|
if (target.kind === "layout") return {
|
|
9
19
|
title: target.layout.status === "modified" ? "Publish changes" : "Publish layout",
|
|
10
20
|
description: `Publishes ${target.name} on every page using this layout. Generated content and external data are not published by this action.`,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { commentQueries } from "../../lib/queries.js";
|
|
2
|
+
import { areCommentsEnabled } from "./commentsEnabled.js";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
|
+
|
|
6
|
+
//#region src/features/preview/usePageComments.ts
|
|
7
|
+
function usePageComments(pageId) {
|
|
8
|
+
const $ = c(8);
|
|
9
|
+
if ($[0] !== "53184284ca58f3bc8c3a79ab856fb86253dedefa1ff319d9aac05c62162bf3b0") {
|
|
10
|
+
for (let $i = 0; $i < 8; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
11
|
+
$[0] = "53184284ca58f3bc8c3a79ab856fb86253dedefa1ff319d9aac05c62162bf3b0";
|
|
12
|
+
}
|
|
13
|
+
let t0;
|
|
14
|
+
if ($[1] !== pageId) {
|
|
15
|
+
t0 = commentQueries.list(pageId ?? 0);
|
|
16
|
+
$[1] = pageId;
|
|
17
|
+
$[2] = t0;
|
|
18
|
+
} else t0 = $[2];
|
|
19
|
+
let t1;
|
|
20
|
+
if ($[3] !== pageId) {
|
|
21
|
+
t1 = areCommentsEnabled() && pageId != null;
|
|
22
|
+
$[3] = pageId;
|
|
23
|
+
$[4] = t1;
|
|
24
|
+
} else t1 = $[4];
|
|
25
|
+
let t2;
|
|
26
|
+
if ($[5] !== t0 || $[6] !== t1) {
|
|
27
|
+
t2 = {
|
|
28
|
+
...t0,
|
|
29
|
+
enabled: t1
|
|
30
|
+
};
|
|
31
|
+
$[5] = t0;
|
|
32
|
+
$[6] = t1;
|
|
33
|
+
$[7] = t2;
|
|
34
|
+
} else t2 = $[7];
|
|
35
|
+
return useQuery(t2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { usePageComments };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AuthContext, createCamoxAuthClient, useProcessOtt } from "../../lib/auth.js";
|
|
2
2
|
import { initApiClient } from "../../lib/api-client.js";
|
|
3
|
-
import { projectQueries } from "../../lib/queries.js";
|
|
4
3
|
import { CamoxAppProvider } from "./components/CamoxAppContext.js";
|
|
4
|
+
import { projectQueries } from "../../lib/queries.js";
|
|
5
5
|
import { useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import * as React from "react";
|
|
7
|
-
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
|
|
9
9
|
//#region src/features/provider/CoreCamoxProvider.tsx
|
|
10
10
|
function isLocalhostPreview() {
|
|
@@ -25,8 +25,31 @@ function CoreCamoxProvider({ apiUrl, authenticationUrl, camoxApp, children, envi
|
|
|
25
25
|
queryClient.setQueryData(projectQueries.getBySlug(projectSlug).queryKey, initialProject);
|
|
26
26
|
seededProject.current = initialProject;
|
|
27
27
|
}
|
|
28
|
-
useProcessOtt(authClient
|
|
29
|
-
|
|
28
|
+
const { ready, error } = useProcessOtt(authClient, {
|
|
29
|
+
projectSlug,
|
|
30
|
+
environmentName,
|
|
31
|
+
apiUrl
|
|
32
|
+
});
|
|
33
|
+
const [isPreviewHandoff, setIsPreviewHandoff] = React.useState(false);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
setIsPreviewHandoff(new URL(window.location.href).searchParams.has("camox-preview"));
|
|
36
|
+
}, []);
|
|
37
|
+
if (error) return /* @__PURE__ */ jsx("div", {
|
|
38
|
+
role: "alert",
|
|
39
|
+
"data-camox-preview": "error",
|
|
40
|
+
children: error
|
|
41
|
+
});
|
|
42
|
+
if (isPreviewHandoff && !ready) return /* @__PURE__ */ jsx("div", {
|
|
43
|
+
role: "status",
|
|
44
|
+
"data-camox-preview": "pending",
|
|
45
|
+
children: "Signing in to draft preview…"
|
|
46
|
+
});
|
|
47
|
+
if (isPreviewHandoff && !initialAuthenticated) return /* @__PURE__ */ jsx("div", {
|
|
48
|
+
role: "alert",
|
|
49
|
+
"data-camox-preview": "error",
|
|
50
|
+
children: "Draft preview is not authenticated. Run camox preview again for a fresh link."
|
|
51
|
+
});
|
|
52
|
+
return /* @__PURE__ */ jsxs(AuthContext.Provider, {
|
|
30
53
|
value: {
|
|
31
54
|
authClient,
|
|
32
55
|
authenticationUrl,
|
|
@@ -35,10 +58,19 @@ function CoreCamoxProvider({ apiUrl, authenticationUrl, camoxApp, children, envi
|
|
|
35
58
|
environmentName,
|
|
36
59
|
initialAuthenticated
|
|
37
60
|
},
|
|
38
|
-
children: /* @__PURE__ */
|
|
61
|
+
children: [isPreviewHandoff && /* @__PURE__ */ jsxs("div", {
|
|
62
|
+
role: "status",
|
|
63
|
+
"data-camox-preview": "ready",
|
|
64
|
+
children: [
|
|
65
|
+
"Draft preview · ",
|
|
66
|
+
projectSlug,
|
|
67
|
+
" · ",
|
|
68
|
+
environmentName
|
|
69
|
+
]
|
|
70
|
+
}), /* @__PURE__ */ jsx(CamoxAppProvider, {
|
|
39
71
|
app: camoxApp,
|
|
40
72
|
children
|
|
41
|
-
})
|
|
73
|
+
})]
|
|
42
74
|
});
|
|
43
75
|
}
|
|
44
76
|
|
|
@@ -13,15 +13,15 @@ import studioCssUrl from "virtual:camox-studio-css";
|
|
|
13
13
|
|
|
14
14
|
//#region src/features/provider/LocalhostPreviewProvider.tsx
|
|
15
15
|
function LocalhostPreviewProvider(t0) {
|
|
16
|
-
const $ = c(
|
|
17
|
-
if ($[0] !== "
|
|
18
|
-
for (let $i = 0; $i <
|
|
19
|
-
$[0] = "
|
|
16
|
+
const $ = c(31);
|
|
17
|
+
if ($[0] !== "197882bd626662aebba1ae79edba0ab6d320481729dba64af237363088a51e3f") {
|
|
18
|
+
for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
19
|
+
$[0] = "197882bd626662aebba1ae79edba0ab6d320481729dba64af237363088a51e3f";
|
|
20
20
|
}
|
|
21
21
|
const { children } = t0;
|
|
22
22
|
const signInRedirect = useSignInRedirect();
|
|
23
23
|
const { resolvedTheme } = useApplyTheme();
|
|
24
|
-
const [
|
|
24
|
+
const [signInReason, setSignInReason] = React.useState(null);
|
|
25
25
|
useAdminShortcuts();
|
|
26
26
|
let t1;
|
|
27
27
|
let t2;
|
|
@@ -33,7 +33,7 @@ function LocalhostPreviewProvider(t0) {
|
|
|
33
33
|
aliases: ["Enter edit mode", "Edit mode"],
|
|
34
34
|
groupLabel: "Preview",
|
|
35
35
|
checkIfAvailable: _temp,
|
|
36
|
-
execute: () =>
|
|
36
|
+
execute: () => setSignInReason("edit"),
|
|
37
37
|
shortcut: EDIT_MODE_SHORTCUT
|
|
38
38
|
}];
|
|
39
39
|
actionsStore.send({
|
|
@@ -81,9 +81,14 @@ function LocalhostPreviewProvider(t0) {
|
|
|
81
81
|
} else t5 = $[6];
|
|
82
82
|
let t6;
|
|
83
83
|
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
|
84
|
-
t6 = {
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
t6 = {
|
|
85
|
+
onEditModeChange: (checked) => {
|
|
86
|
+
if (checked) setSignInReason("edit");
|
|
87
|
+
},
|
|
88
|
+
onCommentModeChange: (enabled) => {
|
|
89
|
+
if (enabled) setSignInReason("feedback");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
87
92
|
$[7] = t6;
|
|
88
93
|
} else t6 = $[7];
|
|
89
94
|
let t7;
|
|
@@ -98,54 +103,85 @@ function LocalhostPreviewProvider(t0) {
|
|
|
98
103
|
$[8] = children;
|
|
99
104
|
$[9] = t7;
|
|
100
105
|
} else t7 = $[9];
|
|
101
|
-
|
|
102
|
-
if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
|
|
103
|
-
t8 = /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsx(AlertDialogTitle, { children: "Sign in to edit" }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: "You need to sign in before you can enable edit mode." })] });
|
|
104
|
-
$[10] = t8;
|
|
105
|
-
} else t8 = $[10];
|
|
106
|
+
const t8 = signInReason !== null;
|
|
106
107
|
let t9;
|
|
107
|
-
if ($[
|
|
108
|
-
t9 =
|
|
108
|
+
if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
|
|
109
|
+
t9 = (open) => {
|
|
110
|
+
if (!open) setSignInReason(null);
|
|
111
|
+
};
|
|
112
|
+
$[10] = t9;
|
|
113
|
+
} else t9 = $[10];
|
|
114
|
+
const t10 = signInReason === "feedback" ? "Sign in to leave feedback" : "Sign in to edit";
|
|
115
|
+
let t11;
|
|
116
|
+
if ($[11] !== t10) {
|
|
117
|
+
t11 = /* @__PURE__ */ jsx(AlertDialogTitle, { children: t10 });
|
|
118
|
+
$[11] = t10;
|
|
119
|
+
$[12] = t11;
|
|
120
|
+
} else t11 = $[12];
|
|
121
|
+
const t12 = signInReason === "feedback" ? "You need to sign in before you can leave feedback for agents." : "You need to sign in before you can enable edit mode.";
|
|
122
|
+
let t13;
|
|
123
|
+
if ($[13] !== t12) {
|
|
124
|
+
t13 = /* @__PURE__ */ jsx(AlertDialogDescription, { children: t12 });
|
|
125
|
+
$[13] = t12;
|
|
126
|
+
$[14] = t13;
|
|
127
|
+
} else t13 = $[14];
|
|
128
|
+
let t14;
|
|
129
|
+
if ($[15] !== t11 || $[16] !== t13) {
|
|
130
|
+
t14 = /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [t11, t13] });
|
|
131
|
+
$[15] = t11;
|
|
132
|
+
$[16] = t13;
|
|
133
|
+
$[17] = t14;
|
|
134
|
+
} else t14 = $[17];
|
|
135
|
+
let t15;
|
|
136
|
+
if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
|
|
137
|
+
t15 = /* @__PURE__ */ jsx(AlertDialogCancel, {
|
|
109
138
|
variant: "outline",
|
|
110
139
|
size: "default",
|
|
111
140
|
children: "Cancel"
|
|
112
141
|
});
|
|
113
|
-
$[
|
|
114
|
-
} else
|
|
115
|
-
let
|
|
116
|
-
if ($[
|
|
117
|
-
|
|
142
|
+
$[18] = t15;
|
|
143
|
+
} else t15 = $[18];
|
|
144
|
+
let t16;
|
|
145
|
+
if ($[19] !== signInRedirect) {
|
|
146
|
+
t16 = /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [t15, /* @__PURE__ */ jsx(AlertDialogAction, {
|
|
118
147
|
onClick: signInRedirect,
|
|
119
148
|
children: "Sign in"
|
|
120
|
-
})] })
|
|
121
|
-
$[
|
|
122
|
-
$[
|
|
123
|
-
} else
|
|
124
|
-
let
|
|
125
|
-
if ($[
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
})] });
|
|
150
|
+
$[19] = signInRedirect;
|
|
151
|
+
$[20] = t16;
|
|
152
|
+
} else t16 = $[20];
|
|
153
|
+
let t17;
|
|
154
|
+
if ($[21] !== t14 || $[22] !== t16) {
|
|
155
|
+
t17 = /* @__PURE__ */ jsxs(AlertDialogContent, { children: [t14, t16] });
|
|
156
|
+
$[21] = t14;
|
|
157
|
+
$[22] = t16;
|
|
158
|
+
$[23] = t17;
|
|
159
|
+
} else t17 = $[23];
|
|
160
|
+
let t18;
|
|
161
|
+
if ($[24] !== t17 || $[25] !== t8) {
|
|
162
|
+
t18 = /* @__PURE__ */ jsx(AlertDialog, {
|
|
163
|
+
open: t8,
|
|
164
|
+
onOpenChange: t9,
|
|
165
|
+
children: t17
|
|
130
166
|
});
|
|
131
|
-
$[
|
|
132
|
-
$[
|
|
133
|
-
$[
|
|
134
|
-
} else
|
|
135
|
-
let
|
|
136
|
-
if ($[
|
|
137
|
-
|
|
167
|
+
$[24] = t17;
|
|
168
|
+
$[25] = t8;
|
|
169
|
+
$[26] = t18;
|
|
170
|
+
} else t18 = $[26];
|
|
171
|
+
let t19;
|
|
172
|
+
if ($[27] !== t18 || $[28] !== t5 || $[29] !== t7) {
|
|
173
|
+
t19 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
138
174
|
t3,
|
|
139
175
|
t5,
|
|
140
176
|
t7,
|
|
141
|
-
|
|
177
|
+
t18
|
|
142
178
|
] });
|
|
143
|
-
$[
|
|
144
|
-
$[
|
|
145
|
-
$[
|
|
146
|
-
$[
|
|
147
|
-
} else
|
|
148
|
-
return
|
|
179
|
+
$[27] = t18;
|
|
180
|
+
$[28] = t5;
|
|
181
|
+
$[29] = t7;
|
|
182
|
+
$[30] = t19;
|
|
183
|
+
} else t19 = $[30];
|
|
184
|
+
return t19;
|
|
149
185
|
}
|
|
150
186
|
function _temp2(action) {
|
|
151
187
|
return action.id;
|