canopycms 0.0.67 → 0.0.68-int.100
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/README.md +21 -14
- package/dist/ai/handler.js +8 -0
- package/dist/api/admin-branch-health.js +17 -20
- package/dist/api/admin.d.ts +30 -9
- package/dist/api/admin.js +32 -2
- package/dist/api/assets.js +84 -76
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +14 -5
- package/dist/api/branch-status.d.ts +5 -5
- package/dist/api/branch-status.js +26 -6
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +7 -2
- package/dist/api/branch.d.ts +15 -2
- package/dist/api/branch.js +111 -92
- package/dist/api/client.d.ts +18 -6
- package/dist/api/client.js +79 -12
- package/dist/api/content.d.ts +6 -5
- package/dist/api/content.js +44 -41
- package/dist/api/entries.js +16 -15
- package/dist/api/github-sync.d.ts +10 -1
- package/dist/api/github-sync.js +20 -10
- package/dist/api/index.d.ts +1 -1
- package/dist/api/reference-options.js +1 -1
- package/dist/api/resolve-references.js +14 -40
- package/dist/api/settings-helpers.d.ts +1 -1
- package/dist/api/settings-helpers.js +1 -4
- package/dist/api/user.d.ts +3 -0
- package/dist/api/user.js +3 -0
- package/dist/assets/asset-url.d.ts +22 -16
- package/dist/assets/asset-url.js +42 -25
- package/dist/assets/factory.d.ts +5 -0
- package/dist/assets/factory.js +5 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +122 -0
- package/dist/assets/materialize.js +305 -0
- package/dist/assets/store-local.d.ts +4 -0
- package/dist/assets/store-local.js +11 -0
- package/dist/assets/store-s3.d.ts +10 -1
- package/dist/assets/store-s3.js +55 -3
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +14 -1
- package/dist/auth/file-based-auth-cache.js +7 -8
- package/dist/authorization/content.d.ts +5 -4
- package/dist/authorization/content.js +5 -5
- package/dist/authorization/path.d.ts +11 -6
- package/dist/authorization/path.js +11 -6
- package/dist/authorization/types.d.ts +2 -2
- package/dist/branch-health.d.ts +7 -1
- package/dist/branch-health.js +8 -3
- package/dist/branch-metadata.d.ts +5 -5
- package/dist/branch-metadata.js +44 -36
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-schema-cache.d.ts +5 -3
- package/dist/branch-schema-cache.js +29 -11
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +240 -94
- package/dist/build/asset-refs.d.ts +93 -0
- package/dist/build/asset-refs.js +251 -0
- package/dist/build-identity.d.ts +3 -0
- package/dist/build-identity.js +13 -0
- package/dist/cli/asset-refs.d.ts +30 -0
- package/dist/cli/asset-refs.js +168 -0
- package/dist/cli/cli.d.ts +2 -1
- package/dist/cli/cli.js +4843 -1870
- package/dist/cli/generate-ai-content.js +1584 -444
- package/dist/cli/github-app-manifest.d.ts +4 -3
- package/dist/cli/github-app-manifest.js +4 -3
- package/dist/cli/init.js +30 -27
- package/dist/cli/migrate.js +12 -8
- package/dist/cli/sync.js +83 -37
- package/dist/cli/template-files/Dockerfile.cms.template +8 -0
- package/dist/cli/template-files/cdk-app.ts.template +4 -0
- package/dist/cli/template-files/cms-stack.ts.template +23 -12
- package/dist/cli/template-files/deploy-cms.yml.template +2 -0
- package/dist/config/helpers.js +1 -2
- package/dist/config/schemas/config.d.ts +19 -32
- package/dist/config/schemas/config.js +9 -11
- package/dist/config/schemas/field.js +1 -0
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +4 -20
- package/dist/config/schemas/url.js +6 -22
- package/dist/config/types.d.ts +30 -20
- package/dist/config/validation.d.ts +6 -0
- package/dist/config/validation.js +37 -0
- package/dist/content-listing.d.ts +12 -12
- package/dist/content-listing.js +10 -6
- package/dist/content-reader.d.ts +11 -2
- package/dist/content-reader.js +22 -14
- package/dist/content-store.d.ts +37 -9
- package/dist/content-store.js +70 -43
- package/dist/content-tree.d.ts +1 -1
- package/dist/content-tree.js +2 -2
- package/dist/context.d.ts +27 -9
- package/dist/context.js +154 -70
- package/dist/editor/BranchManager.d.ts +7 -1
- package/dist/editor/BranchManager.js +29 -14
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +2 -6
- package/dist/editor/Editor.d.ts +4 -3
- package/dist/editor/Editor.js +24 -20
- package/dist/editor/FormRenderer.js +34 -18
- package/dist/editor/admin/SystemHealthPanel.js +47 -7
- package/dist/editor/components/BranchesDrawer.d.ts +9 -0
- package/dist/editor/components/BranchesDrawer.js +4 -0
- package/dist/editor/components/NoEditPermissionNotice.d.ts +10 -0
- package/dist/editor/components/NoEditPermissionNotice.js +17 -0
- package/dist/editor/context/AssetContext.d.ts +18 -35
- package/dist/editor/context/AssetContext.js +20 -29
- package/dist/editor/context/index.d.ts +1 -1
- package/dist/editor/context/index.js +1 -1
- package/dist/editor/editor-config.d.ts +1 -2
- package/dist/editor/editor-config.js +0 -23
- package/dist/editor/editor-utils.d.ts +24 -21
- package/dist/editor/editor-utils.js +75 -62
- package/dist/editor/fields/BlockField.d.ts +1 -0
- package/dist/editor/fields/BlockField.js +6 -4
- package/dist/editor/fields/CodeField.d.ts +1 -0
- package/dist/editor/fields/CodeField.js +2 -2
- package/dist/editor/fields/DateTimeField.d.ts +1 -0
- package/dist/editor/fields/DateTimeField.js +2 -2
- package/dist/editor/fields/FieldDescription.d.ts +16 -0
- package/dist/editor/fields/FieldDescription.js +11 -0
- package/dist/editor/fields/ImageField.d.ts +1 -0
- package/dist/editor/fields/ImageField.js +3 -2
- package/dist/editor/fields/InlineGroupField.js +4 -1
- package/dist/editor/fields/MarkdownField.d.ts +1 -0
- package/dist/editor/fields/MarkdownField.js +115 -20
- package/dist/editor/fields/NumberField.d.ts +1 -0
- package/dist/editor/fields/NumberField.js +2 -2
- package/dist/editor/fields/NumberListField.d.ts +1 -0
- package/dist/editor/fields/NumberListField.js +2 -2
- package/dist/editor/fields/ObjectField.d.ts +1 -0
- package/dist/editor/fields/ObjectField.js +5 -2
- package/dist/editor/fields/ReferenceField.d.ts +1 -0
- package/dist/editor/fields/ReferenceField.js +5 -4
- package/dist/editor/fields/SelectField.d.ts +1 -0
- package/dist/editor/fields/SelectField.js +2 -2
- package/dist/editor/fields/StringListField.d.ts +1 -0
- package/dist/editor/fields/StringListField.js +2 -2
- package/dist/editor/fields/TextField.d.ts +1 -0
- package/dist/editor/fields/TextField.js +2 -2
- package/dist/editor/fields/ToggleField.d.ts +1 -0
- package/dist/editor/fields/ToggleField.js +7 -4
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +3 -2
- package/dist/editor/fields/mdx-jsx-support.d.ts +1 -0
- package/dist/editor/fields/mdx-jsx-support.js +137 -0
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +52 -0
- package/dist/editor/hooks/useBranchActions.d.ts +13 -2
- package/dist/editor/hooks/useBranchActions.js +48 -17
- package/dist/editor/hooks/useBranchManager.d.ts +11 -0
- package/dist/editor/hooks/useBranchManager.js +80 -2
- package/dist/editor/hooks/useBranchesData.d.ts +6 -0
- package/dist/editor/hooks/useBranchesData.js +9 -3
- package/dist/editor/hooks/useCommentSystem.js +25 -6
- package/dist/editor/hooks/useDraftManager.d.ts +16 -1
- package/dist/editor/hooks/useDraftManager.js +193 -36
- package/dist/editor/hooks/useEntriesData.js +3 -3
- package/dist/editor/hooks/useEntryManager.d.ts +2 -1
- package/dist/editor/hooks/useEntryManager.js +34 -19
- package/dist/editor/media/AssetCard.d.ts +1 -1
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +7 -0
- package/dist/editor/media/editor-image-src.js +12 -0
- package/dist/editor/preview-asset-base.d.ts +8 -0
- package/dist/editor/preview-asset-base.js +13 -0
- package/dist/editor/preview-bridge.d.ts +8 -1
- package/dist/editor/preview-bridge.js +15 -2
- package/dist/editor/preview-path.d.ts +10 -0
- package/dist/editor/preview-path.js +20 -0
- package/dist/editor/theme.js +2 -2
- package/dist/entry-schema-registry.d.ts +4 -4
- package/dist/entry-schema-registry.js +10 -6
- package/dist/entry-schema.d.ts +27 -4
- package/dist/entry-schema.js +20 -3
- package/dist/git-manager.d.ts +173 -6
- package/dist/git-manager.js +594 -76
- package/dist/github-service.d.ts +8 -0
- package/dist/github-service.js +5 -1
- package/dist/http/handler.js +37 -13
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +2 -0
- package/dist/http/router.d.ts +2 -0
- package/dist/http/router.js +2 -1
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +10 -0
- package/dist/http/worker-not-ready.js +24 -0
- package/dist/operating-mode/client-unsafe-strategy.js +0 -6
- package/dist/operating-mode/types.d.ts +1 -4
- package/dist/paths/branch-name.d.ts +5 -0
- package/dist/paths/branch-name.js +9 -0
- package/dist/paths/branch.d.ts +6 -2
- package/dist/paths/branch.js +16 -10
- package/dist/paths/index.d.ts +3 -3
- package/dist/paths/index.js +3 -3
- package/dist/paths/normalize.d.ts +7 -0
- package/dist/paths/normalize.js +9 -0
- package/dist/paths/validation.js +0 -2
- package/dist/reference-resolver.d.ts +5 -21
- package/dist/reference-resolver.js +9 -44
- package/dist/resolve-canopy-user.js +2 -1
- package/dist/schema/schema-store.d.ts +54 -31
- package/dist/schema/schema-store.js +77 -31
- package/dist/server.d.ts +24 -9
- package/dist/server.js +22 -8
- package/dist/services.d.ts +18 -6
- package/dist/services.js +85 -70
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +197 -45
- package/dist/static/seo.d.ts +2 -14
- package/dist/static/seo.js +2 -25
- package/dist/submission-attribution.d.ts +73 -0
- package/dist/submission-attribution.js +221 -0
- package/dist/sync-core.d.ts +12 -1
- package/dist/sync-core.js +31 -13
- package/dist/task-queue/worker-status.d.ts +8 -0
- package/dist/task-queue/worker-status.js +17 -0
- package/dist/types.d.ts +31 -2
- package/dist/utils/content-write-lock.d.ts +18 -7
- package/dist/utils/content-write-lock.js +18 -9
- package/dist/utils/debug.d.ts +8 -0
- package/dist/utils/debug.js +10 -2
- package/dist/utils/git.d.ts +32 -0
- package/dist/utils/git.js +42 -0
- package/dist/utils/occ-json-write.js +10 -2
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +35 -5
- package/dist/utils/provisioning-lock.js +73 -12
- package/dist/utils/request-timing.d.ts +25 -0
- package/dist/utils/request-timing.js +101 -0
- package/dist/utils/sanitize-href.d.ts +8 -22
- package/dist/utils/sanitize-href.js +11 -28
- package/dist/utils/url-prefix.d.ts +30 -0
- package/dist/utils/url-prefix.js +61 -0
- package/dist/validation/entry-validator.js +9 -3
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/worker/canopy-state.d.ts +45 -0
- package/dist/worker/canopy-state.js +75 -0
- package/dist/worker/cms-worker.d.ts +8 -3
- package/dist/worker/cms-worker.js +38 -2
- package/dist/worker/git-sync.d.ts +30 -12
- package/dist/worker/git-sync.js +253 -58
- package/dist/worker/history-rewrite.d.ts +1 -1
- package/dist/worker/history-rewrite.js +1 -1
- package/dist/worker/provisioned-workspace.d.ts +35 -0
- package/dist/worker/provisioned-workspace.js +50 -0
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +95 -23
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +11 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +132 -0
- package/dist/worker/task-runner.js +39 -4
- package/dist/worker/worker-context.d.ts +6 -1
- package/package.json +2 -1
package/dist/context.d.ts
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* Only request-time reads on a server deployment resolve a branch workspace —
|
|
16
16
|
* in dev, the clone under `.canopy-dev/content-branches/`.
|
|
17
17
|
*
|
|
18
|
+
* A `branch` arrives from the request (a page passing `?branch=`), so only the
|
|
19
|
+
* active branch is ever provisioned here; any other name must already exist and
|
|
20
|
+
* pass the user's branch access before its files are read (`resolveBranch`).
|
|
21
|
+
*
|
|
18
22
|
* The Next.js adapter wraps this in React `cache()` for per-request memoization
|
|
19
23
|
* (`canopycms-next/src/context-wrapper.ts`).
|
|
20
24
|
*
|
|
@@ -58,8 +62,12 @@ export interface CanopyBuildContext {
|
|
|
58
62
|
* `meta.indexEntry` handed to `extract`, and a collection whose children are
|
|
59
63
|
* all filtered out is pruned. On the build context and on static deployments
|
|
60
64
|
* nothing is filtered — the synthetic admin sees everything.
|
|
65
|
+
*
|
|
66
|
+
* Branch: the `branch` option behaves as on `listEntries`.
|
|
61
67
|
*/
|
|
62
|
-
buildContentTree: <T = unknown, TEntryTypes = DefaultEntryTypes>(options?: BuildContentTreeOptions<T, TEntryTypes>
|
|
68
|
+
buildContentTree: <T = unknown, TEntryTypes = DefaultEntryTypes>(options?: BuildContentTreeOptions<T, TEntryTypes> & {
|
|
69
|
+
branch?: string;
|
|
70
|
+
}) => Promise<ContentTreeNode<T>[]>;
|
|
63
71
|
/**
|
|
64
72
|
* Every content entry as a flat array.
|
|
65
73
|
*
|
|
@@ -67,13 +75,18 @@ export interface CanopyBuildContext {
|
|
|
67
75
|
* cannot `read` are omitted before `extract` runs; on the build context and
|
|
68
76
|
* static deployments nothing is filtered.
|
|
69
77
|
*
|
|
70
|
-
* Branch:
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
78
|
+
* Branch: with no `branch` it lists the active branch
|
|
79
|
+
* (`defaultActiveBranch ?? defaultBaseBranch`): git HEAD in `dev` when that is
|
|
80
|
+
* unset, otherwise usually the base branch in `prod`. Pass the preview
|
|
81
|
+
* iframe's `?branch=` as `branch`, as for `read`, so an index page previewed
|
|
82
|
+
* on a content branch lists that branch. Any other branch must already exist
|
|
83
|
+
* and, on the request-scoped context, be readable by the user; otherwise, or
|
|
84
|
+
* for an array, the result is empty. It selects nothing at build
|
|
85
|
+
* time or on static deployments.
|
|
75
86
|
*/
|
|
76
|
-
listEntries: <T = Record<string, unknown>>(options?: ListEntriesOptions<T>
|
|
87
|
+
listEntries: <T = Record<string, unknown>>(options?: ListEntriesOptions<T> & {
|
|
88
|
+
branch?: string;
|
|
89
|
+
}) => Promise<ListEntriesItem<T>[]>;
|
|
77
90
|
/**
|
|
78
91
|
* Content reader, with the auth context applied automatically (admin at build
|
|
79
92
|
* time).
|
|
@@ -83,6 +96,11 @@ export interface CanopyBuildContext {
|
|
|
83
96
|
* output, since it reveals the deployment's filesystem layout (home dir, EFS
|
|
84
97
|
* mount, branch name). It is for build-time reads of colocated artifacts, e.g.
|
|
85
98
|
* `fs.readFile(path.join(path.dirname(result.meta.physicalPath), 'profile.json'))`.
|
|
99
|
+
*
|
|
100
|
+
* Branch: with no `branch` it reads the active branch, provisioning its workspace if missing.
|
|
101
|
+
* Any other branch must already exist and, on the request-scoped context, be readable by the
|
|
102
|
+
* user; otherwise, or for an array, it throws NOT_FOUND, so a hidden branch reads as a missing
|
|
103
|
+
* one. It selects nothing at build time or on static deployments.
|
|
86
104
|
*/
|
|
87
105
|
read: <T = unknown>(input: {
|
|
88
106
|
entryPath: string;
|
|
@@ -109,8 +127,8 @@ export interface CanopyBuildContext {
|
|
|
109
127
|
*
|
|
110
128
|
* Null when nothing matches — a collection URL with no index entry (use
|
|
111
129
|
* buildContentTree), a non-entry path like `/favicon.ico` the slug validator
|
|
112
|
-
* rejects, or a path the user may not read, so a
|
|
113
|
-
* a 404 via `notFound()
|
|
130
|
+
* rejects, a missing branch, or a path or branch the user may not read, so a
|
|
131
|
+
* denial renders as a 404 via `notFound()`, not a 500. Strict `read()` throws.
|
|
114
132
|
*
|
|
115
133
|
* `meta.physicalPath` is **server-only**, for the reason given on `read`.
|
|
116
134
|
* `meta.entryType`/`entryId` come free with path resolution, so one catch-all
|
package/dist/context.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { isDeployedStatic, isBuildMode, STATIC_DEPLOY_USER } from './build-mode.js';
|
|
1
|
+
import { isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER } from './build-mode.js';
|
|
2
2
|
import { createContentReader } from './content-reader.js';
|
|
3
3
|
import { ContentStoreError } from './content-store.js';
|
|
4
|
-
import { createLogicalPath, parseSlug, resolveBranchPaths } from './paths/index.js';
|
|
4
|
+
import { createLogicalPath, namesNoWorkspace, parseSlug, resolveBranchPaths, } from './paths/index.js';
|
|
5
5
|
import { resolveUrlPathCandidates } from './url-path-resolver.js';
|
|
6
|
-
import { loadOrCreateBranchContext } from './branch-workspace.js';
|
|
6
|
+
import { loadBranchContext, loadOrCreateBranchContext } from './branch-workspace.js';
|
|
7
7
|
import { buildContentTree as buildContentTreeImpl, } from './content-tree.js';
|
|
8
8
|
import { listEntries as listEntriesImpl, } from './content-listing.js';
|
|
9
9
|
import { createDebugLogger } from './utils/debug.js';
|
|
@@ -22,6 +22,30 @@ function isLookupFailure(err) {
|
|
|
22
22
|
function isPageSwallowable(err) {
|
|
23
23
|
return isLookupFailure(err) || err.code === 'FORBIDDEN';
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A `branch` option is typed `string` but usually handed over from untyped
|
|
27
|
+
* request input: a repeated `?branch=` arrives as an array, which names no
|
|
28
|
+
* branch, and `URLSearchParams.get` gives null for an absent one, which is no
|
|
29
|
+
* `branch` at all.
|
|
30
|
+
*/
|
|
31
|
+
function isBranchOption(branch) {
|
|
32
|
+
return branch == null || typeof branch === 'string';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A branch that already has a workspace, or null. A name that cannot name one
|
|
36
|
+
* is null too (`namesNoWorkspace`): it arrives from the request, so it reads as
|
|
37
|
+
* not-found rather than failing the page.
|
|
38
|
+
*/
|
|
39
|
+
async function loadExistingBranch(branchName, mode) {
|
|
40
|
+
try {
|
|
41
|
+
return await loadBranchContext({ branchName, mode });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (namesNoWorkspace(err))
|
|
45
|
+
return null;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
25
49
|
/**
|
|
26
50
|
* Create a Canopy context managing auth + content reading. Framework-agnostic:
|
|
27
51
|
* the adapter supplies extractUser.
|
|
@@ -46,7 +70,66 @@ export function createCanopyContext(options) {
|
|
|
46
70
|
// switching branches mid-session updates what getCanopy() serves.
|
|
47
71
|
await services.refreshActiveBranch();
|
|
48
72
|
const user = await getUser();
|
|
49
|
-
|
|
73
|
+
// Read once, so every read and listing in this request agrees on it even if
|
|
74
|
+
// a concurrent request's refreshActiveBranch() replaces services.config.
|
|
75
|
+
const activeBranch = services.config.defaultActiveBranch ?? services.config.defaultBaseBranch ?? 'main';
|
|
76
|
+
// No ACL enforcement at build time, on static deployments, or for the
|
|
77
|
+
// synthetic admin. For the last this is load-bearing, not an optimization:
|
|
78
|
+
// that user has unconditional access anyway (path checks bypass entirely for
|
|
79
|
+
// an Admins-group user — authorization/path.ts), but BUILDING a path checker
|
|
80
|
+
// costs a getSettingsBranchRoot() call, which in modes with a separate
|
|
81
|
+
// settings branch provisions that branch's git workspace: an EFS round trip
|
|
82
|
+
// in prod. `createBuildCanopy` (build-canopy.ts) runs outside a request or
|
|
83
|
+
// Next.js build phase, so neither other guard fires for it, and without this
|
|
84
|
+
// one every such script pays for a settings-workspace clone it never needed —
|
|
85
|
+
// and hard-fails where that workspace cannot be provisioned. Compared by
|
|
86
|
+
// reference to the STATIC_DEPLOY_USER singleton, not by group membership, so
|
|
87
|
+
// a real authenticated admin at request time still goes through the real
|
|
88
|
+
// check.
|
|
89
|
+
const enforceAcls = !(isDeployedStatic(services.config) ||
|
|
90
|
+
isBuildMode() ||
|
|
91
|
+
user === STATIC_DEPLOY_USER);
|
|
92
|
+
/**
|
|
93
|
+
* `branch` when it names a branch other than the active one, else undefined:
|
|
94
|
+
* no `branch`, an empty one, the active one named explicitly, or any name
|
|
95
|
+
* when reading from the checkout, where it selects nothing.
|
|
96
|
+
*/
|
|
97
|
+
const otherBranch = (branch) => branch && branch !== activeBranch && !readsFromCheckout(services.config) ? branch : undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The workspace for the active branch (undefined) or another named one, or
|
|
100
|
+
* null when it yields nothing this user may see.
|
|
101
|
+
*
|
|
102
|
+
* The active branch is provisioned if missing. Any other name is load-only,
|
|
103
|
+
* never provisioned — a name with no workspace is null (`loadExistingBranch`) — and its branch
|
|
104
|
+
* access, held in its own metadata, is checked here, before its schema or
|
|
105
|
+
* content files are read, so a denied branch is indistinguishable from a
|
|
106
|
+
* missing one. The active branch's access is left to the caller: a listing
|
|
107
|
+
* of it lists nothing, and a strict `read` of it throws FORBIDDEN.
|
|
108
|
+
*/
|
|
109
|
+
const resolveBranch = async (requested) => {
|
|
110
|
+
if (requested === undefined) {
|
|
111
|
+
return loadOrCreateBranchContext({
|
|
112
|
+
config: services.config,
|
|
113
|
+
branchName: activeBranch,
|
|
114
|
+
mode: services.config.mode,
|
|
115
|
+
createdBy: 'canopycms-context',
|
|
116
|
+
remoteUrl: services.config.defaultRemoteUrl,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const context = await loadExistingBranch(requested, services.config.mode);
|
|
120
|
+
if (!context)
|
|
121
|
+
return null;
|
|
122
|
+
if (enforceAcls && !services.checkBranchAccess(context, user).allowed)
|
|
123
|
+
return null;
|
|
124
|
+
return context;
|
|
125
|
+
};
|
|
126
|
+
// The reader resolves every branch through resolveBranch; a null reads as
|
|
127
|
+
// NOT_FOUND. Reads from the checkout bypass the resolver inside the reader.
|
|
128
|
+
const baseReader = createContentReader({
|
|
129
|
+
services,
|
|
130
|
+
defaultBranch: activeBranch,
|
|
131
|
+
getBranchContext: (branchName) => resolveBranch(otherBranch(branchName)),
|
|
132
|
+
});
|
|
50
133
|
// Injects the user and validates strings → branded types at this boundary.
|
|
51
134
|
// `extra` carries options that are NOT part of the public `read` surface —
|
|
52
135
|
// today just readByUrlPath's URL-addressability gate. A separate inner
|
|
@@ -63,10 +146,13 @@ export function createCanopyContext(options) {
|
|
|
63
146
|
}
|
|
64
147
|
slug = slugResult.slug;
|
|
65
148
|
}
|
|
149
|
+
if (!isBranchOption(input.branch)) {
|
|
150
|
+
throw new ContentStoreError(`Branch not found: ${String(input.branch)}`, 'NOT_FOUND');
|
|
151
|
+
}
|
|
66
152
|
const readInput = {
|
|
67
153
|
entryPath,
|
|
68
154
|
slug,
|
|
69
|
-
branch: input.branch,
|
|
155
|
+
branch: input.branch ?? undefined,
|
|
70
156
|
user,
|
|
71
157
|
resolveReferences: input.resolveReferences ?? true,
|
|
72
158
|
...extra,
|
|
@@ -119,80 +205,78 @@ export function createCanopyContext(options) {
|
|
|
119
205
|
}
|
|
120
206
|
return null;
|
|
121
207
|
};
|
|
122
|
-
/** Resolve branch workspace and schema — shared by buildContentTree and listEntries. Memoized per getContext call. */
|
|
123
|
-
let schemaContextPromise = null;
|
|
124
|
-
const resolveSchemaContextImpl = async () => {
|
|
125
|
-
const operatingMode = services.config.mode;
|
|
126
|
-
const defaultBranch = services.config.defaultActiveBranch ?? services.config.defaultBaseBranch ?? 'main';
|
|
127
|
-
const branchContext = await loadOrCreateBranchContext({
|
|
128
|
-
config: services.config,
|
|
129
|
-
branchName: defaultBranch,
|
|
130
|
-
mode: operatingMode,
|
|
131
|
-
createdBy: 'canopycms-context',
|
|
132
|
-
remoteUrl: services.config.defaultRemoteUrl,
|
|
133
|
-
});
|
|
134
|
-
const { branchRoot } = resolveBranchPaths(branchContext, operatingMode);
|
|
135
|
-
const contentRootName = services.config.contentRoot || 'content';
|
|
136
|
-
const { flatSchema } = await services.branchSchemaCache.getSchema(branchRoot, services.entrySchemaRegistry, contentRootName);
|
|
137
|
-
return { branchContext, branchRoot, flatSchema, contentRootName };
|
|
138
|
-
};
|
|
139
|
-
const resolveSchemaContext = () => {
|
|
140
|
-
if (!schemaContextPromise) {
|
|
141
|
-
schemaContextPromise = resolveSchemaContextImpl();
|
|
142
|
-
}
|
|
143
|
-
return schemaContextPromise;
|
|
144
|
-
};
|
|
145
208
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* `services.createContentAccessChecker` is the shared batch primitive
|
|
152
|
-
* (api/entries.ts uses it too): it resolves the request-constant work —
|
|
153
|
-
* branch access, the settings/permissions root, the rule set — once, and
|
|
154
|
-
* returns a synchronous per-path check, so the per-entry cost is an admin
|
|
155
|
-
* short-circuit or one minimatch per configured rule, with no extra I/O.
|
|
156
|
-
*
|
|
157
|
-
* The empty object (no predicate → unfiltered) at build time, on static
|
|
158
|
-
* deployments, and for the synthetic admin is load-bearing, not an
|
|
159
|
-
* optimization. That user has unconditional access anyway (path checks
|
|
160
|
-
* bypass entirely for an Admins-group user — authorization/path.ts), but
|
|
161
|
-
* BUILDING the checker costs a getSettingsBranchRoot() call, which in modes
|
|
162
|
-
* with a separate settings branch provisions that branch's git workspace: an
|
|
163
|
-
* EFS round trip in prod. `createBuildCanopy` (build-canopy.ts) runs outside
|
|
164
|
-
* a request or Next.js build phase, so neither other guard fires for it, and
|
|
165
|
-
* without this one every such script pays for a settings-workspace clone it
|
|
166
|
-
* never needed — and hard-fails where that workspace cannot be provisioned.
|
|
209
|
+
* What a batch read (listEntries / buildContentTree) lists from: the branch's
|
|
210
|
+
* schema plus its path-ACL predicate, or null when the branch yields nothing
|
|
211
|
+
* this user may see. The branch resolves as for `read` (`resolveBranch`). At
|
|
212
|
+
* build time and on static deployments `branch` selects nothing, as for
|
|
213
|
+
* `read` (see the module doc).
|
|
167
214
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* Deliberately NOT wrapped in try/catch — createContentAccessChecker is
|
|
173
|
-
* fail-loud by contract, and swallowing would serve an unfiltered listing.
|
|
215
|
+
* Memoized per getContext call and per branch name, on the in-flight
|
|
216
|
+
* promise; a null result is memoized too, so one request answers a given
|
|
217
|
+
* name consistently. The memo is request-scoped, so it carries no
|
|
218
|
+
* generation protocol (docs/concurrency.md, "Adding a call-scoped memo").
|
|
174
219
|
*/
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
return
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
220
|
+
const listingSources = new Map();
|
|
221
|
+
const resolveListingSource = (branch) => {
|
|
222
|
+
if (!isBranchOption(branch))
|
|
223
|
+
return Promise.resolve(null);
|
|
224
|
+
const requested = otherBranch(branch);
|
|
225
|
+
const key = requested ?? activeBranch;
|
|
226
|
+
let source = listingSources.get(key);
|
|
227
|
+
if (!source) {
|
|
228
|
+
source = resolveListingSourceImpl(requested);
|
|
229
|
+
listingSources.set(key, source);
|
|
230
|
+
}
|
|
231
|
+
return source;
|
|
182
232
|
};
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
233
|
+
const resolveListingSourceImpl = async (requested) => {
|
|
234
|
+
const operatingMode = services.config.mode;
|
|
235
|
+
const branchContext = await resolveBranch(requested);
|
|
236
|
+
if (!branchContext)
|
|
237
|
+
return null;
|
|
238
|
+
// resolveBranch has already checked any other branch.
|
|
239
|
+
if (requested === undefined &&
|
|
240
|
+
enforceAcls &&
|
|
241
|
+
!services.checkBranchAccess(branchContext, user).allowed) {
|
|
242
|
+
return null;
|
|
186
243
|
}
|
|
187
|
-
|
|
244
|
+
const { branchRoot } = resolveBranchPaths(branchContext, operatingMode);
|
|
245
|
+
const contentRootName = services.config.contentRoot || 'content';
|
|
246
|
+
const { flatSchema } = await services.branchSchemaCache.getSchema(branchRoot, services.entrySchemaRegistry, contentRootName);
|
|
247
|
+
if (!enforceAcls)
|
|
248
|
+
return { branchRoot, flatSchema, contentRootName, visibility: {} };
|
|
249
|
+
// The path-ACL predicate keeps an unfiltered listing on this ACL-enforcing
|
|
250
|
+
// context from disclosing entry `data` for paths the user cannot `read()`.
|
|
251
|
+
// `services.createContentAccessChecker` is the shared batch primitive
|
|
252
|
+
// (api/entries.ts uses it too): it resolves the request-constant work —
|
|
253
|
+
// branch access, the settings/permissions root, the rule set — once, and
|
|
254
|
+
// returns a synchronous per-path check, so the per-entry cost is an admin
|
|
255
|
+
// short-circuit or one minimatch per configured rule, with no extra I/O.
|
|
256
|
+
//
|
|
257
|
+
// Deliberately NOT wrapped in try/catch — createContentAccessChecker is
|
|
258
|
+
// fail-loud by contract, and swallowing would serve an unfiltered listing.
|
|
259
|
+
const checkAccess = await services.createContentAccessChecker(branchContext, branchRoot, user);
|
|
260
|
+
return {
|
|
261
|
+
branchRoot,
|
|
262
|
+
flatSchema,
|
|
263
|
+
contentRootName,
|
|
264
|
+
visibility: { shouldInclude: (logicalPath) => checkAccess(logicalPath, 'read').allowed },
|
|
265
|
+
};
|
|
188
266
|
};
|
|
189
267
|
const buildContentTree = async (options) => {
|
|
190
|
-
const {
|
|
191
|
-
|
|
268
|
+
const { branch, ...treeOptions } = options ?? {};
|
|
269
|
+
const source = await resolveListingSource(branch);
|
|
270
|
+
if (!source)
|
|
271
|
+
return [];
|
|
272
|
+
return buildContentTreeImpl(source.branchRoot, source.flatSchema, source.contentRootName, treeOptions, source.visibility);
|
|
192
273
|
};
|
|
193
274
|
const listEntries = async (options) => {
|
|
194
|
-
const {
|
|
195
|
-
|
|
275
|
+
const { branch, ...listOptions } = options ?? {};
|
|
276
|
+
const source = await resolveListingSource(branch);
|
|
277
|
+
if (!source)
|
|
278
|
+
return [];
|
|
279
|
+
return listEntriesImpl(source.branchRoot, source.flatSchema, source.contentRootName, listOptions, source.visibility);
|
|
196
280
|
};
|
|
197
281
|
return {
|
|
198
282
|
read,
|
|
@@ -62,11 +62,17 @@ export interface BranchManagerProps {
|
|
|
62
62
|
/** Current user context for permission checks */
|
|
63
63
|
user?: UserContext;
|
|
64
64
|
onSelect?: (name: string) => void;
|
|
65
|
+
/** Awaited before the spinner starts, so a confirmation it shows never reads as a hung create. `false` abandons the create. */
|
|
66
|
+
onBeforeCreate?: () => Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Resolving `false` means the branch was not created, so the form stays open
|
|
69
|
+
* with the user's values; any other return (including none) closes it.
|
|
70
|
+
*/
|
|
65
71
|
onCreate?: (branch: {
|
|
66
72
|
name: string;
|
|
67
73
|
title?: string;
|
|
68
74
|
description?: string;
|
|
69
|
-
}) => void;
|
|
75
|
+
}) => Promise<boolean> | void;
|
|
70
76
|
onDelete?: (name: string) => void;
|
|
71
77
|
onSubmit?: (name: string) => void;
|
|
72
78
|
onWithdraw?: (name: string) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
4
|
import { Badge, Button, Group, Paper, ScrollArea, Stack, Text, TextInput, Textarea, Collapse, Tooltip, } from '@mantine/core';
|
|
5
5
|
import { BranchComments } from './comments/BranchComments.js';
|
|
6
6
|
import { UserBadge } from './components/UserBadge.js';
|
|
@@ -76,26 +76,41 @@ const statusColorMap = {
|
|
|
76
76
|
editing: { color: 'brand', variant: 'light' },
|
|
77
77
|
submitted: { color: 'green', variant: 'light' },
|
|
78
78
|
};
|
|
79
|
-
export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, }) => {
|
|
79
|
+
export const BranchManager = ({ branches, mode, user, onSelect, onBeforeCreate, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, }) => {
|
|
80
80
|
const supportsBranching = clientOperatingStrategy(mode).supportsBranching();
|
|
81
81
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
82
82
|
const [newBranchName, setNewBranchName] = useState('');
|
|
83
83
|
const [newBranchTitle, setNewBranchTitle] = useState('');
|
|
84
84
|
const [newBranchDescription, setNewBranchDescription] = useState('');
|
|
85
|
-
const
|
|
86
|
-
|
|
85
|
+
const [creating, setCreating] = useState(false);
|
|
86
|
+
// Read synchronously: state would not yet show a first click to a second one in the same tick.
|
|
87
|
+
const creatingRef = useRef(false);
|
|
88
|
+
const handleCreate = async () => {
|
|
89
|
+
if (creatingRef.current || !newBranchName.trim())
|
|
87
90
|
return;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
creatingRef.current = true;
|
|
92
|
+
try {
|
|
93
|
+
if (onBeforeCreate && !(await onBeforeCreate()))
|
|
94
|
+
return;
|
|
95
|
+
setCreating(true);
|
|
96
|
+
const created = await onCreate?.({
|
|
97
|
+
name: newBranchName.trim(),
|
|
98
|
+
title: newBranchTitle.trim() || undefined,
|
|
99
|
+
description: newBranchDescription.trim() || undefined,
|
|
100
|
+
});
|
|
101
|
+
if (created === false)
|
|
102
|
+
return;
|
|
103
|
+
setNewBranchName('');
|
|
104
|
+
setNewBranchTitle('');
|
|
105
|
+
setNewBranchDescription('');
|
|
106
|
+
setShowCreateForm(false);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
creatingRef.current = false;
|
|
110
|
+
setCreating(false);
|
|
111
|
+
}
|
|
97
112
|
};
|
|
98
|
-
return (_jsxs(Stack, { h: "100%", style: { display: 'flex', flexDirection: 'column' }, gap: 0, "data-testid": "branch-manager", children: [currentUserId && onAddComment && onResolveThread && (_jsx(Stack, { pt: "sm", children: _jsx(BranchComments, { comments: comments, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetUserMetadata }) })), !!supportsBranching && (_jsxs(Stack, { gap: "sm", pt: "sm", children: [_jsx(Button, { variant: "light", size: "sm", fullWidth: true, onClick: () => setShowCreateForm(!showCreateForm), "data-testid": "create-branch-button", children: showCreateForm ? 'Cancel' : 'Create New Branch' }), _jsx(Collapse, { in: showCreateForm, children: _jsx(Paper, { withBorder: true, p: "md", radius: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsx(TextInput, { label: "Branch Name", placeholder: "feature/my-branch", value: newBranchName, onChange: (e) => setNewBranchName(e.target.value), required: true, "data-testid": "branch-name-input" }), _jsx(TextInput, { label: "Title (optional)", placeholder: "Brief description", value: newBranchTitle, onChange: (e) => setNewBranchTitle(e.target.value), "data-testid": "branch-title-input" }), _jsx(Textarea, { label: "Description (optional)", placeholder: "Detailed description of the changes", value: newBranchDescription, onChange: (e) => setNewBranchDescription(e.target.value), minRows: 2, "data-testid": "branch-description-textarea" }), _jsx(Button, { onClick: handleCreate, disabled: !newBranchName.trim(), fullWidth: true, "data-testid": "create-branch-submit", children: "Create Branch" })] }) }) })] })), _jsx(ScrollArea, { style: { flex: 1 }, pb: "md", children: branches.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "md", children: !supportsBranching ? 'Branch management is not available.' : 'No branches available.' })) : (_jsx(Stack, { gap: "sm", children: branches.map((b) => {
|
|
113
|
+
return (_jsxs(Stack, { h: "100%", style: { display: 'flex', flexDirection: 'column' }, gap: 0, "data-testid": "branch-manager", children: [currentUserId && onAddComment && onResolveThread && (_jsx(Stack, { pt: "sm", children: _jsx(BranchComments, { comments: comments, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetUserMetadata }) })), !!supportsBranching && (_jsxs(Stack, { gap: "sm", pt: "sm", children: [_jsx(Button, { variant: "light", size: "sm", fullWidth: true, onClick: () => setShowCreateForm(!showCreateForm), disabled: creating, "data-testid": "create-branch-button", children: showCreateForm ? 'Cancel' : 'Create New Branch' }), _jsx(Collapse, { in: showCreateForm, children: _jsx(Paper, { withBorder: true, p: "md", radius: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsx(TextInput, { label: "Branch Name", placeholder: "feature/my-branch", value: newBranchName, onChange: (e) => setNewBranchName(e.target.value), required: true, disabled: creating, "data-testid": "branch-name-input" }), _jsx(TextInput, { label: "Title (optional)", placeholder: "Brief description", value: newBranchTitle, onChange: (e) => setNewBranchTitle(e.target.value), disabled: creating, "data-testid": "branch-title-input" }), _jsx(Textarea, { label: "Description (optional)", placeholder: "Detailed description of the changes", value: newBranchDescription, onChange: (e) => setNewBranchDescription(e.target.value), minRows: 2, disabled: creating, "data-testid": "branch-description-textarea" }), _jsx(Button, { onClick: handleCreate, disabled: !newBranchName.trim(), loading: creating, fullWidth: true, "data-testid": "create-branch-submit", children: "Create Branch" })] }) }) })] })), _jsx(ScrollArea, { style: { flex: 1 }, pb: "md", children: branches.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "md", children: !supportsBranching ? 'Branch management is not available.' : 'No branches available.' })) : (_jsx(Stack, { gap: "sm", children: branches.map((b) => {
|
|
99
114
|
const statusColor = statusColorMap[b.status] ?? {
|
|
100
115
|
color: 'neutral',
|
|
101
116
|
variant: 'light',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { CanopyClientConfig } from '../config/index.js';
|
|
3
3
|
import type { EditorProps } from './Editor.js';
|
|
4
|
-
export interface CanopyEditorProps extends Omit<EditorProps, 'collections' | 'previewBaseByCollection' | 'title' | 'subtitle' | 'themeOptions' | 'entries' | 'contentRoot' | 'operatingMode'> {
|
|
4
|
+
export interface CanopyEditorProps extends Omit<EditorProps, 'collections' | 'previewBaseByCollection' | 'previewPrefix' | 'title' | 'subtitle' | 'themeOptions' | 'entries' | 'contentRoot' | 'operatingMode'> {
|
|
5
5
|
config: CanopyClientConfig;
|
|
6
6
|
entries?: EditorProps['entries'];
|
|
7
7
|
}
|
|
@@ -4,13 +4,9 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import { Editor } from './Editor.js';
|
|
5
5
|
import { ApiClientProvider } from './context/index.js';
|
|
6
6
|
import { EditorAuthGate } from './EditorAuthGate.js';
|
|
7
|
-
import { buildEditorCollections
|
|
7
|
+
import { buildEditorCollections } from './editor-config.js';
|
|
8
8
|
export const CanopyEditor = ({ config, entries = [], initialSelectedId, initialValues, renderPreview, onCreateEntry, branchName, customRenderers, }) => {
|
|
9
9
|
const collections = useMemo(() => buildEditorCollections(config.flatSchema), [config.flatSchema]);
|
|
10
|
-
const previewBase = useMemo(() => ({
|
|
11
|
-
...buildPreviewBaseByCollection(config, config.flatSchema),
|
|
12
|
-
...(config.editor?.previewBase ?? {}),
|
|
13
|
-
}), [config]);
|
|
14
10
|
// Empty string = branchless start; useBranchManager adopts the server's
|
|
15
11
|
// detected default branch on the first branches.list() load.
|
|
16
12
|
const resolvedBranchName = branchName ?? config.defaultActiveBranch ?? config.defaultBaseBranch ?? '';
|
|
@@ -19,6 +15,6 @@ export const CanopyEditor = ({ config, entries = [], initialSelectedId, initialV
|
|
|
19
15
|
const resolvedTheme = config.editor?.theme ?? undefined;
|
|
20
16
|
// The gate owns the SWRProvider (keyed by user id), so it sits between the API client and the
|
|
21
17
|
// editor: see EditorAuthGate for what a signed-out or lapsed session renders.
|
|
22
|
-
return (_jsx(ApiClientProvider, { basePath: config.basePath, children: _jsx(EditorAuthGate, { SignInComponent: config.editor?.SignInComponent, themeOptions: resolvedTheme, children: _jsx(Editor, { entries: entries, title: resolvedTitle, subtitle: resolvedSubtitle, branchName: resolvedBranchName, operatingMode: config.mode, initialSelectedId: initialSelectedId, initialValues: initialValues, renderPreview: renderPreview, onCreateEntry: onCreateEntry, customRenderers: customRenderers, collections: collections, contentRoot: config.contentRoot, entryLinkUrl: config.entryLinkUrl, previewBaseByCollection: previewBase,
|
|
18
|
+
return (_jsx(ApiClientProvider, { basePath: config.basePath, children: _jsx(EditorAuthGate, { SignInComponent: config.editor?.SignInComponent, themeOptions: resolvedTheme, children: _jsx(Editor, { entries: entries, title: resolvedTitle, subtitle: resolvedSubtitle, branchName: resolvedBranchName, operatingMode: config.mode, initialSelectedId: initialSelectedId, initialValues: initialValues, renderPreview: renderPreview, onCreateEntry: onCreateEntry, customRenderers: customRenderers, collections: collections, contentRoot: config.contentRoot, entryLinkUrl: config.entryLinkUrl, previewBaseByCollection: config.editor?.previewBase, previewPrefix: config.editor?.previewPrefix, basePath: config.basePath, themeOptions: resolvedTheme, AccountComponent: config.editor?.AccountComponent, onAccountClick: config.editor?.onAccountClick, onLogoutClick: config.editor?.onLogoutClick }) }) }));
|
|
23
19
|
};
|
|
24
20
|
export default CanopyEditor;
|
package/dist/editor/Editor.d.ts
CHANGED
|
@@ -57,11 +57,12 @@ export interface EditorProps {
|
|
|
57
57
|
renderPreview?: (entry: EditorEntry, value: FormValue | undefined) => React.ReactNode;
|
|
58
58
|
onCreateEntry?: (collectionPath: LogicalPath) => Promise<void> | void;
|
|
59
59
|
themeOptions?: CanopyThemeOptions;
|
|
60
|
-
|
|
60
|
+
/** `editor.previewBase` from config (see `buildPreviewSrc`); `false` marks entries with no page. */
|
|
61
|
+
previewBaseByCollection?: Record<string, string | false>;
|
|
62
|
+
/** `editor.previewPrefix` from config: put in front of every preview iframe `src` (see `buildPreviewSrc`). */
|
|
63
|
+
previewPrefix?: string;
|
|
61
64
|
currentUser?: string;
|
|
62
65
|
canResolveComments?: boolean;
|
|
63
|
-
/** `media.publicBaseUrl` from config - prefixed onto asset URLs the editor builds (MediaLibrary/ImageField/MDX image dialog). Undefined means root-relative (editor and site share an origin). */
|
|
64
|
-
assetBaseUrl?: string;
|
|
65
66
|
/**
|
|
66
67
|
* `CanopyClientConfig.basePath` - the deployment prefix the host Next.js app is served under
|
|
67
68
|
* (e.g. `/preview-123`). Prefixed onto every preview iframe `src` this component builds via
|