canopycms 0.0.68-int.97 → 0.0.68-int.98
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 +2 -2
- package/dist/api/branch.d.ts +10 -2
- package/dist/api/branch.js +28 -16
- package/dist/api/client.d.ts +2 -2
- package/dist/api/client.js +43 -2
- package/dist/api/index.d.ts +1 -1
- package/dist/cli/cli.js +3 -3
- package/dist/cli/generate-ai-content.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/template-files/cms-stack.ts.template +4 -1
- package/dist/config/schemas/config.d.ts +5 -5
- package/dist/config/schemas/config.js +1 -1
- package/dist/config/types.d.ts +7 -10
- package/dist/editor/BranchManager.d.ts +5 -1
- package/dist/editor/BranchManager.js +26 -13
- package/dist/editor/CanopyEditor.js +2 -6
- package/dist/editor/Editor.d.ts +2 -1
- package/dist/editor/Editor.js +6 -6
- package/dist/editor/editor-config.d.ts +1 -2
- package/dist/editor/editor-config.js +0 -23
- package/dist/editor/editor-utils.d.ts +7 -6
- package/dist/editor/editor-utils.js +39 -33
- package/dist/editor/fields/MarkdownField.js +54 -12
- 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/useBranchActions.d.ts +4 -1
- package/dist/editor/hooks/useBranchActions.js +14 -3
- package/dist/editor/hooks/useBranchManager.d.ts +11 -0
- package/dist/editor/hooks/useBranchManager.js +70 -2
- package/dist/editor/hooks/useBranchesData.d.ts +6 -0
- package/dist/editor/hooks/useBranchesData.js +6 -2
- package/dist/editor/hooks/useEntriesData.js +1 -1
- package/dist/editor/hooks/useEntryManager.d.ts +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -388,7 +388,7 @@ import config from '../canopycms.config'
|
|
|
388
388
|
export default CanopyEditorPage(config)
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
-
The editor loads entries on the client from `/api/canopycms/[branch]/entries`, so server prefetch is optional. Use `
|
|
391
|
+
The editor loads entries on the client from `/api/canopycms/[branch]/entries`, so server prefetch is optional. Use `editor.previewBase` to override preview URLs.
|
|
392
392
|
|
|
393
393
|
6. **Theme it**
|
|
394
394
|
Wrap editor surfaces with `CanopyCMSProvider` to load Mantine styles and customize `brand`/`primary`/`neutral`/`accent` colors and color scheme. Pass `themeOptions` into `Editor` if desired.
|
|
@@ -402,7 +402,7 @@ _TODO_ show real examples of what to do
|
|
|
402
402
|
|
|
403
403
|
### Preview branch awareness
|
|
404
404
|
|
|
405
|
-
- When building preview URLs, include the current branch as a query param (e.g., `/?branch=feature-foo` or `/posts/hello?branch=feature-foo`) so SSR preview pages read from the same branch workspace the editor is editing. The `Editor` component appends the branch param
|
|
405
|
+
- When building preview URLs, include the current branch as a query param (e.g., `/?branch=feature-foo` or `/posts/hello?branch=feature-foo`) so SSR preview pages read from the same branch workspace the editor is editing. The `Editor` component appends the branch param to the preview URLs it builds; your page loaders should read `searchParams.branch` and pass it as the `branch` option to `getCanopy()`'s `read`/`readByUrlPath`.
|
|
406
406
|
- For public static builds, omit/ignore the branch param; this pattern is only for the editor/preview environment.
|
|
407
407
|
- Likewise, include `branch` in your editor route (e.g., `/edit?branch=feature-foo`) and have your editor page pass it to `<Editor>` so reloads/links preserve the selected branch. The `Editor` will also reflect branch switches back into the query string.
|
|
408
408
|
|
package/dist/api/branch.d.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface BranchListItem extends BranchMetadata {
|
|
|
45
45
|
*/
|
|
46
46
|
submitBlocked?: boolean;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Response type for branch creation. Carries the list-item shape (server-computed
|
|
50
|
+
* flags included) so the editor can insert the branch without waiting for a
|
|
51
|
+
* listing that may lag behind the create.
|
|
52
|
+
*/
|
|
53
|
+
export type BranchCreateResponse = ApiResponse<{
|
|
54
|
+
branch: BranchListItem;
|
|
55
|
+
}>;
|
|
48
56
|
/** Response type for listing branches */
|
|
49
57
|
export type BranchListResponse = ApiResponse<{
|
|
50
58
|
branches: BranchListItem[];
|
|
@@ -130,7 +138,7 @@ export interface CreateBranchBody {
|
|
|
130
138
|
access?: BranchMetadata['access'];
|
|
131
139
|
}
|
|
132
140
|
/** @internal Exported for tests. */
|
|
133
|
-
export declare const createBranchHandler: (ctx: ApiContext, req: ApiRequest, body: z.infer<typeof createBranchBodySchema>) => Promise<
|
|
141
|
+
export declare const createBranchHandler: (ctx: ApiContext, req: ApiRequest, body: z.infer<typeof createBranchBodySchema>) => Promise<BranchCreateResponse>;
|
|
134
142
|
/** @internal Exported for tests. */
|
|
135
143
|
export declare const listBranchesHandler: (ctx: ApiContext, req: ApiRequest) => Promise<BranchListResponse>;
|
|
136
144
|
/** @internal Exported for tests. */
|
|
@@ -187,7 +195,7 @@ export declare const BRANCH_ROUTES: {
|
|
|
187
195
|
allowedUsers?: string[] | undefined;
|
|
188
196
|
allowedGroups?: string[] | undefined;
|
|
189
197
|
} | undefined;
|
|
190
|
-
}>,
|
|
198
|
+
}>, BranchCreateResponse>;
|
|
191
199
|
readonly delete: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
192
200
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
193
201
|
}, "strip", z.ZodTypeAny, {
|
package/dist/api/branch.js
CHANGED
|
@@ -87,6 +87,20 @@ const resolveReadOnlyMirrorPath = (ctx, strategy) => {
|
|
|
87
87
|
process.env[remoteUrlConfig.envVarName] ??
|
|
88
88
|
remoteUrlConfig.autoDetectRemotePath);
|
|
89
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Attach server-computed protected-base-branch flags to a branch. Reads config
|
|
92
|
+
* per call so dev-mode refreshActiveBranch() updates are reflected.
|
|
93
|
+
*/
|
|
94
|
+
const toBranchListItem = (config, context) => {
|
|
95
|
+
const protection = getBranchWriteProtection(config, context.branch.name, context.branch.baseBranch, context.branch.status);
|
|
96
|
+
return {
|
|
97
|
+
...context.branch,
|
|
98
|
+
isProtected: protection.isProtected,
|
|
99
|
+
readOnly: protection.readOnly,
|
|
100
|
+
writeBlocked: protection.writeBlocked,
|
|
101
|
+
submitBlocked: protection.submitBlockedIncludingStatus,
|
|
102
|
+
};
|
|
103
|
+
};
|
|
90
104
|
/** @internal Exported for tests. */
|
|
91
105
|
export const createBranchHandler = async (ctx, req, body) => {
|
|
92
106
|
return log.timed('api', 'createBranch', async () => {
|
|
@@ -322,7 +336,11 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
322
336
|
access: body.access,
|
|
323
337
|
});
|
|
324
338
|
log.debug('api', 'Branch created', { branchName: context.branch.name });
|
|
325
|
-
return {
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
status: 200,
|
|
342
|
+
data: { branch: toBranchListItem(ctx.services.config, context) },
|
|
343
|
+
};
|
|
326
344
|
});
|
|
327
345
|
};
|
|
328
346
|
/** @internal Exported for tests. */
|
|
@@ -345,24 +363,15 @@ export const listBranchesHandler = async (ctx, req) => {
|
|
|
345
363
|
// editor matches defaultBranch against registry names, so return the form
|
|
346
364
|
// that can actually be found there.
|
|
347
365
|
const defaultBranch = sanitizeBranchName(ctx.services.config.defaultActiveBranch ?? ctx.services.config.defaultBaseBranch ?? 'main');
|
|
348
|
-
// Attach server-computed protected-base-branch flags; read config per-request
|
|
349
|
-
// so dev-mode refreshActiveBranch() updates are reflected here too.
|
|
350
|
-
const toListItem = (context) => {
|
|
351
|
-
const protection = getBranchWriteProtection(ctx.services.config, context.branch.name, context.branch.baseBranch, context.branch.status);
|
|
352
|
-
return {
|
|
353
|
-
...context.branch,
|
|
354
|
-
isProtected: protection.isProtected,
|
|
355
|
-
readOnly: protection.readOnly,
|
|
356
|
-
writeBlocked: protection.writeBlocked,
|
|
357
|
-
submitBlocked: protection.submitBlockedIncludingStatus,
|
|
358
|
-
};
|
|
359
|
-
};
|
|
360
366
|
// Admins and Reviewers see all branches
|
|
361
367
|
if (isPrivileged(req.user.groups)) {
|
|
362
368
|
return {
|
|
363
369
|
ok: true,
|
|
364
370
|
status: 200,
|
|
365
|
-
data: {
|
|
371
|
+
data: {
|
|
372
|
+
branches: allBranches.map((context) => toBranchListItem(ctx.services.config, context)),
|
|
373
|
+
defaultBranch,
|
|
374
|
+
},
|
|
366
375
|
};
|
|
367
376
|
}
|
|
368
377
|
// Regular users only see branches they created or have explicit access to
|
|
@@ -390,7 +399,10 @@ export const listBranchesHandler = async (ctx, req) => {
|
|
|
390
399
|
return {
|
|
391
400
|
ok: true,
|
|
392
401
|
status: 200,
|
|
393
|
-
data: {
|
|
402
|
+
data: {
|
|
403
|
+
branches: visibleBranches.map((context) => toBranchListItem(ctx.services.config, context)),
|
|
404
|
+
defaultBranch,
|
|
405
|
+
},
|
|
394
406
|
};
|
|
395
407
|
};
|
|
396
408
|
/** @internal Exported for tests. */
|
|
@@ -646,7 +658,7 @@ const createBranch = defineEndpoint({
|
|
|
646
658
|
path: '/branches',
|
|
647
659
|
body: createBranchBodySchema,
|
|
648
660
|
bodyType: 'CreateBranchBody',
|
|
649
|
-
responseType: '
|
|
661
|
+
responseType: 'BranchCreateResponse',
|
|
650
662
|
response: {},
|
|
651
663
|
defaultMockData: {
|
|
652
664
|
branch: {
|
package/dist/api/client.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* AUTO-GENERATED by scripts/generate-client.ts; do not edit this file manually
|
|
6
6
|
* (changes will be overwritten), edit the generator's templates instead.
|
|
7
7
|
*/
|
|
8
|
-
import type { BranchDeleteResponse, BranchListResponse, BranchResponse, CreateBranchBody, UpdateBranchAccessBody } from './branch.js';
|
|
8
|
+
import type { BranchCreateResponse, BranchDeleteResponse, BranchListResponse, BranchResponse, CreateBranchBody, UpdateBranchAccessBody } from './branch.js';
|
|
9
9
|
import type { BranchMergeResponse } from './branch-status.js';
|
|
10
10
|
import type { AddCommentBody, AddCommentResponse, CommentsResponse, ResolveCommentResponse } from './comments.js';
|
|
11
11
|
import type { ContentReadResponse, ContentWriteResponse, ReferenceValidationResponse, RenameEntryBody, RenameEntryResponse, ValidateReferencesBody, WriteContentBody } from './content.js';
|
|
@@ -56,7 +56,7 @@ export declare class CanopyApiClient {
|
|
|
56
56
|
/** GET /branches */
|
|
57
57
|
list: () => Promise<BranchListResponse>;
|
|
58
58
|
/** POST /branches */
|
|
59
|
-
create: (body: CreateBranchBody) => Promise<
|
|
59
|
+
create: (body: CreateBranchBody) => Promise<BranchCreateResponse>;
|
|
60
60
|
/** DELETE /:branch */
|
|
61
61
|
delete: (params: Record<string, string>) => Promise<BranchDeleteResponse>;
|
|
62
62
|
/** PATCH /:branch/access */
|
package/dist/api/client.js
CHANGED
|
@@ -326,10 +326,21 @@ export class CanopyApiClient {
|
|
|
326
326
|
if (requestBody !== undefined) {
|
|
327
327
|
init.body = requestBody;
|
|
328
328
|
}
|
|
329
|
-
|
|
329
|
+
let response = await this.fetchFn(url, init);
|
|
330
330
|
// A body from in front of the API (a proxy or CDN error page) may be empty, not JSON, or
|
|
331
331
|
// not an ApiResponse; every such body becomes an `ok: false` ApiResponse, never a throw.
|
|
332
|
-
|
|
332
|
+
let parsed = await response.json().catch(() => undefined);
|
|
333
|
+
// A 429 no handler wrote is a throttle in front of the API, such as Lambda's concurrency
|
|
334
|
+
// cap, which answers before the function runs. The request was never handled, so
|
|
335
|
+
// resending it is safe for writes too.
|
|
336
|
+
for (let attempt = 0; response.status === 429 && !isApiResponseBody(parsed); attempt++) {
|
|
337
|
+
const delay = throttleRetryDelayMs(response, attempt);
|
|
338
|
+
if (delay === undefined)
|
|
339
|
+
break;
|
|
340
|
+
await sleep(delay);
|
|
341
|
+
response = await this.fetchFn(url, init);
|
|
342
|
+
parsed = await response.json().catch(() => undefined);
|
|
343
|
+
}
|
|
333
344
|
if (response.status === 401) {
|
|
334
345
|
this.onUnauthorized?.();
|
|
335
346
|
return { ok: false, status: 401, error: errorFromBody(parsed) ?? 'Unauthorized' };
|
|
@@ -345,6 +356,36 @@ export class CanopyApiClient {
|
|
|
345
356
|
return converted;
|
|
346
357
|
}
|
|
347
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Base wait before each resend of a throttled request. A throttle from a burst of cold starts
|
|
361
|
+
* clears as they finish, so the waits span a few seconds.
|
|
362
|
+
*/
|
|
363
|
+
const THROTTLE_RETRY_DELAYS_MS = [250, 1000, 3000];
|
|
364
|
+
/** The longest `Retry-After` waited; a throttle asking for longer is reported, not resent. */
|
|
365
|
+
const MAX_RETRY_AFTER_MS = 5000;
|
|
366
|
+
/**
|
|
367
|
+
* The wait before resend `attempt`, or undefined when there is to be no resend: the attempts
|
|
368
|
+
* are used up, or `Retry-After` asks for longer than {@link MAX_RETRY_AFTER_MS}. Without a
|
|
369
|
+
* `Retry-After`, the base delay plus up to half again, so throttled requests do not resend in
|
|
370
|
+
* lockstep.
|
|
371
|
+
*/
|
|
372
|
+
function throttleRetryDelayMs(response, attempt) {
|
|
373
|
+
const base = THROTTLE_RETRY_DELAYS_MS[attempt];
|
|
374
|
+
if (base === undefined)
|
|
375
|
+
return undefined;
|
|
376
|
+
// Delta-seconds only; an HTTP-date or a blank value falls back to the base delay. A custom
|
|
377
|
+
// `fetch` option may return a Response-like without headers.
|
|
378
|
+
const header = response.headers?.get('retry-after')?.trim();
|
|
379
|
+
const retryAfter = header ? Number(header) : Number.NaN;
|
|
380
|
+
if (Number.isFinite(retryAfter) && retryAfter >= 0) {
|
|
381
|
+
const ms = retryAfter * 1000;
|
|
382
|
+
return ms > MAX_RETRY_AFTER_MS ? undefined : ms;
|
|
383
|
+
}
|
|
384
|
+
return base + Math.random() * base * 0.5;
|
|
385
|
+
}
|
|
386
|
+
function sleep(ms) {
|
|
387
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
388
|
+
}
|
|
348
389
|
const convertedResponses = new WeakSet();
|
|
349
390
|
/**
|
|
350
391
|
* Whether `result` was converted from a body no CanopyCMS handler wrote (a proxy or CDN
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { ApiResponse, ApiRequest, ApiContext } from './types.js';
|
|
2
|
-
export type { BranchResponse, BranchListResponse, BranchDeleteResponse, CreateBranchBody, UpdateBranchAccessBody, } from './branch.js';
|
|
2
|
+
export type { BranchResponse, BranchCreateResponse, BranchListResponse, BranchDeleteResponse, CreateBranchBody, UpdateBranchAccessBody, } from './branch.js';
|
|
3
3
|
export type { BranchMergeResponse } from './branch-merge.js';
|
|
4
4
|
export type { CommentsResponse, AddCommentResponse, ResolveCommentResponse } from './comments.js';
|
|
5
5
|
export type { EntriesResponse, ListEntriesResponse, CollectionItem, ListEntriesParams, DeleteEntryResponse, } from './entries.js';
|
package/dist/cli/cli.js
CHANGED
|
@@ -1443,7 +1443,7 @@ var init_init = __esm({
|
|
|
1443
1443
|
title: z5.string().optional(),
|
|
1444
1444
|
subtitle: z5.string().optional(),
|
|
1445
1445
|
theme: z5.unknown().optional(),
|
|
1446
|
-
previewBase: z5.record(z5.string()).optional(),
|
|
1446
|
+
previewBase: z5.record(z5.union([z5.string(), z5.literal(false)])).optional(),
|
|
1447
1447
|
previewPrefix: previewPrefixSchema.optional(),
|
|
1448
1448
|
// UI handler functions (runtime only, don't serialize)
|
|
1449
1449
|
onAccountClick: z5.function().returns(z5.void()).optional(),
|
|
@@ -6233,7 +6233,7 @@ var init_generate_ai_content = __esm({
|
|
|
6233
6233
|
title: z62.string().optional(),
|
|
6234
6234
|
subtitle: z62.string().optional(),
|
|
6235
6235
|
theme: z62.unknown().optional(),
|
|
6236
|
-
previewBase: z62.record(z62.string()).optional(),
|
|
6236
|
+
previewBase: z62.record(z62.union([z62.string(), z62.literal(false)])).optional(),
|
|
6237
6237
|
previewPrefix: previewPrefixSchema2.optional(),
|
|
6238
6238
|
// UI handler functions (runtime only, don't serialize)
|
|
6239
6239
|
onAccountClick: z62.function().returns(z62.void()).optional(),
|
|
@@ -10902,7 +10902,7 @@ var init_config4 = __esm({
|
|
|
10902
10902
|
title: z14.string().optional(),
|
|
10903
10903
|
subtitle: z14.string().optional(),
|
|
10904
10904
|
theme: z14.unknown().optional(),
|
|
10905
|
-
previewBase: z14.record(z14.string()).optional(),
|
|
10905
|
+
previewBase: z14.record(z14.union([z14.string(), z14.literal(false)])).optional(),
|
|
10906
10906
|
previewPrefix: previewPrefixSchema3.optional(),
|
|
10907
10907
|
// UI handler functions (runtime only, don't serialize)
|
|
10908
10908
|
onAccountClick: z14.function().returns(z14.void()).optional(),
|
|
@@ -481,7 +481,7 @@ var init_config = __esm({
|
|
|
481
481
|
title: z6.string().optional(),
|
|
482
482
|
subtitle: z6.string().optional(),
|
|
483
483
|
theme: z6.unknown().optional(),
|
|
484
|
-
previewBase: z6.record(z6.string()).optional(),
|
|
484
|
+
previewBase: z6.record(z6.union([z6.string(), z6.literal(false)])).optional(),
|
|
485
485
|
previewPrefix: previewPrefixSchema.optional(),
|
|
486
486
|
// UI handler functions (runtime only, don't serialize)
|
|
487
487
|
onAccountClick: z6.function().returns(z6.void()).optional(),
|
package/dist/cli/init.js
CHANGED
|
@@ -334,7 +334,7 @@ var init_config = __esm({
|
|
|
334
334
|
title: z5.string().optional(),
|
|
335
335
|
subtitle: z5.string().optional(),
|
|
336
336
|
theme: z5.unknown().optional(),
|
|
337
|
-
previewBase: z5.record(z5.string()).optional(),
|
|
337
|
+
previewBase: z5.record(z5.union([z5.string(), z5.literal(false)])).optional(),
|
|
338
338
|
previewPrefix: previewPrefixSchema.optional(),
|
|
339
339
|
// UI handler functions (runtime only, don't serialize)
|
|
340
340
|
onAccountClick: z5.function().returns(z5.void()).optional(),
|
|
@@ -192,7 +192,10 @@ export class CmsStack extends Stack {
|
|
|
192
192
|
},
|
|
193
193
|
|
|
194
194
|
memorySize: 2048,
|
|
195
|
-
|
|
195
|
+
// A ceiling, free while idle. The editor's static chunks count against it
|
|
196
|
+
// until CloudFront caches them, and a cold editor load requests them
|
|
197
|
+
// together, so synth warns below 20. See the prop's doc comment.
|
|
198
|
+
reservedConcurrency: 50,
|
|
196
199
|
})
|
|
197
200
|
|
|
198
201
|
// Media support (uploads, on-demand image transforms). To enable it:
|
|
@@ -84,7 +84,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
|
|
|
84
84
|
title: z.ZodOptional<z.ZodString>;
|
|
85
85
|
subtitle: z.ZodOptional<z.ZodString>;
|
|
86
86
|
theme: z.ZodOptional<z.ZodUnknown>;
|
|
87
|
-
previewBase: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString
|
|
87
|
+
previewBase: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodLiteral<false>]>>>;
|
|
88
88
|
previewPrefix: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
89
89
|
onAccountClick: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodVoid>>;
|
|
90
90
|
onLogoutClick: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodVoid>>;
|
|
@@ -94,7 +94,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
|
|
|
94
94
|
title?: string | undefined;
|
|
95
95
|
subtitle?: string | undefined;
|
|
96
96
|
theme?: unknown;
|
|
97
|
-
previewBase?: Record<string, string> | undefined;
|
|
97
|
+
previewBase?: Record<string, string | false> | undefined;
|
|
98
98
|
previewPrefix?: string | undefined;
|
|
99
99
|
onAccountClick?: ((...args: unknown[]) => void) | undefined;
|
|
100
100
|
onLogoutClick?: ((...args: unknown[]) => void) | undefined;
|
|
@@ -104,7 +104,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
|
|
|
104
104
|
title?: string | undefined;
|
|
105
105
|
subtitle?: string | undefined;
|
|
106
106
|
theme?: unknown;
|
|
107
|
-
previewBase?: Record<string, string> | undefined;
|
|
107
|
+
previewBase?: Record<string, string | false> | undefined;
|
|
108
108
|
previewPrefix?: string | undefined;
|
|
109
109
|
onAccountClick?: ((...args: unknown[]) => void) | undefined;
|
|
110
110
|
onLogoutClick?: ((...args: unknown[]) => void) | undefined;
|
|
@@ -167,7 +167,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
|
|
|
167
167
|
title?: string | undefined;
|
|
168
168
|
subtitle?: string | undefined;
|
|
169
169
|
theme?: unknown;
|
|
170
|
-
previewBase?: Record<string, string> | undefined;
|
|
170
|
+
previewBase?: Record<string, string | false> | undefined;
|
|
171
171
|
previewPrefix?: string | undefined;
|
|
172
172
|
onAccountClick?: ((...args: unknown[]) => void) | undefined;
|
|
173
173
|
onLogoutClick?: ((...args: unknown[]) => void) | undefined;
|
|
@@ -223,7 +223,7 @@ export declare const CanopyConfigSchema: z.ZodObject<{
|
|
|
223
223
|
title?: string | undefined;
|
|
224
224
|
subtitle?: string | undefined;
|
|
225
225
|
theme?: unknown;
|
|
226
|
-
previewBase?: Record<string, string> | undefined;
|
|
226
|
+
previewBase?: Record<string, string | false> | undefined;
|
|
227
227
|
previewPrefix?: string | undefined;
|
|
228
228
|
onAccountClick?: ((...args: unknown[]) => void) | undefined;
|
|
229
229
|
onLogoutClick?: ((...args: unknown[]) => void) | undefined;
|
|
@@ -51,7 +51,7 @@ const editorConfigSchema = z.object({
|
|
|
51
51
|
title: z.string().optional(),
|
|
52
52
|
subtitle: z.string().optional(),
|
|
53
53
|
theme: z.unknown().optional(),
|
|
54
|
-
previewBase: z.record(z.string()).optional(),
|
|
54
|
+
previewBase: z.record(z.union([z.string(), z.literal(false)])).optional(),
|
|
55
55
|
previewPrefix: previewPrefixSchema.optional(),
|
|
56
56
|
// UI handler functions (runtime only, don't serialize)
|
|
57
57
|
onAccountClick: z.function().returns(z.void()).optional(),
|
package/dist/config/types.d.ts
CHANGED
|
@@ -257,18 +257,15 @@ export interface CanopyEditorConfig {
|
|
|
257
257
|
subtitle?: string;
|
|
258
258
|
theme?: unknown;
|
|
259
259
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
260
|
+
* Preview-pane routes for sites whose routes don't mirror their content tree; otherwise an entry
|
|
261
|
+
* previews its own `urlPath`. Keyed by a root entry's path (the whole route), else collection
|
|
262
|
+
* path or name (plus the slug). `false` means no page, and the pane says so instead of framing one.
|
|
262
263
|
*
|
|
263
|
-
* Values are **site-relative
|
|
264
|
-
*
|
|
265
|
-
* prefix
|
|
266
|
-
* `basePath`, `previewPrefix` or trailing-slash change: the escape hatch for previewing against a
|
|
267
|
-
* different origin entirely.
|
|
268
|
-
*
|
|
269
|
-
* A site-relative value is a route on the host site, so `previewPrefix` is applied to it too.
|
|
264
|
+
* Values are **site-relative**, without the deployment `basePath` (applied on top, so including
|
|
265
|
+
* it prefixes it twice), and get `previewPrefix` too. An absolute value (`https://…`) gets no
|
|
266
|
+
* prefix, only the slug (for a collection key) and `?branch=`: for previewing another origin.
|
|
270
267
|
*/
|
|
271
|
-
previewBase?: Record<string, string>;
|
|
268
|
+
previewBase?: Record<string, string | false>;
|
|
272
269
|
/**
|
|
273
270
|
* Where the host mounts the pages the preview pane loads, put in front of every entry's preview
|
|
274
271
|
* URL (`'/preview'` makes `/blog/hello` load `/preview/blog/hello`). Set it when the site's
|
|
@@ -62,11 +62,15 @@ export interface BranchManagerProps {
|
|
|
62
62
|
/** Current user context for permission checks */
|
|
63
63
|
user?: UserContext;
|
|
64
64
|
onSelect?: (name: string) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Resolving `false` means the branch was not created, so the form stays open
|
|
67
|
+
* with the user's values; any other return (including none) closes it.
|
|
68
|
+
*/
|
|
65
69
|
onCreate?: (branch: {
|
|
66
70
|
name: string;
|
|
67
71
|
title?: string;
|
|
68
72
|
description?: string;
|
|
69
|
-
}) => void;
|
|
73
|
+
}) => Promise<boolean> | void;
|
|
70
74
|
onDelete?: (name: string) => void;
|
|
71
75
|
onSubmit?: (name: string) => void;
|
|
72
76
|
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';
|
|
@@ -82,20 +82,33 @@ export const BranchManager = ({ branches, mode, user, onSelect, onCreate, onDele
|
|
|
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
|
+
setCreating(true);
|
|
93
|
+
try {
|
|
94
|
+
const created = await onCreate?.({
|
|
95
|
+
name: newBranchName.trim(),
|
|
96
|
+
title: newBranchTitle.trim() || undefined,
|
|
97
|
+
description: newBranchDescription.trim() || undefined,
|
|
98
|
+
});
|
|
99
|
+
if (created === false)
|
|
100
|
+
return;
|
|
101
|
+
setNewBranchName('');
|
|
102
|
+
setNewBranchTitle('');
|
|
103
|
+
setNewBranchDescription('');
|
|
104
|
+
setShowCreateForm(false);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
creatingRef.current = false;
|
|
108
|
+
setCreating(false);
|
|
109
|
+
}
|
|
97
110
|
};
|
|
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) => {
|
|
111
|
+
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
112
|
const statusColor = statusColorMap[b.status] ?? {
|
|
100
113
|
color: 'neutral',
|
|
101
114
|
variant: 'light',
|
|
@@ -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, previewPrefix: config.editor?.previewPrefix, assetBaseUrl: config.assetBaseUrl, basePath: config.basePath, themeOptions: resolvedTheme, AccountComponent: config.editor?.AccountComponent, onAccountClick: config.editor?.onAccountClick, onLogoutClick: config.editor?.onLogoutClick }) }) }));
|
|
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, assetBaseUrl: config.assetBaseUrl, 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,7 +57,8 @@ 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>;
|
|
61
62
|
/** `editor.previewPrefix` from config: put in front of every preview iframe `src` (see `buildPreviewSrc`). */
|
|
62
63
|
previewPrefix?: string;
|
|
63
64
|
currentUser?: string;
|
package/dist/editor/Editor.js
CHANGED
|
@@ -71,7 +71,7 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
71
71
|
// Comments state (shared between useCommentSystem and useBranchManager)
|
|
72
72
|
const [commentsForBranchSummaries, setCommentsForBranchSummaries] = useState([]);
|
|
73
73
|
// 1. Branch manager (provides branchNameState, no dependencies)
|
|
74
|
-
const { branchName: branchNameState, setBranchName, branchSummaries, currentBranch, handleSubmit, handleWithdraw, handleRequestChanges, handleDelete, handleReloadBranchData, loadBranches, } = useBranchManager({
|
|
74
|
+
const { branchName: branchNameState, setBranchName, branchSummaries, currentBranch, addCreatedBranch, handleSubmit, handleWithdraw, handleRequestChanges, handleDelete, handleReloadBranchData, loadBranches, } = useBranchManager({
|
|
75
75
|
initialBranch: branchName,
|
|
76
76
|
operatingMode,
|
|
77
77
|
setBusy: setBranchesLoading,
|
|
@@ -95,7 +95,6 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
95
95
|
initialSelectedId,
|
|
96
96
|
branchName: branchNameState,
|
|
97
97
|
collections,
|
|
98
|
-
previewBaseByCollection,
|
|
99
98
|
resolvePreviewSrc: (entry) => buildPreviewSrc(entry, {
|
|
100
99
|
branchName: branchNameState,
|
|
101
100
|
previewBaseByCollection,
|
|
@@ -147,6 +146,7 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
147
146
|
setBranchName,
|
|
148
147
|
isAnyDirty,
|
|
149
148
|
onReloadBranches: () => loadBranches(),
|
|
149
|
+
onBranchCreated: addCreatedBranch,
|
|
150
150
|
});
|
|
151
151
|
// 5. Comment system (depends on branchNameState)
|
|
152
152
|
const { comments, focusedFieldPath, highlightThreadId, commentsPanelOpen, setCommentsPanelOpen, handleAddComment, handleResolveThread, handleJumpToField, handleJumpToEntry, handleJumpToBranch, } = useCommentSystem({
|
|
@@ -608,7 +608,9 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
608
608
|
? 'Setting up your branch workspace…'
|
|
609
609
|
: entriesInitializing
|
|
610
610
|
? 'Loading content…'
|
|
611
|
-
:
|
|
611
|
+
: currentEntry && !currentEntry.previewSrc
|
|
612
|
+
? 'No preview for this entry.'
|
|
613
|
+
: 'Select an item to start editing.' }) }));
|
|
612
614
|
const sidebarWidth = 64;
|
|
613
615
|
const footerHeight = 40;
|
|
614
616
|
const headerTitle = currentEntry?.label ?? currentEntry?.slug ?? title;
|
|
@@ -672,9 +674,7 @@ export const Editor = ({ entries, title, subtitle, siteTitle = title, siteSubtit
|
|
|
672
674
|
console.error('Branch change failed or was cancelled:', err);
|
|
673
675
|
// Don't close branch manager if there was an error or user cancelled
|
|
674
676
|
}
|
|
675
|
-
}, onCreate: (
|
|
676
|
-
handleCreateBranch(branch).catch((err) => console.error(err));
|
|
677
|
-
}, onSubmit: (name) => {
|
|
677
|
+
}, onCreate: handleCreateBranch, onSubmit: (name) => {
|
|
678
678
|
handleSubmit(name).catch((err) => console.error(err));
|
|
679
679
|
}, onWithdraw: (name) => {
|
|
680
680
|
handleWithdraw(name).catch((err) => console.error(err));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FlatSchemaItem } from '../config/index.js';
|
|
2
2
|
import type { EditorCollection } from './Editor.js';
|
|
3
3
|
/**
|
|
4
4
|
* Build hierarchical editor collections from the flattened schema.
|
|
@@ -10,4 +10,3 @@ import type { EditorCollection } from './Editor.js';
|
|
|
10
10
|
* shown as separate nodes in the navigation tree.
|
|
11
11
|
*/
|
|
12
12
|
export declare const buildEditorCollections: (flatSchema: FlatSchemaItem[]) => EditorCollection[];
|
|
13
|
-
export declare const buildPreviewBaseByCollection: (config: Pick<CanopyConfig, "contentRoot">, flatSchema: FlatSchemaItem[]) => Record<string, string>;
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import { trimSlashes } from '../paths/normalize.js';
|
|
2
|
-
const normalizeContentRoot = (value) => {
|
|
3
|
-
return trimSlashes(value ?? 'content');
|
|
4
|
-
};
|
|
5
|
-
const stripContentRoot = (logicalPath, contentRoot) => {
|
|
6
|
-
const prefix = contentRoot ? `${contentRoot}/` : '';
|
|
7
|
-
if (prefix && logicalPath.startsWith(prefix)) {
|
|
8
|
-
return logicalPath.slice(prefix.length);
|
|
9
|
-
}
|
|
10
|
-
return logicalPath;
|
|
11
|
-
};
|
|
12
1
|
/**
|
|
13
2
|
* Build hierarchical editor collections from the flattened schema.
|
|
14
3
|
* Uses logicalPath as IDs to match API responses.
|
|
@@ -64,15 +53,3 @@ export const buildEditorCollections = (flatSchema) => {
|
|
|
64
53
|
// This now includes the content root collection itself
|
|
65
54
|
return buildTree(undefined);
|
|
66
55
|
};
|
|
67
|
-
export const buildPreviewBaseByCollection = (config, flatSchema) => {
|
|
68
|
-
const contentRoot = normalizeContentRoot(config.contentRoot);
|
|
69
|
-
const flat = flatSchema;
|
|
70
|
-
const map = {};
|
|
71
|
-
for (const item of flat) {
|
|
72
|
-
// Strip content root from all items and normalize to preview URL
|
|
73
|
-
const base = stripContentRoot(item.logicalPath, contentRoot);
|
|
74
|
-
const normalizedBase = base ? `/${base}` : '/';
|
|
75
|
-
map[item.logicalPath] = normalizedBase;
|
|
76
|
-
}
|
|
77
|
-
return map;
|
|
78
|
-
};
|
|
@@ -3,12 +3,10 @@ import type { ContentFormat, FlatSchemaItem } from '../config/index.js';
|
|
|
3
3
|
import type { FormValue } from './FormRenderer.js';
|
|
4
4
|
import type { EditorEntry, EditorCollection } from './Editor.js';
|
|
5
5
|
import type { TreeNodeData } from '@mantine/core';
|
|
6
|
-
import { normalizeCollectionPath } from '../paths/normalize.js';
|
|
7
|
-
/** @internal Exported for tests. */
|
|
8
|
-
export { normalizeCollectionPath };
|
|
9
6
|
export interface PreviewContext {
|
|
10
7
|
branchName?: string;
|
|
11
|
-
|
|
8
|
+
/** `editor.previewBase`: routes by root entry path, collection path or name; `false` is no page. */
|
|
9
|
+
previewBaseByCollection?: Record<string, string | false>;
|
|
12
10
|
/** `editor.previewPrefix`: where the host mounts the pages the preview pane loads. */
|
|
13
11
|
previewPrefix?: string;
|
|
14
12
|
}
|
|
@@ -29,12 +27,14 @@ type PreviewEntry = {
|
|
|
29
27
|
* The result must equal the framed page's own URL: the `<iframe src>` (`PreviewFrame` in
|
|
30
28
|
* preview-bridge.tsx) 404s without the prefixes, and a URL the host redirects costs a round trip
|
|
31
29
|
* on every load. `trailingSlash` defaults to the value `withCanopy` inlines at build time.
|
|
30
|
+
*
|
|
31
|
+
* `undefined` means the entry has no page, so the editor frames nothing rather than another page.
|
|
32
32
|
*/
|
|
33
33
|
export declare const buildPreviewSrc: (entry: PreviewEntry, context: PreviewContext & {
|
|
34
34
|
contentRoot?: string;
|
|
35
35
|
basePath?: string;
|
|
36
36
|
trailingSlash?: boolean;
|
|
37
|
-
}) => string;
|
|
37
|
+
}) => string | undefined;
|
|
38
38
|
export declare const normalizeContentPayload: (raw: unknown) => FormValue;
|
|
39
39
|
export declare const buildWritePayload: (entry: {
|
|
40
40
|
collectionPath?: string;
|
|
@@ -43,7 +43,7 @@ export declare const buildWritePayload: (entry: {
|
|
|
43
43
|
}, value: FormValue) => FormValue;
|
|
44
44
|
interface BuildEntriesFromListParams {
|
|
45
45
|
response: ListEntriesResponse;
|
|
46
|
-
resolvePreviewSrc: (entry: Pick<CollectionItem, 'collectionPath' | 'collectionName' | 'slug' | 'entryType'>) => string;
|
|
46
|
+
resolvePreviewSrc: (entry: Pick<CollectionItem, 'collectionPath' | 'collectionName' | 'slug' | 'entryType'>) => string | undefined;
|
|
47
47
|
flatSchema: FlatSchemaItem[];
|
|
48
48
|
}
|
|
49
49
|
export declare const buildEntriesFromListResponse: ({ response, resolvePreviewSrc, flatSchema, }: BuildEntriesFromListParams) => EditorEntry[];
|
|
@@ -65,3 +65,4 @@ export declare const buildBreadcrumbSegments: (currentEntry: EditorEntry | undef
|
|
|
65
65
|
* @returns Record<string, boolean> - Expanded state object where keys are collection node values
|
|
66
66
|
*/
|
|
67
67
|
export declare const calculatePathToEntry: (entryPath: string | undefined, treeData: TreeNodeData[]) => Record<string, boolean>;
|
|
68
|
+
export {};
|