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.
Files changed (145) hide show
  1. package/CHANGELOG.md +267 -231
  2. package/README.md +0 -1
  3. package/dist/ai/references.js +3 -1
  4. package/dist/api/assets.js +2 -2
  5. package/dist/api/branch-merge.d.ts +1 -1
  6. package/dist/api/branch.js +1 -1
  7. package/dist/api/content.js +1 -1
  8. package/dist/api/entries-constants.d.ts +1 -1
  9. package/dist/api/entries-constants.js +1 -1
  10. package/dist/api/entries.d.ts +2 -2
  11. package/dist/api/permissions.d.ts +2 -2
  12. package/dist/api/types.d.ts +3 -2
  13. package/dist/cli/cli.js +16 -14
  14. package/dist/cli/generate-ai-content.js +7 -5
  15. package/dist/cli/init.js +4 -4
  16. package/dist/client.d.ts +0 -1
  17. package/dist/client.js +0 -1
  18. package/dist/config/schemas/config.d.ts +0 -5
  19. package/dist/config/schemas/config.js +0 -1
  20. package/dist/config/types.d.ts +0 -1
  21. package/dist/content-index-generation.d.ts +1 -1
  22. package/dist/content-index-generation.js +1 -1
  23. package/dist/content-listing.js +1 -1
  24. package/dist/content-reader.js +1 -1
  25. package/dist/content-store.d.ts +1 -1
  26. package/dist/content-store.js +1 -1
  27. package/dist/dev-content-watcher.d.ts +1 -1
  28. package/dist/dev-content-watcher.js +1 -1
  29. package/dist/editor/BranchManager.d.ts +2 -0
  30. package/dist/editor/BranchManager.js +5 -4
  31. package/dist/editor/CanopyEditor.d.ts +1 -1
  32. package/dist/editor/CanopyEditor.js +1 -2
  33. package/dist/editor/CommentsPanel.js +3 -6
  34. package/dist/editor/Editor.d.ts +0 -2
  35. package/dist/editor/Editor.js +329 -181
  36. package/dist/editor/EditorAuthGate.js +8 -3
  37. package/dist/editor/EntryNavigator.d.ts +10 -0
  38. package/dist/editor/EntryNavigator.js +51 -2
  39. package/dist/editor/FormHeader.js +1 -1
  40. package/dist/editor/PreviewFrame.d.ts +5 -1
  41. package/dist/editor/PreviewFrame.js +3 -2
  42. package/dist/editor/PreviewPane.d.ts +47 -0
  43. package/dist/editor/PreviewPane.js +147 -0
  44. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  45. package/dist/editor/PreviewToolbarTools.js +73 -0
  46. package/dist/editor/comments/form-comments.js +2 -1
  47. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  48. package/dist/editor/components/BranchLockBanner.js +39 -0
  49. package/dist/editor/components/BranchPill.d.ts +32 -0
  50. package/dist/editor/components/BranchPill.js +33 -0
  51. package/dist/editor/components/ConfirmDeleteModal.d.ts +3 -1
  52. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  53. package/dist/editor/components/EditorHeader.d.ts +50 -108
  54. package/dist/editor/components/EditorHeader.js +117 -138
  55. package/dist/editor/components/EditorRail.d.ts +49 -0
  56. package/dist/editor/components/EditorRail.js +65 -0
  57. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  58. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  59. package/dist/editor/components/QuickOpenPalette.d.ts +1 -1
  60. package/dist/editor/components/QuickOpenPalette.js +3 -2
  61. package/dist/editor/components/RenameEntryModal.d.ts +3 -1
  62. package/dist/editor/components/RenameEntryModal.js +3 -2
  63. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  64. package/dist/editor/components/SaveAllResult.js +86 -0
  65. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  66. package/dist/editor/components/SaveSplitButton.js +108 -0
  67. package/dist/editor/components/index.d.ts +2 -2
  68. package/dist/editor/components/index.js +2 -2
  69. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  70. package/dist/editor/context/EditorStateContext.js +4 -4
  71. package/dist/editor/copy.d.ts +3 -0
  72. package/dist/editor/copy.js +3 -0
  73. package/dist/editor/editor-utils.d.ts +11 -6
  74. package/dist/editor/editor-utils.js +8 -17
  75. package/dist/editor/fields/MarkdownField.js +3 -0
  76. package/dist/editor/hooks/save-all.d.ts +68 -0
  77. package/dist/editor/hooks/save-all.js +61 -0
  78. package/dist/editor/hooks/useCommentSystem.d.ts +2 -0
  79. package/dist/editor/hooks/useCommentSystem.js +3 -0
  80. package/dist/editor/hooks/useDraftManager.d.ts +24 -1
  81. package/dist/editor/hooks/useDraftManager.js +297 -16
  82. package/dist/editor/hooks/useEditorLayout.d.ts +15 -11
  83. package/dist/editor/hooks/useEditorLayout.js +13 -25
  84. package/dist/editor/hooks/useEntryManager.d.ts +4 -0
  85. package/dist/editor/hooks/useEntryManager.js +30 -7
  86. package/dist/editor/media/MediaLibrary.d.ts +1 -1
  87. package/dist/editor/preview-path.d.ts +12 -0
  88. package/dist/editor/preview-path.js +45 -1
  89. package/dist/editor/preview-scale.d.ts +27 -0
  90. package/dist/editor/preview-scale.js +34 -0
  91. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  92. package/dist/editor/shell/ActivityRail.js +91 -0
  93. package/dist/editor/shell/EditorShell.d.ts +17 -0
  94. package/dist/editor/shell/EditorShell.js +31 -0
  95. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  96. package/dist/editor/shell/PaneLayout.js +116 -0
  97. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  98. package/dist/editor/shell/SidePanelHost.js +184 -0
  99. package/dist/editor/shell/TopBar.d.ts +12 -0
  100. package/dist/editor/shell/TopBar.js +14 -0
  101. package/dist/editor/shell/host-chrome.d.ts +16 -0
  102. package/dist/editor/shell/host-chrome.js +69 -0
  103. package/dist/editor/theme.js +18 -3
  104. package/dist/git-manager.d.ts +1 -1
  105. package/dist/git-manager.js +2 -2
  106. package/dist/operating-mode/types.d.ts +1 -1
  107. package/dist/resolve-canopy-user.d.ts +1 -1
  108. package/dist/resolve-canopy-user.js +1 -1
  109. package/dist/schema/resolver.d.ts +1 -1
  110. package/dist/schema/resolver.js +1 -1
  111. package/dist/schema/schema-store.d.ts +1 -1
  112. package/dist/schema/schema-store.js +2 -2
  113. package/dist/url-collision.js +3 -3
  114. package/dist/url-exclusivity-fixtures.d.ts +1 -1
  115. package/dist/url-exclusivity-fixtures.js +1 -1
  116. package/dist/utils/content-write-lock.d.ts +1 -1
  117. package/dist/utils/content-write-lock.js +1 -1
  118. package/dist/utils/debug.d.ts +1 -1
  119. package/dist/utils/debug.js +5 -3
  120. package/dist/utils/git.d.ts +4 -0
  121. package/dist/utils/git.js +27 -5
  122. package/dist/validation/block-structural-keys.d.ts +1 -1
  123. package/dist/validation/block-structural-keys.js +1 -1
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/worker/cms-worker.d.ts +1 -1
  127. package/dist/worker/cms-worker.js +2 -2
  128. package/dist/worker/github-auth.d.ts +1 -1
  129. package/dist/worker/github-auth.js +1 -1
  130. package/dist/worker/github-gateway-wire.d.ts +2 -1
  131. package/dist/worker/github-gateway-wire.js +7 -3
  132. package/dist/worker/github-gateway.js +1 -1
  133. package/dist/worker/github-mirror.d.ts +2 -2
  134. package/dist/worker/github-mirror.js +2 -2
  135. package/dist/worker/history-rewrite.d.ts +1 -1
  136. package/dist/worker/history-rewrite.js +1 -1
  137. package/dist/worker/self-checks.d.ts +1 -1
  138. package/dist/worker/shared-repo-git.js +1 -1
  139. package/package.json +5 -4
  140. package/dist/editor/EditorPanes.d.ts +0 -20
  141. package/dist/editor/EditorPanes.js +0 -124
  142. package/dist/editor/components/EditorFooter.d.ts +0 -11
  143. package/dist/editor/components/EditorFooter.js +0 -9
  144. package/dist/editor/components/EditorSidebar.d.ts +0 -70
  145. 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-int.115";
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-int.115';
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
- * (.claude/future-tasks/worker-mirror-detached-work-drain.md). A branch rebase
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
- * (.claude/future-tasks/worker-mirror-detached-work-drain.md). A branch rebase
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
- * .claude/future-tasks/worker-app-auth-cannot-recover-a-rotated-key.md.
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
- // .claude/future-tasks/worker-app-auth-cannot-recover-a-rotated-key.md.
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
- return { kind: 'error', name: 'Error', message: text(String(err)) };
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
- * (.claude/future-tasks/worker-mirror-detached-work-drain.md).
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 (.claude/future-tasks/worker-github-mirror-limits.md).
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 (.claude/future-tasks/worker-mirror-detached-work-drain.md).
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 (.claude/future-tasks/worker-github-mirror-limits.md).
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 (.claude/future-tasks/worker-mirror-detached-work-drain.md).
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 .claude/future-tasks/worker-history-rewrite-marker-races.md.
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 .claude/future-tasks/worker-history-rewrite-marker-races.md.
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-int.115",
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-split-pane": "^0.1.92",
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
- }