canopycms-next 0.0.68-int.93 → 0.0.68-int.94
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 +25 -0
- package/dist/client.js +20 -1
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/context-wrapper.d.ts +16 -0
- package/dist/context-wrapper.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/preview-page.d.ts +35 -0
- package/dist/preview-page.js +36 -0
- package/dist/with-canopy.d.ts +1 -1
- package/dist/with-canopy.js +4 -4
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { useCanopyPreview } from 'canopycms/client';
|
|
1
3
|
import type { CanopyClientConfig, CustomFieldRenderers } from 'canopycms/client';
|
|
2
4
|
/**
|
|
3
5
|
* Next.js-specific wrapper for CanopyEditorPage that automatically reads
|
|
@@ -24,3 +26,26 @@ import type { CanopyClientConfig, CustomFieldRenderers } from 'canopycms/client'
|
|
|
24
26
|
* ```
|
|
25
27
|
*/
|
|
26
28
|
export declare const NextCanopyEditorPage: (config: CanopyClientConfig, customRenderers?: CustomFieldRenderers) => () => import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
/** What a preview view renders from: the editor's live draft, plus `useCanopyPreview`'s helpers. */
|
|
30
|
+
export type CanopyPreviewViewProps<T> = ReturnType<typeof useCanopyPreview<T>>;
|
|
31
|
+
/** The props of a view wrapped by `withCanopyPreview`. */
|
|
32
|
+
export interface CanopyPreviewProps<T> {
|
|
33
|
+
/** The saved entry data, rendered until the editor sends a draft. */
|
|
34
|
+
initialData: T;
|
|
35
|
+
/** Editor origin to trust, for an editor on another origin. Defaults to this page's own. */
|
|
36
|
+
editorOrigin?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a view so it renders the editor's live draft of `initialData`, through `useCanopyPreview`;
|
|
40
|
+
* outside an editor frame it renders `initialData` unchanged.
|
|
41
|
+
*
|
|
42
|
+
* Call it in your own `'use client'` module and pass the result to `createPreviewPage`'s `views`,
|
|
43
|
+
* or render it on a public page. The wrapping lives in your module, not in `createPreviewPage`,
|
|
44
|
+
* because Next ships every client module a page's server code imports: one imported by the
|
|
45
|
+
* context would put the editor in every page that reads content.
|
|
46
|
+
*
|
|
47
|
+
* The returned type is a call signature rather than `ComponentType`, whose `propTypes` would make
|
|
48
|
+
* a view for one content type unassignable to a map of views for many. It returns `ReactElement`,
|
|
49
|
+
* which JSX accepts on every supported TypeScript, where `ReactNode` needs `JSX.ElementType`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function withCanopyPreview<T>(View: (props: CanopyPreviewViewProps<T>) => ReactNode): (props: CanopyPreviewProps<T>) => ReactElement;
|
package/dist/client.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Suspense } from 'react';
|
|
4
4
|
import { useSearchParams } from 'next/navigation';
|
|
5
|
-
import { CanopyEditorPage } from 'canopycms/client';
|
|
5
|
+
import { CanopyEditorPage, useCanopyPreview } from 'canopycms/client';
|
|
6
6
|
/**
|
|
7
7
|
* Next.js-specific wrapper for CanopyEditorPage that automatically reads
|
|
8
8
|
* URL search params (branch, entry) using Next.js's useSearchParams hook.
|
|
@@ -44,3 +44,22 @@ export const NextCanopyEditorPage = (config, customRenderers) => {
|
|
|
44
44
|
return (_jsx(Suspense, { fallback: null, children: _jsx(EditorWithSearchParams, {}) }));
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Wraps a view so it renders the editor's live draft of `initialData`, through `useCanopyPreview`;
|
|
49
|
+
* outside an editor frame it renders `initialData` unchanged.
|
|
50
|
+
*
|
|
51
|
+
* Call it in your own `'use client'` module and pass the result to `createPreviewPage`'s `views`,
|
|
52
|
+
* or render it on a public page. The wrapping lives in your module, not in `createPreviewPage`,
|
|
53
|
+
* because Next ships every client module a page's server code imports: one imported by the
|
|
54
|
+
* context would put the editor in every page that reads content.
|
|
55
|
+
*
|
|
56
|
+
* The returned type is a call signature rather than `ComponentType`, whose `propTypes` would make
|
|
57
|
+
* a view for one content type unassignable to a map of views for many. It returns `ReactElement`,
|
|
58
|
+
* which JSX accepts on every supported TypeScript, where `ReactNode` needs `JSX.ElementType`.
|
|
59
|
+
*/
|
|
60
|
+
export function withCanopyPreview(View) {
|
|
61
|
+
return function CanopyPreview({ initialData, editorOrigin }) {
|
|
62
|
+
const preview = useCanopyPreview({ initialData, editorOrigin });
|
|
63
|
+
return _jsx(View, { ...preview });
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/config.cjs
CHANGED
|
@@ -399,7 +399,7 @@ function withCanopy(nextConfig = {}, options = {}) {
|
|
|
399
399
|
])
|
|
400
400
|
];
|
|
401
401
|
const generateBuildId = nextConfig.generateBuildId ?? (options.staticBuild ? resolveStaticBuildId : void 0);
|
|
402
|
-
const env = nextConfig.trailingSlash ? {
|
|
402
|
+
const env = nextConfig.trailingSlash ? { CANOPY_TRAILING_SLASH: "true", ...nextConfig.env } : void 0;
|
|
403
403
|
return {
|
|
404
404
|
...nextConfig,
|
|
405
405
|
transpilePackages: allPackages,
|
package/dist/config.mjs
CHANGED
|
@@ -363,7 +363,7 @@ function withCanopy(nextConfig = {}, options = {}) {
|
|
|
363
363
|
])
|
|
364
364
|
];
|
|
365
365
|
const generateBuildId = nextConfig.generateBuildId ?? (options.staticBuild ? resolveStaticBuildId : void 0);
|
|
366
|
-
const env = nextConfig.trailingSlash ? {
|
|
366
|
+
const env = nextConfig.trailingSlash ? { CANOPY_TRAILING_SLASH: "true", ...nextConfig.env } : void 0;
|
|
367
367
|
return {
|
|
368
368
|
...nextConfig,
|
|
369
369
|
transpilePackages: allPackages,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type CanopyContext, type CanopyBuildContext, type CanopyServices, type SeoFieldLocation } from 'canopycms/server';
|
|
2
2
|
import type { CanopyConfig, AuthPlugin, FieldConfig } from 'canopycms';
|
|
3
3
|
import type { Metadata, MetadataRoute } from 'next';
|
|
4
|
+
import type { ReactElement } from 'react';
|
|
4
5
|
import { createCanopyCatchAllHandler } from './adapter.js';
|
|
6
|
+
import { type CreatePreviewPageOptions, type PreviewPageProps } from './preview-page.js';
|
|
5
7
|
import { type EntryToMetadataOptions, type GenerateContentSitemapOptions, type GenerateContentStaticParamsOptions } from './static.js';
|
|
6
8
|
/**
|
|
7
9
|
* Merge a context-wide default SEO field location (`createNextCanopyContext({ seo })`) with a
|
|
@@ -132,6 +134,20 @@ export interface NextCanopyContextResult {
|
|
|
132
134
|
* (see that function's doc); pass `fields`/`group` on a given call to override just that call.
|
|
133
135
|
*/
|
|
134
136
|
entryToMetadata: (entryData: unknown, options?: EntryToMetadataOptions) => Metadata;
|
|
137
|
+
/**
|
|
138
|
+
* The page for the preview route the editor's `previewPrefix` names, for a site whose public
|
|
139
|
+
* pages are a static export. Mount it as `app/<prefix>/[[...path]]/page.server.tsx`, so only the
|
|
140
|
+
* CMS build has it:
|
|
141
|
+
*
|
|
142
|
+
* ```tsx
|
|
143
|
+
* export default createPreviewPage({ views: { post: PostPreview, doc: DocPreview } })
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* Each view is made by `withCanopyPreview` (`canopycms-next/client`) in a `'use client'` module.
|
|
147
|
+
* It renders each entry from the `?branch=` the editor names, through `views[entryType]` with
|
|
148
|
+
* the live draft. Reads are request-scoped and ACL-checked, and anything not readable is a 404.
|
|
149
|
+
*/
|
|
150
|
+
createPreviewPage: (options: CreatePreviewPageOptions) => (props: PreviewPageProps) => Promise<ReactElement>;
|
|
135
151
|
/** API catch-all route handler */
|
|
136
152
|
handler: ReturnType<typeof createCanopyCatchAllHandler>;
|
|
137
153
|
/** Underlying services (rarely needed directly) */
|
package/dist/context-wrapper.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createCanopyContext, isBuildMode, startDevContentWatcher, createCanopyS
|
|
|
5
5
|
import { assertAuthPluginAllowedForMode } from 'canopycms/auth';
|
|
6
6
|
import { CachingAuthPlugin, FileBasedAuthCache } from 'canopycms/auth/cache';
|
|
7
7
|
import { createCanopyCatchAllHandler } from './adapter.js';
|
|
8
|
+
import { createPreviewPageFor, } from './preview-page.js';
|
|
8
9
|
import { collectStaticParams, entryToMetadata as entryToMetadataCore, generateContentSitemap, } from './static.js';
|
|
9
10
|
/**
|
|
10
11
|
* Merge a context-wide default SEO field location (`createNextCanopyContext({ seo })`) with a
|
|
@@ -271,6 +272,7 @@ export async function createNextCanopyContext(options) {
|
|
|
271
272
|
generateContentStaticParams,
|
|
272
273
|
generateContentSitemap: boundGenerateContentSitemap,
|
|
273
274
|
entryToMetadata: boundEntryToMetadata,
|
|
275
|
+
createPreviewPage: (previewOptions) => createPreviewPageFor(getCanopy, previewOptions, options.config.deployedAs),
|
|
274
276
|
handler,
|
|
275
277
|
services,
|
|
276
278
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +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 { CreatePreviewPageOptions, PreviewPageProps } from './preview-page.js';
|
|
3
4
|
export { collectStaticParams, generateContentSitemap, entryToMetadata, type GenerateContentStaticParamsOptions, type GenerateContentSitemapOptions, type EntryToMetadataOptions, type SitemapExtraUrl, } from './static.js';
|
|
4
5
|
export { createMockAuthPlugin, createRejectingAuthPlugin } from './test-utils.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { CanopyConfig } from 'canopycms';
|
|
3
|
+
import type { CanopyContext } from 'canopycms/server';
|
|
4
|
+
import type { CanopyPreviewProps } from './client.js';
|
|
5
|
+
export interface CreatePreviewPageOptions {
|
|
6
|
+
/**
|
|
7
|
+
* 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. An entry whose type
|
|
9
|
+
* has no view is a 404.
|
|
10
|
+
*/
|
|
11
|
+
views: Record<string, (props: CanopyPreviewProps<never>) => ReactNode>;
|
|
12
|
+
/** Editor origin to trust, for an editor on another origin. Defaults to the page's own. */
|
|
13
|
+
editorOrigin?: string;
|
|
14
|
+
}
|
|
15
|
+
/** The props Next passes a `[[...path]]` page. */
|
|
16
|
+
export interface PreviewPageProps {
|
|
17
|
+
params: Promise<{
|
|
18
|
+
path?: string[];
|
|
19
|
+
}>;
|
|
20
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The page for a `[[...path]]` route mounted at `editor.previewPrefix`: it reads the entry at
|
|
24
|
+
* the route's path from the `?branch=` the editor names and renders its view.
|
|
25
|
+
*
|
|
26
|
+
* The read goes through the request-scoped `getCanopy()`, never the build context: it is
|
|
27
|
+
* authenticated and ACL-checked, and a branch other than the active one is only loaded, never
|
|
28
|
+
* created. A missing, unreadable or malformed branch, and a path no entry publishes, are all a
|
|
29
|
+
* 404, so a hidden branch looks the same as a missing one.
|
|
30
|
+
*
|
|
31
|
+
* On a `deployedAs: 'static'` deployment every request is a 404: reads there skip access checks,
|
|
32
|
+
* so the route would show any branch to anyone.
|
|
33
|
+
* @internal Exported for tests.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createPreviewPageFor(getCanopy: () => Promise<CanopyContext>, options: CreatePreviewPageOptions, deployedAs?: CanopyConfig['deployedAs']): (props: PreviewPageProps) => Promise<ReactElement>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { notFound } from 'next/navigation';
|
|
3
|
+
/**
|
|
4
|
+
* The page for a `[[...path]]` route mounted at `editor.previewPrefix`: it reads the entry at
|
|
5
|
+
* the route's path from the `?branch=` the editor names and renders its view.
|
|
6
|
+
*
|
|
7
|
+
* The read goes through the request-scoped `getCanopy()`, never the build context: it is
|
|
8
|
+
* authenticated and ACL-checked, and a branch other than the active one is only loaded, never
|
|
9
|
+
* created. A missing, unreadable or malformed branch, and a path no entry publishes, are all a
|
|
10
|
+
* 404, so a hidden branch looks the same as a missing one.
|
|
11
|
+
*
|
|
12
|
+
* On a `deployedAs: 'static'` deployment every request is a 404: reads there skip access checks,
|
|
13
|
+
* so the route would show any branch to anyone.
|
|
14
|
+
* @internal Exported for tests.
|
|
15
|
+
*/
|
|
16
|
+
export function createPreviewPageFor(getCanopy, options, deployedAs = 'server') {
|
|
17
|
+
return async function CanopyPreviewPage({ params, searchParams }) {
|
|
18
|
+
if (deployedAs === 'static')
|
|
19
|
+
notFound();
|
|
20
|
+
const [{ path = [] }, query] = await Promise.all([params, searchParams]);
|
|
21
|
+
const branch = query.branch;
|
|
22
|
+
if (Array.isArray(branch))
|
|
23
|
+
notFound();
|
|
24
|
+
const canopy = await getCanopy();
|
|
25
|
+
const result = await canopy.readByUrlPath(`/${path.join('/')}`, { branch });
|
|
26
|
+
if (!result)
|
|
27
|
+
notFound();
|
|
28
|
+
const { entryType } = result.meta;
|
|
29
|
+
const View = Object.prototype.hasOwnProperty.call(options.views, entryType)
|
|
30
|
+
? options.views[entryType]
|
|
31
|
+
: undefined;
|
|
32
|
+
if (!View)
|
|
33
|
+
notFound();
|
|
34
|
+
return _jsx(View, { initialData: result.data, editorOrigin: options.editorOrigin });
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/with-canopy.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface WithCanopyOptions {
|
|
|
40
40
|
* (kept under `experimental` on Next 13/14 or legacy spellings; your own includes stay), so a
|
|
41
41
|
* Turbopack `output: 'standalone'` server can load sharp — Next's tracing misses that library
|
|
42
42
|
* for sharp 0.35 (doesn't fix webpack builds); warns if the directory or version can't be found.
|
|
43
|
-
* - `trailingSlash: true` sets `env.
|
|
43
|
+
* - `trailingSlash: true` sets `env.CANOPY_TRAILING_SLASH`, so the editor's requests skip Next's 308.
|
|
44
44
|
* - On Next 16+, sets `turbopack: {}` when your config has neither `turbopack` nor `webpack` and
|
|
45
45
|
* the Next version can be read — Next 16 defaults to Turbopack and exits when a config exports
|
|
46
46
|
* `webpack` (the React aliases above add one) with no `turbopack` key; a `turbopack` you set is
|
package/dist/with-canopy.js
CHANGED
|
@@ -337,7 +337,7 @@ function sharpTracingConfig(nextConfig, options) {
|
|
|
337
337
|
* (kept under `experimental` on Next 13/14 or legacy spellings; your own includes stay), so a
|
|
338
338
|
* Turbopack `output: 'standalone'` server can load sharp — Next's tracing misses that library
|
|
339
339
|
* for sharp 0.35 (doesn't fix webpack builds); warns if the directory or version can't be found.
|
|
340
|
-
* - `trailingSlash: true` sets `env.
|
|
340
|
+
* - `trailingSlash: true` sets `env.CANOPY_TRAILING_SLASH`, so the editor's requests skip Next's 308.
|
|
341
341
|
* - On Next 16+, sets `turbopack: {}` when your config has neither `turbopack` nor `webpack` and
|
|
342
342
|
* the Next version can be read — Next 16 defaults to Turbopack and exits when a config exports
|
|
343
343
|
* `webpack` (the React aliases above add one) with no `turbopack` key; a `turbopack` you set is
|
|
@@ -449,10 +449,10 @@ export function withCanopy(nextConfig = {}, options = {}) {
|
|
|
449
449
|
// verbatim (`next/dist/build/generate-build-id.js`) — which is what makes a hex tree hash usable
|
|
450
450
|
// as a build id at all. Do not "helpfully" route this through the fallback.
|
|
451
451
|
const generateBuildId = nextConfig.generateBuildId ?? (options.staticBuild ? resolveStaticBuildId : undefined);
|
|
452
|
-
// Next substitutes `env` into every bundle at build time, where the API client
|
|
453
|
-
// (`
|
|
452
|
+
// Next substitutes `env` into every bundle at build time, where the API client and the editor's
|
|
453
|
+
// preview URLs read it (`readTrailingSlashEnv` in canopycms's `utils/url-prefix.ts`).
|
|
454
454
|
const env = nextConfig.trailingSlash
|
|
455
|
-
? {
|
|
455
|
+
? { CANOPY_TRAILING_SLASH: 'true', ...nextConfig.env }
|
|
456
456
|
: undefined;
|
|
457
457
|
return {
|
|
458
458
|
...nextConfig,
|
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.94",
|
|
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.94"
|
|
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.94"
|
|
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",
|