camox 0.31.5 → 0.33.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/dist/core/components/lexical/InlineLexicalEditor.js +79 -11
- package/dist/core/components/lexical/SelectionBroadcaster.js +106 -20
- package/dist/core/components/lexical/SidebarLexicalEditor.js +442 -8
- package/dist/core/components/lexical/TextLinkPopover.js +327 -0
- package/dist/core/components/lexical/editorConfig.js +11 -5
- package/dist/core/createApp.d.ts +9 -0
- package/dist/core/createBlock.d.ts +7 -2
- package/dist/core/createBlock.js +101 -110
- package/dist/core/hooks/useIsEditable.js +6 -10
- package/dist/core/lib/fieldTypes.js +10 -219
- package/dist/core/lib/lexicalReact.d.ts +26 -0
- package/dist/core/lib/lexicalReact.js +50 -15
- package/dist/core/lib/lexicalState.js +34 -0
- package/dist/core/lib/textLinks.js +28 -0
- package/dist/features/agent-chat/components/AgentChatSidebar.js +172 -0
- package/dist/features/agent-chat/components/AgentChatThread.js +9 -9
- package/dist/features/preview/CamoxPreview.js +137 -729
- package/dist/features/preview/components/{AddBlockSheet.js → AddBlockSidebar.js} +121 -131
- package/dist/features/preview/components/AssetFieldEditor.js +107 -82
- package/dist/features/preview/components/AssetLightbox.js +4 -4
- package/dist/features/preview/components/AssetPickerModal.js +277 -0
- package/dist/features/preview/components/BlockActionsPopover.js +18 -24
- package/dist/features/preview/components/CreatePageModal.js +6 -6
- package/dist/features/preview/components/FieldToolbar.js +118 -111
- package/dist/features/preview/components/ItemFieldsEditor.js +98 -125
- package/dist/features/preview/components/LeftSidebar.js +64 -0
- package/dist/features/preview/components/LinkFieldEditor.js +1 -1
- package/dist/features/preview/components/MultipleAssetFieldEditor.js +94 -56
- package/dist/features/preview/components/OverlayTracker.js +15 -15
- package/dist/features/preview/components/Overlays.js +12 -12
- package/dist/features/preview/components/PageEditorSidebar.js +563 -0
- package/dist/features/preview/components/PageInfoSidebar.js +1453 -0
- package/dist/features/preview/components/PageNavigatorSidebar.js +590 -0
- package/dist/features/preview/components/PagePicker.js +3 -3
- package/dist/features/preview/components/PageStatusBadge.js +1 -1
- package/dist/features/preview/components/PageTree.js +204 -564
- package/dist/features/preview/components/PreviewPanel.js +123 -158
- package/dist/features/preview/components/PreviewToolbar.js +260 -256
- package/dist/features/preview/components/PublishDialog.js +2 -2
- package/dist/features/preview/components/RepeatableItemsList.js +2 -2
- package/dist/features/preview/components/RightSidebar.js +109 -0
- package/dist/features/preview/components/UnlinkAssetButton.js +2 -2
- package/dist/features/preview/components/useRepeatableItemActions.js +3 -138
- package/dist/features/preview/components/useUpdateBlockPosition.js +1 -1
- package/dist/features/preview/previewQueryFns.js +23 -0
- package/dist/features/preview/previewStore.js +46 -63
- package/dist/features/provider/CamoxProvider.js +255 -70
- package/dist/features/provider/useAdminShortcuts.js +5 -48
- package/dist/features/routes/pageRoute.js +1 -1
- package/dist/features/studio/CamoxStudio.js +6 -5
- package/dist/features/studio/components/EnvironmentMenu.js +2 -2
- package/dist/features/studio/components/Navbar.js +53 -71
- package/dist/features/studio/components/ProjectMenu.js +1 -1
- package/dist/features/studio/components/UserButton.js +1 -1
- package/dist/features/studio/routes.js +7 -0
- package/dist/features/studio/useTheme.js +67 -33
- package/dist/features/vite/routeGeneration.js +68 -66
- package/dist/features/vite/vite.js +2 -0
- package/dist/hooks/use-file-upload.js +1 -1
- package/dist/lib/auth.js +23 -22
- package/dist/lib/utils.js +1 -1
- package/dist/studio-overlays.css +1 -1
- package/dist/studio.css +1 -1
- package/package.json +5 -4
- package/skills/camox-block/SKILL.md +23 -1
- package/dist/features/agent-chat/components/AgentChatSheet.js +0 -207
- package/dist/features/preview/components/AssetPickerGrid.js +0 -236
- package/dist/features/preview/components/PageContentSheet.js +0 -608
- package/dist/features/preview/components/PageMetadataModal.js +0 -908
- package/dist/features/preview/components/PreviewSideSheet.js +0 -76
|
@@ -1,224 +1,15 @@
|
|
|
1
|
-
import { previewStore } from "../../features/preview/previewStore.js";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { ChevronDown, FileIcon, FrameIcon, ImageIcon, Images, Link2, List, ToggleLeft, Type } from "lucide-react";
|
|
4
|
-
|
|
5
1
|
//#region src/core/lib/fieldTypes.tsx
|
|
6
2
|
const fieldTypesDictionary = {
|
|
7
|
-
String: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
18
|
-
previewStore.send({
|
|
19
|
-
type: "selectBlockField",
|
|
20
|
-
blockId,
|
|
21
|
-
fieldName,
|
|
22
|
-
fieldType: "String"
|
|
23
|
-
});
|
|
24
|
-
previewStore.send({
|
|
25
|
-
type: "openBlockContentSheet",
|
|
26
|
-
blockId
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
Repeater: {
|
|
31
|
-
label: "Repeater",
|
|
32
|
-
isScalar: false,
|
|
33
|
-
isContentEditable: false,
|
|
34
|
-
hasOwnView: false,
|
|
35
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(List, { ...props }),
|
|
36
|
-
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
37
|
-
onTreeDoubleClick: ({ blockId }) => {
|
|
38
|
-
previewStore.send({
|
|
39
|
-
type: "setFocusedBlock",
|
|
40
|
-
blockId
|
|
41
|
-
});
|
|
42
|
-
previewStore.send({
|
|
43
|
-
type: "openBlockContentSheet",
|
|
44
|
-
blockId
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
Enum: {
|
|
49
|
-
label: "Enum",
|
|
50
|
-
isScalar: true,
|
|
51
|
-
isContentEditable: false,
|
|
52
|
-
hasOwnView: false,
|
|
53
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(ChevronDown, { ...props }),
|
|
54
|
-
getLabel: (value) => value,
|
|
55
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
56
|
-
previewStore.send({
|
|
57
|
-
type: "selectBlockField",
|
|
58
|
-
blockId,
|
|
59
|
-
fieldName,
|
|
60
|
-
fieldType: "Enum"
|
|
61
|
-
});
|
|
62
|
-
previewStore.send({
|
|
63
|
-
type: "openBlockContentSheet",
|
|
64
|
-
blockId
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
Boolean: {
|
|
69
|
-
label: "Boolean",
|
|
70
|
-
isScalar: true,
|
|
71
|
-
isContentEditable: false,
|
|
72
|
-
hasOwnView: false,
|
|
73
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(ToggleLeft, { ...props }),
|
|
74
|
-
getLabel: (value) => JSON.stringify(value),
|
|
75
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
76
|
-
previewStore.send({
|
|
77
|
-
type: "selectBlockField",
|
|
78
|
-
blockId,
|
|
79
|
-
fieldName,
|
|
80
|
-
fieldType: "Boolean"
|
|
81
|
-
});
|
|
82
|
-
previewStore.send({
|
|
83
|
-
type: "openBlockContentSheet",
|
|
84
|
-
blockId
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
Embed: {
|
|
89
|
-
label: "Embed",
|
|
90
|
-
isScalar: true,
|
|
91
|
-
isContentEditable: false,
|
|
92
|
-
hasOwnView: false,
|
|
93
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(FrameIcon, { ...props }),
|
|
94
|
-
getLabel: (value, { schemaTitle, fieldName, fetchedTitle }) => {
|
|
95
|
-
let domain = null;
|
|
96
|
-
try {
|
|
97
|
-
domain = new URL(value).hostname.replace(/^www\./, "");
|
|
98
|
-
} catch {}
|
|
99
|
-
return fetchedTitle ?? schemaTitle ?? domain ?? fieldName;
|
|
100
|
-
},
|
|
101
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
102
|
-
previewStore.send({
|
|
103
|
-
type: "selectBlockField",
|
|
104
|
-
blockId,
|
|
105
|
-
fieldName,
|
|
106
|
-
fieldType: "Embed"
|
|
107
|
-
});
|
|
108
|
-
previewStore.send({
|
|
109
|
-
type: "openBlockContentSheet",
|
|
110
|
-
blockId
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
Link: {
|
|
115
|
-
label: "Link",
|
|
116
|
-
isScalar: false,
|
|
117
|
-
isContentEditable: false,
|
|
118
|
-
hasOwnView: true,
|
|
119
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(Link2, { ...props }),
|
|
120
|
-
getLabel: (value) => value?.text ?? "",
|
|
121
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
122
|
-
previewStore.send({
|
|
123
|
-
type: "selectBlockField",
|
|
124
|
-
blockId,
|
|
125
|
-
fieldName,
|
|
126
|
-
fieldType: "Link"
|
|
127
|
-
});
|
|
128
|
-
previewStore.send({
|
|
129
|
-
type: "openBlockContentSheet",
|
|
130
|
-
blockId
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
Image: {
|
|
135
|
-
label: "Image",
|
|
136
|
-
isScalar: false,
|
|
137
|
-
isContentEditable: false,
|
|
138
|
-
hasOwnView: true,
|
|
139
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(ImageIcon, { ...props }),
|
|
140
|
-
getLabel: (value, { schemaTitle, fieldName }) => {
|
|
141
|
-
const filename = value?.filename;
|
|
142
|
-
if (!filename) return `Missing ${schemaTitle ?? fieldName}`;
|
|
143
|
-
return filename;
|
|
144
|
-
},
|
|
145
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
146
|
-
previewStore.send({
|
|
147
|
-
type: "selectBlockField",
|
|
148
|
-
blockId,
|
|
149
|
-
fieldName,
|
|
150
|
-
fieldType: "Image"
|
|
151
|
-
});
|
|
152
|
-
previewStore.send({
|
|
153
|
-
type: "openBlockContentSheet",
|
|
154
|
-
blockId
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
File: {
|
|
159
|
-
label: "File",
|
|
160
|
-
isScalar: false,
|
|
161
|
-
isContentEditable: false,
|
|
162
|
-
hasOwnView: true,
|
|
163
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(FileIcon, { ...props }),
|
|
164
|
-
getLabel: (value, { schemaTitle, fieldName }) => {
|
|
165
|
-
const filename = value?.filename;
|
|
166
|
-
if (!filename) return `Missing ${schemaTitle ?? fieldName}`;
|
|
167
|
-
return filename;
|
|
168
|
-
},
|
|
169
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
170
|
-
previewStore.send({
|
|
171
|
-
type: "selectBlockField",
|
|
172
|
-
blockId,
|
|
173
|
-
fieldName,
|
|
174
|
-
fieldType: "File"
|
|
175
|
-
});
|
|
176
|
-
previewStore.send({
|
|
177
|
-
type: "openBlockContentSheet",
|
|
178
|
-
blockId
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
ImageList: {
|
|
183
|
-
label: "Image list",
|
|
184
|
-
isScalar: false,
|
|
185
|
-
isContentEditable: false,
|
|
186
|
-
hasOwnView: true,
|
|
187
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(Images, { ...props }),
|
|
188
|
-
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
189
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
190
|
-
previewStore.send({
|
|
191
|
-
type: "selectBlockField",
|
|
192
|
-
blockId,
|
|
193
|
-
fieldName,
|
|
194
|
-
fieldType: "Image"
|
|
195
|
-
});
|
|
196
|
-
previewStore.send({
|
|
197
|
-
type: "openBlockContentSheet",
|
|
198
|
-
blockId
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
FileList: {
|
|
203
|
-
label: "File list",
|
|
204
|
-
isScalar: false,
|
|
205
|
-
isContentEditable: false,
|
|
206
|
-
hasOwnView: true,
|
|
207
|
-
getIcon: () => (props) => /* @__PURE__ */ jsx(FileIcon, { ...props }),
|
|
208
|
-
getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
|
|
209
|
-
onTreeDoubleClick: ({ blockId, fieldName }) => {
|
|
210
|
-
previewStore.send({
|
|
211
|
-
type: "selectBlockField",
|
|
212
|
-
blockId,
|
|
213
|
-
fieldName,
|
|
214
|
-
fieldType: "File"
|
|
215
|
-
});
|
|
216
|
-
previewStore.send({
|
|
217
|
-
type: "openBlockContentSheet",
|
|
218
|
-
blockId
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
3
|
+
String: { hasOwnView: false },
|
|
4
|
+
Repeater: { hasOwnView: false },
|
|
5
|
+
Enum: { hasOwnView: false },
|
|
6
|
+
Boolean: { hasOwnView: false },
|
|
7
|
+
Embed: { hasOwnView: false },
|
|
8
|
+
Link: { hasOwnView: true },
|
|
9
|
+
Image: { hasOwnView: true },
|
|
10
|
+
File: { hasOwnView: true },
|
|
11
|
+
ImageList: { hasOwnView: true },
|
|
12
|
+
FileList: { hasOwnView: true }
|
|
222
13
|
};
|
|
223
14
|
|
|
224
15
|
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/core/lib/lexicalReact.d.ts
|
|
4
|
+
interface MarkdownLinkRenderProps {
|
|
5
|
+
href: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
rel?: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
interface MarkdownLinkRenderData {
|
|
11
|
+
target: string;
|
|
12
|
+
href: string;
|
|
13
|
+
external: boolean;
|
|
14
|
+
pageId?: string;
|
|
15
|
+
}
|
|
16
|
+
interface MarkdownInlineComponents {
|
|
17
|
+
link?: (props: MarkdownLinkRenderProps, data: MarkdownLinkRenderData) => React.ReactNode;
|
|
18
|
+
strong?: (props: {
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
}) => React.ReactNode;
|
|
21
|
+
emphasis?: (props: {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { MarkdownInlineComponents, MarkdownLinkRenderData, MarkdownLinkRenderProps };
|
|
@@ -1,35 +1,70 @@
|
|
|
1
|
-
import "
|
|
1
|
+
import { getPageIdFromTextLinkTarget, isHttpTextLinkTarget, isValidTextLinkTarget, resolveTextLinkHref, shouldOpenTextLinkInNewTab } from "./textLinks.js";
|
|
2
|
+
import * as React from "react";
|
|
2
3
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
4
|
|
|
4
5
|
//#region src/core/lib/lexicalReact.tsx
|
|
5
6
|
/**
|
|
6
|
-
* Parse a markdown string with **bold
|
|
7
|
+
* Parse a markdown string with **bold**, *italic*, and text links into React nodes.
|
|
7
8
|
* Falls back to rendering the raw string if it's not a string value.
|
|
8
9
|
*/
|
|
9
|
-
function markdownToReactNodes(value) {
|
|
10
|
+
function markdownToReactNodes(value, options = {}) {
|
|
10
11
|
if (typeof value !== "string") return null;
|
|
11
12
|
if (!value) return null;
|
|
12
|
-
const regex = /(\*{1,3})((?:(?!\1).)+)\1/g;
|
|
13
13
|
const parts = [];
|
|
14
|
-
let lastIndex = 0;
|
|
15
|
-
let match;
|
|
16
14
|
let key = 0;
|
|
15
|
+
const fallbackHref = options.fallbackHref ?? "#";
|
|
17
16
|
const pushWithLineBreaks = (text) => {
|
|
18
17
|
text.split("\n").forEach((line, i) => {
|
|
19
18
|
if (i > 0) parts.push(/* @__PURE__ */ jsx("br", {}, key++));
|
|
20
19
|
if (line) parts.push(line);
|
|
21
20
|
});
|
|
22
21
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const pushFormatted = (text) => {
|
|
23
|
+
const regex = /(\*{1,3})((?:(?!\1).)+)\1/g;
|
|
24
|
+
let lastIndex = 0;
|
|
25
|
+
let match;
|
|
26
|
+
while ((match = regex.exec(text)) !== null) {
|
|
27
|
+
if (match.index > lastIndex) pushWithLineBreaks(text.slice(lastIndex, match.index));
|
|
28
|
+
const stars = match[1].length;
|
|
29
|
+
const content = match[2];
|
|
30
|
+
if (stars === 3) {
|
|
31
|
+
const emphasis = options.components?.emphasis?.({ children: content }) ?? /* @__PURE__ */ jsx("em", { children: content });
|
|
32
|
+
parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.strong?.({ children: emphasis }) ?? /* @__PURE__ */ jsx("strong", { children: emphasis }) }, key++));
|
|
33
|
+
} else if (stars === 2) parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.strong?.({ children: content }) ?? /* @__PURE__ */ jsx("strong", { children: content }) }, key++));
|
|
34
|
+
else parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.emphasis?.({ children: content }) ?? /* @__PURE__ */ jsx("em", { children: content }) }, key++));
|
|
35
|
+
lastIndex = match.index + match[0].length;
|
|
36
|
+
}
|
|
37
|
+
if (lastIndex < text.length) pushWithLineBreaks(text.slice(lastIndex));
|
|
38
|
+
};
|
|
39
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
40
|
+
let lastLinkIndex = 0;
|
|
41
|
+
let linkMatch;
|
|
42
|
+
while ((linkMatch = linkRegex.exec(value)) !== null) {
|
|
43
|
+
if (linkMatch.index > lastLinkIndex) pushFormatted(value.slice(lastLinkIndex, linkMatch.index));
|
|
44
|
+
const [, label, target] = linkMatch;
|
|
45
|
+
const href = isValidTextLinkTarget(target) ? resolveTextLinkHref(target, options.pages, fallbackHref) : null;
|
|
46
|
+
if (href) {
|
|
47
|
+
const openInNewTab = shouldOpenTextLinkInNewTab(target);
|
|
48
|
+
const linkProps = {
|
|
49
|
+
href,
|
|
50
|
+
target: openInNewTab ? "_blank" : void 0,
|
|
51
|
+
rel: openInNewTab ? "noreferrer" : void 0,
|
|
52
|
+
children: label
|
|
53
|
+
};
|
|
54
|
+
const linkData = {
|
|
55
|
+
target,
|
|
56
|
+
href,
|
|
57
|
+
external: isHttpTextLinkTarget(target),
|
|
58
|
+
pageId: getPageIdFromTextLinkTarget(target) ?? void 0
|
|
59
|
+
};
|
|
60
|
+
parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.link?.(linkProps, linkData) ?? /* @__PURE__ */ jsx("a", {
|
|
61
|
+
...linkProps,
|
|
62
|
+
style: { textDecorationLine: "underline" }
|
|
63
|
+
}) }, key++));
|
|
64
|
+
} else pushFormatted(label);
|
|
65
|
+
lastLinkIndex = linkMatch.index + linkMatch[0].length;
|
|
31
66
|
}
|
|
32
|
-
if (
|
|
67
|
+
if (lastLinkIndex < value.length) pushFormatted(value.slice(lastLinkIndex));
|
|
33
68
|
if (parts.length === 0) return value;
|
|
34
69
|
return /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
35
70
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FORMAT_FLAGS, lexicalTextToMarkdown } from "./modifierFormats.js";
|
|
2
|
+
import { isValidTextLinkTarget } from "./textLinks.js";
|
|
2
3
|
|
|
3
4
|
//#region src/core/lib/lexicalState.ts
|
|
4
5
|
function isLexicalState(value) {
|
|
@@ -19,6 +20,12 @@ function lexicalStateToMarkdown(serialized) {
|
|
|
19
20
|
}
|
|
20
21
|
function extractMarkdownFromNode(node) {
|
|
21
22
|
if (node.type === "text") return lexicalTextToMarkdown(node.text ?? "", node.format ?? 0);
|
|
23
|
+
if (node.type === "link") {
|
|
24
|
+
const text = (node.children ?? []).map(extractMarkdownFromNode).join("");
|
|
25
|
+
const url = typeof node.url === "string" ? node.url : "";
|
|
26
|
+
if (!url) return text;
|
|
27
|
+
return `[${text}](${url})`;
|
|
28
|
+
}
|
|
22
29
|
if (node.type === "linebreak") return "\n";
|
|
23
30
|
if (!node.children) return "";
|
|
24
31
|
const parts = [];
|
|
@@ -67,6 +74,33 @@ function parseParagraphWithLineBreaks(para) {
|
|
|
67
74
|
return nodes;
|
|
68
75
|
}
|
|
69
76
|
function parseInlineMarkdown(text) {
|
|
77
|
+
const nodes = [];
|
|
78
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
79
|
+
let lastLinkIndex = 0;
|
|
80
|
+
let linkMatch;
|
|
81
|
+
while ((linkMatch = linkRegex.exec(text)) !== null) {
|
|
82
|
+
if (linkMatch.index > lastLinkIndex) nodes.push(...parseFormattedText(text.slice(lastLinkIndex, linkMatch.index)));
|
|
83
|
+
const [, label, target] = linkMatch;
|
|
84
|
+
if (isValidTextLinkTarget(target)) nodes.push({
|
|
85
|
+
children: parseFormattedText(label),
|
|
86
|
+
direction: "ltr",
|
|
87
|
+
format: "",
|
|
88
|
+
indent: 0,
|
|
89
|
+
rel: null,
|
|
90
|
+
target: null,
|
|
91
|
+
title: null,
|
|
92
|
+
type: "link",
|
|
93
|
+
url: target,
|
|
94
|
+
version: 1
|
|
95
|
+
});
|
|
96
|
+
else nodes.push(...parseFormattedText(label));
|
|
97
|
+
lastLinkIndex = linkMatch.index + linkMatch[0].length;
|
|
98
|
+
}
|
|
99
|
+
if (lastLinkIndex < text.length) nodes.push(...parseFormattedText(text.slice(lastLinkIndex)));
|
|
100
|
+
if (nodes.length === 0) return parseFormattedText(text);
|
|
101
|
+
return nodes;
|
|
102
|
+
}
|
|
103
|
+
function parseFormattedText(text) {
|
|
70
104
|
const segments = [];
|
|
71
105
|
const regex = /(\*{1,3})((?:(?!\1).)+)\1/g;
|
|
72
106
|
let lastIndex = 0;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/core/lib/textLinks.ts
|
|
2
|
+
const PAGE_TEXT_LINK_PREFIX = "camox:page:";
|
|
3
|
+
function getPageIdFromTextLinkTarget(target) {
|
|
4
|
+
if (!target.startsWith("camox:page:")) return null;
|
|
5
|
+
const pageId = target.slice(11);
|
|
6
|
+
if (!pageId) return null;
|
|
7
|
+
return pageId;
|
|
8
|
+
}
|
|
9
|
+
function createPageTextLinkTarget(pageId) {
|
|
10
|
+
return `${PAGE_TEXT_LINK_PREFIX}${pageId}`;
|
|
11
|
+
}
|
|
12
|
+
function isHttpTextLinkTarget(target) {
|
|
13
|
+
return /^https?:\/\//i.test(target);
|
|
14
|
+
}
|
|
15
|
+
function isValidTextLinkTarget(target) {
|
|
16
|
+
return getPageIdFromTextLinkTarget(target) != null || isHttpTextLinkTarget(target);
|
|
17
|
+
}
|
|
18
|
+
function resolveTextLinkHref(target, pages, fallbackHref) {
|
|
19
|
+
const pageId = getPageIdFromTextLinkTarget(target);
|
|
20
|
+
if (pageId == null) return isHttpTextLinkTarget(target) ? target : null;
|
|
21
|
+
return (pages?.find((p) => String(p.id) === pageId))?.fullPath ?? fallbackHref;
|
|
22
|
+
}
|
|
23
|
+
function shouldOpenTextLinkInNewTab(target) {
|
|
24
|
+
return isHttpTextLinkTarget(target);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { createPageTextLinkTarget, getPageIdFromTextLinkTarget, isHttpTextLinkTarget, isValidTextLinkTarget, resolveTextLinkHref, shouldOpenTextLinkInNewTab };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useProjectSlug } from "../../../lib/auth.js";
|
|
2
|
+
import { projectQueries } from "../../../lib/queries.js";
|
|
3
|
+
import { previewStore } from "../../preview/previewStore.js";
|
|
4
|
+
import { AgentChatThread } from "./AgentChatThread.js";
|
|
5
|
+
import { c } from "react/compiler-runtime";
|
|
6
|
+
import { useQuery } from "@tanstack/react-query";
|
|
7
|
+
import { useLocation } from "@tanstack/react-router";
|
|
8
|
+
import { useSelector } from "@xstate/store-react";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Button } from "@camox/ui/button";
|
|
12
|
+
import { Lock, Plus, X } from "lucide-react";
|
|
13
|
+
import { Alert, AlertDescription, AlertTitle } from "@camox/ui/alert";
|
|
14
|
+
|
|
15
|
+
//#region src/features/agent-chat/components/AgentChatSidebar.tsx
|
|
16
|
+
const AgentChatSidebar = () => {
|
|
17
|
+
const $ = c(20);
|
|
18
|
+
if ($[0] !== "fa80c907e2ec181da4f3cb91fbf7d8dc7f1330b0892397f5d2dd7e7058b18f66") {
|
|
19
|
+
for (let $i = 0; $i < 20; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
20
|
+
$[0] = "fa80c907e2ec181da4f3cb91fbf7d8dc7f1330b0892397f5d2dd7e7058b18f66";
|
|
21
|
+
}
|
|
22
|
+
const previewSource = useSelector(previewStore, _temp);
|
|
23
|
+
const pageScaffoldContext = useSelector(previewStore, _temp2);
|
|
24
|
+
const [agentChatKey, setAgentChatKey] = React.useState(0);
|
|
25
|
+
const [composerFocusKey, setComposerFocusKey] = React.useState(0);
|
|
26
|
+
const { pathname } = useLocation();
|
|
27
|
+
const projectSlug = useProjectSlug();
|
|
28
|
+
let t0;
|
|
29
|
+
if ($[1] !== projectSlug) {
|
|
30
|
+
t0 = projectQueries.getBySlug(projectSlug);
|
|
31
|
+
$[1] = projectSlug;
|
|
32
|
+
$[2] = t0;
|
|
33
|
+
} else t0 = $[2];
|
|
34
|
+
const { data: project } = useQuery(t0);
|
|
35
|
+
const isLiveSource = previewSource === "live";
|
|
36
|
+
let t1;
|
|
37
|
+
let t2;
|
|
38
|
+
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
|
39
|
+
t1 = () => {
|
|
40
|
+
setComposerFocusKey(_temp3);
|
|
41
|
+
};
|
|
42
|
+
t2 = [];
|
|
43
|
+
$[3] = t1;
|
|
44
|
+
$[4] = t2;
|
|
45
|
+
} else {
|
|
46
|
+
t1 = $[3];
|
|
47
|
+
t2 = $[4];
|
|
48
|
+
}
|
|
49
|
+
React.useEffect(t1, t2);
|
|
50
|
+
let t3;
|
|
51
|
+
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
|
52
|
+
t3 = /* @__PURE__ */ jsx("p", {
|
|
53
|
+
className: "flex-1 text-base font-semibold",
|
|
54
|
+
children: "Agent Chat"
|
|
55
|
+
});
|
|
56
|
+
$[5] = t3;
|
|
57
|
+
} else t3 = $[5];
|
|
58
|
+
let t4;
|
|
59
|
+
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
|
60
|
+
t4 = /* @__PURE__ */ jsx(Button, {
|
|
61
|
+
type: "button",
|
|
62
|
+
variant: "ghost",
|
|
63
|
+
size: "icon-sm",
|
|
64
|
+
"aria-label": "Start a new Agent Chat",
|
|
65
|
+
onClick: () => {
|
|
66
|
+
previewStore.send({ type: "clearAgentChatPageScaffoldContext" });
|
|
67
|
+
setAgentChatKey(_temp4);
|
|
68
|
+
setComposerFocusKey(_temp5);
|
|
69
|
+
},
|
|
70
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
|
|
71
|
+
});
|
|
72
|
+
$[6] = t4;
|
|
73
|
+
} else t4 = $[6];
|
|
74
|
+
let t5;
|
|
75
|
+
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
|
76
|
+
t5 = /* @__PURE__ */ jsxs("div", {
|
|
77
|
+
className: "flex items-center gap-1 p-2",
|
|
78
|
+
children: [
|
|
79
|
+
t3,
|
|
80
|
+
t4,
|
|
81
|
+
/* @__PURE__ */ jsx(Button, {
|
|
82
|
+
type: "button",
|
|
83
|
+
variant: "ghost",
|
|
84
|
+
size: "icon-sm",
|
|
85
|
+
"aria-label": "Close",
|
|
86
|
+
onClick: _temp6,
|
|
87
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
});
|
|
91
|
+
$[7] = t5;
|
|
92
|
+
} else t5 = $[7];
|
|
93
|
+
let t6;
|
|
94
|
+
if ($[8] !== isLiveSource) {
|
|
95
|
+
t6 = isLiveSource && /* @__PURE__ */ jsx("div", {
|
|
96
|
+
className: "p-2 pb-0",
|
|
97
|
+
children: /* @__PURE__ */ jsxs(Alert, { children: [
|
|
98
|
+
/* @__PURE__ */ jsx(Lock, { className: "size-4" }),
|
|
99
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: "Live Source is read-only" }),
|
|
100
|
+
/* @__PURE__ */ jsxs(AlertDescription, {
|
|
101
|
+
className: "space-y-3",
|
|
102
|
+
children: [/* @__PURE__ */ jsx("p", { children: "Agent Chat can inspect live content, but edits require switching to Draft Source." }), /* @__PURE__ */ jsx(Button, {
|
|
103
|
+
type: "button",
|
|
104
|
+
size: "sm",
|
|
105
|
+
onClick: _temp7,
|
|
106
|
+
children: "Switch to draft"
|
|
107
|
+
})]
|
|
108
|
+
})
|
|
109
|
+
] })
|
|
110
|
+
});
|
|
111
|
+
$[8] = isLiveSource;
|
|
112
|
+
$[9] = t6;
|
|
113
|
+
} else t6 = $[9];
|
|
114
|
+
let t7;
|
|
115
|
+
if ($[10] !== agentChatKey || $[11] !== composerFocusKey || $[12] !== pageScaffoldContext || $[13] !== pathname || $[14] !== previewSource || $[15] !== project) {
|
|
116
|
+
t7 = project ? /* @__PURE__ */ jsx(AgentChatThread, {
|
|
117
|
+
projectId: project.id,
|
|
118
|
+
currentPath: pathname,
|
|
119
|
+
source: previewSource,
|
|
120
|
+
focusKey: composerFocusKey,
|
|
121
|
+
pageScaffoldContext: pageScaffoldContext ?? void 0
|
|
122
|
+
}, `${agentChatKey}:${pageScaffoldContext?.id ?? 0}`) : /* @__PURE__ */ jsx("div", {
|
|
123
|
+
className: "text-muted-foreground flex flex-1 items-center justify-center p-6 text-sm",
|
|
124
|
+
children: "Loading project…"
|
|
125
|
+
});
|
|
126
|
+
$[10] = agentChatKey;
|
|
127
|
+
$[11] = composerFocusKey;
|
|
128
|
+
$[12] = pageScaffoldContext;
|
|
129
|
+
$[13] = pathname;
|
|
130
|
+
$[14] = previewSource;
|
|
131
|
+
$[15] = project;
|
|
132
|
+
$[16] = t7;
|
|
133
|
+
} else t7 = $[16];
|
|
134
|
+
let t8;
|
|
135
|
+
if ($[17] !== t6 || $[18] !== t7) {
|
|
136
|
+
t8 = /* @__PURE__ */ jsxs(Fragment, { children: [t5, /* @__PURE__ */ jsxs("div", {
|
|
137
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
138
|
+
children: [t6, t7]
|
|
139
|
+
})] });
|
|
140
|
+
$[17] = t6;
|
|
141
|
+
$[18] = t7;
|
|
142
|
+
$[19] = t8;
|
|
143
|
+
} else t8 = $[19];
|
|
144
|
+
return t8;
|
|
145
|
+
};
|
|
146
|
+
function _temp(state) {
|
|
147
|
+
return state.context.previewSource;
|
|
148
|
+
}
|
|
149
|
+
function _temp2(state_0) {
|
|
150
|
+
return state_0.context.agentChatPageScaffoldContext;
|
|
151
|
+
}
|
|
152
|
+
function _temp3(key) {
|
|
153
|
+
return key + 1;
|
|
154
|
+
}
|
|
155
|
+
function _temp4(key_0) {
|
|
156
|
+
return key_0 + 1;
|
|
157
|
+
}
|
|
158
|
+
function _temp5(key_1) {
|
|
159
|
+
return key_1 + 1;
|
|
160
|
+
}
|
|
161
|
+
function _temp6() {
|
|
162
|
+
return previewStore.send({ type: "closeAgentChatSidebar" });
|
|
163
|
+
}
|
|
164
|
+
function _temp7() {
|
|
165
|
+
return previewStore.send({
|
|
166
|
+
type: "setPreviewSource",
|
|
167
|
+
source: "draft"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
export { AgentChatSidebar };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { previewStore } from "../../preview/previewStore.js";
|
|
2
1
|
import { getAuthCookieHeader } from "../../../lib/auth.js";
|
|
3
2
|
import { getApiClient, getApiUrl, getEnvironmentName } from "../../../lib/api-client.js";
|
|
4
3
|
import { cn } from "../../../lib/utils.js";
|
|
4
|
+
import { previewStore } from "../../preview/previewStore.js";
|
|
5
5
|
import { buildAgentChatToolLabelContext, getToolCallLabel } from "../agent-chat-labels.js";
|
|
6
6
|
import { AgentToolCallCard } from "./AgentToolCallCard.js";
|
|
7
7
|
import { c } from "react/compiler-runtime";
|
|
@@ -106,9 +106,9 @@ const markdownComponents = {
|
|
|
106
106
|
};
|
|
107
107
|
const MessageBubble = (t0) => {
|
|
108
108
|
const $ = c(34);
|
|
109
|
-
if ($[0] !== "
|
|
109
|
+
if ($[0] !== "f3b8a7664c3b99923b26867f113f97eb01d8277569c870abd2bc42ddb6f634bf") {
|
|
110
110
|
for (let $i = 0; $i < 34; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
111
|
-
$[0] = "
|
|
111
|
+
$[0] = "f3b8a7664c3b99923b26867f113f97eb01d8277569c870abd2bc42ddb6f634bf";
|
|
112
112
|
}
|
|
113
113
|
const { message, source, isThinkingActive, activeThinkingDurationKeys, thinkingDurations, toolLabelContext, onApprovalResponse } = t0;
|
|
114
114
|
const isUser = message.role === "user";
|
|
@@ -158,7 +158,7 @@ const MessageBubble = (t0) => {
|
|
|
158
158
|
if (text != null) {
|
|
159
159
|
if (text.trim().length === 0) return null;
|
|
160
160
|
return /* @__PURE__ */ jsx(Streamdown, {
|
|
161
|
-
className:
|
|
161
|
+
className: "wrap-break-words space-y-2",
|
|
162
162
|
components: markdownComponents,
|
|
163
163
|
controls: false,
|
|
164
164
|
isAnimating: !isUser,
|
|
@@ -226,9 +226,9 @@ const MessageBubble = (t0) => {
|
|
|
226
226
|
};
|
|
227
227
|
const AgentThinkingIndicator = (t0) => {
|
|
228
228
|
const $ = c(7);
|
|
229
|
-
if ($[0] !== "
|
|
229
|
+
if ($[0] !== "f3b8a7664c3b99923b26867f113f97eb01d8277569c870abd2bc42ddb6f634bf") {
|
|
230
230
|
for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
231
|
-
$[0] = "
|
|
231
|
+
$[0] = "f3b8a7664c3b99923b26867f113f97eb01d8277569c870abd2bc42ddb6f634bf";
|
|
232
232
|
}
|
|
233
233
|
const { durationSeconds } = t0;
|
|
234
234
|
if (durationSeconds !== void 0) {
|
|
@@ -517,7 +517,7 @@ const AgentChatThread = ({ projectId, currentPath, source, disabled, focusKey =
|
|
|
517
517
|
className: "flex min-h-0 flex-1 flex-col",
|
|
518
518
|
children: [/* @__PURE__ */ jsx("div", {
|
|
519
519
|
ref: messagesScrollRef,
|
|
520
|
-
className: "min-h-0 flex-1 overflow-y-auto border-t
|
|
520
|
+
className: "min-h-0 flex-1 overflow-y-auto border-t px-2 py-4",
|
|
521
521
|
children: /* @__PURE__ */ jsxs("div", {
|
|
522
522
|
className: "flex min-h-full flex-col justify-end",
|
|
523
523
|
children: [
|
|
@@ -552,7 +552,7 @@ const AgentChatThread = ({ projectId, currentPath, source, disabled, focusKey =
|
|
|
552
552
|
})
|
|
553
553
|
}), /* @__PURE__ */ jsx("form", {
|
|
554
554
|
onSubmit: handleSubmit,
|
|
555
|
-
className: "border-border border-t
|
|
555
|
+
className: "border-border border-t px-2 py-4",
|
|
556
556
|
children: /* @__PURE__ */ jsxs("div", {
|
|
557
557
|
className: "border-input focus-within:border-ring focus-within:ring-ring/50 flex items-center gap-2 rounded-2xl border px-3 transition-colors focus-within:ring-[3px]",
|
|
558
558
|
children: [/* @__PURE__ */ jsx(Textarea, {
|
|
@@ -566,7 +566,7 @@ const AgentChatThread = ({ projectId, currentPath, source, disabled, focusKey =
|
|
|
566
566
|
}
|
|
567
567
|
},
|
|
568
568
|
disabled,
|
|
569
|
-
placeholder: disabled ? "Switch to draft to chat" : "Ask for
|
|
569
|
+
placeholder: disabled ? "Switch to draft to chat" : "Ask for anything",
|
|
570
570
|
className: "max-h-32 min-h-10 resize-none border-0 bg-inherit! px-0 py-4 leading-6 shadow-none focus-visible:ring-0"
|
|
571
571
|
}), isLoading ? /* @__PURE__ */ jsx(Button, {
|
|
572
572
|
type: "button",
|