camox 0.41.1 → 0.43.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/{GradientNode.js → HighlightNode.js} +34 -34
- package/dist/core/components/lexical/InlineLexicalEditor.js +14 -14
- package/dist/core/components/lexical/InlineStylesPlugin.js +6 -6
- package/dist/core/components/lexical/SelectionBroadcaster.js +5 -5
- package/dist/core/components/lexical/SidebarLexicalEditor.js +52 -49
- package/dist/core/components/lexical/TextLinkPopover.js +191 -208
- package/dist/core/components/lexical/editorConfig.js +2 -2
- package/dist/core/createApp.d.ts +19 -4
- package/dist/core/createApp.js +2 -1
- 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 +8 -1
- package/dist/core/editing/createEditableBlock.js +636 -459
- package/dist/core/hooks/useOverlayState.js +11 -0
- package/dist/core/lib/fieldTypes.js +5 -5
- package/dist/core/lib/lexicalReact.js +4 -4
- package/dist/core/lib/lexicalState.js +20 -4
- package/dist/core/lib/modifierFormats.js +1 -1
- package/dist/core/lib/textLinks.js +5 -2
- package/dist/core/lib/textStyles.d.ts +2 -2
- package/dist/core/lib/textStyles.js +6 -11
- package/dist/core/pageDestinations.js +37 -0
- package/dist/features/og/imageResponse.node.js +11 -9
- package/dist/features/preview/CamoxPreview.js +108 -124
- package/dist/features/preview/commentsEnabled.js +8 -0
- package/dist/features/preview/components/AddBlockSidebar.js +80 -90
- package/dist/features/preview/components/AssetLightbox.js +4 -11
- package/dist/features/preview/components/AttachedComments.js +194 -0
- package/dist/features/preview/components/BlockActionsPopover.js +14 -65
- package/dist/features/preview/components/CommentHeader.js +122 -0
- package/dist/features/preview/components/CommentSidebar.js +29 -0
- package/dist/features/preview/components/CommentTargetQuote.js +166 -0
- package/dist/features/preview/components/CreatePageModal.js +4 -10
- package/dist/features/preview/components/DerivedLayoutSidebar.js +45 -26
- package/dist/features/preview/components/DerivedPageInfoSidebar.js +38 -26
- package/dist/features/preview/components/FieldToolbar.js +7 -7
- package/dist/features/preview/components/ItemFieldsEditor.js +275 -204
- package/dist/features/preview/components/LeftSidebar.js +18 -18
- package/dist/features/preview/components/LinkFieldEditor.js +212 -209
- package/dist/features/preview/components/OverlayTracker.js +14 -20
- package/dist/features/preview/components/Overlays.js +26 -35
- package/dist/features/preview/components/PageEditorSidebar.js +90 -91
- package/dist/features/preview/components/PageInfoSidebar.js +128 -159
- package/dist/features/preview/components/PagePicker.js +29 -24
- package/dist/features/preview/components/PageTree.js +159 -157
- package/dist/features/preview/components/PreviewComments.js +56 -0
- package/dist/features/preview/components/PreviewPanel.js +122 -78
- package/dist/features/preview/components/PreviewToolbar.js +280 -231
- package/dist/features/preview/components/PublicationRow.js +8 -11
- package/dist/features/preview/components/RightSidebar.js +14 -11
- package/dist/features/preview/components/SidebarSection.js +121 -0
- package/dist/features/preview/components/useRepeatableItemActions.js +1 -1
- package/dist/features/preview/previewCommentsStore.js +115 -0
- package/dist/features/preview/previewSelection.js +75 -0
- package/dist/features/preview/previewStore.js +19 -39
- package/dist/features/preview/publication.js +10 -0
- package/dist/features/provider/AuthenticatedCamoxProvider.js +46 -34
- package/dist/features/provider/CoreCamoxProvider.js +1 -1
- package/dist/features/provider/components/CommandPalette.js +142 -58
- package/dist/features/provider/useAdminShortcuts.js +19 -8
- package/dist/features/routes/pageRuntime.js +0 -6
- package/dist/features/runtime/RuntimeChrome.js +40 -37
- package/dist/features/runtime/runtime.js +20 -21
- package/dist/features/studio/CamoxStudio.js +30 -50
- package/dist/features/studio/components/EnvironmentMenu.js +3 -2
- package/dist/features/studio/components/Navbar.js +53 -80
- package/dist/features/vite/layoutGeneration.js +7 -3
- package/dist/features/vite/runtimeDev.js +4 -1
- package/dist/features/vite/vite.d.ts +3 -2
- package/dist/features/vite/vite.js +14 -6
- package/dist/hooks/use-file-upload.js +0 -2
- package/dist/hooks/use-page-destinations.js +53 -0
- package/dist/lib/auth.js +11 -14
- package/dist/lib/server-auth-cookie.js +21 -0
- package/dist/studio.css +1 -1
- package/package.json +9 -7
- package/skills/camox/SKILL.md +32 -9
- package/skills/camox/references/block-definitions.md +84 -290
- package/skills/camox/references/cli-common.md +2 -4
- package/skills/camox/references/cli-content.md +147 -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 +52 -0
- package/dist/lib/telemetry-client.js +0 -66
- package/dist/lib/telemetry.js +0 -26
- package/skills/camox/references/cli-blocks.md +0 -78
- package/skills/camox/references/cli-pages.md +0 -53
package/dist/core/createApp.js
CHANGED
|
@@ -7,7 +7,7 @@ function createApp({ blocks, layouts = [] }) {
|
|
|
7
7
|
for (const layout of layouts) {
|
|
8
8
|
const { id, kind } = layout._internal;
|
|
9
9
|
if (layoutsMap.has(id)) throw new Error(`Duplicate layout: ${id}`);
|
|
10
|
-
if (kind
|
|
10
|
+
if (kind !== "curated") {
|
|
11
11
|
const pattern = id.split(".").map((segment) => segment.startsWith("$") ? "$" : segment).join(".");
|
|
12
12
|
if (derivedPatterns.has(pattern)) throw new Error(`Equivalent derived route: ${id}`);
|
|
13
13
|
derivedPatterns.add(pattern);
|
|
@@ -46,6 +46,7 @@ function createApp({ blocks, layouts = [] }) {
|
|
|
46
46
|
getSerializableLayoutDefinitions() {
|
|
47
47
|
return Array.from(layoutsMap.values()).map((layout) => ({
|
|
48
48
|
layoutId: layout._internal.id,
|
|
49
|
+
kind: layout._internal.kind,
|
|
49
50
|
description: layout._internal.description,
|
|
50
51
|
blocks: layout._internal.blockDefinitions
|
|
51
52
|
}));
|
|
@@ -51,7 +51,7 @@ type ValidateLayoutOnlyBlocks<T extends readonly LayoutBlock[]> = { [K in keyof
|
|
|
51
51
|
type ValidatePageContentBlocks<T extends readonly LayoutBlock[]> = { [K in keyof T]: T[K] extends LayoutBlock<true> ? "❌ Camox: blocks in `blocks.initial` must NOT be `layoutOnly: true` — `initial` is for page-content blocks. Remove `layoutOnly: true` from this block's `createBlock` options." : T[K] };
|
|
52
52
|
interface CreateLayoutOptions<TBefore extends readonly LayoutBlock[], TAfter extends readonly LayoutBlock[], TInitial extends readonly LayoutBlock[]> {
|
|
53
53
|
id: string;
|
|
54
|
-
kind?: "curated" | "derived";
|
|
54
|
+
kind?: "curated" | "derived" | "singleton";
|
|
55
55
|
loader?: (context: {
|
|
56
56
|
params: Record<string, string>;
|
|
57
57
|
}) => unknown;
|
|
@@ -84,7 +84,7 @@ declare function createLayoutDefinition<const TBefore extends readonly LayoutBlo
|
|
|
84
84
|
useData(): unknown;
|
|
85
85
|
_internal: {
|
|
86
86
|
id: string;
|
|
87
|
-
kind: "curated" | "derived";
|
|
87
|
+
kind: "curated" | "derived" | "singleton";
|
|
88
88
|
loader: ((context: {
|
|
89
89
|
params: Record<string, string>;
|
|
90
90
|
}) => unknown) | undefined;
|
|
@@ -156,6 +156,19 @@ type FileOptions<Id extends string, Data, B extends readonly LayoutBlock[], A ex
|
|
|
156
156
|
children: React.ReactNode;
|
|
157
157
|
}>;
|
|
158
158
|
loader?: never;
|
|
159
|
+
} | {
|
|
160
|
+
kind: "singleton";
|
|
161
|
+
blocks: {
|
|
162
|
+
before: ValidateLayoutOnlyBlocks<B>;
|
|
163
|
+
after: ValidateLayoutOnlyBlocks<A>;
|
|
164
|
+
initial?: never;
|
|
165
|
+
};
|
|
166
|
+
loader?: (context: {
|
|
167
|
+
params: Record<string, never>;
|
|
168
|
+
}) => Data;
|
|
169
|
+
component: React.ComponentType<{
|
|
170
|
+
children?: never;
|
|
171
|
+
}>;
|
|
159
172
|
} | {
|
|
160
173
|
kind: "derived";
|
|
161
174
|
blocks: {
|
|
@@ -172,7 +185,7 @@ type FileOptions<Id extends string, Data, B extends readonly LayoutBlock[], A ex
|
|
|
172
185
|
children?: never;
|
|
173
186
|
}>;
|
|
174
187
|
});
|
|
175
|
-
declare function createLayout<const Id extends string>(id: Id): <Data, const B extends readonly LayoutBlock[], const A extends readonly LayoutBlock[], const I extends readonly LayoutBlock[] = []>(options: FileOptions<Id, Data, B, A, I>) => Omit<Layout, "useData"> & {
|
|
188
|
+
declare function createLayout<const Id extends string>(id: Id): <Data = undefined, const B extends readonly LayoutBlock[] = [], const A extends readonly LayoutBlock[] = [], const I extends readonly LayoutBlock[] = []>(options: FileOptions<Id, Data, B, A, I>) => Omit<Layout, "useData"> & {
|
|
176
189
|
useData: () => Awaited<Data>;
|
|
177
190
|
};
|
|
178
191
|
declare function createLayout<const B extends readonly LayoutBlock[], const A extends readonly LayoutBlock[], const I extends readonly LayoutBlock[] = []>(options: CreateLayoutOptions<B, A, I>): Layout;
|
|
@@ -5,15 +5,17 @@ import { Fragment, jsx } from "react/jsx-runtime";
|
|
|
5
5
|
|
|
6
6
|
//#region src/core/createLayout.tsx
|
|
7
7
|
function createLayoutDefinition(options) {
|
|
8
|
+
if (options.kind === "singleton" && !/^[\w-]+(?:\.[\w-]+)*$/.test(options.id)) throw new Error(`Singleton layouts require a fixed file route: ${options.id}`);
|
|
9
|
+
if (options.kind && options.kind !== "curated" && options.blocks.initial) throw new Error("Code-owned layouts cannot define initial page blocks");
|
|
8
10
|
const LayoutContext = React.createContext(null);
|
|
9
11
|
const beforeBlocks = options.blocks.before;
|
|
10
12
|
const afterBlocks = options.blocks.after;
|
|
11
13
|
const initialBlocks = options.blocks.initial;
|
|
12
14
|
const BeforeBlocks = () => {
|
|
13
15
|
const $ = c(5);
|
|
14
|
-
if ($[0] !== "
|
|
16
|
+
if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
|
|
15
17
|
for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
16
|
-
$[0] = "
|
|
18
|
+
$[0] = "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433";
|
|
17
19
|
}
|
|
18
20
|
const ctx = React.use(LayoutContext);
|
|
19
21
|
if (!ctx) throw new Error(`Layout "${options.id}" BeforeBlocks must be rendered inside its Provider`);
|
|
@@ -29,7 +31,7 @@ function createLayoutDefinition(options) {
|
|
|
29
31
|
children: /* @__PURE__ */ jsx(block._internal.Component, {
|
|
30
32
|
blockData,
|
|
31
33
|
mode: "layout",
|
|
32
|
-
showAddBlockBottom: options.kind
|
|
34
|
+
showAddBlockBottom: (options.kind ?? "curated") === "curated" && isLastBefore || void 0,
|
|
33
35
|
addBlockAfterPosition: isLastBefore ? "" : void 0
|
|
34
36
|
})
|
|
35
37
|
}, block._internal.id);
|
|
@@ -48,9 +50,9 @@ function createLayoutDefinition(options) {
|
|
|
48
50
|
BeforeBlocks.displayName = `LayoutBeforeBlocks(${options.id})`;
|
|
49
51
|
const AfterBlocks = () => {
|
|
50
52
|
const $ = c(5);
|
|
51
|
-
if ($[0] !== "
|
|
53
|
+
if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
|
|
52
54
|
for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
53
|
-
$[0] = "
|
|
55
|
+
$[0] = "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433";
|
|
54
56
|
}
|
|
55
57
|
const ctx = React.use(LayoutContext);
|
|
56
58
|
if (!ctx) throw new Error(`Layout "${options.id}" AfterBlocks must be rendered inside its Provider`);
|
|
@@ -66,7 +68,7 @@ function createLayoutDefinition(options) {
|
|
|
66
68
|
children: /* @__PURE__ */ jsx(block._internal.Component, {
|
|
67
69
|
blockData,
|
|
68
70
|
mode: "layout",
|
|
69
|
-
showAddBlockTop: options.kind
|
|
71
|
+
showAddBlockTop: (options.kind ?? "curated") === "curated" && isFirstAfter || void 0,
|
|
70
72
|
addBlockAfterPosition: isFirstAfter ? null : void 0
|
|
71
73
|
})
|
|
72
74
|
}, block._internal.id);
|
|
@@ -85,9 +87,9 @@ function createLayoutDefinition(options) {
|
|
|
85
87
|
AfterBlocks.displayName = `LayoutAfterBlocks(${options.id})`;
|
|
86
88
|
const Provider = (t0) => {
|
|
87
89
|
const $ = c(7);
|
|
88
|
-
if ($[0] !== "
|
|
90
|
+
if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
|
|
89
91
|
for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
90
|
-
$[0] = "
|
|
92
|
+
$[0] = "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433";
|
|
91
93
|
}
|
|
92
94
|
const { layoutBlocks, children, data } = t0;
|
|
93
95
|
let t1;
|
|
@@ -133,6 +135,7 @@ function createLayoutDefinition(options) {
|
|
|
133
135
|
};
|
|
134
136
|
})];
|
|
135
137
|
const buildOgImage = options.buildOgImage ? async (params) => {
|
|
138
|
+
if (import.meta.env?.SSR === false) throw new Error("OG images can only be rendered on the server.");
|
|
136
139
|
const { ImageResponse } = await import("camox/_internal/imageResponse");
|
|
137
140
|
return new ImageResponse(options.buildOgImage(params), {
|
|
138
141
|
width: 1200,
|
|
@@ -173,13 +176,10 @@ function createLayoutDefinition(options) {
|
|
|
173
176
|
}
|
|
174
177
|
function createLayout(idOrOptions) {
|
|
175
178
|
if (typeof idOrOptions !== "string") return createLayoutDefinition(idOrOptions);
|
|
176
|
-
return (options) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
id: idOrOptions
|
|
181
|
-
});
|
|
182
|
-
};
|
|
179
|
+
return (options) => createLayoutDefinition({
|
|
180
|
+
...options,
|
|
181
|
+
id: idOrOptions
|
|
182
|
+
});
|
|
183
183
|
}
|
|
184
184
|
/** Signal a missing derived page; other loader failures remain server errors. */
|
|
185
185
|
function notFound() {
|
|
@@ -4,7 +4,7 @@ function routeSegments(id) {
|
|
|
4
4
|
}
|
|
5
5
|
function matchDerivedLayout(layouts, pathname) {
|
|
6
6
|
const path = pathname.replace(/^\/|\/$/g, "").split("/");
|
|
7
|
-
const candidates = layouts.filter((layout) => layout._internal.kind
|
|
7
|
+
const candidates = layouts.filter((layout) => layout._internal.kind !== "curated").sort((a, b) => routeSegments(a._internal.id).filter((s) => s.startsWith("$")).length - routeSegments(b._internal.id).filter((s) => s.startsWith("$")).length);
|
|
8
8
|
for (const layout of candidates) {
|
|
9
9
|
const segments = routeSegments(layout._internal.id);
|
|
10
10
|
if (segments.length !== path.length) continue;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/core/dom.d.ts
|
|
2
|
+
/** The browsing context that owns a rendered element. */
|
|
3
|
+
interface ElementContext {
|
|
4
|
+
document: Document;
|
|
5
|
+
window: Window & typeof globalThis;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve DOM APIs from an element rather than the component's JavaScript realm.
|
|
9
|
+
* In Camox preview, elements belong to an iframe but component globals belong
|
|
10
|
+
* to the editor. Call this after mounting (for example, inside an effect).
|
|
11
|
+
*
|
|
12
|
+
* Returns null for a missing element or a document without a window. Never
|
|
13
|
+
* falls back to globals. This does not convert objects into the target realm.
|
|
14
|
+
*/
|
|
15
|
+
declare function getElementContext(element: Element | null | undefined): ElementContext | null;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ElementContext, getElementContext };
|
package/dist/core/dom.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/core/dom.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve DOM APIs from an element rather than the component's JavaScript realm.
|
|
4
|
+
* In Camox preview, elements belong to an iframe but component globals belong
|
|
5
|
+
* to the editor. Call this after mounting (for example, inside an effect).
|
|
6
|
+
*
|
|
7
|
+
* Returns null for a missing element or a document without a window. Never
|
|
8
|
+
* falls back to globals. This does not convert objects into the target realm.
|
|
9
|
+
*/
|
|
10
|
+
function getElementContext(element) {
|
|
11
|
+
if (!element) return null;
|
|
12
|
+
const document = element.ownerDocument;
|
|
13
|
+
const window = document.defaultView;
|
|
14
|
+
if (!window) return null;
|
|
15
|
+
return {
|
|
16
|
+
document,
|
|
17
|
+
window
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { getElementContext };
|
|
@@ -115,9 +115,10 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
115
115
|
children
|
|
116
116
|
}: {
|
|
117
117
|
children: (props: {
|
|
118
|
+
"data-camox-comment-block-id"?: number;
|
|
118
119
|
ref: (element: HTMLElement | null) => void;
|
|
119
120
|
style: React.CSSProperties;
|
|
120
|
-
|
|
121
|
+
onClickCapture: (e: React.MouseEvent) => void;
|
|
121
122
|
onMouseEnter: () => void;
|
|
122
123
|
onMouseLeave: () => void;
|
|
123
124
|
}) => React.ReactNode;
|
|
@@ -171,6 +172,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
171
172
|
"data-camox-overlay-mode"?: string;
|
|
172
173
|
onMouseEnter?: () => void;
|
|
173
174
|
onMouseLeave?: () => void;
|
|
175
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
174
176
|
}, data: {
|
|
175
177
|
/** Raw markdown source. Use `props.children` for rendered content. */text: string;
|
|
176
178
|
}) => React.ReactNode;
|
|
@@ -260,6 +262,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
260
262
|
"data-camox-overlay-mode"?: string;
|
|
261
263
|
onMouseEnter?: () => void;
|
|
262
264
|
onMouseLeave?: () => void;
|
|
265
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
263
266
|
}, data: {
|
|
264
267
|
text: string;
|
|
265
268
|
href: string;
|
|
@@ -530,6 +533,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
530
533
|
"data-camox-overlay-mode"?: string;
|
|
531
534
|
onMouseEnter?: () => void;
|
|
532
535
|
onMouseLeave?: () => void;
|
|
536
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
533
537
|
}, data: {
|
|
534
538
|
/** Raw markdown source. Use `props.children` for rendered content. */text: string;
|
|
535
539
|
}) => React.ReactNode;
|
|
@@ -629,6 +633,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
629
633
|
"data-camox-overlay-mode"?: string;
|
|
630
634
|
onMouseEnter?: () => void;
|
|
631
635
|
onMouseLeave?: () => void;
|
|
636
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
632
637
|
}, data: {
|
|
633
638
|
text: string;
|
|
634
639
|
href: string;
|
|
@@ -1254,6 +1259,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
1254
1259
|
"data-camox-overlay-mode"?: string;
|
|
1255
1260
|
onMouseEnter?: () => void;
|
|
1256
1261
|
onMouseLeave?: () => void;
|
|
1262
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
1257
1263
|
}, data: {
|
|
1258
1264
|
/** Raw markdown source. Use `props.children` for rendered content. */text: string;
|
|
1259
1265
|
}) => React.ReactNode;
|
|
@@ -1281,6 +1287,7 @@ declare function createEditableBlock<TSchemaShape extends Record<string, TSchema
|
|
|
1281
1287
|
"data-camox-overlay-mode"?: string;
|
|
1282
1288
|
onMouseEnter?: () => void;
|
|
1283
1289
|
onMouseLeave?: () => void;
|
|
1290
|
+
onClickCapture?: (event: React.MouseEvent<HTMLElement>) => void;
|
|
1284
1291
|
}, data: {
|
|
1285
1292
|
text: string;
|
|
1286
1293
|
href: string;
|