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.
Files changed (93) hide show
  1. package/dist/core/components/lexical/{GradientNode.js → HighlightNode.js} +34 -34
  2. package/dist/core/components/lexical/InlineLexicalEditor.js +14 -14
  3. package/dist/core/components/lexical/InlineStylesPlugin.js +6 -6
  4. package/dist/core/components/lexical/SelectionBroadcaster.js +5 -5
  5. package/dist/core/components/lexical/SidebarLexicalEditor.js +52 -49
  6. package/dist/core/components/lexical/TextLinkPopover.js +191 -208
  7. package/dist/core/components/lexical/editorConfig.js +2 -2
  8. package/dist/core/createApp.d.ts +19 -4
  9. package/dist/core/createApp.js +2 -1
  10. package/dist/core/createLayout.d.ts +16 -3
  11. package/dist/core/createLayout.js +15 -15
  12. package/dist/core/derivedRoutes.js +1 -1
  13. package/dist/core/dom.d.ts +17 -0
  14. package/dist/core/dom.js +22 -0
  15. package/dist/core/editing/createEditableBlock.d.ts +8 -1
  16. package/dist/core/editing/createEditableBlock.js +636 -459
  17. package/dist/core/hooks/useOverlayState.js +11 -0
  18. package/dist/core/lib/fieldTypes.js +5 -5
  19. package/dist/core/lib/lexicalReact.js +4 -4
  20. package/dist/core/lib/lexicalState.js +20 -4
  21. package/dist/core/lib/modifierFormats.js +1 -1
  22. package/dist/core/lib/textLinks.js +5 -2
  23. package/dist/core/lib/textStyles.d.ts +2 -2
  24. package/dist/core/lib/textStyles.js +6 -11
  25. package/dist/core/pageDestinations.js +37 -0
  26. package/dist/features/og/imageResponse.node.js +11 -9
  27. package/dist/features/preview/CamoxPreview.js +108 -124
  28. package/dist/features/preview/commentsEnabled.js +8 -0
  29. package/dist/features/preview/components/AddBlockSidebar.js +80 -90
  30. package/dist/features/preview/components/AssetLightbox.js +4 -11
  31. package/dist/features/preview/components/AttachedComments.js +194 -0
  32. package/dist/features/preview/components/BlockActionsPopover.js +14 -65
  33. package/dist/features/preview/components/CommentHeader.js +122 -0
  34. package/dist/features/preview/components/CommentSidebar.js +29 -0
  35. package/dist/features/preview/components/CommentTargetQuote.js +166 -0
  36. package/dist/features/preview/components/CreatePageModal.js +4 -10
  37. package/dist/features/preview/components/DerivedLayoutSidebar.js +45 -26
  38. package/dist/features/preview/components/DerivedPageInfoSidebar.js +38 -26
  39. package/dist/features/preview/components/FieldToolbar.js +7 -7
  40. package/dist/features/preview/components/ItemFieldsEditor.js +275 -204
  41. package/dist/features/preview/components/LeftSidebar.js +18 -18
  42. package/dist/features/preview/components/LinkFieldEditor.js +212 -209
  43. package/dist/features/preview/components/OverlayTracker.js +14 -20
  44. package/dist/features/preview/components/Overlays.js +26 -35
  45. package/dist/features/preview/components/PageEditorSidebar.js +90 -91
  46. package/dist/features/preview/components/PageInfoSidebar.js +128 -159
  47. package/dist/features/preview/components/PagePicker.js +29 -24
  48. package/dist/features/preview/components/PageTree.js +159 -157
  49. package/dist/features/preview/components/PreviewComments.js +56 -0
  50. package/dist/features/preview/components/PreviewPanel.js +122 -78
  51. package/dist/features/preview/components/PreviewToolbar.js +280 -231
  52. package/dist/features/preview/components/PublicationRow.js +8 -11
  53. package/dist/features/preview/components/RightSidebar.js +14 -11
  54. package/dist/features/preview/components/SidebarSection.js +121 -0
  55. package/dist/features/preview/components/useRepeatableItemActions.js +1 -1
  56. package/dist/features/preview/previewCommentsStore.js +115 -0
  57. package/dist/features/preview/previewSelection.js +75 -0
  58. package/dist/features/preview/previewStore.js +19 -39
  59. package/dist/features/preview/publication.js +10 -0
  60. package/dist/features/provider/AuthenticatedCamoxProvider.js +46 -34
  61. package/dist/features/provider/CoreCamoxProvider.js +1 -1
  62. package/dist/features/provider/components/CommandPalette.js +142 -58
  63. package/dist/features/provider/useAdminShortcuts.js +19 -8
  64. package/dist/features/routes/pageRuntime.js +0 -6
  65. package/dist/features/runtime/RuntimeChrome.js +40 -37
  66. package/dist/features/runtime/runtime.js +20 -21
  67. package/dist/features/studio/CamoxStudio.js +30 -50
  68. package/dist/features/studio/components/EnvironmentMenu.js +3 -2
  69. package/dist/features/studio/components/Navbar.js +53 -80
  70. package/dist/features/vite/layoutGeneration.js +7 -3
  71. package/dist/features/vite/runtimeDev.js +4 -1
  72. package/dist/features/vite/vite.d.ts +3 -2
  73. package/dist/features/vite/vite.js +14 -6
  74. package/dist/hooks/use-file-upload.js +0 -2
  75. package/dist/hooks/use-page-destinations.js +53 -0
  76. package/dist/lib/auth.js +11 -14
  77. package/dist/lib/server-auth-cookie.js +21 -0
  78. package/dist/studio.css +1 -1
  79. package/package.json +9 -7
  80. package/skills/camox/SKILL.md +32 -9
  81. package/skills/camox/references/block-definitions.md +84 -290
  82. package/skills/camox/references/cli-common.md +2 -4
  83. package/skills/camox/references/cli-content.md +147 -0
  84. package/skills/camox/references/cli-environments.md +1 -1
  85. package/skills/camox/references/dom-integrations.md +93 -0
  86. package/skills/camox/references/field-styling.md +50 -0
  87. package/skills/camox/references/layout-definitions.md +33 -147
  88. package/skills/camox/references/routed-layouts.md +71 -0
  89. package/skills/camoxify/SKILL.md +52 -0
  90. package/dist/lib/telemetry-client.js +0 -66
  91. package/dist/lib/telemetry.js +0 -26
  92. package/skills/camox/references/cli-blocks.md +0 -78
  93. package/skills/camox/references/cli-pages.md +0 -53
@@ -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 === "derived") {
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] !== "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a") {
16
+ if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
15
17
  for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
16
- $[0] = "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a";
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 !== "derived" && isLastBefore || void 0,
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] !== "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a") {
53
+ if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
52
54
  for (let $i = 0; $i < 5; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
53
- $[0] = "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a";
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 !== "derived" && isFirstAfter || void 0,
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] !== "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a") {
90
+ if ($[0] !== "776e504f06a16915ba8008212a5b0af4dec7762a3d0b05161ac1048bec619433") {
89
91
  for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
90
- $[0] = "2ae925f72331cad3d35d77334ec5712b5849211fd0dcab4e27af95282e583b7a";
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
- if (options.kind === "derived" && options.blocks.initial) throw new Error("Derived layouts cannot define initial page blocks");
178
- return createLayoutDefinition({
179
- ...options,
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 === "derived").sort((a, b) => routeSegments(a._internal.id).filter((s) => s.startsWith("$")).length - routeSegments(b._internal.id).filter((s) => s.startsWith("$")).length);
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 };
@@ -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
- onClick: (e: React.MouseEvent) => void;
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;