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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Link as Link$1, useLocation, useNavigate } from "../../navigation/navigation.js";
|
|
2
2
|
import { actionsStore } from "../../provider/actionsStore.js";
|
|
3
|
-
import {
|
|
4
|
-
import { pageQueries, projectQueries } from "../../../lib/queries.js";
|
|
3
|
+
import { usePageDestinations } from "../../../hooks/use-page-destinations.js";
|
|
5
4
|
import { PlatformLabel } from "../../../components/PlatformLabel.js";
|
|
6
5
|
import { cn } from "../../../lib/utils.js";
|
|
7
6
|
import { STUDIO_CONTENT_PATH } from "../routes.js";
|
|
@@ -10,7 +9,6 @@ import { EnvironmentMenu } from "./EnvironmentMenu.js";
|
|
|
10
9
|
import { ProjectMenu } from "./ProjectMenu.js";
|
|
11
10
|
import { UserButton } from "./UserButton.js";
|
|
12
11
|
import { c } from "react/compiler-runtime";
|
|
13
|
-
import { useQuery } from "@tanstack/react-query";
|
|
14
12
|
import * as React from "react";
|
|
15
13
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
16
14
|
import { Kbd } from "@camox/ui/kbd";
|
|
@@ -36,97 +34,72 @@ const links = [{
|
|
|
36
34
|
]
|
|
37
35
|
}];
|
|
38
36
|
const Navbar = (t0) => {
|
|
39
|
-
const $ = c(
|
|
40
|
-
if ($[0] !== "
|
|
41
|
-
for (let $i = 0; $i <
|
|
42
|
-
$[0] = "
|
|
37
|
+
const $ = c(13);
|
|
38
|
+
if ($[0] !== "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6") {
|
|
39
|
+
for (let $i = 0; $i < 13; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
40
|
+
$[0] = "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6";
|
|
43
41
|
}
|
|
44
42
|
const { isPreview: t1 } = t0;
|
|
45
43
|
const isPreview = t1 === void 0 ? false : t1;
|
|
46
|
-
const
|
|
47
|
-
let t2;
|
|
48
|
-
if ($[1] !== projectSlug) {
|
|
49
|
-
t2 = projectQueries.getBySlug(projectSlug);
|
|
50
|
-
$[1] = projectSlug;
|
|
51
|
-
$[2] = t2;
|
|
52
|
-
} else t2 = $[2];
|
|
53
|
-
const { data: project } = useQuery(t2);
|
|
54
|
-
let t3;
|
|
55
|
-
if ($[3] !== project?.id) {
|
|
56
|
-
t3 = pageQueries.list(project?.id ?? 0);
|
|
57
|
-
$[3] = project?.id;
|
|
58
|
-
$[4] = t3;
|
|
59
|
-
} else t3 = $[4];
|
|
60
|
-
const t4 = !!project;
|
|
61
|
-
let t5;
|
|
62
|
-
if ($[5] !== t3 || $[6] !== t4) {
|
|
63
|
-
t5 = {
|
|
64
|
-
...t3,
|
|
65
|
-
enabled: t4
|
|
66
|
-
};
|
|
67
|
-
$[5] = t3;
|
|
68
|
-
$[6] = t4;
|
|
69
|
-
$[7] = t5;
|
|
70
|
-
} else t5 = $[7];
|
|
71
|
-
const { data: pages } = useQuery(t5);
|
|
44
|
+
const pages = usePageDestinations();
|
|
72
45
|
const { pathname } = useLocation();
|
|
73
|
-
let
|
|
74
|
-
if ($[
|
|
75
|
-
|
|
46
|
+
let t2;
|
|
47
|
+
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
|
48
|
+
t2 = /* @__PURE__ */ jsxs("div", {
|
|
76
49
|
className: "flew-row flex gap-1",
|
|
77
50
|
children: [/* @__PURE__ */ jsx(ProjectMenu, {}), /* @__PURE__ */ jsx(EnvironmentMenu, {})]
|
|
78
51
|
});
|
|
79
|
-
$[
|
|
80
|
-
} else
|
|
81
|
-
let
|
|
82
|
-
if ($[
|
|
83
|
-
|
|
52
|
+
$[1] = t2;
|
|
53
|
+
} else t2 = $[1];
|
|
54
|
+
let t3;
|
|
55
|
+
if ($[2] !== isPreview || $[3] !== pages || $[4] !== pathname) {
|
|
56
|
+
t3 = links.map((link, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link$1, {
|
|
84
57
|
to: link.to,
|
|
85
58
|
className: cn("flex gap-2 items-center rounded-md px-4 py-2 text-sm font-medium", "hover:bg-accent hover:text-accent-foreground outline-none transition-[color,box-shadow] focus-visible:ring-ring/50 focus-visible:ring-[3px] focus-visible:outline-1", (isPreview || pages?.some((page) => page.fullPath === pathname)) && index === 0 ? "bg-accent hover:bg-accent text-accent-foreground" : "text-muted-foreground"),
|
|
86
59
|
activeProps: { className: "bg-accent hover:bg-accent text-accent-foreground!" },
|
|
87
60
|
children: link.children
|
|
88
61
|
}) }, link.to));
|
|
89
|
-
$[
|
|
90
|
-
$[
|
|
91
|
-
$[
|
|
92
|
-
$[
|
|
93
|
-
} else
|
|
94
|
-
let
|
|
95
|
-
if ($[
|
|
96
|
-
|
|
62
|
+
$[2] = isPreview;
|
|
63
|
+
$[3] = pages;
|
|
64
|
+
$[4] = pathname;
|
|
65
|
+
$[5] = t3;
|
|
66
|
+
} else t3 = $[5];
|
|
67
|
+
let t4;
|
|
68
|
+
if ($[6] !== t3) {
|
|
69
|
+
t4 = /* @__PURE__ */ jsxs("div", {
|
|
97
70
|
className: "flex flex-row gap-2",
|
|
98
|
-
children: [
|
|
71
|
+
children: [t2, /* @__PURE__ */ jsx("ul", {
|
|
99
72
|
className: "flex items-center gap-1",
|
|
100
|
-
children:
|
|
73
|
+
children: t3
|
|
101
74
|
})]
|
|
102
75
|
});
|
|
103
|
-
$[
|
|
104
|
-
$[
|
|
105
|
-
} else
|
|
106
|
-
let
|
|
107
|
-
let
|
|
108
|
-
if ($[
|
|
109
|
-
|
|
110
|
-
|
|
76
|
+
$[6] = t3;
|
|
77
|
+
$[7] = t4;
|
|
78
|
+
} else t4 = $[7];
|
|
79
|
+
let t5;
|
|
80
|
+
let t6;
|
|
81
|
+
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
|
82
|
+
t5 = /* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground size-4" });
|
|
83
|
+
t6 = /* @__PURE__ */ jsx("span", {
|
|
111
84
|
className: "text-muted-foreground",
|
|
112
85
|
children: "Quick find"
|
|
113
86
|
});
|
|
114
|
-
$[
|
|
115
|
-
$[
|
|
87
|
+
$[8] = t5;
|
|
88
|
+
$[9] = t6;
|
|
116
89
|
} else {
|
|
117
|
-
|
|
118
|
-
|
|
90
|
+
t5 = $[8];
|
|
91
|
+
t6 = $[9];
|
|
119
92
|
}
|
|
120
|
-
let
|
|
121
|
-
if ($[
|
|
122
|
-
|
|
93
|
+
let t7;
|
|
94
|
+
if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
|
|
95
|
+
t7 = /* @__PURE__ */ jsxs("div", {
|
|
123
96
|
className: "flex items-center gap-2",
|
|
124
97
|
children: [/* @__PURE__ */ jsxs(Button, {
|
|
125
98
|
variant: "outline",
|
|
126
99
|
onClick: _temp,
|
|
127
100
|
children: [
|
|
128
|
-
|
|
129
|
-
|
|
101
|
+
t5,
|
|
102
|
+
t6,
|
|
130
103
|
/* @__PURE__ */ jsx(Kbd, {
|
|
131
104
|
className: "ml-4",
|
|
132
105
|
children: /* @__PURE__ */ jsx(PlatformLabel, {
|
|
@@ -137,24 +110,24 @@ const Navbar = (t0) => {
|
|
|
137
110
|
]
|
|
138
111
|
}), /* @__PURE__ */ jsx(UserButton, {})]
|
|
139
112
|
});
|
|
140
|
-
$[
|
|
141
|
-
} else
|
|
142
|
-
let
|
|
143
|
-
if ($[
|
|
144
|
-
|
|
113
|
+
$[10] = t7;
|
|
114
|
+
} else t7 = $[10];
|
|
115
|
+
let t8;
|
|
116
|
+
if ($[11] !== t4) {
|
|
117
|
+
t8 = /* @__PURE__ */ jsxs("nav", {
|
|
145
118
|
className: "bg-background relative flex items-center justify-between gap-4 border-b-2 px-2 py-2",
|
|
146
|
-
children: [
|
|
119
|
+
children: [t4, t7]
|
|
147
120
|
});
|
|
148
|
-
$[
|
|
149
|
-
$[
|
|
150
|
-
} else
|
|
151
|
-
return
|
|
121
|
+
$[11] = t4;
|
|
122
|
+
$[12] = t8;
|
|
123
|
+
} else t8 = $[12];
|
|
124
|
+
return t8;
|
|
152
125
|
};
|
|
153
126
|
function useNavbarActions() {
|
|
154
127
|
const $ = c(4);
|
|
155
|
-
if ($[0] !== "
|
|
128
|
+
if ($[0] !== "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6") {
|
|
156
129
|
for (let $i = 0; $i < 4; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
157
|
-
$[0] = "
|
|
130
|
+
$[0] = "9182f23e40dc6d439cec2c533b4f9436ee59f885213d6d7a79d434c7aa8349c6";
|
|
158
131
|
}
|
|
159
132
|
const navigate = useNavigate();
|
|
160
133
|
let t0;
|
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { readAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl } from "@camox/cli/auth";
|
|
4
2
|
|
|
5
|
-
//#region src/features/vite/auth.ts
|
|
6
|
-
const AUTH_DIR = path.join(os.homedir(), ".camox");
|
|
7
|
-
const AUTH_FILE = path.join(AUTH_DIR, "auth.json");
|
|
8
|
-
function normalizeUrl(url) {
|
|
9
|
-
return url.replace(/\/+$/, "");
|
|
10
|
-
}
|
|
11
|
-
function readAllTokens() {
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8"));
|
|
14
|
-
} catch {
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function writeAllTokens(tokens) {
|
|
19
|
-
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
20
|
-
fs.writeFileSync(AUTH_FILE, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
21
|
-
}
|
|
22
|
-
function readAuthTokenForUrl(authenticationUrl) {
|
|
23
|
-
const entry = readAllTokens()[normalizeUrl(authenticationUrl)];
|
|
24
|
-
if (entry?.token && entry?.name && typeof entry.email === "string") return entry;
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
function writeAuthTokenForUrl(authenticationUrl, token) {
|
|
28
|
-
const tokens = readAllTokens();
|
|
29
|
-
tokens[normalizeUrl(authenticationUrl)] = token;
|
|
30
|
-
writeAllTokens(tokens);
|
|
31
|
-
}
|
|
32
|
-
async function verifyOneTimeToken(apiUrl, token) {
|
|
33
|
-
const response = await fetch(`${normalizeUrl(apiUrl)}/api/auth/one-time-token/verify`, {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: { "Content-Type": "application/json" },
|
|
36
|
-
body: JSON.stringify({ token })
|
|
37
|
-
});
|
|
38
|
-
if (!response.ok) throw new Error(`OTT verification failed: ${response.status}`);
|
|
39
|
-
const data = await response.json();
|
|
40
|
-
const user = data.user;
|
|
41
|
-
if (!user?.name) throw new Error("No user info in verification response");
|
|
42
|
-
const sessionToken = data.session?.token;
|
|
43
|
-
if (!sessionToken) throw new Error("No session token in verification response");
|
|
44
|
-
return {
|
|
45
|
-
name: user.name,
|
|
46
|
-
email: user.email ?? "",
|
|
47
|
-
token: sessionToken
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
3
|
export { readAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { iconCollectionIds, loadIconCollection } from "@camox/api-contract";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/features/vite/iconGeneration.ts
|
|
6
|
+
async function generateIconTypes(root, prefix) {
|
|
7
|
+
const ids = prefix ? iconCollectionIds(await loadIconCollection(prefix)) : [];
|
|
8
|
+
mkdirSync(resolve(root, "src/camox"), { recursive: true });
|
|
9
|
+
const union = ids.map((id) => JSON.stringify(id));
|
|
10
|
+
const type = union.length > 1 ? `\n | ${union.join("\n | ")}` : union[0] ?? "never";
|
|
11
|
+
writeFileSync(resolve(root, "src/camox/icons.gen.d.ts"), `// Generated by Camox. Do not edit.\nexport {};\ndeclare global {\n interface CamoxIconRegistry {\n ids:${union.length > 1 ? "" : " "}${type};\n }\n}\n`);
|
|
12
|
+
return ids;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { generateIconTypes };
|
|
@@ -3,6 +3,9 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
//#region src/features/vite/layoutGeneration.ts
|
|
6
|
+
function propertyName(name) {
|
|
7
|
+
return /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u.test(name) ? name : JSON.stringify(name);
|
|
8
|
+
}
|
|
6
9
|
/** Keep file identities stable across moves without executing application modules. */
|
|
7
10
|
function generateLayoutFiles(appRoot) {
|
|
8
11
|
const directory = resolve(appRoot, "src/layouts");
|
|
@@ -28,10 +31,11 @@ export const Layout = createLayout(${JSON.stringify(id)})({
|
|
|
28
31
|
writeIfChanged(path, source);
|
|
29
32
|
const segments = id.split(".");
|
|
30
33
|
const route = "/" + segments.map((segment) => segment.startsWith("$") ? `:${segment.slice(1)}` : segment).join("/");
|
|
31
|
-
const params = segments.filter((segment) => segment.startsWith("$")).map((segment) =>
|
|
32
|
-
|
|
34
|
+
const params = segments.filter((segment) => segment.startsWith("$")).map((segment) => ` ${propertyName(segment.slice(1))}: string;`);
|
|
35
|
+
const paramsType = params.length ? `{\n${params.join("\n")}\n }` : "{}";
|
|
36
|
+
declarations.push(` ${propertyName(id)}: {\n route: ${JSON.stringify(route)};\n params: ${paramsType};\n };`);
|
|
33
37
|
}
|
|
34
|
-
writeIfChanged(resolve(appRoot, "src/camox/layouts.gen.d.ts"), `// Generated by Camox.\nimport "camox/createLayout";\ndeclare module "camox/createLayout" {\n interface LayoutFileRoutes {\n
|
|
38
|
+
writeIfChanged(resolve(appRoot, "src/camox/layouts.gen.d.ts"), `// Generated by Camox.\nimport "camox/createLayout";\ndeclare module "camox/createLayout" {\n interface LayoutFileRoutes ${declarations.length ? `{\n${declarations.join("\n")}\n }` : "{}"}\n}\n`);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
//#endregion
|
|
@@ -23,14 +23,16 @@ type CamoxVitePlugin = Plugin & {
|
|
|
23
23
|
interface CamoxPluginOptions {
|
|
24
24
|
/** Stable, human-readable slug identifying this project (e.g. "prestigious-impala-84") */
|
|
25
25
|
projectSlug: string;
|
|
26
|
+
/** Iconify collection available to all icon fields. */
|
|
27
|
+
icons?: string;
|
|
26
28
|
/** Disable telemetry collection (default: false) */
|
|
27
29
|
disableTelemetry?: boolean;
|
|
28
30
|
/** Internal options (intended for Camox contributors in development, not for public use) */
|
|
29
31
|
_internal?: {
|
|
30
32
|
/** URL of the Camox API backend, used for data fetching */apiUrl?: string; /** URL of the Camox authentication backend (default: https://app.camox.dev) */
|
|
31
33
|
authenticationUrl?: string; /** Show Tanstack query devtools (default: false) */
|
|
32
|
-
enableTanstackDevtools?: boolean; /** Enable
|
|
33
|
-
|
|
34
|
+
enableTanstackDevtools?: boolean; /** Enable experimental features, including feedback (default: false). */
|
|
35
|
+
enableExperimentalFeatures?: boolean; /** Disable automatic code generation (route files and app file) (default: false) */
|
|
34
36
|
disableCodeGen?: boolean; /** Mount the Camox-owned dev runtime at this base path. Use "/" for root mode. */
|
|
35
37
|
runtimeBasePath?: string;
|
|
36
38
|
};
|
|
@@ -2,6 +2,7 @@ import { generateAppFile, watchAppFile } from "./appGeneration.js";
|
|
|
2
2
|
import { readAuthTokenForUrl } from "./auth.js";
|
|
3
3
|
import { watchNewBlockFiles } from "./blockBoilerplate.js";
|
|
4
4
|
import { installDevAuthenticationMiddleware } from "./devAuthentication.js";
|
|
5
|
+
import { generateIconTypes } from "./iconGeneration.js";
|
|
5
6
|
import { syncDefinitions, syncDefinitionsToApi } from "./definitionsSync.js";
|
|
6
7
|
import { cleanupGeneratedRouteFiles } from "./routeGeneration.js";
|
|
7
8
|
import { installRuntimeNitroRoutes, loadRuntimeDevModule, resolveRuntimeDevId } from "./runtimeDev.js";
|
|
@@ -82,7 +83,11 @@ function camox(options) {
|
|
|
82
83
|
nitro: { setup(nitro) {
|
|
83
84
|
if (nitro.options.noExternals !== true) {
|
|
84
85
|
const noExternals = Array.isArray(nitro.options.noExternals) ? nitro.options.noExternals : [];
|
|
85
|
-
nitro.options.noExternals = [
|
|
86
|
+
nitro.options.noExternals = [
|
|
87
|
+
...noExternals,
|
|
88
|
+
"camox",
|
|
89
|
+
"tslib"
|
|
90
|
+
];
|
|
86
91
|
}
|
|
87
92
|
installRuntimeNitroRoutes(nitro, { runtimeBasePath });
|
|
88
93
|
nitro.hooks.hook("compiled", ({ options }) => {
|
|
@@ -91,7 +96,8 @@ function camox(options) {
|
|
|
91
96
|
cpSync(serverAssets, join(options.output.publicDir, "assets"), { recursive: true });
|
|
92
97
|
});
|
|
93
98
|
} },
|
|
94
|
-
config(_config, env) {
|
|
99
|
+
async config(_config, env) {
|
|
100
|
+
const iconIds = await generateIconTypes(resolve(_config.root ?? process.cwd()), options.icons);
|
|
95
101
|
isBuild = env.command === "build";
|
|
96
102
|
isRelease = isBuild && process.env.CAMOX_INTERNAL_RELEASE === "1";
|
|
97
103
|
if (isRelease) environmentName = "production";
|
|
@@ -103,9 +109,10 @@ function camox(options) {
|
|
|
103
109
|
return {
|
|
104
110
|
build: { emitAssets: true },
|
|
105
111
|
define: {
|
|
112
|
+
__CAMOX_ICON_IDS__: JSON.stringify(iconIds),
|
|
106
113
|
__CAMOX_TELEMETRY_DISABLED__: JSON.stringify(!!options.disableTelemetry),
|
|
107
114
|
__ENABLE_TANSTACK_DEVTOOLS__: JSON.stringify(enableTanstackDevtools),
|
|
108
|
-
|
|
115
|
+
__CAMOX_ENABLE_EXPERIMENTAL_FEATURES__: JSON.stringify(options._internal?.enableExperimentalFeatures ?? false),
|
|
109
116
|
__CAMOX_ENVIRONMENT_NAME__: JSON.stringify(environmentName),
|
|
110
117
|
__CAMOX_API_URL__: JSON.stringify(apiUrl),
|
|
111
118
|
__CAMOX_AUTHENTICATION_URL__: JSON.stringify(authenticationUrl),
|
|
@@ -135,6 +142,8 @@ function camox(options) {
|
|
|
135
142
|
"camox > @base-ui/react/accordion",
|
|
136
143
|
"camox > @base-ui/react/alert-dialog",
|
|
137
144
|
"camox > @base-ui/react/avatar",
|
|
145
|
+
"camox > @base-ui/react/button",
|
|
146
|
+
"camox > @base-ui/react/combobox",
|
|
138
147
|
"camox > @base-ui/react/dialog",
|
|
139
148
|
"camox > @base-ui/react/input",
|
|
140
149
|
"camox > @base-ui/react/menu",
|
|
@@ -149,9 +158,12 @@ function camox(options) {
|
|
|
149
158
|
"camox > @base-ui/react/use-render",
|
|
150
159
|
"camox > @camox/api-contract",
|
|
151
160
|
"camox > @camox/api-contract/query-keys",
|
|
161
|
+
"camox > @camox/ui > class-variance-authority",
|
|
162
|
+
"camox > @camox/ui > clsx",
|
|
152
163
|
"camox > @camox/ui > cmdk",
|
|
153
164
|
"camox > @camox/ui > lucide-react",
|
|
154
165
|
"camox > @camox/ui > sonner",
|
|
166
|
+
"camox > @camox/ui > tailwind-merge",
|
|
155
167
|
"camox > @dnd-kit/core",
|
|
156
168
|
"camox > @dnd-kit/modifiers",
|
|
157
169
|
"camox > @dnd-kit/sortable",
|
|
@@ -175,7 +187,14 @@ function camox(options) {
|
|
|
175
187
|
"camox > @sinclair/typebox",
|
|
176
188
|
"camox > @takumi-rs/image-response",
|
|
177
189
|
"camox > @tanstack/react-form",
|
|
190
|
+
"camox > @tanstack/react-query",
|
|
191
|
+
"camox > @tanstack/react-router",
|
|
192
|
+
"camox > @tanstack/react-virtual",
|
|
193
|
+
"camox > @unhead/react/client",
|
|
194
|
+
"camox > @unhead/react/server",
|
|
178
195
|
"camox > @xstate/store-react",
|
|
196
|
+
"camox > better-auth/client/plugins",
|
|
197
|
+
"camox > better-auth/react",
|
|
179
198
|
"camox > better-auth > @better-auth/core/env",
|
|
180
199
|
"camox > better-auth > @better-auth/core/error",
|
|
181
200
|
"camox > better-auth > @better-auth/core/utils/error-codes",
|
|
@@ -186,6 +205,7 @@ function camox(options) {
|
|
|
186
205
|
"camox > fractional-indexing",
|
|
187
206
|
"camox > lexical",
|
|
188
207
|
"camox > lucide-react",
|
|
208
|
+
"camox > marked",
|
|
189
209
|
"camox > @tanstack/react-query-devtools/production",
|
|
190
210
|
"camox > partysocket/react"
|
|
191
211
|
] }
|
|
@@ -242,6 +262,7 @@ function camox(options) {
|
|
|
242
262
|
root: resolvedConfig.root,
|
|
243
263
|
cacheDir: resolve(resolvedConfig.root, "node_modules", ".vite-camox-temp"),
|
|
244
264
|
resolve: resolvedConfig.resolve,
|
|
265
|
+
define: resolvedConfig.define,
|
|
245
266
|
server: { middlewareMode: true },
|
|
246
267
|
logLevel: "silent"
|
|
247
268
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useProjectSlug } from "../lib/auth.js";
|
|
2
|
+
import { getPageDestinations } from "../core/pageDestinations.js";
|
|
3
|
+
import { useCamoxApp } from "../features/provider/components/CamoxAppContext.js";
|
|
4
|
+
import { pageQueries, projectQueries } from "../lib/queries.js";
|
|
5
|
+
import { c } from "react/compiler-runtime";
|
|
6
|
+
import { useQuery } from "@tanstack/react-query";
|
|
7
|
+
|
|
8
|
+
//#region src/hooks/use-page-destinations.ts
|
|
9
|
+
function usePageDestinations() {
|
|
10
|
+
const $ = c(11);
|
|
11
|
+
if ($[0] !== "5104e69847ee48202a1abdefc32d4e478db3a82331029421687a5ec8f7a46eaa") {
|
|
12
|
+
for (let $i = 0; $i < 11; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
13
|
+
$[0] = "5104e69847ee48202a1abdefc32d4e478db3a82331029421687a5ec8f7a46eaa";
|
|
14
|
+
}
|
|
15
|
+
const app = useCamoxApp();
|
|
16
|
+
const projectSlug = useProjectSlug();
|
|
17
|
+
let t0;
|
|
18
|
+
if ($[1] !== projectSlug) {
|
|
19
|
+
t0 = projectQueries.getBySlug(projectSlug);
|
|
20
|
+
$[1] = projectSlug;
|
|
21
|
+
$[2] = t0;
|
|
22
|
+
} else t0 = $[2];
|
|
23
|
+
const { data: project } = useQuery(t0);
|
|
24
|
+
let t1;
|
|
25
|
+
if ($[3] !== project?.id) {
|
|
26
|
+
t1 = pageQueries.list(project?.id ?? 0);
|
|
27
|
+
$[3] = project?.id;
|
|
28
|
+
$[4] = t1;
|
|
29
|
+
} else t1 = $[4];
|
|
30
|
+
const t2 = !!project;
|
|
31
|
+
let t3;
|
|
32
|
+
if ($[5] !== t1 || $[6] !== t2) {
|
|
33
|
+
t3 = {
|
|
34
|
+
...t1,
|
|
35
|
+
enabled: t2
|
|
36
|
+
};
|
|
37
|
+
$[5] = t1;
|
|
38
|
+
$[6] = t2;
|
|
39
|
+
$[7] = t3;
|
|
40
|
+
} else t3 = $[7];
|
|
41
|
+
const { data: pages } = useQuery(t3);
|
|
42
|
+
let t4;
|
|
43
|
+
if ($[8] !== app || $[9] !== pages) {
|
|
44
|
+
t4 = getPageDestinations(pages ?? [], app.getLayouts());
|
|
45
|
+
$[8] = app;
|
|
46
|
+
$[9] = pages;
|
|
47
|
+
$[10] = t4;
|
|
48
|
+
} else t4 = $[10];
|
|
49
|
+
return t4;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { usePageDestinations };
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { actionsStore } from "../features/provider/actionsStore.js";
|
|
2
|
+
import { validatePreviewTarget } from "./preview-handoff.js";
|
|
3
|
+
import { SERVER_AUTH_COOKIE_NAME, buildClearServerAuthCookieHeader, getServerAuthCookieHeader } from "./server-auth-cookie.js";
|
|
2
4
|
import { c } from "react/compiler-runtime";
|
|
3
5
|
import * as React from "react";
|
|
4
6
|
import { oneTimeTokenClient, organizationClient } from "better-auth/client/plugins";
|
|
5
7
|
import { createAuthClient } from "better-auth/react";
|
|
6
8
|
|
|
7
9
|
//#region src/lib/auth.ts
|
|
8
|
-
const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
|
|
9
|
-
function buildClearServerAuthCookieHeader() {
|
|
10
|
-
return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
|
|
11
|
-
}
|
|
12
10
|
function writeServerAuthCookie(cookie) {
|
|
13
11
|
if (typeof document === "undefined") return;
|
|
14
12
|
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
@@ -184,21 +182,32 @@ function createCamoxAuthClient(apiUrl) {
|
|
|
184
182
|
* Process a `?ott=` one-time token from the URL before the provider mounts.
|
|
185
183
|
* Verifies the token against the API backend and notifies the session store.
|
|
186
184
|
*
|
|
187
|
-
*
|
|
185
|
+
* Reports completion and explicit handoff failures instead of silently showing live content.
|
|
188
186
|
*/
|
|
189
|
-
function useProcessOtt(authClient) {
|
|
187
|
+
function useProcessOtt(authClient, target) {
|
|
188
|
+
const [error, setError] = React.useState(null);
|
|
189
|
+
const processing = React.useRef(false);
|
|
190
190
|
const [ready, setReady] = React.useState(() => {
|
|
191
191
|
if (typeof window === "undefined") return true;
|
|
192
192
|
return !new URL(window.location.href).searchParams.has("ott");
|
|
193
193
|
});
|
|
194
194
|
React.useEffect(() => {
|
|
195
|
-
if (ready) return;
|
|
196
195
|
const url = new URL(window.location.href);
|
|
196
|
+
const targetError = validatePreviewTarget(url.searchParams.get("camox-preview"), target);
|
|
197
|
+
if (targetError) {
|
|
198
|
+
url.searchParams.delete("ott");
|
|
199
|
+
window.history.replaceState({}, "", url);
|
|
200
|
+
setError(targetError);
|
|
201
|
+
setReady(true);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (ready || processing.current) return;
|
|
197
205
|
const ott = url.searchParams.get("ott");
|
|
198
206
|
if (!ott) {
|
|
199
207
|
setReady(true);
|
|
200
208
|
return;
|
|
201
209
|
}
|
|
210
|
+
processing.current = true;
|
|
202
211
|
url.searchParams.delete("ott");
|
|
203
212
|
window.history.replaceState({}, "", url);
|
|
204
213
|
(async () => {
|
|
@@ -208,18 +217,29 @@ function useProcessOtt(authClient) {
|
|
|
208
217
|
if (!verification.data) throw new Error("OTT verification returned no session");
|
|
209
218
|
window.location.reload();
|
|
210
219
|
return;
|
|
211
|
-
} catch {
|
|
220
|
+
} catch {
|
|
221
|
+
setError("Preview sign-in failed. The link may have expired or already been used. Run camox preview again for a fresh link.");
|
|
222
|
+
}
|
|
212
223
|
setReady(true);
|
|
213
224
|
})();
|
|
214
|
-
}, [
|
|
215
|
-
|
|
225
|
+
}, [
|
|
226
|
+
authClient,
|
|
227
|
+
ready,
|
|
228
|
+
target.projectSlug,
|
|
229
|
+
target.environmentName,
|
|
230
|
+
target.apiUrl
|
|
231
|
+
]);
|
|
232
|
+
return {
|
|
233
|
+
ready,
|
|
234
|
+
error
|
|
235
|
+
};
|
|
216
236
|
}
|
|
217
237
|
const AuthContext = React.createContext(null);
|
|
218
238
|
function useAuthContext() {
|
|
219
239
|
const $ = c(1);
|
|
220
|
-
if ($[0] !== "
|
|
240
|
+
if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
|
|
221
241
|
for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
222
|
-
$[0] = "
|
|
242
|
+
$[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
|
|
223
243
|
}
|
|
224
244
|
const ctx = React.useContext(AuthContext);
|
|
225
245
|
if (!ctx) throw new Error("Missing CamoxProvider");
|
|
@@ -227,9 +247,9 @@ function useAuthContext() {
|
|
|
227
247
|
}
|
|
228
248
|
function useProjectSlug() {
|
|
229
249
|
const $ = c(1);
|
|
230
|
-
if ($[0] !== "
|
|
250
|
+
if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
|
|
231
251
|
for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
232
|
-
$[0] = "
|
|
252
|
+
$[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
|
|
233
253
|
}
|
|
234
254
|
return useAuthContext().projectSlug;
|
|
235
255
|
}
|
|
@@ -245,18 +265,18 @@ function useAuthState() {
|
|
|
245
265
|
}
|
|
246
266
|
function useIsAuthenticated() {
|
|
247
267
|
const $ = c(1);
|
|
248
|
-
if ($[0] !== "
|
|
268
|
+
if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
|
|
249
269
|
for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
250
|
-
$[0] = "
|
|
270
|
+
$[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
|
|
251
271
|
}
|
|
252
272
|
const { isAuthenticated } = useAuthState();
|
|
253
273
|
return isAuthenticated;
|
|
254
274
|
}
|
|
255
275
|
function useSignInRedirect(callbackHref) {
|
|
256
276
|
const $ = c(4);
|
|
257
|
-
if ($[0] !== "
|
|
277
|
+
if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
|
|
258
278
|
for (let $i = 0; $i < 4; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
259
|
-
$[0] = "
|
|
279
|
+
$[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
|
|
260
280
|
}
|
|
261
281
|
const { authenticationUrl } = useAuthContext();
|
|
262
282
|
let t0;
|
|
@@ -278,9 +298,9 @@ function useSignInRedirect(callbackHref) {
|
|
|
278
298
|
*/
|
|
279
299
|
function useAuthActions() {
|
|
280
300
|
const $ = c(5);
|
|
281
|
-
if ($[0] !== "
|
|
301
|
+
if ($[0] !== "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525") {
|
|
282
302
|
for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
283
|
-
$[0] = "
|
|
303
|
+
$[0] = "2870dba5d074cbe2d9a238f9ae39bb51493bf31c0dd30caa79404003309e1525";
|
|
284
304
|
}
|
|
285
305
|
const { authClient, authenticationUrl } = useAuthContext();
|
|
286
306
|
let t0;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/lib/preview-handoff.ts
|
|
2
|
+
/** Validate the destination before exchanging any credential with its backend. */
|
|
3
|
+
function validatePreviewTarget(raw, target) {
|
|
4
|
+
if (raw === null) return null;
|
|
5
|
+
try {
|
|
6
|
+
const expected = JSON.parse(raw);
|
|
7
|
+
if (expected.projectSlug === target.projectSlug && expected.environmentName === target.environmentName && typeof expected.apiUrl === "string" && expected.apiUrl.replace(/\/+$/, "") === target.apiUrl.replace(/\/+$/, "")) return null;
|
|
8
|
+
} catch {}
|
|
9
|
+
return "Preview destination does not match the CLI project, backend, or environment. Check --cwd and --url, then run camox preview again.";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { validatePreviewTarget };
|
package/dist/lib/queries.js
CHANGED
|
@@ -3,6 +3,17 @@ import { getApiUrl, getEnvironmentName, getOrpc } from "./api-client.js";
|
|
|
3
3
|
import { queryKeys } from "@camox/api-contract/query-keys";
|
|
4
4
|
|
|
5
5
|
//#region src/lib/queries.ts
|
|
6
|
+
const commentQueries = { list: (pageId) => ({
|
|
7
|
+
...getOrpc().comments.list.queryOptions({
|
|
8
|
+
input: { pageId },
|
|
9
|
+
staleTime: 3e4
|
|
10
|
+
}),
|
|
11
|
+
queryKey: [...queryKeys.comments.list(pageId), getEnvironmentName() ?? "production"]
|
|
12
|
+
}) };
|
|
13
|
+
const commentMutations = {
|
|
14
|
+
create: () => getOrpc().comments.create.mutationOptions(),
|
|
15
|
+
setResolved: () => getOrpc().comments.setResolved.mutationOptions()
|
|
16
|
+
};
|
|
6
17
|
const pageQueries = {
|
|
7
18
|
list: (projectId) => ({
|
|
8
19
|
...getOrpc().pages.list.queryOptions({
|
|
@@ -183,4 +194,4 @@ const fileMutations = {
|
|
|
183
194
|
};
|
|
184
195
|
|
|
185
196
|
//#endregion
|
|
186
|
-
export { blockMutations, blockQueries, environmentMutations, environmentQueries, fileMutations, fileQueries, layoutQueries, pageMutations, pageQueries, projectQueries, repeatableItemMutations };
|
|
197
|
+
export { blockMutations, blockQueries, commentMutations, commentQueries, environmentMutations, environmentQueries, fileMutations, fileQueries, layoutQueries, pageMutations, pageQueries, projectQueries, repeatableItemMutations };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/lib/server-auth-cookie.ts
|
|
2
|
+
const SERVER_AUTH_COOKIE_NAME = "camox_auth_cookie";
|
|
3
|
+
function buildClearServerAuthCookieHeader() {
|
|
4
|
+
return `${SERVER_AUTH_COOKIE_NAME}=; Path=/; SameSite=Lax; Max-Age=0`;
|
|
5
|
+
}
|
|
6
|
+
/** Forward credentials to the API for validation; cookie presence is not authentication.
|
|
7
|
+
* Cross-domain sessions use the mirror. Same-host sessions (including localhost
|
|
8
|
+
* on different ports) may only have Better Auth's native, HttpOnly cookie.
|
|
9
|
+
*/
|
|
10
|
+
function getServerAuthCookieHeader(headers) {
|
|
11
|
+
const cookies = (headers.get("Cookie") ?? "").split(";").map((part) => part.trim());
|
|
12
|
+
const mirror = cookies.find((part) => part.startsWith(`${SERVER_AUTH_COOKIE_NAME}=`));
|
|
13
|
+
if (mirror) try {
|
|
14
|
+
const value = decodeURIComponent(mirror.slice(18));
|
|
15
|
+
if (value) return value;
|
|
16
|
+
} catch {}
|
|
17
|
+
return cookies.filter((part) => part.startsWith("better-auth.session_token=") || part.startsWith("__Secure-better-auth.session_token=")).join("; ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
export { SERVER_AUTH_COOKIE_NAME, buildClearServerAuthCookieHeader, getServerAuthCookieHeader };
|