camox 0.47.0 → 0.51.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/createApp.d.ts +19 -6
- package/dist/core/createApp.js +7 -2
- package/dist/core/createBlock.js +54 -43
- package/dist/core/createCollection.d.ts +25 -7
- package/dist/core/createCollection.js +28 -9
- package/dist/core/createLayout.d.ts +98 -71
- package/dist/core/createLayout.js +10 -16
- package/dist/core/derivedRoutes.js +1 -1
- package/dist/core/editing/BlockEditingRuntime.d.ts +3 -2
- package/dist/core/editing/CompleteBlockEditingRuntime.js +2 -1
- package/dist/core/editing/createEditableBlock.d.ts +59 -19
- package/dist/core/editing/createEditableBlock.js +162 -151
- package/dist/core/editing/useLatestRecord.js +7 -7
- package/dist/core/layoutContext.d.ts +55 -0
- package/dist/core/layoutContext.js +66 -0
- package/dist/core/lib/contentType.d.ts +91 -4
- package/dist/core/lib/contentType.js +84 -7
- package/dist/core/lib/recordScope.js +19 -17
- package/dist/core/lib/reference.d.ts +6 -2
- package/dist/core/recordItem.js +55 -2
- package/dist/core/requestContext.d.ts +18 -0
- package/dist/core/structuredData.d.ts +126 -0
- package/dist/core/structuredData.js +40 -0
- package/dist/features/canvas/canvasCamera.js +1 -1
- package/dist/features/canvas/sidebar/CanvasInspector.js +1 -1
- package/dist/features/canvas/sidebar/CanvasSidebarFrame.js +1 -1
- package/dist/features/content/ContentAssets.js +84 -88
- package/dist/features/content/ContentCollection.js +13 -7
- package/dist/features/content/collection-form.js +6 -2
- package/dist/features/content/components/MissingFromLocale.js +1 -1
- package/dist/features/og/pageImage.js +89 -0
- package/dist/features/page/CuratedPageContent.js +45 -32
- package/dist/features/page/DerivedPageContent.js +49 -22
- package/dist/features/page/SharedLayoutContent.js +26 -22
- package/dist/features/preview/CamoxPreview.js +41 -27
- package/dist/features/preview/EditablePageExperience.js +17 -9
- package/dist/features/preview/components/AddBlockSheet.js +1 -0
- package/dist/features/preview/components/BlockActionsPopover.js +19 -41
- package/dist/features/preview/components/FieldToolbar.js +1 -1
- package/dist/features/preview/components/MobilePreviewDrawer.js +6 -9
- package/dist/features/preview/components/PageInfo.js +244 -264
- package/dist/features/preview/components/PageTree.js +63 -53
- package/dist/features/preview/components/PreviewPanel.js +10 -64
- package/dist/features/preview/components/PreviewToolbar.js +1 -1
- package/dist/features/preview/components/RecordCard.js +50 -34
- package/dist/features/preview/components/RecordCombobox.js +12 -5
- package/dist/features/preview/components/ReferenceFieldEditor.js +4 -2
- package/dist/features/preview/components/ReferenceListFieldEditor.js +24 -19
- package/dist/features/preview/components/RepeatableItemsList.js +62 -46
- package/dist/features/preview/components/SelectionEditor.js +3 -2
- package/dist/features/preview/components/useFieldWriter.js +2 -4
- package/dist/features/preview/components/useRecordView.js +2 -2
- package/dist/features/preview/entityName.js +11 -0
- package/dist/features/preview/pageRecord.js +17 -18
- package/dist/features/preview/previewConstants.js +2 -8
- package/dist/features/preview/usePreviewViewportActions.js +1 -1
- package/dist/features/provider/AuthenticatedCamoxProvider.js +1 -1
- package/dist/features/provider/components/CommandPalette.js +2 -5
- package/dist/features/provider/signInShortcut.js +1 -2
- package/dist/features/provider/useAdminShortcuts.js +47 -53
- package/dist/features/routes/pageRuntime.d.ts +17 -2
- package/dist/features/routes/pageRuntime.js +61 -57
- package/dist/features/runtime/RuntimeChrome.js +1 -1
- package/dist/features/runtime/breadcrumbs.js +68 -0
- package/dist/features/runtime/livePages.js +3 -2
- package/dist/features/runtime/pageGraph.js +92 -0
- package/dist/features/runtime/projectLogo.js +110 -0
- package/dist/features/runtime/publicCache.js +83 -0
- package/dist/features/runtime/runtime.d.ts +24 -9
- package/dist/features/runtime/runtime.js +447 -130
- package/dist/features/runtime/runtimeRoutes.d.ts +1 -0
- package/dist/features/runtime/runtimeRoutes.js +1 -0
- package/dist/features/runtime/structuredData.js +213 -0
- package/dist/features/runtime/worker.d.ts +5 -1
- package/dist/features/runtime/worker.js +20 -3
- package/dist/features/settings/AnalyticsSettings.js +622 -0
- package/dist/features/settings/CamoxSettings.js +95 -0
- package/dist/features/settings/FaviconSettings.js +254 -0
- package/dist/features/settings/ProjectNameSettings.js +145 -0
- package/dist/features/settings/SettingsPart.js +73 -0
- package/dist/features/settings/SocialProfilesSettings.js +245 -0
- package/dist/features/settings/useProjectMutation.js +61 -0
- package/dist/features/studio/components/ChatSidebar.js +5 -5
- package/dist/features/studio/components/DraftsControl.js +136 -131
- package/dist/features/studio/components/LocalePicker.js +178 -174
- package/dist/features/studio/components/Navbar.js +107 -66
- package/dist/features/studio/components/ProjectMenu.js +85 -110
- package/dist/features/studio/components/ReviewDraftSheet.js +1046 -990
- package/dist/features/studio/components/ThreadMessages.js +2 -2
- package/dist/features/studio/components/UserButton.js +1 -1
- package/dist/features/studio/routes.js +3 -1
- package/dist/features/studio/showInCanvas.js +129 -0
- package/dist/features/vite/blockBoilerplate.js +3 -2
- package/dist/features/vite/definitionsSync.js +4 -5
- package/dist/features/vite/layoutGeneration.js +3 -2
- package/dist/features/vite/runtimeDev.js +32 -17
- package/dist/features/vite/staticAssetHeaders.js +52 -0
- package/dist/features/vite/vite.d.ts +1 -1
- package/dist/features/vite/vite.js +17 -4
- package/dist/lib/api-client-server.d.ts +1 -1
- package/dist/lib/api-client.d.ts +2 -1
- package/dist/lib/draft-scope.d.ts +1 -0
- package/dist/lib/locale-path.d.ts +3 -11
- package/dist/lib/locale-path.js +3 -27
- package/dist/lib/queries.d.ts +4 -0
- package/dist/lib/queries.js +8 -1
- package/dist/lib/shortcuts.js +29 -0
- package/dist/lib/studio-reads.d.ts +1 -0
- package/dist/lib/studio-reads.js +3 -1
- package/dist/lib/utils.js +1 -104
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +6 -4
- package/skills/camox/references/block-definitions.md +105 -25
- package/skills/camox/references/layout-definitions.md +53 -17
- package/skills/camox/references/routed-layouts.md +17 -14
- package/dist/features/studio/components/FaviconDialog.js +0 -318
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AnyLayoutContext, CuratedLayoutContext, LayoutPage, LayoutProject, LoadedLayoutContext, RecordLayoutContext } from "./layoutContext.js";
|
|
2
|
+
import { ReferenceKeys, ReferenceListKeys, ReferenceScope } from "./lib/reference.js";
|
|
3
|
+
import { CollectionRecordId, LayoutRequestContext, LoadedRecord, RecordPageStructuredData } from "./requestContext.js";
|
|
4
|
+
import { KnownPropertiesOnly, LayoutStructuredData, MainEntity, StructuredDataNode, StructuredDataType, WebPageKind } from "./structuredData.js";
|
|
3
5
|
import { notFound } from "./notFound.js";
|
|
4
6
|
import { ContentProxy } from "./lib/contentType.js";
|
|
5
7
|
import * as React from "react";
|
|
8
|
+
import { RecordLink } from "@camox/api-contract";
|
|
6
9
|
import { TSchema } from "@sinclair/typebox";
|
|
7
10
|
//#region src/core/createLayout.d.ts
|
|
8
|
-
export interface OgImageParams {
|
|
9
|
-
title: string;
|
|
10
|
-
description: string;
|
|
11
|
-
projectName: string;
|
|
12
|
-
}
|
|
13
11
|
export interface LayoutBlockData {
|
|
14
12
|
_id: string;
|
|
15
13
|
type: string;
|
|
@@ -74,6 +72,13 @@ export interface LayoutCollectionItemResult<Content = Record<string, unknown>, C
|
|
|
74
72
|
kind: "collection-item";
|
|
75
73
|
collectionId: CollectionId;
|
|
76
74
|
data: LoadedRecord<Content>;
|
|
75
|
+
/** The record's Structured Data, for its page's graph. Set by `collection.pages()`. */
|
|
76
|
+
structuredData?: RecordPageStructuredData;
|
|
77
|
+
/** When the record was first and last published to Live, for its page's graph. */
|
|
78
|
+
published?: {
|
|
79
|
+
first: number | null;
|
|
80
|
+
last: number | null;
|
|
81
|
+
};
|
|
77
82
|
/**
|
|
78
83
|
* Type only, never set: the collection's fields, which type `layout.Item`. Invariant, so that
|
|
79
84
|
* records of two collections never merge into one.
|
|
@@ -103,8 +108,8 @@ type LoaderReturn = AnyLayoutLoaderResult | PromiseLike<AnyLayoutLoaderResult>;
|
|
|
103
108
|
/** The record a discovered page renders, independent of its current params. */
|
|
104
109
|
export interface LayoutRouteTarget extends CollectionRecordId {
|
|
105
110
|
kind: "collection-item";
|
|
106
|
-
/** The record's
|
|
107
|
-
|
|
111
|
+
/** The record's title, which names its page where pages are listed (llms.txt). */
|
|
112
|
+
title?: string;
|
|
108
113
|
}
|
|
109
114
|
/** Route discovery result: the params of each page a derived layout can render. */
|
|
110
115
|
export interface LayoutRoutesResult<Params = Record<string, string>> {
|
|
@@ -126,10 +131,6 @@ type RecordFields<Result> = [Awaited<Result>] extends [never] ? never : [Awaited
|
|
|
126
131
|
type StringFieldKeys<F> = { [K in keyof F & string]: F[K] extends {
|
|
127
132
|
fieldType: "String";
|
|
128
133
|
} ? K : never; }[keyof F & string];
|
|
129
|
-
/** Record pages' meta callbacks also get the record's String fields as plain text. */
|
|
130
|
-
type MetaItem<F> = [F] extends [never] ? {} : {
|
|
131
|
-
item: { [K in StringFieldKeys<F>]: string; };
|
|
132
|
-
};
|
|
133
134
|
/** Record pages' `toMarkdown` also gets `item`, the reference-list Markdown builder. */
|
|
134
135
|
type MarkdownItem<F> = [F] extends [never] ? {} : F extends Record<string, TSchema> ? {
|
|
135
136
|
item: ContentProxy<F>;
|
|
@@ -141,39 +142,51 @@ type ItemScope<F> = [F] extends [never] ? {} : F extends Record<string, TSchema>
|
|
|
141
142
|
children: (item: ReferenceScope<F>) => React.ReactNode;
|
|
142
143
|
}) => React.ReactNode;
|
|
143
144
|
} : {};
|
|
144
|
-
/** What a page's metadata callbacks know about it. Curated pages have no loader data or params. */
|
|
145
|
-
export interface PageMetaContext<Data = unknown, Params = Record<string, string>> {
|
|
146
|
-
projectName: string;
|
|
147
|
-
pageFullPath: string;
|
|
148
|
-
data: Data;
|
|
149
|
-
params: Params;
|
|
150
|
-
}
|
|
151
|
-
/** On singleton and derived pages, `pageMetaTitle` is the layout's `title`. */
|
|
152
|
-
type BuildMetaTitle<Data, Params, F = never> = (params: PageMetaContext<Data, Params> & {
|
|
153
|
-
pageMetaTitle: string;
|
|
154
|
-
} & MetaItem<F>) => string;
|
|
155
|
-
/** Without it, curated pages use the editor's description and other pages have none. */
|
|
156
|
-
type BuildMetaDescription<Data, Params, F = never> = (params: PageMetaContext<Data, Params> & {
|
|
157
|
-
pageMetaDescription: string | undefined;
|
|
158
|
-
} & MetaItem<F>) => string | undefined;
|
|
159
145
|
/** The Markdown Representation of a code-owned page, between its layout blocks. */
|
|
160
146
|
type LayoutToMarkdown<Data, Params, F = never> = (context: {
|
|
161
147
|
data: Data;
|
|
162
148
|
params: Params;
|
|
163
149
|
} & MarkdownItem<F>) => readonly string[];
|
|
164
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* On record pages, the context's `content` holds the record's String fields as plain text, and
|
|
152
|
+
* its references as links to their records' nodes, for `toStructuredData`.
|
|
153
|
+
*/
|
|
154
|
+
type RecordContent<F> = [F] extends [never] ? {} : F extends Record<string, TSchema> ? {
|
|
155
|
+
content: { [K in StringFieldKeys<F>]: string; } & { [K in ReferenceKeys<F>]: RecordLink | undefined; } & { [K in ReferenceListKeys<F>]: RecordLink[]; };
|
|
156
|
+
} : {
|
|
157
|
+
content: { [K in StringFieldKeys<F>]: string; };
|
|
158
|
+
};
|
|
159
|
+
/** The context a singleton or derived layout's `to*` options receive. */
|
|
160
|
+
type LoadedContext<Data, Params, F = never> = LoadedLayoutContext<Data, Params> & RecordContent<F>;
|
|
161
|
+
/**
|
|
162
|
+
* A layout's outputs, each receiving the page's context: `toReact`, `toTitle`, `toDescription`,
|
|
163
|
+
* `toImage` and `toStructuredData`.
|
|
164
|
+
*/
|
|
165
|
+
type LayoutOutputs<Context, Children, Declared extends LayoutStructuredData | undefined> = {
|
|
166
|
+
toReact: React.ComponentType<Context & Children>;
|
|
167
|
+
toTitle: (context: Context) => string;
|
|
168
|
+
/** Required, but may return nothing, such as `({ page }) => page.description`. */
|
|
169
|
+
toDescription: (context: Context) => string | undefined;
|
|
170
|
+
toImage?: (context: Context) => React.ReactElement;
|
|
171
|
+
/**
|
|
172
|
+
* What the page is: a kind of web page (`AboutPage`, `ContactPage`, …), which its node
|
|
173
|
+
* becomes, or a main entity (`BlogPosting`, `Product`, `Event`, …), which Camox emits as the
|
|
174
|
+
* page's `mainEntity`. Camox fills the main entity's name or headline, description, URL,
|
|
175
|
+
* image, dates, publisher and language where the layout leaves them out.
|
|
176
|
+
*/
|
|
177
|
+
toStructuredData?: (context: Context) => Declared & KnownPropertiesOnly<Declared, LayoutStructuredData>;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* The collection a layout's record pages render, and the field each record is found by. `param`
|
|
181
|
+
* names the route param holding that field's value; only the collection and field are synced.
|
|
182
|
+
*/
|
|
165
183
|
export type RecordPages = {
|
|
166
184
|
collectionId: string;
|
|
167
185
|
field: string;
|
|
186
|
+
param?: string;
|
|
168
187
|
};
|
|
169
|
-
interface
|
|
188
|
+
interface LayoutBaseOptions<TBefore extends readonly LayoutBlock[], TAfter extends readonly LayoutBlock[], TInitial extends readonly InitialBlockImport[]> {
|
|
170
189
|
id: string;
|
|
171
|
-
kind?: "curated" | "derived" | "singleton";
|
|
172
|
-
loader?: (context: LayoutLoaderContext) => Result;
|
|
173
|
-
/** Lists the pages of a derived layout, for the sitemap and the Canvas. */
|
|
174
|
-
routes?: (context: LayoutRoutesContext) => RoutesReturn<Record<string, string>>;
|
|
175
|
-
/** A derived layout's record pages, set by spreading `collection.pages(...)`. */
|
|
176
|
-
recordPages?: RecordPages;
|
|
177
190
|
title: string;
|
|
178
191
|
description: string;
|
|
179
192
|
blocks: {
|
|
@@ -185,16 +198,33 @@ interface CreateLayoutOptions<TBefore extends readonly LayoutBlock[], TAfter ext
|
|
|
185
198
|
*/
|
|
186
199
|
initial?: ValidatePageContentBlocks<TInitial>;
|
|
187
200
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
}
|
|
202
|
+
type CreateLayoutOptions<TBefore extends readonly LayoutBlock[], TAfter extends readonly LayoutBlock[], TInitial extends readonly InitialBlockImport[], Result extends LoaderReturn = LoaderReturn, Declared extends LayoutStructuredData | undefined = LayoutStructuredData | undefined> = LayoutBaseOptions<TBefore, TAfter, TInitial> & {
|
|
203
|
+
kind?: "curated" | "derived" | "singleton";
|
|
204
|
+
loader?: (context: LayoutLoaderContext) => Result;
|
|
205
|
+
/** Lists the pages of a derived layout, for the sitemap and the Canvas. */
|
|
206
|
+
routes?: (context: LayoutRoutesContext) => RoutesReturn<Record<string, string>>;
|
|
207
|
+
/** A derived layout's record pages, set by spreading `collection.pages(...)`. */
|
|
208
|
+
recordPages?: RecordPages;
|
|
193
209
|
/** Singleton and derived layouts only. */
|
|
194
210
|
toMarkdown?: LayoutToMarkdown<LoaderData<Result>, Record<string, string>, RecordFields<Result>>;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
} & LayoutOutputs<LoadedContext<LoaderData<Result>, Record<string, string>, RecordFields<Result>>, {
|
|
212
|
+
children: React.ReactNode;
|
|
213
|
+
}, Declared>;
|
|
214
|
+
/** Every option a layout may define, as the implementation reads them. */
|
|
215
|
+
type AnyLayoutOptions = LayoutBaseOptions<any, any, any> & {
|
|
216
|
+
kind?: "curated" | "derived" | "singleton";
|
|
217
|
+
loader?: (context: LayoutLoaderContext) => LoaderReturn;
|
|
218
|
+
routes?: (context: LayoutRoutesContext) => RoutesReturn<Record<string, string>>;
|
|
219
|
+
recordPages?: RecordPages;
|
|
220
|
+
toMarkdown?: LayoutToMarkdown<any, any, any>;
|
|
221
|
+
toReact: React.ComponentType<any>;
|
|
222
|
+
toTitle: (context: any) => string;
|
|
223
|
+
toDescription: (context: any) => string | undefined;
|
|
224
|
+
toImage?: (context: any) => React.ReactElement;
|
|
225
|
+
toStructuredData?: (context: any) => LayoutStructuredData | undefined;
|
|
226
|
+
};
|
|
227
|
+
declare function createLayoutDefinition(options: AnyLayoutOptions): {
|
|
198
228
|
BeforeBlocks: {
|
|
199
229
|
(): React.JSX.Element;
|
|
200
230
|
displayName: string;
|
|
@@ -218,14 +248,19 @@ declare function createLayoutDefinition<const TBefore extends readonly LayoutBlo
|
|
|
218
248
|
recordPages: RecordPages | undefined;
|
|
219
249
|
title: string;
|
|
220
250
|
description: string;
|
|
221
|
-
|
|
222
|
-
|
|
251
|
+
/** The page's title, from the page's context. */
|
|
252
|
+
toTitle: (context: AnyLayoutContext) => string;
|
|
253
|
+
/** The page's description, from the page's context. */
|
|
254
|
+
toDescription: (context: AnyLayoutContext) => string | undefined;
|
|
223
255
|
toMarkdown: ((context: {
|
|
224
256
|
data: unknown;
|
|
225
257
|
params: unknown;
|
|
226
258
|
item?: unknown;
|
|
227
259
|
}) => readonly string[]) | undefined;
|
|
228
|
-
|
|
260
|
+
/** The page's shared image, which `/og` renders. Without one, Camox's default image. */
|
|
261
|
+
toImage: ((context: AnyLayoutContext) => React.ReactElement) | undefined;
|
|
262
|
+
/** What the page is, from the page's context. Without it, a plain web page. */
|
|
263
|
+
toStructuredData: (context: AnyLayoutContext) => LayoutStructuredData | undefined;
|
|
229
264
|
blockDefinitions: ({
|
|
230
265
|
type: string;
|
|
231
266
|
content: Record<string, unknown>;
|
|
@@ -263,8 +298,9 @@ declare function createLayoutDefinition<const TBefore extends readonly LayoutBlo
|
|
|
263
298
|
position: string;
|
|
264
299
|
}[];
|
|
265
300
|
}[]>) | undefined;
|
|
266
|
-
|
|
267
|
-
|
|
301
|
+
/** Renders the layout around the page, with the page's context as props. */
|
|
302
|
+
toReact: React.ComponentType<AnyLayoutContext & {
|
|
303
|
+
children?: React.ReactNode;
|
|
268
304
|
}>;
|
|
269
305
|
Provider: ({ layoutBlocks, children, result }: {
|
|
270
306
|
layoutBlocks: Record<string, LayoutBlockData>;
|
|
@@ -284,18 +320,15 @@ type Params<Id extends string> = Id extends `${infer Head}.${infer Tail}` ? Para
|
|
|
284
320
|
type RouteParams<Id extends string> = Id extends keyof LayoutFileRoutes ? LayoutFileRoutes[Id] extends {
|
|
285
321
|
params: infer P;
|
|
286
322
|
} ? P : Params<Id> : Params<Id>;
|
|
287
|
-
type FileOptions<Id extends string, Result extends LoaderReturn, B extends readonly LayoutBlock[], A extends readonly LayoutBlock[], I extends readonly InitialBlockImport[]> = Omit<
|
|
323
|
+
type FileOptions<Id extends string, Result extends LoaderReturn, B extends readonly LayoutBlock[], A extends readonly LayoutBlock[], I extends readonly InitialBlockImport[], Declared extends LayoutStructuredData | undefined> = Omit<LayoutBaseOptions<B, A, I>, "id"> & (({
|
|
288
324
|
kind: "curated";
|
|
289
|
-
component: React.ComponentType<{
|
|
290
|
-
children: React.ReactNode;
|
|
291
|
-
}>;
|
|
292
325
|
loader?: never;
|
|
293
326
|
routes?: never;
|
|
294
327
|
recordPages?: never;
|
|
295
328
|
toMarkdown?: never;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
} | {
|
|
329
|
+
} & LayoutOutputs<CuratedLayoutContext, {
|
|
330
|
+
children: React.ReactNode;
|
|
331
|
+
}, Declared>) | ({
|
|
299
332
|
kind: "singleton";
|
|
300
333
|
blocks: {
|
|
301
334
|
before: ValidateLayoutOnlyBlocks<B>;
|
|
@@ -306,12 +339,9 @@ type FileOptions<Id extends string, Result extends LoaderReturn, B extends reado
|
|
|
306
339
|
routes?: never;
|
|
307
340
|
recordPages?: never;
|
|
308
341
|
toMarkdown?: LayoutToMarkdown<LoaderData<Result>, Record<string, never>, RecordFields<Result>>;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
buildMetaTitle: BuildMetaTitle<LoaderData<Result>, Record<string, never>, RecordFields<Result>>;
|
|
313
|
-
buildMetaDescription?: BuildMetaDescription<LoaderData<Result>, Record<string, never>, RecordFields<Result>>;
|
|
314
|
-
} | {
|
|
342
|
+
} & LayoutOutputs<LoadedContext<LoaderData<Result>, Record<string, never>, RecordFields<Result>>, {
|
|
343
|
+
children?: never;
|
|
344
|
+
}, Declared>) | ({
|
|
315
345
|
kind: "derived";
|
|
316
346
|
blocks: {
|
|
317
347
|
before: ValidateLayoutOnlyBlocks<B>;
|
|
@@ -322,13 +352,10 @@ type FileOptions<Id extends string, Result extends LoaderReturn, B extends reado
|
|
|
322
352
|
routes?: (context: LayoutRoutesContext) => RoutesReturn<RouteParams<Id>>;
|
|
323
353
|
recordPages?: RecordPages;
|
|
324
354
|
toMarkdown?: LayoutToMarkdown<LoaderData<Result>, RouteParams<Id>, RecordFields<Result>>;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
});
|
|
331
|
-
export declare function createLayout<const B extends readonly LayoutBlock[], const A extends readonly LayoutBlock[], const I extends readonly InitialBlockImport[] = [], Result extends LoaderReturn = never>(options: CreateLayoutOptions<B, A, I, Result>): LayoutFor<Result>;
|
|
332
|
-
export declare function createLayout<const Id extends string>(id: Id): <Result extends LoaderReturn = never, const B extends readonly LayoutBlock[] = [], const A extends readonly LayoutBlock[] = [], const I extends readonly InitialBlockImport[] = []>(options: FileOptions<Id, Result, B, A, I>) => LayoutFor<Result>;
|
|
355
|
+
} & LayoutOutputs<LoadedContext<LoaderData<Result>, RouteParams<Id>, RecordFields<Result>>, {
|
|
356
|
+
children?: never;
|
|
357
|
+
}, Declared>));
|
|
358
|
+
export declare function createLayout<const B extends readonly LayoutBlock[], const A extends readonly LayoutBlock[], const I extends readonly InitialBlockImport[] = [], Result extends LoaderReturn = never, Declared extends LayoutStructuredData | undefined = undefined>(options: CreateLayoutOptions<B, A, I, Result, Declared>): LayoutFor<Result>;
|
|
359
|
+
export declare function createLayout<const Id extends string>(id: Id): <Result extends LoaderReturn = never, const B extends readonly LayoutBlock[] = [], const A extends readonly LayoutBlock[] = [], const I extends readonly InitialBlockImport[] = [], Declared extends LayoutStructuredData | undefined = undefined>(options: FileOptions<Id, Result, B, A, I, Declared>) => LayoutFor<Result>;
|
|
333
360
|
//#endregion
|
|
334
|
-
export { type LayoutRequestContext, notFound };
|
|
361
|
+
export { type CuratedLayoutContext, type LayoutPage, type LayoutProject, type LayoutRequestContext, type LayoutStructuredData, type LoadedLayoutContext, type MainEntity, type RecordLayoutContext, type StructuredDataNode, type StructuredDataType, type WebPageKind, notFound };
|
|
@@ -2,7 +2,7 @@ import { notFound } from "./notFound.js";
|
|
|
2
2
|
import { useOptionalCamoxApp } from "../features/provider/components/CamoxAppContext.js";
|
|
3
3
|
import { useBlockEditingRuntime } from "./editing/BlockEditingRuntime.js";
|
|
4
4
|
import { BlockErrorBoundary } from "../features/preview/components/BlockErrorBoundary.js";
|
|
5
|
-
import { asCollectionItem
|
|
5
|
+
import { asCollectionItem } from "./recordItem.js";
|
|
6
6
|
import { RecordScope } from "./lib/recordScope.js";
|
|
7
7
|
import { c } from "react/compiler-runtime";
|
|
8
8
|
import * as React from "react";
|
|
@@ -133,12 +133,13 @@ function createLayoutDefinition(options) {
|
|
|
133
133
|
t2 = null;
|
|
134
134
|
break bb0;
|
|
135
135
|
}
|
|
136
|
-
const { contentSchema
|
|
136
|
+
const { contentSchema } = collection._internal;
|
|
137
137
|
const { id, content, references } = result.data;
|
|
138
138
|
const readOnly = /* @__PURE__ */ jsx(RecordScope, {
|
|
139
139
|
record: {
|
|
140
140
|
id,
|
|
141
|
-
|
|
141
|
+
title: result.data.title ?? "",
|
|
142
|
+
description: result.data.description ?? null,
|
|
142
143
|
content,
|
|
143
144
|
references
|
|
144
145
|
},
|
|
@@ -152,7 +153,7 @@ function createLayoutDefinition(options) {
|
|
|
152
153
|
}
|
|
153
154
|
t1 = editingRuntime.renderLayoutItem({
|
|
154
155
|
collectionId: result.collectionId,
|
|
155
|
-
|
|
156
|
+
collection,
|
|
156
157
|
properties: contentSchema.properties,
|
|
157
158
|
record: result.data,
|
|
158
159
|
children,
|
|
@@ -192,14 +193,6 @@ function createLayoutDefinition(options) {
|
|
|
192
193
|
slot: "after"
|
|
193
194
|
};
|
|
194
195
|
})];
|
|
195
|
-
const buildOgImage = options.buildOgImage ? async (params) => {
|
|
196
|
-
if (import.meta.env?.SSR === false) throw new Error("OG images can only be rendered on the server.");
|
|
197
|
-
const { ImageResponse } = await import("camox/_internal/imageResponse");
|
|
198
|
-
return new ImageResponse(options.buildOgImage(params), {
|
|
199
|
-
width: 1200,
|
|
200
|
-
height: 630
|
|
201
|
-
});
|
|
202
|
-
} : void 0;
|
|
203
196
|
const loadInitialBlockBundles = initialBlocks && (() => Promise.all(initialBlocks.map(async (load) => {
|
|
204
197
|
const { block } = await load();
|
|
205
198
|
if (block._internal.layoutOnly) throw new Error(`${INITIAL_LAYOUT_ONLY_ERROR} (block "${block._internal.id}")`);
|
|
@@ -228,13 +221,14 @@ function createLayoutDefinition(options) {
|
|
|
228
221
|
recordPages: options.recordPages,
|
|
229
222
|
title: options.title,
|
|
230
223
|
description: options.description,
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
toTitle: (context) => options.toTitle(context),
|
|
225
|
+
toDescription: (context) => options.toDescription(context),
|
|
233
226
|
toMarkdown: options.toMarkdown,
|
|
234
|
-
|
|
227
|
+
toImage: options.toImage,
|
|
228
|
+
toStructuredData: (context) => options.toStructuredData?.(context),
|
|
235
229
|
blockDefinitions,
|
|
236
230
|
loadInitialBlockBundles,
|
|
237
|
-
|
|
231
|
+
toReact: options.toReact,
|
|
238
232
|
Provider
|
|
239
233
|
}
|
|
240
234
|
};
|
|
@@ -55,7 +55,7 @@ function targetRecord(entry) {
|
|
|
55
55
|
if (typeof target?.id !== "string") return {};
|
|
56
56
|
return {
|
|
57
57
|
recordId: target.id,
|
|
58
|
-
...typeof target.
|
|
58
|
+
...typeof target.title === "string" && { title: target.title }
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LoadedRecord } from "../requestContext.js";
|
|
2
|
+
import { Collection } from "../createCollection.js";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
//#region src/core/editing/BlockEditingRuntime.d.ts
|
|
4
5
|
/** A record page's record, as `layout.Item` hands it to the editing runtime. */
|
|
5
6
|
export interface LayoutItemProps {
|
|
6
7
|
collectionId: string;
|
|
7
|
-
/** The
|
|
8
|
-
|
|
8
|
+
/** The record's collection, whose templates title and describe it. */
|
|
9
|
+
collection: Collection;
|
|
9
10
|
/** The collection's field schemas. */
|
|
10
11
|
properties: Record<string, unknown>;
|
|
11
12
|
record: LoadedRecord;
|
|
@@ -3,13 +3,14 @@ import { ArtifactRenderProps } from "../lib/artifact.js";
|
|
|
3
3
|
import { InlineStyle, InlineTextStyles, TextLinkStyleData, TextStyleData } from "../lib/textStyles.js";
|
|
4
4
|
import "../lib/lexicalReact.js";
|
|
5
5
|
import { ReferenceContent, ReferenceKeys, ReferenceListKeys, ReferenceScope } from "../lib/reference.js";
|
|
6
|
+
import { BlockStructuredData } from "../structuredData.js";
|
|
6
7
|
import { IconValue } from "../lib/iconTypes.js";
|
|
7
|
-
import { ArtifactDataCheck, ArtifactSource, ContentFieldBuilder, EmbedURL, FileValue, ImageValue, ItemSettingsBrand, LinkValue, SettingBuilder, ToMarkdownBuilder } from "../lib/contentType.js";
|
|
8
|
+
import { ArtifactDataCheck, ArtifactSource, ContentFieldBuilder, EmbedURL, FileValue, ImageValue, ItemSettingsBrand, LinkValue, SettingBuilder, ToMarkdownBuilder, ToStructuredDataBuilder, ToTitleBuilder } from "../lib/contentType.js";
|
|
8
9
|
import { LayoutItemProps } from "./BlockEditingRuntime.js";
|
|
9
10
|
import * as React from "react";
|
|
10
11
|
import { Static, TArray, TObject, TSchema, Type } from "@sinclair/typebox";
|
|
11
12
|
//#region src/core/editing/createEditableBlock.d.ts
|
|
12
|
-
interface
|
|
13
|
+
interface BlockBaseOptions<TSchemaShape extends Record<string, TSchema>, TSettingsShape extends Record<string, TSchema>, TLayoutOnly extends boolean, TNodes extends BlockStructuredData = BlockStructuredData> {
|
|
13
14
|
id: string;
|
|
14
15
|
/**
|
|
15
16
|
* Human-readable title for the block (JSON Schema `title`).
|
|
@@ -31,7 +32,8 @@ interface CreateBlockOptions<TSchemaShape extends Record<string, TSchema> = Reco
|
|
|
31
32
|
* content: (field) => ({ name: field.string({ default: "Item" }) }),
|
|
32
33
|
* minItems: 1,
|
|
33
34
|
* maxItems: 10,
|
|
34
|
-
*
|
|
35
|
+
* toTitle: ({ content }) => content.name,
|
|
36
|
+
* toMarkdown: ({ content }) => [content.name],
|
|
35
37
|
* }),
|
|
36
38
|
* })
|
|
37
39
|
*/
|
|
@@ -54,28 +56,62 @@ interface CreateBlockOptions<TSchemaShape extends Record<string, TSchema> = Reco
|
|
|
54
56
|
layoutOnly?: TLayoutOnly;
|
|
55
57
|
/** Share content and settings across all placements of this block definition. Defaults to false. */
|
|
56
58
|
synced?: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* React component that renders the block.
|
|
59
|
-
* Must be defined as a separate function (not inline, not an arrow function).
|
|
60
|
-
* Should use the Field component returned by createBlock to render editable content.
|
|
61
|
-
*/
|
|
62
|
-
component: React.ComponentType<{
|
|
63
|
-
content: Static<ReturnType<typeof Type.Object<TSchemaShape>>>;
|
|
64
|
-
}>;
|
|
65
59
|
/**
|
|
66
60
|
* Builder for rendering block content as markdown.
|
|
67
|
-
* `
|
|
68
|
-
* `
|
|
69
|
-
* (`
|
|
70
|
-
* to wrap lines in a conditional. Each returned entry
|
|
71
|
-
* (joined with `\n\n`). Lines where all referenced fields resolve to empty
|
|
61
|
+
* `content` is a proxy typed on `content` keys — `content.title`, `content.description`, etc.
|
|
62
|
+
* `settings` is a proxy typed on `settings` keys — call a boolean setting with a line
|
|
63
|
+
* (`settings.showCta(content.cta)`) or an enum with a value
|
|
64
|
+
* (`settings.variant("banner", [...])`) to wrap lines in a conditional. Each returned entry
|
|
65
|
+
* becomes a paragraph (joined with `\n\n`). Lines where all referenced fields resolve to empty
|
|
72
66
|
* (or whose condition is false) are omitted at render time.
|
|
73
67
|
*
|
|
74
68
|
* @example
|
|
75
|
-
* toMarkdown: (
|
|
69
|
+
* toMarkdown: ({ content, settings }) => [
|
|
70
|
+
* `# ${content.title}`,
|
|
71
|
+
* content.description,
|
|
72
|
+
* settings.showCta(content.cta),
|
|
73
|
+
* ]
|
|
76
74
|
*/
|
|
77
75
|
toMarkdown: ToMarkdownBuilder<TSchemaShape, TSettingsShape>;
|
|
76
|
+
/**
|
|
77
|
+
* Names the block in the studio and to agents until it has a Summary: one line from
|
|
78
|
+
* `{ content, settings }`, read like `toMarkdown`'s, on the default locale's content.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* toTitle: ({ content }) => content.title
|
|
82
|
+
*/
|
|
83
|
+
toTitle: ToTitleBuilder<TSchemaShape, TSettingsShape>;
|
|
84
|
+
/**
|
|
85
|
+
* The things the block shows, as Structured Data nodes from `{ content, settings }`: one node,
|
|
86
|
+
* or a list. A repeater's token places its items' nodes. Camox links each node to the page,
|
|
87
|
+
* and each placement gets its own. Blocks never say what the page is, and can't describe an
|
|
88
|
+
* `Organization`: Camox emits the site's.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* toStructuredData: ({ content }) => ({
|
|
92
|
+
* "@type": "VideoObject",
|
|
93
|
+
* name: content.title,
|
|
94
|
+
* embedUrl: content.video,
|
|
95
|
+
* thumbnailUrl: content.thumbnail,
|
|
96
|
+
* uploadDate: content.uploadDate,
|
|
97
|
+
* })
|
|
98
|
+
*/
|
|
99
|
+
toStructuredData?: ToStructuredDataBuilder<TSchemaShape, TSettingsShape, TNodes>;
|
|
78
100
|
}
|
|
101
|
+
/** What a block's `toReact` receives as props: its content and settings. */
|
|
102
|
+
export interface BlockContext<TSchemaShape extends Record<string, TSchema>, TSettingsShape extends Record<string, TSchema>> {
|
|
103
|
+
content: Static<ReturnType<typeof Type.Object<TSchemaShape>>>;
|
|
104
|
+
settings: Static<ReturnType<typeof Type.Object<TSettingsShape>>>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The React component that renders the block. Should use the Field component returned by
|
|
108
|
+
* createBlock to render editable content.
|
|
109
|
+
*/
|
|
110
|
+
type BlockToReact<TSchemaShape extends Record<string, TSchema>, TSettingsShape extends Record<string, TSchema>> = {
|
|
111
|
+
/** Renders the block, with its content and settings as props. */
|
|
112
|
+
toReact: React.ComponentType<BlockContext<TSchemaShape, TSettingsShape>>;
|
|
113
|
+
};
|
|
114
|
+
type CreateBlockOptions<TSchemaShape extends Record<string, TSchema> = Record<string, TSchema>, TSettingsShape extends Record<string, TSchema> = Record<string, TSchema>, TLayoutOnly extends boolean = false, TNodes extends BlockStructuredData = BlockStructuredData> = BlockBaseOptions<TSchemaShape, TSettingsShape, TLayoutOnly, TNodes> & BlockToReact<TSchemaShape, TSettingsShape>;
|
|
79
115
|
interface BlockData<TContent> {
|
|
80
116
|
_id: string;
|
|
81
117
|
type: string;
|
|
@@ -99,6 +135,7 @@ export interface PeekItem {
|
|
|
99
135
|
content: unknown;
|
|
100
136
|
settings: Record<string, unknown> | null;
|
|
101
137
|
summary: string;
|
|
138
|
+
title: string;
|
|
102
139
|
position: string;
|
|
103
140
|
createdAt: number;
|
|
104
141
|
updatedAt: number;
|
|
@@ -115,7 +152,7 @@ export interface RepeatableItemSeed {
|
|
|
115
152
|
settings?: Record<string, unknown>;
|
|
116
153
|
position: string;
|
|
117
154
|
}
|
|
118
|
-
export declare function createEditableBlock<TSchemaShape extends Record<string, TSchema>, TSettingsShape extends Record<string, TSchema> = Record<string, never>, TLayoutOnly extends boolean = false>(options: CreateBlockOptions<TSchemaShape, TSettingsShape, TLayoutOnly>): {
|
|
155
|
+
export declare function createEditableBlock<TSchemaShape extends Record<string, TSchema>, TSettingsShape extends Record<string, TSchema> = Record<string, never>, TLayoutOnly extends boolean = false, TNodes extends BlockStructuredData = BlockStructuredData>(options: CreateBlockOptions<TSchemaShape, TSettingsShape, TLayoutOnly, TNodes>): {
|
|
119
156
|
Detached: ({ children }: {
|
|
120
157
|
children: (props: {
|
|
121
158
|
"data-camox-block-id"?: string;
|
|
@@ -1657,17 +1694,19 @@ export declare function createEditableBlock<TSchemaShape extends Record<string,
|
|
|
1657
1694
|
params: unknown[] & [];
|
|
1658
1695
|
})["static"]; }, Exclude<keyof TSchemaShape, { [K_1 in keyof TSchemaShape]: TSchemaShape[K_1] extends import("@sinclair/typebox").TReadonly<TSchema> ? TSchemaShape[K_1] extends import("@sinclair/typebox").TOptional<TSchemaShape[K_1]> ? K_1 : never : never; }[keyof TSchemaShape] | { [K_2 in keyof TSchemaShape]: TSchemaShape[K_2] extends import("@sinclair/typebox").TReadonly<TSchema> ? TSchemaShape[K_2] extends import("@sinclair/typebox").TOptional<TSchemaShape[K_2]> ? never : K_2 : never; }[keyof TSchemaShape] | { [K_3 in keyof TSchemaShape]: TSchemaShape[K_3] extends import("@sinclair/typebox").TOptional<TSchema> ? TSchemaShape[K_3] extends import("@sinclair/typebox").TReadonly<TSchemaShape[K_3]> ? never : K_3 : never; }[keyof TSchemaShape]>>>>>) => React.JSX.Element;
|
|
1659
1696
|
/** Renders a record page's record; see `BlockEditingRuntime.renderLayoutItem`. */
|
|
1660
|
-
LayoutItem: ({ collectionId,
|
|
1697
|
+
LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
1661
1698
|
id: string;
|
|
1662
1699
|
title: string;
|
|
1663
1700
|
description: string;
|
|
1664
1701
|
contentSchema: {
|
|
1702
|
+
toStructuredData?: unknown;
|
|
1665
1703
|
type: "object";
|
|
1666
1704
|
title: string;
|
|
1667
1705
|
description: string;
|
|
1668
1706
|
properties: TSchemaShape;
|
|
1669
1707
|
required: string[];
|
|
1670
1708
|
toMarkdown: string[];
|
|
1709
|
+
toTitle: string;
|
|
1671
1710
|
};
|
|
1672
1711
|
settingsSchema: {
|
|
1673
1712
|
type: "object";
|
|
@@ -1705,6 +1744,7 @@ export declare function createEditableBlock<TSchemaShape extends Record<string,
|
|
|
1705
1744
|
settings: Record<string, unknown>;
|
|
1706
1745
|
slot: null;
|
|
1707
1746
|
summary: string;
|
|
1747
|
+
title: string;
|
|
1708
1748
|
position: string;
|
|
1709
1749
|
createdAt: number;
|
|
1710
1750
|
updatedAt: number;
|