canopycms-next 0.0.68-int.94 → 0.0.68-int.96
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/client.d.ts +9 -2
- package/dist/client.js +4 -2
- package/dist/context-wrapper.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/preview-page.d.ts +58 -3
- package/dist/preview-page.js +31 -3
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -29,11 +29,16 @@ export declare const NextCanopyEditorPage: (config: CanopyClientConfig, customRe
|
|
|
29
29
|
/** What a preview view renders from: the editor's live draft, plus `useCanopyPreview`'s helpers. */
|
|
30
30
|
export type CanopyPreviewViewProps<T> = ReturnType<typeof useCanopyPreview<T>>;
|
|
31
31
|
/** The props of a view wrapped by `withCanopyPreview`. */
|
|
32
|
-
export interface CanopyPreviewProps<T> {
|
|
32
|
+
export interface CanopyPreviewProps<T, X = undefined> {
|
|
33
33
|
/** The saved entry data, rendered until the editor sends a draft. */
|
|
34
34
|
initialData: T;
|
|
35
35
|
/** Editor origin to trust, for an editor on another origin. Defaults to this page's own. */
|
|
36
36
|
editorOrigin?: string;
|
|
37
|
+
/**
|
|
38
|
+
* What the route's `load` returned (see `previewView`), handed to the view untouched as its
|
|
39
|
+
* `extras` prop. Absent on a public page and for a view with no loader.
|
|
40
|
+
*/
|
|
41
|
+
extras?: X;
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
44
|
* Wraps a view so it renders the editor's live draft of `initialData`, through `useCanopyPreview`;
|
|
@@ -48,4 +53,6 @@ export interface CanopyPreviewProps<T> {
|
|
|
48
53
|
* a view for one content type unassignable to a map of views for many. It returns `ReactElement`,
|
|
49
54
|
* which JSX accepts on every supported TypeScript, where `ReactNode` needs `JSX.ElementType`.
|
|
50
55
|
*/
|
|
51
|
-
export declare function withCanopyPreview<T>(View: (props: CanopyPreviewViewProps<T>
|
|
56
|
+
export declare function withCanopyPreview<T, X = undefined>(View: (props: CanopyPreviewViewProps<T> & {
|
|
57
|
+
extras: X | undefined;
|
|
58
|
+
}) => ReactNode): (props: CanopyPreviewProps<T, X>) => ReactElement;
|
package/dist/client.js
CHANGED
|
@@ -58,8 +58,10 @@ export const NextCanopyEditorPage = (config, customRenderers) => {
|
|
|
58
58
|
* which JSX accepts on every supported TypeScript, where `ReactNode` needs `JSX.ElementType`.
|
|
59
59
|
*/
|
|
60
60
|
export function withCanopyPreview(View) {
|
|
61
|
-
return function CanopyPreview({ initialData, editorOrigin }) {
|
|
61
|
+
return function CanopyPreview({ initialData, editorOrigin, extras }) {
|
|
62
62
|
const preview = useCanopyPreview({ initialData, editorOrigin });
|
|
63
|
-
|
|
63
|
+
// `extras` is a prop of its own, never spread into the hook's result, so it cannot shadow
|
|
64
|
+
// `data` or `fieldProps`.
|
|
65
|
+
return _jsx(View, { ...preview, extras: extras });
|
|
64
66
|
};
|
|
65
67
|
}
|
|
@@ -146,6 +146,8 @@ export interface NextCanopyContextResult {
|
|
|
146
146
|
* Each view is made by `withCanopyPreview` (`canopycms-next/client`) in a `'use client'` module.
|
|
147
147
|
* It renders each entry from the `?branch=` the editor names, through `views[entryType]` with
|
|
148
148
|
* the live draft. Reads are request-scoped and ACL-checked, and anything not readable is a 404.
|
|
149
|
+
* A view that needs more server reads is `previewView({ view, load })`: `load` runs after the
|
|
150
|
+
* entry is read and its result arrives as the view's `extras` prop.
|
|
149
151
|
*/
|
|
150
152
|
createPreviewPage: (options: CreatePreviewPageOptions) => (props: PreviewPageProps) => Promise<ReactElement>;
|
|
151
153
|
/** API catch-all route handler */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createCanopyCatchAllHandler, wrapNextRequest, type CanopyNextOptions } from './adapter.js';
|
|
2
2
|
export { createNextCanopyContext, type NextCanopyOptions, type NextCanopyContextResult, } from './context-wrapper.js';
|
|
3
|
-
export type
|
|
3
|
+
export { previewView, type CreatePreviewPageOptions, type PreviewEntry, type PreviewLoadContext, type PreviewPageProps, type PreviewViewWithLoader, } from './preview-page.js';
|
|
4
4
|
export { collectStaticParams, generateContentSitemap, entryToMetadata, type GenerateContentStaticParamsOptions, type GenerateContentSitemapOptions, type EntryToMetadataOptions, type SitemapExtraUrl, } from './static.js';
|
|
5
5
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createCanopyCatchAllHandler, wrapNextRequest } from './adapter.js';
|
|
2
2
|
export { createNextCanopyContext, } from './context-wrapper.js';
|
|
3
|
+
export { previewView, } from './preview-page.js';
|
|
3
4
|
export { collectStaticParams, generateContentSitemap, entryToMetadata, } from './static.js';
|
|
4
5
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils.js';
|
package/dist/preview-page.d.ts
CHANGED
|
@@ -2,13 +2,67 @@ import type { ReactElement, ReactNode } from 'react';
|
|
|
2
2
|
import type { CanopyConfig } from 'canopycms';
|
|
3
3
|
import type { CanopyContext } from 'canopycms/server';
|
|
4
4
|
import type { CanopyPreviewProps } from './client.js';
|
|
5
|
+
type ReadEntry = NonNullable<Awaited<ReturnType<CanopyContext['readByUrlPath']>>>;
|
|
6
|
+
/**
|
|
7
|
+
* The entry a `load` receives. It carries no `meta.physicalPath`, so a loader that returns its
|
|
8
|
+
* whole `entry` cannot send the server's filesystem layout to the browser.
|
|
9
|
+
*/
|
|
10
|
+
export interface PreviewEntry {
|
|
11
|
+
data: unknown;
|
|
12
|
+
path: string;
|
|
13
|
+
entryType: string;
|
|
14
|
+
entryId?: ReadEntry['meta']['entryId'];
|
|
15
|
+
}
|
|
16
|
+
/** What a `load` receives. */
|
|
17
|
+
export interface PreviewLoadContext {
|
|
18
|
+
/** The entry the route is rendering, as read from the requested branch. */
|
|
19
|
+
entry: PreviewEntry;
|
|
20
|
+
/** The request-scoped context the entry was read through: authenticated and ACL-checked. */
|
|
21
|
+
canopy: CanopyContext;
|
|
22
|
+
/** The `?branch=` the editor named, to pass on to further reads. */
|
|
23
|
+
branch: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A view and the server loader that feeds its `extras` prop. Make one with `previewView`.
|
|
27
|
+
*
|
|
28
|
+
* `load` runs on the server, once per request, after the entry is read and its view found, so an
|
|
29
|
+
* entry type with no view is a 404 without running it. It may throw, or call `notFound()`: either
|
|
30
|
+
* rejects the page rather than rendering.
|
|
31
|
+
*
|
|
32
|
+
* Its result reaches the browser like any RSC prop, so it must be RSC-serializable: plain data
|
|
33
|
+
* or React elements. A server-rendered element such as `<RelatedPosts />` is fine, which lets a
|
|
34
|
+
* view reuse a server component for a section that need not update live. The result is a
|
|
35
|
+
* snapshot of the request and does not follow the editor's live draft; only `data` does.
|
|
36
|
+
*/
|
|
37
|
+
export interface PreviewViewWithLoader<X> {
|
|
38
|
+
view: (props: CanopyPreviewProps<never, X>) => ReactNode;
|
|
39
|
+
load: (ctx: PreviewLoadContext) => X | Promise<X>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The form `views` holds, with `X` erased. `view` takes `extras?: never`, so a view of any `X`
|
|
43
|
+
* is assignable (its parameter is only wider), and `load` returns `unknown`, so any loader is
|
|
44
|
+
* too. That drops the check that `load`'s result is what `view` expects, which `previewView`
|
|
45
|
+
* restores where each pair is written.
|
|
46
|
+
*/
|
|
47
|
+
interface ErasedPreviewViewWithLoader {
|
|
48
|
+
view: (props: CanopyPreviewProps<never, never>) => ReactNode;
|
|
49
|
+
load: (ctx: PreviewLoadContext) => unknown;
|
|
50
|
+
}
|
|
51
|
+
type PreviewView = (props: CanopyPreviewProps<never>) => ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Pairs a view with its loader, so TypeScript checks that `load`'s result is what the view's
|
|
54
|
+
* `extras` prop expects. A record of views cannot infer a distinct `X` per key, so this identity
|
|
55
|
+
* function infers it for each entry on its own.
|
|
56
|
+
*/
|
|
57
|
+
export declare function previewView<X>(entry: PreviewViewWithLoader<X>): PreviewViewWithLoader<X>;
|
|
5
58
|
export interface CreatePreviewPageOptions {
|
|
6
59
|
/**
|
|
7
60
|
* The view for each entry type the preview route serves, keyed by entry type name, each made by
|
|
8
|
-
* `withCanopyPreview` (`canopycms-next/client`) in a `'use client'` module
|
|
9
|
-
*
|
|
61
|
+
* `withCanopyPreview` (`canopycms-next/client`) in a `'use client'` module, or a
|
|
62
|
+
* `previewView({ view, load })` pair when the view needs more than the entry. An entry whose
|
|
63
|
+
* type has no view is a 404.
|
|
10
64
|
*/
|
|
11
|
-
views: Record<string,
|
|
65
|
+
views: Record<string, PreviewView | ErasedPreviewViewWithLoader>;
|
|
12
66
|
/** Editor origin to trust, for an editor on another origin. Defaults to the page's own. */
|
|
13
67
|
editorOrigin?: string;
|
|
14
68
|
}
|
|
@@ -33,3 +87,4 @@ export interface PreviewPageProps {
|
|
|
33
87
|
* @internal Exported for tests.
|
|
34
88
|
*/
|
|
35
89
|
export declare function createPreviewPageFor(getCanopy: () => Promise<CanopyContext>, options: CreatePreviewPageOptions, deployedAs?: CanopyConfig['deployedAs']): (props: PreviewPageProps) => Promise<ReactElement>;
|
|
90
|
+
export {};
|
package/dist/preview-page.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { notFound } from 'next/navigation';
|
|
3
|
+
/**
|
|
4
|
+
* Pairs a view with its loader, so TypeScript checks that `load`'s result is what the view's
|
|
5
|
+
* `extras` prop expects. A record of views cannot infer a distinct `X` per key, so this identity
|
|
6
|
+
* function infers it for each entry on its own.
|
|
7
|
+
*/
|
|
8
|
+
export function previewView(entry) {
|
|
9
|
+
return entry;
|
|
10
|
+
}
|
|
11
|
+
function hasLoader(entry) {
|
|
12
|
+
return (typeof entry === 'object' &&
|
|
13
|
+
entry !== null &&
|
|
14
|
+
Object.prototype.hasOwnProperty.call(entry, 'load'));
|
|
15
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* The page for a `[[...path]]` route mounted at `editor.previewPrefix`: it reads the entry at
|
|
5
18
|
* the route's path from the `?branch=` the editor names and renders its view.
|
|
@@ -26,11 +39,26 @@ export function createPreviewPageFor(getCanopy, options, deployedAs = 'server')
|
|
|
26
39
|
if (!result)
|
|
27
40
|
notFound();
|
|
28
41
|
const { entryType } = result.meta;
|
|
29
|
-
const
|
|
42
|
+
const viewEntry = Object.prototype.hasOwnProperty.call(options.views, entryType)
|
|
30
43
|
? options.views[entryType]
|
|
31
44
|
: undefined;
|
|
32
|
-
if (!
|
|
45
|
+
if (!viewEntry)
|
|
33
46
|
notFound();
|
|
34
|
-
|
|
47
|
+
if (!hasLoader(viewEntry)) {
|
|
48
|
+
const View = viewEntry;
|
|
49
|
+
return _jsx(View, { initialData: result.data, editorOrigin: options.editorOrigin });
|
|
50
|
+
}
|
|
51
|
+
const { meta } = result;
|
|
52
|
+
const entry = {
|
|
53
|
+
data: result.data,
|
|
54
|
+
path: result.path,
|
|
55
|
+
entryType: meta.entryType,
|
|
56
|
+
entryId: meta.entryId,
|
|
57
|
+
};
|
|
58
|
+
const extras = await viewEntry.load({ entry, canopy, branch });
|
|
59
|
+
// The record erases `view` to `extras?: never`, so it cannot check `load`'s result against the
|
|
60
|
+
// view; `previewView` does, where a pair is written.
|
|
61
|
+
const View = viewEntry.view;
|
|
62
|
+
return _jsx(View, { initialData: result.data, editorOrigin: options.editorOrigin, extras: extras });
|
|
35
63
|
};
|
|
36
64
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-next",
|
|
3
|
-
"version": "0.0.68-int.
|
|
3
|
+
"version": "0.0.68-int.96",
|
|
4
4
|
"description": "Next.js adapter for CanopyCMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"next": "^13.5.7 || ^14.2.25 || ^15.5.21 || >=16.0.0 <16.2.0 || >=16.3.0 <17.0.0",
|
|
40
40
|
"react": "^18.0.0 || ^19.0.0",
|
|
41
|
-
"canopycms": "0.0.68-int.
|
|
41
|
+
"canopycms": "0.0.68-int.96"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^22.9.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"react": "^18.3.1",
|
|
49
49
|
"typescript": "^5.6.3",
|
|
50
50
|
"vitest": "^4.1.0",
|
|
51
|
-
"canopycms": "0.0.68-int.
|
|
51
|
+
"canopycms": "0.0.68-int.96"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/add-js-extensions.mjs dist && esbuild src/config.ts --bundle --platform=node --packages=external --external:next --format=cjs --outfile=dist/config.cjs && esbuild src/config.ts --bundle --platform=node --packages=external --external:next --format=esm --outfile=dist/config.mjs",
|