canopycms 0.1.0-int.115 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +267 -231
- package/README.md +0 -1
- package/dist/ai/references.js +3 -1
- package/dist/api/assets.js +2 -2
- package/dist/api/branch-merge.d.ts +1 -1
- package/dist/api/branch.js +1 -1
- package/dist/api/content.js +1 -1
- package/dist/api/entries-constants.d.ts +1 -1
- package/dist/api/entries-constants.js +1 -1
- package/dist/api/entries.d.ts +2 -2
- package/dist/api/permissions.d.ts +2 -2
- package/dist/api/types.d.ts +3 -2
- package/dist/cli/cli.js +16 -14
- package/dist/cli/generate-ai-content.js +7 -5
- package/dist/cli/init.js +4 -4
- package/dist/client.d.ts +0 -1
- package/dist/client.js +0 -1
- package/dist/config/schemas/config.d.ts +0 -5
- package/dist/config/schemas/config.js +0 -1
- package/dist/config/types.d.ts +0 -1
- package/dist/content-index-generation.d.ts +1 -1
- package/dist/content-index-generation.js +1 -1
- package/dist/content-listing.js +1 -1
- package/dist/content-reader.js +1 -1
- package/dist/content-store.d.ts +1 -1
- package/dist/content-store.js +1 -1
- package/dist/dev-content-watcher.d.ts +1 -1
- package/dist/dev-content-watcher.js +1 -1
- package/dist/editor/BranchManager.d.ts +2 -0
- package/dist/editor/BranchManager.js +5 -4
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +1 -2
- package/dist/editor/CommentsPanel.js +3 -6
- package/dist/editor/Editor.d.ts +0 -2
- package/dist/editor/Editor.js +329 -181
- package/dist/editor/EditorAuthGate.js +8 -3
- package/dist/editor/EntryNavigator.d.ts +10 -0
- package/dist/editor/EntryNavigator.js +51 -2
- package/dist/editor/FormHeader.js +1 -1
- package/dist/editor/PreviewFrame.d.ts +5 -1
- package/dist/editor/PreviewFrame.js +3 -2
- package/dist/editor/PreviewPane.d.ts +47 -0
- package/dist/editor/PreviewPane.js +147 -0
- package/dist/editor/PreviewToolbarTools.d.ts +22 -0
- package/dist/editor/PreviewToolbarTools.js +73 -0
- package/dist/editor/comments/form-comments.js +2 -1
- package/dist/editor/components/BranchLockBanner.d.ts +22 -0
- package/dist/editor/components/BranchLockBanner.js +39 -0
- package/dist/editor/components/BranchPill.d.ts +32 -0
- package/dist/editor/components/BranchPill.js +33 -0
- package/dist/editor/components/ConfirmDeleteModal.d.ts +3 -1
- package/dist/editor/components/ConfirmDeleteModal.js +3 -2
- package/dist/editor/components/EditorHeader.d.ts +50 -108
- package/dist/editor/components/EditorHeader.js +117 -138
- package/dist/editor/components/EditorRail.d.ts +49 -0
- package/dist/editor/components/EditorRail.js +65 -0
- package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
- package/dist/editor/components/EntryBreadcrumb.js +56 -0
- package/dist/editor/components/QuickOpenPalette.d.ts +1 -1
- package/dist/editor/components/QuickOpenPalette.js +3 -2
- package/dist/editor/components/RenameEntryModal.d.ts +3 -1
- package/dist/editor/components/RenameEntryModal.js +3 -2
- package/dist/editor/components/SaveAllResult.d.ts +25 -0
- package/dist/editor/components/SaveAllResult.js +86 -0
- package/dist/editor/components/SaveSplitButton.d.ts +30 -0
- package/dist/editor/components/SaveSplitButton.js +108 -0
- package/dist/editor/components/index.d.ts +2 -2
- package/dist/editor/components/index.js +2 -2
- package/dist/editor/context/EditorStateContext.d.ts +4 -4
- package/dist/editor/context/EditorStateContext.js +4 -4
- package/dist/editor/copy.d.ts +3 -0
- package/dist/editor/copy.js +3 -0
- package/dist/editor/editor-utils.d.ts +11 -6
- package/dist/editor/editor-utils.js +8 -17
- package/dist/editor/fields/MarkdownField.js +3 -0
- package/dist/editor/hooks/save-all.d.ts +68 -0
- package/dist/editor/hooks/save-all.js +61 -0
- package/dist/editor/hooks/useCommentSystem.d.ts +2 -0
- package/dist/editor/hooks/useCommentSystem.js +3 -0
- package/dist/editor/hooks/useDraftManager.d.ts +24 -1
- package/dist/editor/hooks/useDraftManager.js +297 -16
- package/dist/editor/hooks/useEditorLayout.d.ts +15 -11
- package/dist/editor/hooks/useEditorLayout.js +13 -25
- package/dist/editor/hooks/useEntryManager.d.ts +4 -0
- package/dist/editor/hooks/useEntryManager.js +30 -7
- package/dist/editor/media/MediaLibrary.d.ts +1 -1
- package/dist/editor/preview-path.d.ts +12 -0
- package/dist/editor/preview-path.js +45 -1
- package/dist/editor/preview-scale.d.ts +27 -0
- package/dist/editor/preview-scale.js +34 -0
- package/dist/editor/shell/ActivityRail.d.ts +37 -0
- package/dist/editor/shell/ActivityRail.js +91 -0
- package/dist/editor/shell/EditorShell.d.ts +17 -0
- package/dist/editor/shell/EditorShell.js +31 -0
- package/dist/editor/shell/PaneLayout.d.ts +28 -0
- package/dist/editor/shell/PaneLayout.js +116 -0
- package/dist/editor/shell/SidePanelHost.d.ts +35 -0
- package/dist/editor/shell/SidePanelHost.js +184 -0
- package/dist/editor/shell/TopBar.d.ts +12 -0
- package/dist/editor/shell/TopBar.js +14 -0
- package/dist/editor/shell/host-chrome.d.ts +16 -0
- package/dist/editor/shell/host-chrome.js +69 -0
- package/dist/editor/theme.js +18 -3
- package/dist/git-manager.d.ts +1 -1
- package/dist/git-manager.js +2 -2
- package/dist/operating-mode/types.d.ts +1 -1
- package/dist/resolve-canopy-user.d.ts +1 -1
- package/dist/resolve-canopy-user.js +1 -1
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +1 -1
- package/dist/schema/schema-store.d.ts +1 -1
- package/dist/schema/schema-store.js +2 -2
- package/dist/url-collision.js +3 -3
- package/dist/url-exclusivity-fixtures.d.ts +1 -1
- package/dist/url-exclusivity-fixtures.js +1 -1
- package/dist/utils/content-write-lock.d.ts +1 -1
- package/dist/utils/content-write-lock.js +1 -1
- package/dist/utils/debug.d.ts +1 -1
- package/dist/utils/debug.js +5 -3
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.js +27 -5
- package/dist/validation/block-structural-keys.d.ts +1 -1
- package/dist/validation/block-structural-keys.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/cms-worker.d.ts +1 -1
- package/dist/worker/cms-worker.js +2 -2
- package/dist/worker/github-auth.d.ts +1 -1
- package/dist/worker/github-auth.js +1 -1
- package/dist/worker/github-gateway-wire.d.ts +2 -1
- package/dist/worker/github-gateway-wire.js +7 -3
- package/dist/worker/github-gateway.js +1 -1
- package/dist/worker/github-mirror.d.ts +2 -2
- package/dist/worker/github-mirror.js +2 -2
- package/dist/worker/history-rewrite.d.ts +1 -1
- package/dist/worker/history-rewrite.js +1 -1
- package/dist/worker/self-checks.d.ts +1 -1
- package/dist/worker/shared-repo-git.js +1 -1
- package/package.json +5 -4
- package/dist/editor/EditorPanes.d.ts +0 -20
- package/dist/editor/EditorPanes.js +0 -124
- package/dist/editor/components/EditorFooter.d.ts +0 -11
- package/dist/editor/components/EditorFooter.js +0 -9
- package/dist/editor/components/EditorSidebar.d.ts +0 -70
- package/dist/editor/components/EditorSidebar.js +0 -50
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CANOPYCMS_VERSION = "0.1.0
|
|
1
|
+
export declare const CANOPYCMS_VERSION = "0.1.0";
|
package/dist/version.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated by scripts/bump-version.mjs, its only writer. The package builds with plain
|
|
2
2
|
// tsc (rootDir: src), so src/ cannot import ../package.json; version.test.ts keeps the two equal.
|
|
3
|
-
export const CANOPYCMS_VERSION = '0.1.0
|
|
3
|
+
export const CANOPYCMS_VERSION = '0.1.0';
|
|
@@ -251,7 +251,7 @@ export declare class CmsWorker {
|
|
|
251
251
|
* and GitHub calls are killed and the task is released to pending with no
|
|
252
252
|
* retry spent; a sync's bundle, unbundle, ref update and settings push
|
|
253
253
|
* are killed. The gateway's GitHub fetches and mirror maintenance run on
|
|
254
|
-
* (
|
|
254
|
+
* (safeinsights/canopycms#907). A branch rebase
|
|
255
255
|
* is never aborted, because a rebase killed mid-branch is recovered
|
|
256
256
|
* lossily; anything still unsettled after a short grace is abandoned to
|
|
257
257
|
* the process exit.
|
|
@@ -46,7 +46,7 @@ export { GatewayInstance } from './gateway-instance.js';
|
|
|
46
46
|
// are not GitHub tokens (the auth provider's key, the App's private key). `redactEgress` scrubs
|
|
47
47
|
// the cause a failed boot hands the worker.
|
|
48
48
|
export { gatewayLog, gatewayLogWarn, gatewayLogError, registerSecretText, registerPrivateKey, redactEgress, } from './gateway-egress.js';
|
|
49
|
-
// The boot self-checks the split entrypoint runs in each role (.claude/future-tasks/
|
|
49
|
+
// The boot self-checks the split entrypoint runs in each role (.claude/future-tasks/resolved/
|
|
50
50
|
// worker-shared-repo-git-process-split.md §5).
|
|
51
51
|
export { assertSelfChecks, gatewaySelfChecks, workerSelfChecks, } from './self-checks.js';
|
|
52
52
|
/** Per workspace, so two workers on one host (tests, dev) never share a mirror. */
|
|
@@ -497,7 +497,7 @@ export class CmsWorker {
|
|
|
497
497
|
* and GitHub calls are killed and the task is released to pending with no
|
|
498
498
|
* retry spent; a sync's bundle, unbundle, ref update and settings push
|
|
499
499
|
* are killed. The gateway's GitHub fetches and mirror maintenance run on
|
|
500
|
-
* (
|
|
500
|
+
* (safeinsights/canopycms#907). A branch rebase
|
|
501
501
|
* is never aborted, because a rebase killed mid-branch is recovered
|
|
502
502
|
* lossily; anything still unsettled after a short grace is abandoned to
|
|
503
503
|
* the process exit.
|
|
@@ -58,7 +58,7 @@ export interface GitHubAuthConfig {
|
|
|
58
58
|
* 59 minutes and mints a new one after that, so `refreshCredential()` below
|
|
59
59
|
* is a no-op on that path. It does NOT recover a rotated private key or an
|
|
60
60
|
* early-revoked token — see
|
|
61
|
-
*
|
|
61
|
+
* safeinsights/canopycms#894.
|
|
62
62
|
*/
|
|
63
63
|
refreshGitHubToken?: () => Promise<string | undefined>;
|
|
64
64
|
/**
|
|
@@ -74,7 +74,7 @@ export function resolveWorkerGitHubAuth(config) {
|
|
|
74
74
|
// key does not expire, and `@octokit/auth-app`'s own cache mints a new
|
|
75
75
|
// installation token when the old one expires. A rotated key or an
|
|
76
76
|
// early-revoked token is NOT recovered here — see
|
|
77
|
-
//
|
|
77
|
+
// safeinsights/canopycms#894.
|
|
78
78
|
refreshCredential: async () => { },
|
|
79
79
|
};
|
|
80
80
|
}
|
|
@@ -570,7 +570,8 @@ export declare function refusedRequestError(message: string, status: number | un
|
|
|
570
570
|
* by the exact token forms and `redactCredentials`'s patterns (worker/gateway-egress.ts): a
|
|
571
571
|
* credentialed operation already did (`LocalGitHubGateway.credentialed`), and this is the last
|
|
572
572
|
* place anything could slip past it. No stack, no
|
|
573
|
-
* `cause` and no Octokit `request` crosses.
|
|
573
|
+
* `cause` and no Octokit `request` crosses. A thrown value that is not an Error crosses as a fixed
|
|
574
|
+
* message and is logged here instead: its string form can be anything, a stack included.
|
|
574
575
|
*/
|
|
575
576
|
export declare function encodeError(err: unknown): ErrorEnvelope;
|
|
576
577
|
/**
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { GatewayRefusalError, MAX_PROTECTED_BRANCHES } from './branch-policy.js';
|
|
3
3
|
import { GitHubPushError, MAX_FETCH_HAVE } from './github-gateway.js';
|
|
4
4
|
import { GitHubTransientError, RefusedPushError } from './github-mirror.js';
|
|
5
|
-
import { redactEgress } from './gateway-egress.js';
|
|
5
|
+
import { gatewayLogWarn, redactEgress } from './gateway-egress.js';
|
|
6
6
|
/**
|
|
7
7
|
* The GitHub gateway's wire format (worker/github-gateway-http.ts): what each operation sends and
|
|
8
8
|
* answers over the socket, and how a thrown error crosses it. Both ends validate everything they
|
|
@@ -350,12 +350,15 @@ export function refusedRequestError(message, status) {
|
|
|
350
350
|
? new GatewayRequestRefusedError(message, status)
|
|
351
351
|
: new GitHubGatewayProtocolError(message);
|
|
352
352
|
}
|
|
353
|
+
/** The message a thrown value that is not an Error crosses with. */
|
|
354
|
+
const NOT_AN_ERROR_MESSAGE = 'The GitHub gateway failed with a value that is not an Error';
|
|
353
355
|
/**
|
|
354
356
|
* The envelope for an error leaving the gateway. Every string in it is scrubbed as an error is,
|
|
355
357
|
* by the exact token forms and `redactCredentials`'s patterns (worker/gateway-egress.ts): a
|
|
356
358
|
* credentialed operation already did (`LocalGitHubGateway.credentialed`), and this is the last
|
|
357
359
|
* place anything could slip past it. No stack, no
|
|
358
|
-
* `cause` and no Octokit `request` crosses.
|
|
360
|
+
* `cause` and no Octokit `request` crosses. A thrown value that is not an Error crosses as a fixed
|
|
361
|
+
* message and is logged here instead: its string form can be anything, a stack included.
|
|
359
362
|
*/
|
|
360
363
|
export function encodeError(err) {
|
|
361
364
|
const text = (value) => redactEgress(value).slice(0, MAX_MESSAGE_LENGTH);
|
|
@@ -376,7 +379,8 @@ export function encodeError(err) {
|
|
|
376
379
|
return { kind: 'transient', code: err.code, message: text(err.message) };
|
|
377
380
|
}
|
|
378
381
|
if (!(err instanceof Error)) {
|
|
379
|
-
|
|
382
|
+
gatewayLogWarn('Answered a thrown value that is not an Error:', text(String(err)));
|
|
383
|
+
return { kind: 'error', name: 'Error', message: NOT_AN_ERROR_MESSAGE };
|
|
380
384
|
}
|
|
381
385
|
const status = err.status;
|
|
382
386
|
if (typeof status === 'number' && Number.isInteger(status)) {
|
|
@@ -549,7 +549,7 @@ class LocalGitHubGateway {
|
|
|
549
549
|
* branch first. The mirror kills its git when `signal` aborts, and GitHub moves the ref only
|
|
550
550
|
* after receiving the whole pack, so a killed push changes nothing or is found already done by
|
|
551
551
|
* the re-run, unless git's transport child outlives it
|
|
552
|
-
* (
|
|
552
|
+
* (safeinsights/canopycms#907).
|
|
553
553
|
*/
|
|
554
554
|
async pushInSession(mirror, credential, request, received, signal) {
|
|
555
555
|
const { branch, sha } = request;
|
|
@@ -31,7 +31,7 @@ import { BranchPolicy, GatewayRefusalError } from './branch-policy.js';
|
|
|
31
31
|
*
|
|
32
32
|
* Every transfer runs with `--progress`: the timeout is inactivity, and a quiet fetch of a whole
|
|
33
33
|
* repository would otherwise be killed for its size. The connectivity walk after a fetch still
|
|
34
|
-
* prints nothing (
|
|
34
|
+
* prints nothing (safeinsights/canopycms#901).
|
|
35
35
|
*/
|
|
36
36
|
export declare class GitHubMirror {
|
|
37
37
|
private readonly stateDirectory;
|
|
@@ -93,7 +93,7 @@ export declare class GitHubMirror {
|
|
|
93
93
|
* Clear what a killed gateway left, before any git runs: finish or undo an interrupted rebuild,
|
|
94
94
|
* then every lock and temporary in the mirror. Nothing in this process writes it yet:
|
|
95
95
|
* maintenance starts only after a session. A git a stopped dev worker left running is not
|
|
96
|
-
* covered (
|
|
96
|
+
* covered (safeinsights/canopycms#907).
|
|
97
97
|
*/
|
|
98
98
|
private sweepAtStart;
|
|
99
99
|
/**
|
|
@@ -39,7 +39,7 @@ import { mirrorGitOptions, mirrorPins, quarantinePins } from './shared-repo-git.
|
|
|
39
39
|
*
|
|
40
40
|
* Every transfer runs with `--progress`: the timeout is inactivity, and a quiet fetch of a whole
|
|
41
41
|
* repository would otherwise be killed for its size. The connectivity walk after a fetch still
|
|
42
|
-
* prints nothing (
|
|
42
|
+
* prints nothing (safeinsights/canopycms#901).
|
|
43
43
|
*/
|
|
44
44
|
export class GitHubMirror {
|
|
45
45
|
/**
|
|
@@ -137,7 +137,7 @@ export class GitHubMirror {
|
|
|
137
137
|
* Clear what a killed gateway left, before any git runs: finish or undo an interrupted rebuild,
|
|
138
138
|
* then every lock and temporary in the mirror. Nothing in this process writes it yet:
|
|
139
139
|
* maintenance starts only after a session. A git a stopped dev worker left running is not
|
|
140
|
-
* covered (
|
|
140
|
+
* covered (safeinsights/canopycms#907).
|
|
141
141
|
*/
|
|
142
142
|
async sweepAtStart() {
|
|
143
143
|
await fs.mkdir(this.stateDirectory, { recursive: true, mode: 0o700 });
|
|
@@ -54,7 +54,7 @@ export declare function markHistoryRewritten(ctx: Pick<HistoryRewriteContext, 'c
|
|
|
54
54
|
* it on GitHub" diagnosis that is false -- we did.
|
|
55
55
|
*
|
|
56
56
|
* Tracked, with the concurrent-clear race that can drop a marker mid-arming,
|
|
57
|
-
* in
|
|
57
|
+
* in safeinsights/canopycms#902.
|
|
58
58
|
*/
|
|
59
59
|
export declare function clearHistoryRewrittenMarker(ctx: Pick<HistoryRewriteContext, 'contentBranchesPath'>, branchPath: string, branchDir: string): Promise<void>;
|
|
60
60
|
/**
|
|
@@ -62,7 +62,7 @@ export async function markHistoryRewritten(ctx, branchPath, branchDir, expectedS
|
|
|
62
62
|
* it on GitHub" diagnosis that is false -- we did.
|
|
63
63
|
*
|
|
64
64
|
* Tracked, with the concurrent-clear race that can drop a marker mid-arming,
|
|
65
|
-
* in
|
|
65
|
+
* in safeinsights/canopycms#902.
|
|
66
66
|
*/
|
|
67
67
|
export async function clearHistoryRewrittenMarker(ctx, branchPath, branchDir) {
|
|
68
68
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The boot self-checks of the worker process split (.claude/future-tasks/
|
|
2
|
+
* The boot self-checks of the worker process split (.claude/future-tasks/resolved/
|
|
3
3
|
* worker-shared-repo-git-process-split.md §5): each proves a sandbox layer the split rests on took
|
|
4
4
|
* effect, because systemd applies some directives silently or not at all (`IPAddressDeny` without
|
|
5
5
|
* cgroup-BPF). Every check fails closed: what it cannot read counts against it. They only report;
|
|
@@ -19,7 +19,7 @@ import { getErrorMessage } from '../utils/error.js';
|
|
|
19
19
|
* merge, checkout) through a driver it plants in between, as does one that edits a rebase the
|
|
20
20
|
* worker has stopped at a conflict (an `exec` line in `git-rebase-todo`, the `strategy` file). Only
|
|
21
21
|
* moving that git into a process without the credential closes those:
|
|
22
|
-
* .claude/future-tasks/worker-shared-repo-git-process-split.md.
|
|
22
|
+
* .claude/future-tasks/resolved/worker-shared-repo-git-process-split.md.
|
|
23
23
|
*/
|
|
24
24
|
/**
|
|
25
25
|
* Every hook event in githooks(5) at git 2.55 except git-p4's, which only git-p4 fires and the worker
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "@codemirror/language, @lezer/highlight: workaround — @mdxeditor/editor uses cm6-theme-basic-light which peer-requires these but mdxeditor doesn't declare them as dependencies",
|
|
3
3
|
"name": "canopycms",
|
|
4
|
-
"version": "0.1.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"description": "CanopyCMS core package: schema-driven content, branch-aware editing, and editor UI for Next.js.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -144,7 +144,8 @@
|
|
|
144
144
|
"picocolors": "^1.1.1",
|
|
145
145
|
"proper-lockfile": "^4.1.2",
|
|
146
146
|
"react-easy-crop": "^6.2.2",
|
|
147
|
-
"react-
|
|
147
|
+
"react-remove-scroll": "^2.7.2",
|
|
148
|
+
"react-resizable-panels": "4.14.1",
|
|
148
149
|
"sanitize-html": "^2.17.5",
|
|
149
150
|
"sharp": "^0.35.3",
|
|
150
151
|
"short-uuid": "^6.0.3",
|
|
@@ -158,8 +159,8 @@
|
|
|
158
159
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
159
160
|
},
|
|
160
161
|
"devDependencies": {
|
|
161
|
-
"@smithy/util-stream": "^3.0.0 || ^4.0.0",
|
|
162
162
|
"@playwright/test": "^1.57.0",
|
|
163
|
+
"@smithy/util-stream": "^3.0.0 || ^4.0.0",
|
|
163
164
|
"@storybook/addon-essentials": "^8.1.11",
|
|
164
165
|
"@storybook/react": "^8.1.11",
|
|
165
166
|
"@storybook/react-vite": "^8.1.11",
|
|
@@ -172,9 +173,9 @@
|
|
|
172
173
|
"@types/react": "^18.3.12",
|
|
173
174
|
"@types/react-dom": "^18.3.1",
|
|
174
175
|
"@types/sanitize-html": "^2.16.1",
|
|
175
|
-
"aws-sdk-client-mock": "^4.0.0",
|
|
176
176
|
"@vitejs/plugin-react": "^5.1.0",
|
|
177
177
|
"autoprefixer": "^10.4.20",
|
|
178
|
+
"aws-sdk-client-mock": "^4.0.0",
|
|
178
179
|
"esbuild": "^0.25.0",
|
|
179
180
|
"jsdom": "^24.1.3",
|
|
180
181
|
"postcss": "^8.5.10",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
export type PaneLayout = 'side' | 'stacked';
|
|
3
|
-
export declare const SPLIT_PERCENT_MIN = 15;
|
|
4
|
-
export declare const SPLIT_PERCENT_MAX = 85;
|
|
5
|
-
export declare const DEFAULT_SIDE_SPLIT_PERCENT = 52;
|
|
6
|
-
export declare const DEFAULT_STACKED_SPLIT_PERCENT = 58;
|
|
7
|
-
export interface EditorPanesProps {
|
|
8
|
-
layout?: PaneLayout;
|
|
9
|
-
onLayoutChange?: (layout: PaneLayout) => void;
|
|
10
|
-
/** Primary-pane size (percent) in the side layout; follows this prop when it changes. */
|
|
11
|
-
sideSplitPercent?: number;
|
|
12
|
-
/** Primary-pane size (percent) in the stacked layout; follows this prop when it changes. */
|
|
13
|
-
stackedSplitPercent?: number;
|
|
14
|
-
/** Called once per drag, when it ends, with the clamped percent; live dragging never calls it. */
|
|
15
|
-
onSplitPercentChange?: (layout: PaneLayout, percent: number) => void;
|
|
16
|
-
preview?: React.ReactNode;
|
|
17
|
-
form?: React.ReactNode;
|
|
18
|
-
}
|
|
19
|
-
export declare const EditorPanes: React.FC<EditorPanesProps>;
|
|
20
|
-
export default EditorPanes;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useMemo, useRef, useState, useEffect } from 'react';
|
|
4
|
-
import { Box, Paper, rem } from '@mantine/core';
|
|
5
|
-
import SplitPane from 'react-split-pane';
|
|
6
|
-
export const SPLIT_PERCENT_MIN = 15;
|
|
7
|
-
export const SPLIT_PERCENT_MAX = 85;
|
|
8
|
-
export const DEFAULT_SIDE_SPLIT_PERCENT = 52;
|
|
9
|
-
export const DEFAULT_STACKED_SPLIT_PERCENT = 58;
|
|
10
|
-
export const EditorPanes = ({ layout: layoutProp = 'side', onLayoutChange: _, sideSplitPercent = DEFAULT_SIDE_SPLIT_PERCENT, stackedSplitPercent = DEFAULT_STACKED_SPLIT_PERCENT, onSplitPercentChange, preview, form, }) => {
|
|
11
|
-
const TypedSplitPane = SplitPane;
|
|
12
|
-
const splitContainerRef = useRef(null);
|
|
13
|
-
const [layout, setLayout] = useState(layoutProp);
|
|
14
|
-
// Live drag state: the props change only when a drag ends or stored prefs load, so dragging
|
|
15
|
-
// never writes storage.
|
|
16
|
-
const [sidePrimarySize, setSidePrimarySize] = useState(sideSplitPercent);
|
|
17
|
-
const [stackedPrimarySize, setStackedPrimarySize] = useState(stackedSplitPercent);
|
|
18
|
-
// Turn off iframe/pane pointer events while dragging so the gutter keeps receiving mouse events.
|
|
19
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
20
|
-
// The last percent a drag produced. `onDragFinished` cannot supply it: with `size` controlled,
|
|
21
|
-
// react-split-pane hands back that prop's string ("37%"), a click with no movement included.
|
|
22
|
-
const dragPercentRef = useRef(undefined);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
setLayout(layoutProp);
|
|
25
|
-
}, [layoutProp]);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
setSidePrimarySize(sideSplitPercent);
|
|
28
|
-
}, [sideSplitPercent]);
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
setStackedPrimarySize(stackedSplitPercent);
|
|
31
|
-
}, [stackedSplitPercent]);
|
|
32
|
-
const direction = layout === 'side' ? 'vertical' : 'horizontal';
|
|
33
|
-
const primarySize = useMemo(() => (layout === 'side' ? sidePrimarySize : stackedPrimarySize), [layout, sidePrimarySize, stackedPrimarySize]);
|
|
34
|
-
const resizerStyle = useMemo(() => ({
|
|
35
|
-
background: layout === 'side'
|
|
36
|
-
? 'linear-gradient(90deg, var(--canopy-surface-subtle), var(--canopy-surface-muted))'
|
|
37
|
-
: 'linear-gradient(180deg, var(--canopy-surface-subtle), var(--canopy-surface-muted))',
|
|
38
|
-
boxShadow: isDragging
|
|
39
|
-
? 'inset 0 0 0 1px var(--mantine-color-brand-4), 0 0 0 1px var(--mantine-color-brand-1)'
|
|
40
|
-
: 'inset 0 0 0 1px var(--mantine-color-default-border)',
|
|
41
|
-
cursor: layout === 'side' ? 'col-resize' : 'row-resize',
|
|
42
|
-
width: layout === 'side' ? rem(12) : '100%',
|
|
43
|
-
height: layout === 'side' ? '100%' : rem(12),
|
|
44
|
-
margin: layout === 'side' ? `0 -${rem(2)}` : `-${rem(2)} 0`,
|
|
45
|
-
borderRadius: layout === 'side' ? rem(0) : rem(0),
|
|
46
|
-
transition: 'box-shadow 120ms ease, background 120ms ease',
|
|
47
|
-
flexShrink: 0,
|
|
48
|
-
}), [isDragging, layout]);
|
|
49
|
-
const updateSizeFromPixels = (nextPixels, nextLayout) => {
|
|
50
|
-
const total = nextLayout === 'side'
|
|
51
|
-
? (splitContainerRef.current?.clientWidth ?? 0)
|
|
52
|
-
: (splitContainerRef.current?.clientHeight ?? 0);
|
|
53
|
-
// Guard: ensure valid dimensions before calculating
|
|
54
|
-
if (!total || total <= 0 || !Number.isFinite(nextPixels) || nextPixels <= 0)
|
|
55
|
-
return undefined;
|
|
56
|
-
const percent = Math.min(SPLIT_PERCENT_MAX, Math.max(SPLIT_PERCENT_MIN, (nextPixels / total) * 100));
|
|
57
|
-
if (nextLayout === 'side') {
|
|
58
|
-
setSidePrimarySize(percent);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
setStackedPrimarySize(percent);
|
|
62
|
-
}
|
|
63
|
-
return percent;
|
|
64
|
-
};
|
|
65
|
-
return (_jsx(Box, { h: "100%", style: { minHeight: '70vh' }, children: _jsx(Paper, { radius: 0, shadow: "xs", withBorder: true, style: {
|
|
66
|
-
display: 'flex',
|
|
67
|
-
height: '100%',
|
|
68
|
-
minHeight: 0,
|
|
69
|
-
overflow: 'hidden',
|
|
70
|
-
flexDirection: layout === 'side' ? 'row' : 'column',
|
|
71
|
-
width: '100%',
|
|
72
|
-
}, children: _jsx(Box, { ref: splitContainerRef, style: { display: 'flex', flex: 1, minHeight: 0, width: '100%' }, children: _jsxs(TypedSplitPane, { split: direction, minSize: 120, size: `${primarySize}%`, allowResize: true, onChange: (next) => {
|
|
73
|
-
dragPercentRef.current = updateSizeFromPixels(next, layout) ?? dragPercentRef.current;
|
|
74
|
-
}, onDragStarted: () => {
|
|
75
|
-
dragPercentRef.current = undefined;
|
|
76
|
-
setIsDragging(true);
|
|
77
|
-
}, onDragFinished: () => {
|
|
78
|
-
const percent = dragPercentRef.current;
|
|
79
|
-
dragPercentRef.current = undefined;
|
|
80
|
-
if (percent !== undefined)
|
|
81
|
-
onSplitPercentChange?.(layout, percent);
|
|
82
|
-
setIsDragging(false);
|
|
83
|
-
}, style: {
|
|
84
|
-
position: 'relative',
|
|
85
|
-
minHeight: 0,
|
|
86
|
-
width: '100%',
|
|
87
|
-
height: '100%',
|
|
88
|
-
userSelect: isDragging ? 'none' : undefined,
|
|
89
|
-
}, paneStyle: {
|
|
90
|
-
minWidth: 0,
|
|
91
|
-
minHeight: 0,
|
|
92
|
-
display: 'flex',
|
|
93
|
-
overflow: 'auto',
|
|
94
|
-
}, resizerStyle: resizerStyle, children: [_jsx(Box, { "data-testid": "preview-pane", style: {
|
|
95
|
-
minWidth: 0,
|
|
96
|
-
minHeight: 0,
|
|
97
|
-
overflow: 'auto',
|
|
98
|
-
flex: 1,
|
|
99
|
-
pointerEvents: isDragging ? 'none' : undefined,
|
|
100
|
-
}, children: _jsx(Box, { h: "100%", w: "100%", style: {
|
|
101
|
-
minHeight: '100%',
|
|
102
|
-
minWidth: 0,
|
|
103
|
-
overflow: 'auto',
|
|
104
|
-
flex: 1,
|
|
105
|
-
pointerEvents: isDragging ? 'none' : undefined,
|
|
106
|
-
}, children: preview ?? 'Preview' }) }), _jsx(Box, { "data-testid": "form-pane", style: {
|
|
107
|
-
minWidth: 0,
|
|
108
|
-
minHeight: 0,
|
|
109
|
-
overflow: 'auto',
|
|
110
|
-
flex: 1,
|
|
111
|
-
pointerEvents: isDragging ? 'none' : undefined,
|
|
112
|
-
}, children: _jsx(Box, { h: "100%", w: "100%", style: {
|
|
113
|
-
// FormHeader reads this to span the padding.
|
|
114
|
-
'--canopy-form-pane-padding': '20px',
|
|
115
|
-
padding: 'var(--canopy-form-pane-padding)',
|
|
116
|
-
minHeight: '100%',
|
|
117
|
-
minWidth: 0,
|
|
118
|
-
overflow: 'auto',
|
|
119
|
-
flex: 1,
|
|
120
|
-
pointerEvents: isDragging ? 'none' : undefined,
|
|
121
|
-
backgroundColor: 'var(--canopy-surface-muted)',
|
|
122
|
-
}, children: form ?? 'Form' }) })] }) }) }) }));
|
|
123
|
-
};
|
|
124
|
-
export default EditorPanes;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface EditorFooterProps {
|
|
2
|
-
/**
|
|
3
|
-
* Optional custom footer content.
|
|
4
|
-
*/
|
|
5
|
-
children?: React.ReactNode;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Footer component for the Editor.
|
|
9
|
-
* Displays static links (Terms, Privacy) and copyright notice.
|
|
10
|
-
*/
|
|
11
|
-
export declare function EditorFooter({ children }: EditorFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Group, Paper, Text } from '@mantine/core';
|
|
3
|
-
/**
|
|
4
|
-
* Footer component for the Editor.
|
|
5
|
-
* Displays static links (Terms, Privacy) and copyright notice.
|
|
6
|
-
*/
|
|
7
|
-
export function EditorFooter({ children }) {
|
|
8
|
-
return (_jsx(Paper, { withBorder: true, radius: 0, shadow: "sm", px: "md", py: "xs", style: { position: 'fixed', left: 0, right: 0, bottom: 0, zIndex: 40 }, children: children ?? (_jsxs(Group, { gap: "md", justify: "center", children: [_jsx(Text, { size: "xs", c: "dimmed", children: "Terms" }), _jsx(Text, { size: "xs", c: "dimmed", children: "Privacy" }), _jsx(Text, { size: "xs", c: "dimmed", children: "\u00A9 CanopyCMS" })] })) }));
|
|
9
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Ref } from 'react';
|
|
2
|
-
import type { PaneLayout } from '../EditorPanes.js';
|
|
3
|
-
import type { InexactMark } from '../preview-marks.js';
|
|
4
|
-
export interface EditorSidebarProps {
|
|
5
|
-
/**
|
|
6
|
-
* Current layout mode (side or stacked).
|
|
7
|
-
*/
|
|
8
|
-
layout: PaneLayout;
|
|
9
|
-
/**
|
|
10
|
-
* Whether field highlighting is enabled.
|
|
11
|
-
*/
|
|
12
|
-
highlightEnabled: boolean;
|
|
13
|
-
/** The preview reported marking no elements; unknown (an older bridge) is false. */
|
|
14
|
-
previewMarksNothing?: boolean;
|
|
15
|
-
/** The preview's marks that name no field of the entry; see `findInexactMarks`. */
|
|
16
|
-
previewInexactMarks?: readonly InexactMark[];
|
|
17
|
-
/**
|
|
18
|
-
* Width of the sidebar in pixels.
|
|
19
|
-
*/
|
|
20
|
-
sidebarWidth: number;
|
|
21
|
-
/**
|
|
22
|
-
* Height of the header in pixels (for positioning).
|
|
23
|
-
*/
|
|
24
|
-
headerHeight: number;
|
|
25
|
-
/**
|
|
26
|
-
* Height of the footer in pixels (for positioning).
|
|
27
|
-
*/
|
|
28
|
-
footerHeight: number;
|
|
29
|
-
/**
|
|
30
|
-
* Callback when layout changes.
|
|
31
|
-
*/
|
|
32
|
-
onLayoutChange: (layout: PaneLayout) => void;
|
|
33
|
-
/**
|
|
34
|
-
* Callback when highlight toggle is clicked.
|
|
35
|
-
*/
|
|
36
|
-
onHighlightToggle: () => void;
|
|
37
|
-
/**
|
|
38
|
-
* Opens the permission manager. Admin-only like `onSystemHealthOpen`: the menu item renders
|
|
39
|
-
* only when this is provided.
|
|
40
|
-
*/
|
|
41
|
-
onPermissionManagerOpen?: () => void;
|
|
42
|
-
/** Opens the group manager. Admin-only; the menu item renders only when this is provided. */
|
|
43
|
-
onGroupManagerOpen?: () => void;
|
|
44
|
-
/**
|
|
45
|
-
* Callback when the media library should be opened.
|
|
46
|
-
*/
|
|
47
|
-
onMediaLibraryOpen: () => void;
|
|
48
|
-
/**
|
|
49
|
-
* Callback when the System Health panel should be opened. Admin-only:
|
|
50
|
-
* the "System health" menu item only renders when this is provided --
|
|
51
|
-
* the caller (Editor.tsx) is responsible for the isAdmin() gate.
|
|
52
|
-
*/
|
|
53
|
-
onSystemHealthOpen?: () => void;
|
|
54
|
-
/** Ref to the Settings button, which takes focus back when a panel opened from its menu closes. */
|
|
55
|
-
settingsToggleRef?: Ref<HTMLButtonElement>;
|
|
56
|
-
/**
|
|
57
|
-
* Custom account component to render (e.g., Clerk's UserButton).
|
|
58
|
-
* If provided, replaces the default account button.
|
|
59
|
-
*/
|
|
60
|
-
AccountComponent?: React.ComponentType;
|
|
61
|
-
/**
|
|
62
|
-
* Callback when account button is clicked (if no AccountComponent provided).
|
|
63
|
-
*/
|
|
64
|
-
onAccountClick?: () => void;
|
|
65
|
-
/**
|
|
66
|
-
* Callback when logout button is clicked.
|
|
67
|
-
*/
|
|
68
|
-
onLogoutClick?: () => void;
|
|
69
|
-
}
|
|
70
|
-
export declare function EditorSidebar({ layout, highlightEnabled, previewMarksNothing, previewInexactMarks, sidebarWidth, headerHeight, footerHeight, onLayoutChange, onHighlightToggle, onPermissionManagerOpen, onGroupManagerOpen, onMediaLibraryOpen, onSystemHealthOpen, settingsToggleRef, AccountComponent, onAccountClick, onLogoutClick, }: EditorSidebarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useRef, useState } from 'react';
|
|
3
|
-
import { ActionIcon, Indicator, Menu, Paper, Stack, Tooltip } from '@mantine/core';
|
|
4
|
-
import { useMergedRef } from '@mantine/hooks';
|
|
5
|
-
import { IconUserCircle, IconLogout, IconPhoto, IconSettings, IconLayoutColumns, IconLayoutRows, IconMarquee, IconActivity, } from '@tabler/icons-react';
|
|
6
|
-
import { useMenuItemFocusReturn } from '../utils/focus-return.js';
|
|
7
|
-
/**
|
|
8
|
-
* Sidebar component for the Editor.
|
|
9
|
-
* Contains layout toggle, highlight toggle, settings menu, account button, and logout button.
|
|
10
|
-
*/
|
|
11
|
-
const inexactMarksNote = (marks) => {
|
|
12
|
-
const shown = marks.slice(0, 3).map((mark) => mark.path || '""');
|
|
13
|
-
const more = marks.length > shown.length ? `, and ${marks.length - shown.length} more` : '';
|
|
14
|
-
const noun = marks.length === 1 ? "mark doesn't" : "marks don't";
|
|
15
|
-
return `${marks.length} preview ${noun} match a field: ${shown.join(', ')}${more}. The browser console names the nearest field of each.`;
|
|
16
|
-
};
|
|
17
|
-
export function EditorSidebar({ layout, highlightEnabled, previewMarksNothing = false, previewInexactMarks = [], sidebarWidth, headerHeight, footerHeight, onLayoutChange, onHighlightToggle, onPermissionManagerOpen, onGroupManagerOpen, onMediaLibraryOpen, onSystemHealthOpen, settingsToggleRef, AccountComponent, onAccountClick, onLogoutClick, }) {
|
|
18
|
-
const [settingsOpened, setSettingsOpened] = useState(false);
|
|
19
|
-
const settingsToggle = useRef(null);
|
|
20
|
-
const settingsMergedRef = useMergedRef(settingsToggle, settingsToggleRef);
|
|
21
|
-
const settingsMenuFocus = useMenuItemFocusReturn(settingsToggle);
|
|
22
|
-
return (_jsxs(Paper, { withBorder: true, shadow: "sm", radius: 0, style: {
|
|
23
|
-
position: 'fixed',
|
|
24
|
-
top: headerHeight,
|
|
25
|
-
bottom: footerHeight,
|
|
26
|
-
right: 0,
|
|
27
|
-
width: sidebarWidth,
|
|
28
|
-
display: 'flex',
|
|
29
|
-
flexDirection: 'column',
|
|
30
|
-
justifyContent: 'space-between',
|
|
31
|
-
alignItems: 'center',
|
|
32
|
-
padding: '10px 6px',
|
|
33
|
-
gap: 8,
|
|
34
|
-
}, children: [_jsxs(Stack, { gap: "sm", align: "center", style: { width: '100%', paddingTop: 6 }, children: [_jsx(Tooltip, { label: "Toggle layout", position: "left", children: _jsx(ActionIcon, { variant: "subtle", size: "lg", radius: "md", "aria-label": "Toggle layout", onClick: () => onLayoutChange(layout === 'side' ? 'stacked' : 'side'), children: layout === 'side' ? _jsx(IconLayoutRows, { size: 18 }) : _jsx(IconLayoutColumns, { size: 18 }) }) }), _jsx(Tooltip, { label: previewMarksNothing
|
|
35
|
-
? "This preview page marks no editable elements, so nothing can be highlighted or clicked to focus. The site's developer can mark them with fieldProps."
|
|
36
|
-
: inexactMarksNote(previewInexactMarks), opened: !highlightEnabled
|
|
37
|
-
? false
|
|
38
|
-
: previewMarksNothing
|
|
39
|
-
? true
|
|
40
|
-
: previewInexactMarks.length > 0
|
|
41
|
-
? undefined
|
|
42
|
-
: false, position: "left", multiline: true, w: 240, withArrow: true, children: _jsx(Indicator, { label: previewInexactMarks.length, size: 16, withBorder: true, disabled: !highlightEnabled || previewInexactMarks.length === 0, styles: {
|
|
43
|
-
indicator: {
|
|
44
|
-
'--indicator-color': 'var(--canopy-count-bg-orange)',
|
|
45
|
-
'--indicator-text-color': 'var(--mantine-color-text)',
|
|
46
|
-
},
|
|
47
|
-
}, children: _jsx(ActionIcon, { variant: highlightEnabled ? 'filled' : 'subtle', color: highlightEnabled ? 'brand' : 'gray', size: "lg", radius: "md", "aria-pressed": highlightEnabled, "aria-label": "Toggle highlights", "data-testid": "toggle-highlights-button", "aria-description": highlightEnabled && previewInexactMarks.length > 0
|
|
48
|
-
? inexactMarksNote(previewInexactMarks)
|
|
49
|
-
: undefined, onClick: onHighlightToggle, children: _jsx(IconMarquee, { size: 18 }) }) }) })] }), _jsxs(Stack, { gap: "xs", align: "center", children: [_jsxs(Menu, { shadow: "md", width: 200, position: "left", opened: settingsOpened, onChange: setSettingsOpened, ...settingsMenuFocus.menu, children: [_jsx(Tooltip, { label: "Settings", position: "left", disabled: settingsOpened, children: _jsx(Menu.Target, { children: _jsx(ActionIcon, { ref: settingsMergedRef, variant: "subtle", size: "lg", radius: "md", "aria-label": "Settings", "data-testid": "settings-button", children: _jsx(IconSettings, { size: 18 }) }) }) }), _jsxs(Menu.Dropdown, { "data-testid": "settings-menu", ...settingsMenuFocus.dropdown, children: [_jsx(Menu.Label, { children: "Settings" }), onPermissionManagerOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-permissions", onClick: onPermissionManagerOpen, children: "Manage permissions" })), onGroupManagerOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-groups", onClick: onGroupManagerOpen, children: "Manage groups" })), _jsx(Menu.Item, { "data-testid": "settings-menu-media-library", leftSection: _jsx(IconPhoto, { size: 14 }), onClick: onMediaLibraryOpen, children: "Media library" }), onSystemHealthOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-system-health", leftSection: _jsx(IconActivity, { size: 14 }), onClick: onSystemHealthOpen, children: "System health" }))] })] }), AccountComponent ? (_jsx(AccountComponent, {})) : (_jsxs(_Fragment, { children: [onAccountClick && (_jsx(ActionIcon, { variant: "subtle", size: "lg", radius: "md", "aria-label": "Account", onClick: onAccountClick, children: _jsx(IconUserCircle, { size: 18 }) })), onLogoutClick && (_jsx(ActionIcon, { variant: "subtle", size: "lg", radius: "md", "aria-label": "Sign out", onClick: onLogoutClick, children: _jsx(IconLogout, { size: 18 }) }))] }))] })] }));
|
|
50
|
-
}
|