canopycms 0.0.68-int.98 → 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 (688) hide show
  1. package/CHANGELOG.md +2131 -0
  2. package/README.md +47 -63
  3. package/dist/ai/generate.d.ts +5 -0
  4. package/dist/ai/generate.js +244 -115
  5. package/dist/ai/index.d.ts +1 -1
  6. package/dist/ai/json-to-markdown.d.ts +21 -1
  7. package/dist/ai/json-to-markdown.js +120 -57
  8. package/dist/ai/references.d.ts +72 -0
  9. package/dist/ai/references.js +223 -0
  10. package/dist/ai/resolve-branch.js +9 -2
  11. package/dist/ai/to-plain-text.d.ts +17 -8
  12. package/dist/ai/to-plain-text.js +18 -15
  13. package/dist/ai/types.d.ts +84 -3
  14. package/dist/api/admin-branch-health.d.ts +24 -1
  15. package/dist/api/admin-branch-health.js +52 -20
  16. package/dist/api/admin.d.ts +23 -5
  17. package/dist/api/admin.js +36 -1
  18. package/dist/api/assets.js +88 -78
  19. package/dist/api/branch-create-window.d.ts +12 -0
  20. package/dist/api/branch-create-window.js +13 -0
  21. package/dist/api/branch-merge.d.ts +1 -1
  22. package/dist/api/branch-merge.js +1 -1
  23. package/dist/api/branch-review.d.ts +3 -3
  24. package/dist/api/branch-review.js +16 -7
  25. package/dist/api/branch-status.d.ts +16 -6
  26. package/dist/api/branch-status.js +57 -12
  27. package/dist/api/branch-withdraw.d.ts +2 -2
  28. package/dist/api/branch-withdraw.js +8 -3
  29. package/dist/api/branch.d.ts +25 -12
  30. package/dist/api/branch.js +164 -103
  31. package/dist/api/client.d.ts +78 -58
  32. package/dist/api/client.js +150 -117
  33. package/dist/api/comments.d.ts +12 -3
  34. package/dist/api/comments.js +118 -9
  35. package/dist/api/content.d.ts +9 -9
  36. package/dist/api/content.js +131 -22
  37. package/dist/api/entries-constants.d.ts +14 -3
  38. package/dist/api/entries-constants.js +14 -3
  39. package/dist/api/entries.d.ts +40 -2
  40. package/dist/api/entries.js +102 -6
  41. package/dist/api/github-sync.d.ts +19 -0
  42. package/dist/api/github-sync.js +50 -3
  43. package/dist/api/groups.d.ts +4 -4
  44. package/dist/api/groups.js +2 -1
  45. package/dist/api/guards.js +23 -3
  46. package/dist/api/index.d.ts +3 -1
  47. package/dist/api/index.js +1 -0
  48. package/dist/api/permissions.d.ts +21 -2
  49. package/dist/api/permissions.js +50 -3
  50. package/dist/api/request-deadline.d.ts +59 -0
  51. package/dist/api/request-deadline.js +73 -0
  52. package/dist/api/resolve-references.js +8 -7
  53. package/dist/api/route-builder.d.ts +14 -0
  54. package/dist/api/route-builder.js +14 -1
  55. package/dist/api/schema.d.ts +11 -9
  56. package/dist/api/schema.js +2 -0
  57. package/dist/api/settings-helpers.d.ts +5 -1
  58. package/dist/api/settings-helpers.js +10 -2
  59. package/dist/api/types.d.ts +18 -0
  60. package/dist/api/users-constants.d.ts +6 -0
  61. package/dist/api/users-constants.js +6 -0
  62. package/dist/assets/asset-url.d.ts +28 -16
  63. package/dist/assets/asset-url.js +51 -25
  64. package/dist/assets/finalize.js +3 -0
  65. package/dist/assets/index.d.ts +1 -1
  66. package/dist/assets/materialize.d.ts +153 -0
  67. package/dist/assets/materialize.js +402 -0
  68. package/dist/assets/store-local.d.ts +19 -5
  69. package/dist/assets/store-local.js +69 -10
  70. package/dist/assets/store-s3.d.ts +38 -7
  71. package/dist/assets/store-s3.js +128 -22
  72. package/dist/assets/transform-directives.d.ts +52 -13
  73. package/dist/assets/transform-directives.js +87 -27
  74. package/dist/assets/transform.d.ts +3 -3
  75. package/dist/assets/transform.js +13 -8
  76. package/dist/assets/types.d.ts +31 -4
  77. package/dist/auth/cache.d.ts +1 -1
  78. package/dist/auth/cache.js +1 -1
  79. package/dist/auth/caching-auth-plugin.d.ts +2 -0
  80. package/dist/auth/caching-auth-plugin.js +12 -2
  81. package/dist/auth/file-based-auth-cache.d.ts +8 -0
  82. package/dist/auth/file-based-auth-cache.js +18 -8
  83. package/dist/auth/person-name.d.ts +5 -0
  84. package/dist/auth/person-name.js +8 -0
  85. package/dist/auth/plugin.d.ts +10 -1
  86. package/dist/auth/types.d.ts +2 -0
  87. package/dist/auth/user-metadata-lookup.d.ts +21 -0
  88. package/dist/auth/user-metadata-lookup.js +116 -0
  89. package/dist/authorization/branch.d.ts +1 -1
  90. package/dist/authorization/branch.js +12 -13
  91. package/dist/authorization/groups/schema.d.ts +4 -4
  92. package/dist/authorization/helpers.d.ts +8 -0
  93. package/dist/authorization/helpers.js +8 -0
  94. package/dist/authorization/index.d.ts +1 -1
  95. package/dist/authorization/index.js +1 -1
  96. package/dist/authorization/protected-branch.d.ts +5 -4
  97. package/dist/authorization/protected-branch.js +7 -5
  98. package/dist/branch-health.d.ts +35 -8
  99. package/dist/branch-health.js +54 -18
  100. package/dist/branch-metadata-error.d.ts +27 -0
  101. package/dist/branch-metadata-error.js +23 -0
  102. package/dist/branch-metadata-file.d.ts +205 -24
  103. package/dist/branch-metadata-file.js +126 -25
  104. package/dist/branch-metadata.d.ts +45 -6
  105. package/dist/branch-metadata.js +142 -50
  106. package/dist/branch-provisioning.d.ts +167 -0
  107. package/dist/branch-provisioning.js +501 -0
  108. package/dist/branch-registry.d.ts +8 -0
  109. package/dist/branch-registry.js +61 -9
  110. package/dist/branch-schema-cache.d.ts +29 -12
  111. package/dist/branch-schema-cache.js +64 -21
  112. package/dist/branch-sparse.d.ts +23 -0
  113. package/dist/branch-sparse.js +118 -0
  114. package/dist/branch-workspace.d.ts +42 -3
  115. package/dist/branch-workspace.js +239 -100
  116. package/dist/build/asset-refs.d.ts +94 -0
  117. package/dist/build/asset-refs.js +267 -0
  118. package/dist/build/generate-ai-content.js +5 -0
  119. package/dist/build/inline-flight.d.ts +37 -0
  120. package/dist/build/inline-flight.js +142 -0
  121. package/dist/cli/asset-refs.d.ts +52 -0
  122. package/dist/cli/asset-refs.js +194 -0
  123. package/dist/cli/cli.d.ts +15 -1
  124. package/dist/cli/cli.js +8543 -3193
  125. package/dist/cli/configured-asset-store.d.ts +12 -0
  126. package/dist/cli/configured-asset-store.js +46 -0
  127. package/dist/cli/generate-ai-content.js +4244 -1072
  128. package/dist/cli/init.js +217 -126
  129. package/dist/cli/project-detect.d.ts +2 -0
  130. package/dist/cli/project-detect.js +2 -0
  131. package/dist/cli/template-files/Dockerfile.cms.template +13 -5
  132. package/dist/cli/template-files/cms-stack.ts.template +16 -13
  133. package/dist/client.d.ts +1 -2
  134. package/dist/client.js +1 -2
  135. package/dist/comment-store.d.ts +25 -0
  136. package/dist/comment-store.js +43 -8
  137. package/dist/config/flatten.js +2 -0
  138. package/dist/config/helpers.js +2 -2
  139. package/dist/config/index.d.ts +1 -1
  140. package/dist/config/schemas/config.d.ts +12 -29
  141. package/dist/config/schemas/config.js +9 -12
  142. package/dist/config/schemas/field.d.ts +3 -0
  143. package/dist/config/schemas/field.js +58 -4
  144. package/dist/config/schemas/media.d.ts +4 -13
  145. package/dist/config/schemas/media.js +5 -8
  146. package/dist/config/schemas/url.d.ts +2 -20
  147. package/dist/config/schemas/url.js +2 -25
  148. package/dist/config/types.d.ts +116 -22
  149. package/dist/config/types.js +3 -9
  150. package/dist/config/validation.d.ts +25 -4
  151. package/dist/config/validation.js +116 -3
  152. package/dist/content-id-index.js +2 -2
  153. package/dist/content-index-generation.d.ts +1 -1
  154. package/dist/content-index-generation.js +1 -1
  155. package/dist/content-listing.d.ts +10 -1
  156. package/dist/content-listing.js +22 -5
  157. package/dist/content-reader.d.ts +9 -0
  158. package/dist/content-reader.js +16 -11
  159. package/dist/content-store.d.ts +75 -12
  160. package/dist/content-store.js +249 -86
  161. package/dist/content-tree.js +2 -2
  162. package/dist/context.js +5 -2
  163. package/dist/dangling-reference-log.d.ts +17 -0
  164. package/dist/dangling-reference-log.js +29 -0
  165. package/dist/dev-content-watcher.d.ts +1 -1
  166. package/dist/dev-content-watcher.js +1 -1
  167. package/dist/editor/BranchManager.d.ts +23 -7
  168. package/dist/editor/BranchManager.js +119 -54
  169. package/dist/editor/CanopyEditor.d.ts +1 -1
  170. package/dist/editor/CanopyEditor.js +1 -2
  171. package/dist/editor/CommentsPanel.d.ts +14 -0
  172. package/dist/editor/CommentsPanel.js +68 -43
  173. package/dist/editor/Editor.d.ts +11 -5
  174. package/dist/editor/Editor.js +891 -218
  175. package/dist/editor/EditorAuthGate.d.ts +7 -1
  176. package/dist/editor/EditorAuthGate.js +41 -9
  177. package/dist/editor/EntryNavigator.d.ts +22 -0
  178. package/dist/editor/EntryNavigator.js +353 -105
  179. package/dist/editor/FormHeader.d.ts +25 -0
  180. package/dist/editor/FormHeader.js +82 -0
  181. package/dist/editor/FormRenderer.d.ts +27 -3
  182. package/dist/editor/FormRenderer.js +171 -76
  183. package/dist/editor/PreviewFrame.d.ts +40 -0
  184. package/dist/editor/PreviewFrame.js +215 -0
  185. package/dist/editor/PreviewPane.d.ts +47 -0
  186. package/dist/editor/PreviewPane.js +147 -0
  187. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  188. package/dist/editor/PreviewToolbarTools.js +73 -0
  189. package/dist/editor/admin/SystemHealthPanel.d.ts +2 -1
  190. package/dist/editor/admin/SystemHealthPanel.js +163 -33
  191. package/dist/editor/admin/useSystemHealth.d.ts +18 -0
  192. package/dist/editor/admin/useSystemHealth.js +70 -1
  193. package/dist/editor/branch-status.d.ts +17 -0
  194. package/dist/editor/branch-status.js +29 -0
  195. package/dist/editor/canopy-path.d.ts +9 -6
  196. package/dist/editor/canopy-path.js +8 -11
  197. package/dist/editor/client-reference-resolver.d.ts +43 -16
  198. package/dist/editor/client-reference-resolver.js +155 -64
  199. package/dist/editor/comments/BranchComments.d.ts +1 -1
  200. package/dist/editor/comments/BranchComments.js +2 -2
  201. package/dist/editor/comments/CloseRefusedNotice.d.ts +4 -0
  202. package/dist/editor/comments/CloseRefusedNotice.js +7 -0
  203. package/dist/editor/comments/CommentThreadsPopover.d.ts +47 -0
  204. package/dist/editor/comments/CommentThreadsPopover.js +274 -0
  205. package/dist/editor/comments/CommentWriteAlert.d.ts +11 -0
  206. package/dist/editor/comments/CommentWriteAlert.js +13 -0
  207. package/dist/editor/comments/EntryComments.d.ts +10 -4
  208. package/dist/editor/comments/EntryComments.js +14 -17
  209. package/dist/editor/comments/FieldWrapper.d.ts +18 -4
  210. package/dist/editor/comments/FieldWrapper.js +62 -28
  211. package/dist/editor/comments/InlineCommentThread.d.ts +9 -2
  212. package/dist/editor/comments/InlineCommentThread.js +11 -47
  213. package/dist/editor/comments/ThreadCarousel.d.ts +1 -1
  214. package/dist/editor/comments/ThreadCarousel.js +18 -21
  215. package/dist/editor/comments/ThreadReplyControls.d.ts +28 -0
  216. package/dist/editor/comments/ThreadReplyControls.js +92 -0
  217. package/dist/editor/comments/field-comment-context.d.ts +14 -0
  218. package/dist/editor/comments/field-comment-context.js +14 -0
  219. package/dist/editor/comments/form-comments.d.ts +48 -0
  220. package/dist/editor/comments/form-comments.js +178 -0
  221. package/dist/editor/comments/item-anchor-copy.d.ts +6 -0
  222. package/dist/editor/comments/item-anchor-copy.js +6 -0
  223. package/dist/editor/comments/move-focus-if-unmoved.d.ts +6 -0
  224. package/dist/editor/comments/move-focus-if-unmoved.js +11 -0
  225. package/dist/editor/comments/popover-placement.d.ts +18 -0
  226. package/dist/editor/comments/popover-placement.js +58 -0
  227. package/dist/editor/comments/use-tracked-writes.d.ts +16 -0
  228. package/dist/editor/comments/use-tracked-writes.js +53 -0
  229. package/dist/editor/components/BranchAccessList.d.ts +15 -0
  230. package/dist/editor/components/BranchAccessList.js +52 -0
  231. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  232. package/dist/editor/components/BranchLockBanner.js +39 -0
  233. package/dist/editor/components/BranchPill.d.ts +32 -0
  234. package/dist/editor/components/BranchPill.js +33 -0
  235. package/dist/editor/components/BranchesDrawer.d.ts +11 -0
  236. package/dist/editor/components/BranchesDrawer.js +5 -0
  237. package/dist/editor/components/ConfirmDeleteModal.d.ts +6 -1
  238. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  239. package/dist/editor/components/DestructiveMenuItem.d.ts +7 -0
  240. package/dist/editor/components/DestructiveMenuItem.js +8 -0
  241. package/dist/editor/components/EditorCrashScreen.d.ts +17 -0
  242. package/dist/editor/components/EditorCrashScreen.js +22 -0
  243. package/dist/editor/components/EditorErrorBoundary.d.ts +38 -0
  244. package/dist/editor/components/EditorErrorBoundary.js +40 -0
  245. package/dist/editor/components/EditorHeader.d.ts +56 -106
  246. package/dist/editor/components/EditorHeader.js +130 -131
  247. package/dist/editor/components/EditorRail.d.ts +49 -0
  248. package/dist/editor/components/EditorRail.js +65 -0
  249. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  250. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  251. package/dist/editor/components/EntryCreateModal.d.ts +12 -7
  252. package/dist/editor/components/EntryCreateModal.js +51 -57
  253. package/dist/editor/components/NoEditPermissionNotice.d.ts +6 -5
  254. package/dist/editor/components/NoEditPermissionNotice.js +5 -4
  255. package/dist/editor/components/QuickOpenPalette.d.ts +22 -0
  256. package/dist/editor/components/QuickOpenPalette.js +66 -0
  257. package/dist/editor/components/ReadOnlyDraftNotice.d.ts +11 -0
  258. package/dist/editor/components/ReadOnlyDraftNotice.js +12 -0
  259. package/dist/editor/components/ReasonedMenuItem.d.ts +15 -0
  260. package/dist/editor/components/ReasonedMenuItem.js +15 -0
  261. package/dist/editor/components/ReferencedByList.d.ts +11 -0
  262. package/dist/editor/components/ReferencedByList.js +28 -0
  263. package/dist/editor/components/RelativeTime.d.ts +10 -0
  264. package/dist/editor/components/RelativeTime.js +10 -0
  265. package/dist/editor/components/RenameEntryModal.d.ts +9 -1
  266. package/dist/editor/components/RenameEntryModal.js +19 -31
  267. package/dist/editor/components/RetryToastMessage.d.ts +9 -0
  268. package/dist/editor/components/RetryToastMessage.js +6 -0
  269. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  270. package/dist/editor/components/SaveAllResult.js +86 -0
  271. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  272. package/dist/editor/components/SaveSplitButton.js +108 -0
  273. package/dist/editor/components/StagedChangesDrawer.d.ts +39 -0
  274. package/dist/editor/components/StagedChangesDrawer.js +92 -0
  275. package/dist/editor/components/UnavailableEntryNotice.d.ts +6 -0
  276. package/dist/editor/components/UnavailableEntryNotice.js +9 -0
  277. package/dist/editor/components/UnavailableTypeMessage.d.ts +6 -0
  278. package/dist/editor/components/UnavailableTypeMessage.js +9 -0
  279. package/dist/editor/components/UndoToastMessage.d.ts +18 -0
  280. package/dist/editor/components/UndoToastMessage.js +10 -0
  281. package/dist/editor/components/UserBadge.d.ts +2 -5
  282. package/dist/editor/components/UserBadge.js +62 -33
  283. package/dist/editor/components/ValidationSummary.d.ts +14 -0
  284. package/dist/editor/components/ValidationSummary.js +24 -0
  285. package/dist/editor/components/index.d.ts +2 -2
  286. package/dist/editor/components/index.js +2 -2
  287. package/dist/editor/context/ApiClientContext.d.ts +8 -2
  288. package/dist/editor/context/ApiClientContext.js +19 -8
  289. package/dist/editor/context/AssetContext.d.ts +13 -35
  290. package/dist/editor/context/AssetContext.js +12 -31
  291. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  292. package/dist/editor/context/EditorStateContext.js +4 -4
  293. package/dist/editor/context/SWRProvider.d.ts +3 -3
  294. package/dist/editor/context/SiteMdxAllowContext.d.ts +4 -0
  295. package/dist/editor/context/SiteMdxAllowContext.js +7 -0
  296. package/dist/editor/context/index.d.ts +2 -1
  297. package/dist/editor/context/index.js +2 -1
  298. package/dist/editor/contrast.d.ts +19 -0
  299. package/dist/editor/contrast.js +41 -0
  300. package/dist/editor/copy.d.ts +18 -0
  301. package/dist/editor/copy.js +18 -0
  302. package/dist/editor/editor-config.js +16 -0
  303. package/dist/editor/editor-utils.d.ts +34 -8
  304. package/dist/editor/editor-utils.js +42 -20
  305. package/dist/editor/entry-search.d.ts +20 -0
  306. package/dist/editor/entry-search.js +34 -0
  307. package/dist/editor/field-breadcrumb.d.ts +12 -0
  308. package/dist/editor/field-breadcrumb.js +85 -0
  309. package/dist/editor/field-props.d.ts +86 -0
  310. package/dist/editor/field-props.js +27 -0
  311. package/dist/editor/field-target.d.ts +30 -0
  312. package/dist/editor/field-target.js +77 -0
  313. package/dist/editor/fields/BlockField.d.ts +12 -0
  314. package/dist/editor/fields/BlockField.js +89 -73
  315. package/dist/editor/fields/CodeField.d.ts +2 -0
  316. package/dist/editor/fields/CodeField.js +6 -2
  317. package/dist/editor/fields/DateTimeField.d.ts +4 -29
  318. package/dist/editor/fields/DateTimeField.js +79 -41
  319. package/dist/editor/fields/ExpandAllButton.d.ts +6 -0
  320. package/dist/editor/fields/ExpandAllButton.js +4 -0
  321. package/dist/editor/fields/FieldCrashFallback.d.ts +17 -0
  322. package/dist/editor/fields/FieldCrashFallback.js +30 -0
  323. package/dist/editor/fields/FieldLabel.d.ts +37 -0
  324. package/dist/editor/fields/FieldLabel.js +19 -0
  325. package/dist/editor/fields/ImageField.d.ts +4 -0
  326. package/dist/editor/fields/ImageField.js +29 -14
  327. package/dist/editor/fields/InlineGroupField.js +4 -3
  328. package/dist/editor/fields/ItemList.d.ts +42 -0
  329. package/dist/editor/fields/ItemList.js +130 -0
  330. package/dist/editor/fields/MarkdownField.d.ts +32 -0
  331. package/dist/editor/fields/MarkdownField.js +223 -46
  332. package/dist/editor/fields/MdxImageDialog.js +1 -1
  333. package/dist/editor/fields/NumberField.d.ts +2 -0
  334. package/dist/editor/fields/NumberField.js +8 -5
  335. package/dist/editor/fields/NumberListField.d.ts +2 -0
  336. package/dist/editor/fields/NumberListField.js +19 -16
  337. package/dist/editor/fields/ObjectField.d.ts +2 -7
  338. package/dist/editor/fields/ObjectField.js +15 -5
  339. package/dist/editor/fields/ObjectListField.d.ts +29 -0
  340. package/dist/editor/fields/ObjectListField.js +57 -0
  341. package/dist/editor/fields/ReferenceField.d.ts +2 -0
  342. package/dist/editor/fields/ReferenceField.js +34 -9
  343. package/dist/editor/fields/SelectField.d.ts +2 -0
  344. package/dist/editor/fields/SelectField.js +11 -2
  345. package/dist/editor/fields/StringListField.d.ts +4 -1
  346. package/dist/editor/fields/StringListField.js +22 -8
  347. package/dist/editor/fields/TextField.d.ts +2 -0
  348. package/dist/editor/fields/TextField.js +6 -2
  349. package/dist/editor/fields/ToggleField.d.ts +2 -0
  350. package/dist/editor/fields/ToggleField.js +8 -3
  351. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
  352. package/dist/editor/fields/entry-link/InsertEntryLink.js +1 -1
  353. package/dist/editor/fields/form-expansion.d.ts +9 -0
  354. package/dist/editor/fields/form-expansion.js +2 -0
  355. package/dist/editor/fields/item-expansion.d.ts +15 -0
  356. package/dist/editor/fields/item-expansion.js +60 -0
  357. package/dist/editor/fields/list-item-keys.d.ts +8 -0
  358. package/dist/editor/fields/list-item-keys.js +50 -0
  359. package/dist/editor/fields/list-item-title.d.ts +20 -0
  360. package/dist/editor/fields/list-item-title.js +85 -0
  361. package/dist/editor/fields/markdown-export-options.d.ts +2 -0
  362. package/dist/editor/fields/markdown-export-options.js +8 -0
  363. package/dist/editor/fields/markdown-fidelity-visitors.d.ts +11 -0
  364. package/dist/editor/fields/markdown-fidelity-visitors.js +160 -0
  365. package/dist/editor/fields/mdx-jsx-support.d.ts +5 -1
  366. package/dist/editor/fields/mdx-jsx-support.js +157 -69
  367. package/dist/editor/fields/rich-text-failures.d.ts +5 -0
  368. package/dist/editor/fields/rich-text-failures.js +26 -0
  369. package/dist/editor/fields/toolbar-wrap.d.ts +7 -0
  370. package/dist/editor/fields/toolbar-wrap.js +35 -0
  371. package/dist/editor/form-outline.d.ts +13 -0
  372. package/dist/editor/form-outline.js +70 -0
  373. package/dist/editor/group-manager/GroupCard.d.ts +4 -1
  374. package/dist/editor/group-manager/GroupCard.js +12 -3
  375. package/dist/editor/group-manager/GroupForm.js +1 -1
  376. package/dist/editor/group-manager/InternalGroupsTab.d.ts +2 -1
  377. package/dist/editor/group-manager/InternalGroupsTab.js +4 -4
  378. package/dist/editor/group-manager/MemberList.d.ts +2 -1
  379. package/dist/editor/group-manager/MemberList.js +15 -4
  380. package/dist/editor/group-manager/hooks/useGroupState.d.ts +5 -0
  381. package/dist/editor/group-manager/hooks/useGroupState.js +30 -7
  382. package/dist/editor/group-manager/index.js +34 -5
  383. package/dist/editor/group-manager/types.d.ts +5 -2
  384. package/dist/editor/hooks/create-branch-request.d.ts +20 -0
  385. package/dist/editor/hooks/create-branch-request.js +54 -0
  386. package/dist/editor/hooks/save-all.d.ts +68 -0
  387. package/dist/editor/hooks/save-all.js +61 -0
  388. package/dist/editor/hooks/useBranchActions.d.ts +11 -1
  389. package/dist/editor/hooks/useBranchActions.js +42 -19
  390. package/dist/editor/hooks/useBranchManager.d.ts +21 -0
  391. package/dist/editor/hooks/useBranchManager.js +321 -125
  392. package/dist/editor/hooks/useBranchesData.d.ts +8 -4
  393. package/dist/editor/hooks/useBranchesData.js +9 -5
  394. package/dist/editor/hooks/useCommentSystem.d.ts +14 -2
  395. package/dist/editor/hooks/useCommentSystem.js +79 -55
  396. package/dist/editor/hooks/useCommentsData.d.ts +11 -7
  397. package/dist/editor/hooks/useCommentsData.js +20 -19
  398. package/dist/editor/hooks/useDraftManager.d.ts +77 -3
  399. package/dist/editor/hooks/useDraftManager.js +887 -94
  400. package/dist/editor/hooks/useEditorLayout.d.ts +53 -7
  401. package/dist/editor/hooks/useEditorLayout.js +98 -20
  402. package/dist/editor/hooks/useEntriesData.d.ts +2 -1
  403. package/dist/editor/hooks/useEntriesData.js +18 -4
  404. package/dist/editor/hooks/useEntryManager.d.ts +41 -2
  405. package/dist/editor/hooks/useEntryManager.js +227 -41
  406. package/dist/editor/hooks/useGroupManager.js +13 -16
  407. package/dist/editor/hooks/usePermissionManager.js +14 -6
  408. package/dist/editor/hooks/usePreviewMarks.d.ts +21 -0
  409. package/dist/editor/hooks/usePreviewMarks.js +33 -0
  410. package/dist/editor/hooks/useReferenceResolution.d.ts +10 -3
  411. package/dist/editor/hooks/useReferenceResolution.js +34 -177
  412. package/dist/editor/hooks/useSchemaManager.d.ts +25 -6
  413. package/dist/editor/hooks/useSchemaManager.js +97 -39
  414. package/dist/editor/hooks/useUserMetadata.d.ts +14 -4
  415. package/dist/editor/hooks/useUserMetadata.js +76 -47
  416. package/dist/editor/hooks/user-metadata-batcher.d.ts +8 -0
  417. package/dist/editor/hooks/user-metadata-batcher.js +33 -0
  418. package/dist/editor/media/AssetCard.d.ts +6 -7
  419. package/dist/editor/media/AssetCard.js +24 -31
  420. package/dist/editor/media/AssetListRow.d.ts +16 -0
  421. package/dist/editor/media/AssetListRow.js +41 -0
  422. package/dist/editor/media/CropStep.js +6 -1
  423. package/dist/editor/media/MediaLibrary.d.ts +4 -3
  424. package/dist/editor/media/MediaLibrary.js +11 -4
  425. package/dist/editor/media/MediaLibraryBody.d.ts +3 -3
  426. package/dist/editor/media/MediaLibraryBody.js +195 -80
  427. package/dist/editor/media/MediaLibrarySkeleton.d.ts +6 -0
  428. package/dist/editor/media/MediaLibrarySkeleton.js +14 -0
  429. package/dist/editor/media/crop-math.d.ts +3 -4
  430. package/dist/editor/media/crop-math.js +11 -22
  431. package/dist/editor/media/editor-image-src.d.ts +8 -0
  432. package/dist/editor/media/editor-image-src.js +10 -0
  433. package/dist/editor/media/format-bytes.d.ts +2 -0
  434. package/dist/editor/media/format-bytes.js +23 -0
  435. package/dist/editor/media/media-library-prefs.d.ts +23 -0
  436. package/dist/editor/media/media-library-prefs.js +49 -0
  437. package/dist/editor/media/media-library-view.d.ts +34 -0
  438. package/dist/editor/media/media-library-view.js +40 -0
  439. package/dist/editor/media/useAssetLibrary.d.ts +30 -0
  440. package/dist/editor/media/useAssetLibrary.js +126 -0
  441. package/dist/editor/media/useStableAssetOrder.d.ts +11 -0
  442. package/dist/editor/media/useStableAssetOrder.js +37 -0
  443. package/dist/editor/media/useThumbnailFailure.d.ts +6 -0
  444. package/dist/editor/media/useThumbnailFailure.js +16 -0
  445. package/dist/editor/permission-manager/GroupSelector.js +2 -2
  446. package/dist/editor/permission-manager/PermissionEditor.d.ts +1 -1
  447. package/dist/editor/permission-manager/PermissionEditor.js +15 -4
  448. package/dist/editor/permission-manager/PermissionLevelBadge.js +1 -1
  449. package/dist/editor/permission-manager/PermissionTree.d.ts +1 -1
  450. package/dist/editor/permission-manager/UserSelector.d.ts +1 -1
  451. package/dist/editor/permission-manager/UserSelector.js +3 -3
  452. package/dist/editor/permission-manager/index.js +12 -3
  453. package/dist/editor/permission-manager/types.d.ts +5 -2
  454. package/dist/editor/platform.d.ts +3 -0
  455. package/dist/editor/platform.js +9 -0
  456. package/dist/editor/preview-asset-base.d.ts +9 -0
  457. package/dist/editor/preview-asset-base.js +17 -0
  458. package/dist/editor/preview-bridge.d.ts +51 -27
  459. package/dist/editor/preview-bridge.js +103 -144
  460. package/dist/editor/preview-marks.d.ts +15 -0
  461. package/dist/editor/preview-marks.js +74 -0
  462. package/dist/editor/preview-path.d.ts +12 -0
  463. package/dist/editor/preview-path.js +45 -1
  464. package/dist/editor/preview-scale.d.ts +27 -0
  465. package/dist/editor/preview-scale.js +34 -0
  466. package/dist/editor/raw-asset-base.d.ts +5 -0
  467. package/dist/editor/raw-asset-base.js +14 -0
  468. package/dist/editor/relative-time.d.ts +4 -4
  469. package/dist/editor/relative-time.js +16 -8
  470. package/dist/editor/schema-editor/CollectionEditor.d.ts +20 -3
  471. package/dist/editor/schema-editor/CollectionEditor.js +361 -62
  472. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +5 -1
  473. package/dist/editor/schema-editor/EntryTypeEditor.js +11 -6
  474. package/dist/editor/schema-editor/collection-draft.d.ts +22 -0
  475. package/dist/editor/schema-editor/collection-draft.js +40 -0
  476. package/dist/editor/schema-editor/index.d.ts +1 -1
  477. package/dist/editor/schema-label.d.ts +10 -0
  478. package/dist/editor/schema-label.js +17 -0
  479. package/dist/editor/settings-conflict.d.ts +5 -0
  480. package/dist/editor/settings-conflict.js +8 -0
  481. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  482. package/dist/editor/shell/ActivityRail.js +91 -0
  483. package/dist/editor/shell/EditorShell.d.ts +17 -0
  484. package/dist/editor/shell/EditorShell.js +31 -0
  485. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  486. package/dist/editor/shell/PaneLayout.js +116 -0
  487. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  488. package/dist/editor/shell/SidePanelHost.js +184 -0
  489. package/dist/editor/shell/TopBar.d.ts +12 -0
  490. package/dist/editor/shell/TopBar.js +14 -0
  491. package/dist/editor/shell/host-chrome.d.ts +16 -0
  492. package/dist/editor/shell/host-chrome.js +69 -0
  493. package/dist/editor/slug.d.ts +16 -0
  494. package/dist/editor/slug.js +43 -0
  495. package/dist/editor/theme.d.ts +2 -1
  496. package/dist/editor/theme.js +314 -13
  497. package/dist/editor/unanswered.d.ts +17 -0
  498. package/dist/editor/unanswered.js +26 -0
  499. package/dist/editor/unavailable-entry-type.d.ts +20 -0
  500. package/dist/editor/unavailable-entry-type.js +28 -0
  501. package/dist/editor/undo-removal.d.ts +57 -0
  502. package/dist/editor/undo-removal.js +163 -0
  503. package/dist/editor/user-display.d.ts +4 -0
  504. package/dist/editor/user-display.js +5 -0
  505. package/dist/editor/utils/confirm-modal.d.ts +8 -0
  506. package/dist/editor/utils/confirm-modal.js +12 -0
  507. package/dist/editor/utils/editor-errors.d.ts +19 -0
  508. package/dist/editor/utils/editor-errors.js +32 -0
  509. package/dist/editor/utils/focus-return.d.ts +56 -0
  510. package/dist/editor/utils/focus-return.js +106 -0
  511. package/dist/entry-link-resolver.d.ts +11 -1
  512. package/dist/entry-link-resolver.js +65 -5
  513. package/dist/entry-schema-registry.d.ts +3 -3
  514. package/dist/entry-schema-registry.js +7 -4
  515. package/dist/entry-schema.d.ts +49 -3
  516. package/dist/entry-schema.js +18 -0
  517. package/dist/git-manager.d.ts +172 -10
  518. package/dist/git-manager.js +449 -107
  519. package/dist/github-service.d.ts +39 -2
  520. package/dist/github-service.js +101 -7
  521. package/dist/http/handler.js +74 -9
  522. package/dist/http/types.d.ts +2 -0
  523. package/dist/http/worker-not-ready.d.ts +9 -4
  524. package/dist/http/worker-not-ready.js +59 -6
  525. package/dist/index.d.ts +2 -0
  526. package/dist/index.js +3 -0
  527. package/dist/item-keys.d.ts +46 -0
  528. package/dist/item-keys.js +235 -0
  529. package/dist/operating-mode/editor-mode-check.d.ts +15 -0
  530. package/dist/operating-mode/editor-mode-check.js +24 -0
  531. package/dist/operating-mode/mode-env.d.ts +13 -14
  532. package/dist/operating-mode/mode-env.js +13 -14
  533. package/dist/operating-mode/types.d.ts +2 -2
  534. package/dist/paths/branch.d.ts +0 -1
  535. package/dist/paths/branch.js +1 -8
  536. package/dist/paths/index.d.ts +1 -1
  537. package/dist/paths/index.js +1 -1
  538. package/dist/paths/validation.js +0 -2
  539. package/dist/preview.d.ts +8 -0
  540. package/dist/preview.js +7 -0
  541. package/dist/reference-resolver.js +1 -0
  542. package/dist/resolve-canopy-user.d.ts +1 -1
  543. package/dist/resolve-canopy-user.js +1 -1
  544. package/dist/schema/meta-loader.d.ts +18 -8
  545. package/dist/schema/meta-loader.js +42 -9
  546. package/dist/schema/registry-fingerprint.d.ts +14 -0
  547. package/dist/schema/registry-fingerprint.js +63 -0
  548. package/dist/schema/resolver.d.ts +7 -4
  549. package/dist/schema/resolver.js +7 -6
  550. package/dist/schema/schema-store.d.ts +7 -10
  551. package/dist/schema/schema-store.js +13 -11
  552. package/dist/schema/schema-unavailable-error.d.ts +11 -0
  553. package/dist/schema/schema-unavailable-error.js +15 -0
  554. package/dist/schema/types.d.ts +22 -0
  555. package/dist/schema-registry-record.d.ts +35 -0
  556. package/dist/schema-registry-record.js +134 -0
  557. package/dist/server.d.ts +79 -9
  558. package/dist/server.js +62 -8
  559. package/dist/services.d.ts +33 -5
  560. package/dist/services.js +155 -37
  561. package/dist/settings-workspace.d.ts +23 -3
  562. package/dist/settings-workspace.js +159 -45
  563. package/dist/stamp-on-publish.d.ts +36 -0
  564. package/dist/stamp-on-publish.js +71 -0
  565. package/dist/static/index.d.ts +58 -5
  566. package/dist/static/index.js +133 -3
  567. package/dist/static/seo.d.ts +5 -5
  568. package/dist/static/seo.js +5 -5
  569. package/dist/submission-attribution.d.ts +8 -0
  570. package/dist/submission-attribution.js +22 -0
  571. package/dist/system-users.d.ts +12 -0
  572. package/dist/system-users.js +12 -0
  573. package/dist/task-queue/cms-task-queue.d.ts +1 -1
  574. package/dist/task-queue/cms-task-queue.js +1 -1
  575. package/dist/task-queue/index.d.ts +1 -1
  576. package/dist/task-queue/index.js +1 -1
  577. package/dist/task-queue/task-queue.d.ts +6 -0
  578. package/dist/task-queue/task-queue.js +29 -0
  579. package/dist/task-queue/worker-status.d.ts +35 -9
  580. package/dist/task-queue/worker-status.js +84 -17
  581. package/dist/types.d.ts +90 -9
  582. package/dist/url-collision.js +3 -3
  583. package/dist/url-exclusivity-fixtures.d.ts +8 -1
  584. package/dist/url-exclusivity-fixtures.js +11 -1
  585. package/dist/user.js +2 -1
  586. package/dist/utils/atomic-write.js +2 -1
  587. package/dist/utils/base-branch.d.ts +13 -0
  588. package/dist/utils/base-branch.js +23 -0
  589. package/dist/utils/content-serialize.d.ts +15 -7
  590. package/dist/utils/content-serialize.js +353 -51
  591. package/dist/utils/content-write-lock.d.ts +6 -1
  592. package/dist/utils/content-write-lock.js +21 -6
  593. package/dist/utils/debug.d.ts +1 -1
  594. package/dist/utils/debug.js +5 -3
  595. package/dist/utils/error.js +13 -2
  596. package/dist/utils/escape-text.d.ts +7 -0
  597. package/dist/utils/escape-text.js +27 -0
  598. package/dist/utils/fs.d.ts +12 -4
  599. package/dist/utils/fs.js +18 -5
  600. package/dist/utils/git.d.ts +21 -1
  601. package/dist/utils/git.js +63 -8
  602. package/dist/utils/json-source-splice.d.ts +14 -0
  603. package/dist/utils/json-source-splice.js +120 -0
  604. package/dist/utils/markdown-body-splice.d.ts +39 -0
  605. package/dist/utils/markdown-body-splice.js +421 -0
  606. package/dist/utils/occ-json-write.js +18 -12
  607. package/dist/utils/provision-log.d.ts +25 -0
  608. package/dist/utils/provision-log.js +49 -0
  609. package/dist/utils/provisioning-lock.d.ts +32 -1
  610. package/dist/utils/provisioning-lock.js +78 -32
  611. package/dist/utils/sanitize-href.d.ts +9 -23
  612. package/dist/utils/sanitize-href.js +14 -28
  613. package/dist/utils/title-field.d.ts +10 -0
  614. package/dist/utils/title-field.js +47 -0
  615. package/dist/utils/yaml-source-splice.d.ts +58 -0
  616. package/dist/utils/yaml-source-splice.js +515 -0
  617. package/dist/validation/block-structural-keys.d.ts +1 -1
  618. package/dist/validation/block-structural-keys.js +1 -1
  619. package/dist/validation/deletion-checker.d.ts +21 -29
  620. package/dist/validation/deletion-checker.js +41 -117
  621. package/dist/validation/entry-validator.d.ts +6 -10
  622. package/dist/validation/entry-validator.js +19 -26
  623. package/dist/validation/field-traversal.d.ts +22 -0
  624. package/dist/validation/field-traversal.js +39 -8
  625. package/dist/validation/markdown-safety.d.ts +68 -0
  626. package/dist/validation/markdown-safety.js +596 -0
  627. package/dist/validation/mdx-allowlist.d.ts +55 -0
  628. package/dist/validation/mdx-allowlist.js +183 -0
  629. package/dist/validation/reference-validator.d.ts +2 -0
  630. package/dist/validation/reference-validator.js +3 -0
  631. package/dist/version.d.ts +1 -1
  632. package/dist/version.js +1 -1
  633. package/dist/worker/branch-policy.d.ts +124 -0
  634. package/dist/worker/branch-policy.js +243 -0
  635. package/dist/worker/bundle-header.d.ts +117 -0
  636. package/dist/worker/bundle-header.js +253 -0
  637. package/dist/worker/cms-worker.d.ts +236 -142
  638. package/dist/worker/cms-worker.js +772 -285
  639. package/dist/worker/gateway-egress.d.ts +43 -0
  640. package/dist/worker/gateway-egress.js +209 -0
  641. package/dist/worker/gateway-instance.d.ts +42 -0
  642. package/dist/worker/gateway-instance.js +129 -0
  643. package/dist/worker/git-process.d.ts +81 -0
  644. package/dist/worker/git-process.js +234 -0
  645. package/dist/worker/git-sync.d.ts +34 -17
  646. package/dist/worker/git-sync.js +394 -131
  647. package/dist/worker/github-auth.d.ts +9 -9
  648. package/dist/worker/github-auth.js +13 -15
  649. package/dist/worker/github-gateway-http.d.ts +62 -0
  650. package/dist/worker/github-gateway-http.js +977 -0
  651. package/dist/worker/github-gateway-wire.d.ts +590 -0
  652. package/dist/worker/github-gateway-wire.js +488 -0
  653. package/dist/worker/github-gateway.d.ts +238 -0
  654. package/dist/worker/github-gateway.js +726 -0
  655. package/dist/worker/github-mirror-upkeep.d.ts +75 -0
  656. package/dist/worker/github-mirror-upkeep.js +266 -0
  657. package/dist/worker/github-mirror.d.ts +364 -0
  658. package/dist/worker/github-mirror.js +1030 -0
  659. package/dist/worker/github-transfer.d.ts +56 -0
  660. package/dist/worker/github-transfer.js +444 -0
  661. package/dist/worker/history-rewrite.d.ts +4 -5
  662. package/dist/worker/history-rewrite.js +12 -17
  663. package/dist/worker/provisioned-workspace.d.ts +5 -5
  664. package/dist/worker/provisioned-workspace.js +5 -5
  665. package/dist/worker/rebase.d.ts +1 -1
  666. package/dist/worker/rebase.js +58 -54
  667. package/dist/worker/remote-git-maintenance.d.ts +3 -0
  668. package/dist/worker/remote-git-maintenance.js +12 -0
  669. package/dist/worker/schema-gate.d.ts +49 -0
  670. package/dist/worker/schema-gate.js +226 -0
  671. package/dist/worker/self-checks.d.ts +107 -0
  672. package/dist/worker/self-checks.js +358 -0
  673. package/dist/worker/shared-repo-git.d.ts +96 -0
  674. package/dist/worker/shared-repo-git.js +459 -0
  675. package/dist/worker/sparse-cone.d.ts +20 -0
  676. package/dist/worker/sparse-cone.js +136 -0
  677. package/dist/worker/stale-mirror.d.ts +82 -0
  678. package/dist/worker/stale-mirror.js +130 -0
  679. package/dist/worker/task-runner.d.ts +5 -3
  680. package/dist/worker/task-runner.js +287 -186
  681. package/dist/worker/worker-context.d.ts +36 -37
  682. package/package.json +35 -7
  683. package/dist/editor/EditorPanes.d.ts +0 -10
  684. package/dist/editor/EditorPanes.js +0 -98
  685. package/dist/editor/components/EditorFooter.d.ts +0 -11
  686. package/dist/editor/components/EditorFooter.js +0 -9
  687. package/dist/editor/components/EditorSidebar.d.ts +0 -67
  688. package/dist/editor/components/EditorSidebar.js +0 -22
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useRef, useState } from 'react';
4
+ import { ActionIcon, Menu, Stack, Text, Tooltip, VisuallyHidden } from '@mantine/core';
5
+ import { useMergedRef } from '@mantine/hooks';
6
+ import { IconActivity, IconExternalLink, IconFiles, IconGitBranch, IconInfoCircle, IconLayoutColumns, IconLayoutRows, IconLogout, IconMessageCircle, IconPhoto, IconSearch, IconSettings, IconUserCircle, } from '@tabler/icons-react';
7
+ import { ActivityRail, RailButton } from '../shell/ActivityRail.js';
8
+ import { useMenuItemFocusReturn } from '../utils/focus-return.js';
9
+ import { CANOPYCMS_VERSION } from '../../version.js';
10
+ import { EDITOR_ACTIONS } from '../copy.js';
11
+ const ABOUT_URL = 'https://github.com/safeinsights/canopycms';
12
+ /**
13
+ * @internal Exported for tests. The Settings button and its menu of `items`; with none, no
14
+ * button, since a menu that opens empty offers nothing.
15
+ */
16
+ export function SettingsMenu({ items, toggleRef, }) {
17
+ const [opened, setOpened] = useState(false);
18
+ const toggle = useRef(null);
19
+ const mergedRef = useMergedRef(toggle, toggleRef);
20
+ const menuFocus = useMenuItemFocusReturn(toggle);
21
+ if (items.length === 0)
22
+ return null;
23
+ return (_jsxs(Menu, { shadow: "md", width: 220, position: "right-end", opened: opened, onChange: setOpened, ...menuFocus.menu, children: [_jsx(Menu.Target, { children: _jsx(RailButton, { railId: "settings", label: "Settings", icon: _jsx(IconSettings, { size: 20 }), tooltipDisabled: opened, ref: mergedRef, "data-testid": "settings-button" }) }), _jsx(Menu.Dropdown, { "data-testid": "settings-menu", ...menuFocus.dropdown, children: items })] }));
24
+ }
25
+ const LAYOUTS = {
26
+ side: {
27
+ name: 'side by side',
28
+ title: 'Side by side layout',
29
+ other: 'stacked',
30
+ Icon: IconLayoutColumns,
31
+ },
32
+ stacked: { name: 'stacked', title: 'Stacked layout', other: 'side', Icon: IconLayoutRows },
33
+ };
34
+ /** One button that shows the current layout and switches to the other. */
35
+ function LayoutButton({ layout, onChange, blocked = false, }) {
36
+ const [switchedTo, setSwitchedTo] = useState('');
37
+ const { name, other, Icon } = LAYOUTS[layout];
38
+ return (_jsxs(_Fragment, { children: [_jsx(RailButton, { railId: "layout", label: `Switch to ${LAYOUTS[other].name} layout (now ${name})`, icon: _jsx(Icon, { size: 20 }), disabledReason: blocked ? 'Restore the preview to change the layout.' : undefined, "data-testid": "rail-layout-button", "data-layout": layout, onClick: () => {
39
+ onChange(other);
40
+ setSwitchedTo(LAYOUTS[other].title);
41
+ } }), _jsx(VisuallyHidden, { role: "status", "data-testid": "rail-layout-status", children: switchedTo })] }));
42
+ }
43
+ const monogram = (title) => Array.from(title.trim())[0]?.toUpperCase() ?? '?';
44
+ /** The editor's activity rail: what `ActivityRail` lays out, wired to the editor's surfaces. */
45
+ export function EditorRail({ siteTitle, onHomeOpen, contentPanelOpen, contentPanelId, onContentToggle, onQuickOpen, quickOpenShortcut, quickOpenKeys, branchesOpen, onBranchesOpen, onCommentsOpen, commentsOpen, unresolvedCommentCount, mediaLibraryOpen, onMediaLibraryOpen, mediaButtonRef, layout, onLayoutChange, layoutBlocked, onPermissionManagerOpen, onGroupManagerOpen, onSystemHealthOpen, settingsToggleRef, AccountComponent, onAccountClick, onLogoutClick, }) {
46
+ const adminItems = [
47
+ onPermissionManagerOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-permissions", onClick: onPermissionManagerOpen, children: "Manage permissions" }, "permissions")),
48
+ onGroupManagerOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-groups", onClick: onGroupManagerOpen, children: "Manage groups" }, "groups")),
49
+ onSystemHealthOpen && (_jsx(Menu.Item, { "data-testid": "settings-menu-system-health", leftSection: _jsx(IconActivity, { size: 14 }), onClick: onSystemHealthOpen, children: "System health" }, "system-health")),
50
+ ].filter((item) => !!item);
51
+ // Every user gets the version, which bug reports need, so the menu is never empty today.
52
+ const settingsItems = [
53
+ ...(adminItems.length > 0
54
+ ? [
55
+ _jsx(Menu.Label, { children: "Settings" }, "admin"),
56
+ ...adminItems,
57
+ _jsx(Menu.Divider, {}, "divider"),
58
+ ]
59
+ : []),
60
+ _jsx(Menu.Label, { "data-testid": "settings-menu-version", children: `CanopyCMS ${CANOPYCMS_VERSION}` }, "version"),
61
+ _jsxs(Menu.Item, { component: "a", href: ABOUT_URL, target: "_blank", rel: "noopener noreferrer", "data-testid": "settings-menu-about", leftSection: _jsx(IconInfoCircle, { size: 14 }), rightSection: _jsx(IconExternalLink, { size: 14, "aria-hidden": true }), children: ["About CanopyCMS", _jsx(VisuallyHidden, { component: "span", children: " (opens in a new tab)" })] }, "about"),
62
+ ];
63
+ const commentsLabel = unresolvedCommentCount > 0 ? `Comments, ${unresolvedCommentCount} unresolved` : 'Comments';
64
+ return (_jsx(ActivityRail, { top: _jsxs(_Fragment, { children: [_jsx(RailButton, { railId: "home", label: `Open the first entry of ${siteTitle}`, icon: _jsx(Text, { component: "span", fw: 700, size: "md", "aria-hidden": true, children: monogram(siteTitle) }), tinted: true, "data-testid": "rail-home-button", onClick: onHomeOpen }), _jsx(RailButton, { railId: "content", label: "Content", icon: _jsx(IconFiles, { size: 20 }), active: contentPanelOpen, "aria-expanded": contentPanelOpen, "aria-controls": contentPanelOpen ? contentPanelId : undefined, "data-testid": "content-panel-toggle", onClick: onContentToggle }), _jsx(RailButton, { railId: "search", label: EDITOR_ACTIONS.openEntry, shortcut: quickOpenShortcut, "aria-keyshortcuts": quickOpenKeys, icon: _jsx(IconSearch, { size: 20 }), "aria-haspopup": "dialog", "data-testid": "rail-search-button", onClick: onQuickOpen }), _jsx(RailButton, { railId: "branches", label: "Branches", icon: _jsx(IconGitBranch, { size: 20 }), active: branchesOpen, "aria-haspopup": "dialog", "data-testid": "rail-branches-button", onClick: onBranchesOpen }), onCommentsOpen && (_jsx(RailButton, { railId: "comments", label: commentsLabel, icon: _jsx(IconMessageCircle, { size: 20 }), active: commentsOpen, count: unresolvedCommentCount, countTestId: "comments-unresolved-count", "aria-haspopup": "dialog", "data-testid": "comments-button", onClick: onCommentsOpen })), _jsx(RailButton, { railId: "media", label: "Media library", icon: _jsx(IconPhoto, { size: 20 }), active: mediaLibraryOpen, "aria-haspopup": "dialog", "data-testid": "media-library-button", ref: mediaButtonRef, onClick: onMediaLibraryOpen })] }), bottom: _jsxs(_Fragment, { children: [_jsx(LayoutButton, { layout: layout, onChange: onLayoutChange, blocked: layoutBlocked }), _jsx(SettingsMenu, { items: settingsItems, toggleRef: settingsToggleRef })] }), account: AccountComponent ? (_jsx(AccountComponent, {})) : onAccountClick || onLogoutClick ? (_jsxs(Stack, { gap: 6, align: "center", children: [onAccountClick && (_jsx(Tooltip, { label: "Account", position: "right", children: _jsx(ActionIcon, { variant: "subtle", color: "gray", size: "lg", radius: "md", "aria-label": "Account", onClick: onAccountClick, children: _jsx(IconUserCircle, { size: 20 }) }) })), onLogoutClick && (_jsx(Tooltip, { label: "Sign out", position: "right", children: _jsx(ActionIcon, { variant: "subtle", color: "gray", size: "lg", radius: "md", "aria-label": "Sign out", onClick: onLogoutClick, children: _jsx(IconLogout, { size: 20 }) }) }))] })) : undefined }));
65
+ }
@@ -0,0 +1,33 @@
1
+ import { type Ref } from 'react';
2
+ import type { BreadcrumbSegment } from '../editor-utils.js';
3
+ export interface EntryBreadcrumbProps {
4
+ /** The crumbs before the entry's own, from `buildBreadcrumbSegments`. */
5
+ segments: BreadcrumbSegment[];
6
+ /** The open entry's label; undefined when no entry is open. */
7
+ entryLabel: string | undefined;
8
+ /** The open entry has unsaved changes. */
9
+ entryDirty: boolean;
10
+ /** Why the open entry or its branch is read-only; set, the "Read-only" chip shows. */
11
+ readOnlyReason?: string;
12
+ onAllContent: () => void;
13
+ onCollection: (collectionPath: string) => void;
14
+ onRefresh: () => void;
15
+ onDiscard: () => void;
16
+ /** With `onDelete`, offers "Rename…" and "Delete entry"; absent where a path rule refuses them. */
17
+ onRename?: () => void;
18
+ onDelete?: () => void;
19
+ refreshBlockedReason?: string;
20
+ discardBlockedReason?: string;
21
+ /** Why the entry can't be renamed or deleted now. */
22
+ changeBlockedReason?: string;
23
+ /** The entry menu's trigger, which stands in for a menu item that opened something. */
24
+ entryMenuRef?: Ref<HTMLButtonElement>;
25
+ /** The "All content" crumb, where focus goes once the open entry is gone. */
26
+ allContentRef?: Ref<HTMLButtonElement>;
27
+ }
28
+ /**
29
+ * Where the open entry sits: "All content" opens the content panel, a collection crumb reveals
30
+ * that collection in it, and the entry's own crumb is current and opens the entry menu. Crumbs
31
+ * shrink and truncate before the entry's own does.
32
+ */
33
+ export declare function EntryBreadcrumb({ segments, entryLabel, entryDirty, readOnlyReason, onAllContent, onCollection, onRefresh, onDiscard, onRename, onDelete, refreshBlockedReason, discardBlockedReason, changeBlockedReason, entryMenuRef, allContentRef, }: EntryBreadcrumbProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Badge, Box, Button, Group, Menu, Text, Tooltip, VisuallyHidden } from '@mantine/core';
5
+ import { useMergedRef } from '@mantine/hooks';
6
+ import { IconChevronDown, IconChevronRight, IconLock } from '@tabler/icons-react';
7
+ import { EDITOR_ACTIONS } from '../copy.js';
8
+ import { DestructiveMenuItem } from './DestructiveMenuItem.js';
9
+ import { ReasonedMenuItem } from './ReasonedMenuItem.js';
10
+ import { useMenuItemFocusReturn } from '../utils/focus-return.js';
11
+ /** Whether the element's text is cut short, kept current as it resizes. */
12
+ function useTruncated(label) {
13
+ const ref = useRef(null);
14
+ const [truncated, setTruncated] = useState(false);
15
+ useEffect(() => {
16
+ const el = ref.current;
17
+ if (!el)
18
+ return;
19
+ const check = () => setTruncated(el.scrollWidth > el.clientWidth);
20
+ check();
21
+ const observer = new ResizeObserver(check);
22
+ observer.observe(el);
23
+ return () => observer.disconnect();
24
+ }, [label]);
25
+ return [ref, truncated];
26
+ }
27
+ const crumbText = (label, ref) => (_jsx(Text, { ref: ref, span: true, inherit: true, truncate: "end", display: "block", children: label }));
28
+ /** A cut-short crumb shows its full label in a Tooltip, on hover and on focus. */
29
+ function CrumbTooltip({ label, truncated, children, }) {
30
+ return (_jsx(Tooltip, { label: label, disabled: !truncated, events: { hover: true, focus: true, touch: false }, children: children }));
31
+ }
32
+ function CollectionCrumb({ label, onClick, buttonRef, }) {
33
+ const [ref, truncated] = useTruncated(label);
34
+ return (_jsx(CrumbTooltip, { label: label, truncated: truncated, children: _jsx(Button, { ref: buttonRef, variant: "subtle", color: "gray", size: "compact-sm", fw: 400, miw: 0, maw: 180, onClick: onClick, "data-testid": "breadcrumb-segment", styles: { label: { minWidth: 0 } }, children: crumbText(label, ref) }) }));
35
+ }
36
+ const separator = (_jsx(IconChevronRight, { size: 14, "aria-hidden": "true", style: { flexShrink: 0, color: 'var(--mantine-color-dimmed)' } }));
37
+ /**
38
+ * Where the open entry sits: "All content" opens the content panel, a collection crumb reveals
39
+ * that collection in it, and the entry's own crumb is current and opens the entry menu. Crumbs
40
+ * shrink and truncate before the entry's own does.
41
+ */
42
+ export function EntryBreadcrumb({ segments, entryLabel, entryDirty, readOnlyReason, onAllContent, onCollection, onRefresh, onDiscard, onRename, onDelete, refreshBlockedReason, discardBlockedReason, changeBlockedReason, entryMenuRef, allContentRef, }) {
43
+ const trigger = useRef(null);
44
+ const triggerRef = useMergedRef(trigger, entryMenuRef);
45
+ const menuFocus = useMenuItemFocusReturn(trigger);
46
+ const [entryLabelRef, entryTruncated] = useTruncated(entryLabel ?? '');
47
+ return (_jsxs(Group, { gap: "xs", wrap: "nowrap", miw: 0, children: [_jsx(Box, { component: "nav", "aria-label": "Breadcrumb", miw: 0,
48
+ // Clips crumbs that overflow, padded by the focus ring's reach so the ring is not clipped.
49
+ style: { flex: '0 1 auto', overflow: 'hidden', padding: 4, margin: -4 }, children: _jsxs(Group, { component: "ol", gap: 2, wrap: "nowrap", miw: 0, style: { listStyle: 'none', margin: 0, padding: 0 }, children: [segments.map((segment, index) => (_jsxs(Group, { component: "li", gap: 2, wrap: "nowrap", miw: 0,
50
+ // Collections give up their width long before the entry's own crumb does.
51
+ style: { flex: '0 100 auto' }, children: [index > 0 && separator, segment.folder ? (_jsx(Text, { size: "sm", c: "dimmed", px: 4, miw: 0, truncate: "end", children: segment.label })) : (_jsx(CollectionCrumb, { label: segment.label, buttonRef: index === 0 ? allContentRef : undefined, onClick: () => segment.collectionPath ? onCollection(segment.collectionPath) : onAllContent() }))] }, `${segment.label}-${index}`))), _jsxs(Group, { component: "li", gap: 2, wrap: "nowrap", miw: 64, style: { flex: '0 1 auto' }, children: [separator, entryLabel === undefined ? (_jsx(Text, { size: "sm", c: "dimmed", px: 4, "data-testid": "file-dropdown-button", children: "No entry open" })) : (_jsxs(Menu, { withinPortal: true, shadow: "sm", position: "bottom-start", ...menuFocus.menu, children: [_jsx(CrumbTooltip, { label: entryLabel, truncated: entryTruncated, children: _jsx(Menu.Target, { children: _jsxs(Button, { ref: triggerRef, variant: "subtle", color: "gray", size: "compact-sm", fw: 600, miw: 0, maw: "100%", "aria-current": "page", rightSection: _jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), "data-testid": "file-dropdown-button", styles: { label: { minWidth: 0, gap: 6 } }, children: [crumbText(entryLabel, entryLabelRef), entryDirty && (_jsxs(_Fragment, { children: [_jsx(Box, { component: "span", "aria-hidden": "true", w: 8, h: 8, style: {
52
+ borderRadius: '50%',
53
+ flexShrink: 0,
54
+ background: 'var(--mantine-color-text)',
55
+ }, "data-testid": "entry-unsaved-dot" }), _jsx(VisuallyHidden, { component: "span", children: ", unsaved changes" })] }))] }) }) }), _jsxs(Menu.Dropdown, { ...menuFocus.dropdown, children: [_jsx(ReasonedMenuItem, { onClick: onRefresh, blockedReason: refreshBlockedReason, "data-testid": "refresh-entry-menu-item", children: EDITOR_ACTIONS.refreshFromServer }), _jsx(ReasonedMenuItem, { "data-testid": "discard-file-draft-menu-item", onClick: onDiscard, blockedReason: discardBlockedReason, children: "Discard changes" }), onRename && onDelete && (_jsxs(_Fragment, { children: [_jsx(Menu.Divider, {}), _jsx(ReasonedMenuItem, { onClick: onRename, blockedReason: changeBlockedReason, "data-testid": "entry-menu-rename", children: "Rename\u2026" }), _jsx(DestructiveMenuItem, { onClick: onDelete, blockedReason: changeBlockedReason, "data-testid": "entry-menu-delete", children: "Delete entry" })] }))] })] }))] })] }) }), readOnlyReason && (_jsx(Tooltip, { label: readOnlyReason, multiline: true, maw: 300, children: _jsxs(Badge, { variant: "light", color: "gray", radius: "sm", leftSection: _jsx(IconLock, { size: 12, "aria-hidden": "true" }), style: { flexShrink: 0, textTransform: 'none' }, "data-testid": "read-only-chip", children: ["Read-only", _jsxs(VisuallyHidden, { component: "span", children: [". ", readOnlyReason] })] }) }))] }));
56
+ }
@@ -1,21 +1,26 @@
1
+ import type { EntryTypeUnavailable } from '../../config/index.js';
1
2
  export interface EntryType {
2
3
  name: string;
3
4
  label?: string;
4
5
  format: 'json' | 'md' | 'mdx' | 'yaml';
5
6
  default?: boolean;
6
7
  maxItems?: number;
8
+ /** An unavailable type is never offered: it has no schema to create an entry against. */
9
+ unavailable?: EntryTypeUnavailable;
10
+ /** Data path of the field that titles an entry of this type; its presence adds the Title input. */
11
+ titleField?: readonly string[];
7
12
  }
8
13
  export interface EntryCreateModalProps {
9
14
  /** Whether the modal is open */
10
15
  isOpen: boolean;
11
16
  /** Collection name for display */
12
17
  collectionLabel: string;
13
- /** Available entry types (if multiple, show selector) */
18
+ /** The collection's entry types (if more than one is available, show selector) */
14
19
  entryTypes: EntryType[];
15
20
  /** Pre-selected entry type (if specified) */
16
21
  selectedEntryTypeName?: string;
17
- /** Called when create is clicked */
18
- onCreate: (slug: string, entryTypeName: string) => Promise<void>;
22
+ /** Called when the form is submitted; `title` is given only for a type with a `titleField`. */
23
+ onCreate: (slug: string, entryTypeName: string, title?: string) => Promise<void>;
19
24
  /** Called when modal is closed */
20
25
  onClose: () => void;
21
26
  /** Whether a create operation is in progress */
@@ -25,10 +30,10 @@ export interface EntryCreateModalProps {
25
30
  /**
26
31
  * Slugs already taken in the target collection (from the already-loaded
27
32
  * entries list). Lets the client flag an obvious collision early with a
28
- * clear message; the server is still the authority, and without this
29
- * check an entry type with no required fields would silently succeed via
30
- * an update path instead of erroring.
33
+ * clear message; the server is still the authority.
31
34
  */
32
35
  existingSlugs?: Set<string>;
36
+ /** The URL path the entry will be served at, shown under the slug; undefined when it has no page. */
37
+ previewPath?: (slug: string, entryTypeName: string) => string | undefined;
33
38
  }
34
- export declare function EntryCreateModal({ isOpen, collectionLabel, entryTypes, selectedEntryTypeName, onCreate, onClose, isCreating, error, existingSlugs, }: EntryCreateModalProps): import("react/jsx-runtime").JSX.Element;
39
+ export declare function EntryCreateModal({ isOpen, collectionLabel, entryTypes: allEntryTypes, selectedEntryTypeName, onCreate, onClose, isCreating, error, existingSlugs, previewPath, }: EntryCreateModalProps): import("react/jsx-runtime").JSX.Element;
@@ -1,24 +1,36 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /** Modal for creating a new entry: replaces window.prompt with entry-type selection, slug entry, and validation. */
3
+ /** Modal for adding an entry: entry-type selection, a title that seeds the slug, and the slug. */
4
4
  import { useState, useEffect, useRef } from 'react';
5
5
  import { Modal, Stack, TextInput, Group, Button, Alert, Text, Select } from '@mantine/core';
6
6
  import { IconAlertCircle } from '@tabler/icons-react';
7
- import { parseSlug } from '../../paths/validation.js';
8
- /** Slug the form is seeded with each time the modal opens. */
9
- const DEFAULT_SLUG = 'untitled';
10
- export function EntryCreateModal({ isOpen, collectionLabel, entryTypes, selectedEntryTypeName, onCreate, onClose, isCreating = false, error = null, existingSlugs, }) {
7
+ import { slugFromTitle, validateEntrySlug } from '../slug.js';
8
+ import { unavailableSchemaRefs } from '../unavailable-entry-type.js';
9
+ import { UnavailableTypeMessage } from './UnavailableTypeMessage.js';
10
+ export function EntryCreateModal({ isOpen, collectionLabel, entryTypes: allEntryTypes, selectedEntryTypeName, onCreate, onClose, isCreating = false, error = null, existingSlugs, previewPath, }) {
11
+ const entryTypes = allEntryTypes.filter((et) => !et.unavailable);
12
+ const skippedSchemaRefs = unavailableSchemaRefs(allEntryTypes);
13
+ // Only when nothing is left to create: a collection with some usable types shows them alone.
14
+ const nothingToCreate = entryTypes.length === 0 && skippedSchemaRefs.length > 0;
15
+ // A collection that holds only collections has no entry type to add, available or not.
16
+ const noEntryTypes = allEntryTypes.length === 0;
11
17
  const getDefaultEntryTypeName = () => {
12
- if (selectedEntryTypeName)
18
+ const selectedIsUnavailable = allEntryTypes.some((et) => et.name === selectedEntryTypeName && et.unavailable);
19
+ if (selectedEntryTypeName && !selectedIsUnavailable)
13
20
  return selectedEntryTypeName;
14
21
  if (entryTypes.length === 1)
15
22
  return entryTypes[0].name;
16
23
  const defaultType = entryTypes.find((et) => et.default);
17
24
  return defaultType?.name || entryTypes[0]?.name || '';
18
25
  };
19
- const [slug, setSlug] = useState(DEFAULT_SLUG);
26
+ const [title, setTitle] = useState('');
27
+ const [slug, setSlug] = useState('');
28
+ // Once the slug is typed by hand, the title stops rewriting it.
29
+ const [slugEdited, setSlugEdited] = useState(false);
30
+ const [titleTouched, setTitleTouched] = useState(false);
31
+ const [slugTouched, setSlugTouched] = useState(false);
32
+ const [submitAttempted, setSubmitAttempted] = useState(false);
20
33
  const [entryTypeName, setEntryTypeName] = useState(getDefaultEntryTypeName());
21
- const [validationError, setValidationError] = useState(null);
22
34
  // Seeds the form once per open, on the closed -> open transition. Keys on
23
35
  // `isOpen` alone, not on `entryTypes`/`selectedEntryTypeName`: `entryTypes`
24
36
  // is an array callers build inline, a fresh identity on every parent
@@ -28,65 +40,47 @@ export function EntryCreateModal({ isOpen, collectionLabel, entryTypes, selected
28
40
  const wasOpenRef = useRef(false);
29
41
  useEffect(() => {
30
42
  if (isOpen && !wasOpenRef.current) {
31
- setSlug(DEFAULT_SLUG);
43
+ setTitle('');
44
+ setSlug('');
45
+ setSlugEdited(false);
46
+ setTitleTouched(false);
47
+ setSlugTouched(false);
48
+ setSubmitAttempted(false);
32
49
  setEntryTypeName(getDefaultEntryTypeName());
33
- setValidationError(null);
34
50
  }
35
51
  wasOpenRef.current = isOpen;
36
52
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
53
  }, [isOpen]);
38
- // Validate slug format.
39
- //
40
- // Delegates to `parseSlug` rather than restating its rule: the server refuses to CREATE an
41
- // entry whose slug fails it (api/content.ts and ContentStore.write's [SLUG] guards), and the
42
- // static build fails outright on one that slipped through (static/index.ts's
43
- // assertRoutableSlugs). A second, hand-maintained copy of the charset/length/separator rules
44
- // here could only ever drift out of agreement with the boundary that actually decides.
45
- //
46
- // Imported from '../../paths/validation.js' directly, NOT the `paths` barrel, which pulls
47
- // node:fs into the browser bundle (pnpm lint:bundle enforces this).
48
- const validateSlug = (value) => {
49
- if (!value.trim()) {
50
- return 'Slug cannot be empty';
51
- }
52
- const parsed = parseSlug(value);
53
- if (!parsed.ok) {
54
- return parsed.error;
55
- }
56
- // `parseSlug` lowercases before testing, so it accepts 'My-Post' and hands back 'my-post'.
57
- // Keep the field itself strict: the value typed here is what the duplicate check below and
58
- // the create request are keyed on, so silently normalizing would let 'My-Post' sail past a
59
- // collision with an existing 'my-post' and land as a 409 from the server instead of an
60
- // inline message here.
61
- if (parsed.slug !== value) {
62
- return 'Slug must be lowercase';
63
- }
64
- if (existingSlugs?.has(value)) {
65
- return 'An entry with this slug already exists';
66
- }
67
- return null;
54
+ const selectedType = entryTypes.find((et) => et.name === entryTypeName);
55
+ const typeLabel = selectedType?.label || selectedType?.name || '';
56
+ const hasTitle = !!selectedType?.titleField;
57
+ // Errors stay quiet until the user has typed, so a fresh form does not open on a red field.
58
+ const showErrors = submitAttempted || titleTouched || slugTouched;
59
+ const slugError = validateEntrySlug(slug, { existingSlugs });
60
+ const titleError = hasTitle && !title.trim() ? 'Enter a title' : null;
61
+ const handleTitleChange = (value) => {
62
+ setTitle(value);
63
+ setTitleTouched(true);
64
+ if (!slugEdited)
65
+ setSlug(slugFromTitle(value));
68
66
  };
69
67
  const handleSlugChange = (value) => {
70
68
  setSlug(value);
71
- setValidationError(validateSlug(value));
69
+ setSlugEdited(true);
70
+ setSlugTouched(true);
72
71
  };
73
- const handleCreate = async () => {
74
- const validation = validateSlug(slug);
75
- if (validation) {
76
- setValidationError(validation);
72
+ const handleSubmit = async (event) => {
73
+ event.preventDefault();
74
+ if (isCreating || !entryTypeName)
77
75
  return;
78
- }
79
- if (!entryTypeName) {
80
- setValidationError('Please select an entry type');
76
+ setSubmitAttempted(true);
77
+ if (slugError || titleError)
81
78
  return;
82
- }
83
- await onCreate(slug, entryTypeName);
79
+ await onCreate(slug, entryTypeName, hasTitle ? title.trim() : undefined);
84
80
  };
85
- const canCreate = !validationError && slug.trim() !== '' && entryTypeName !== '' && !isCreating;
86
- const selectedType = entryTypes.find((et) => et.name === entryTypeName);
87
- const typeLabel = selectedType?.label || selectedType?.name || '';
88
- return (_jsx(Modal, { opened: isOpen, onClose: onClose, title: _jsx(Text, { fw: 600, children: "Create New Entry" }), size: "md", closeOnClickOutside: !isCreating, closeOnEscape: !isCreating, children: _jsxs(Stack, { gap: "md", "data-testid": "create-entry-modal", children: [error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", variant: "light", children: error })), _jsxs(Text, { size: "sm", c: "dimmed", children: ["Creating in: ", collectionLabel] }), entryTypes.length > 1 && (_jsx(Select, { label: "Entry Type", description: "Select the type of entry to create", value: entryTypeName, onChange: (value) => setEntryTypeName(value || ''), data: entryTypes.map((et) => ({
89
- value: et.name,
90
- label: et.label || et.name,
91
- })), required: true, disabled: isCreating, searchable: true, placeholder: "Select entry type..." })), entryTypes.length === 1 && (_jsxs(Text, { size: "sm", c: "dimmed", children: ["Entry type: ", _jsx("strong", { children: typeLabel })] })), _jsx(TextInput, { label: "Slug", description: "URL-friendly identifier (lowercase, alphanumeric + hyphens)", value: slug, onChange: (e) => handleSlugChange(e.currentTarget.value), error: validationError, placeholder: "my-entry-slug", required: true, disabled: isCreating, "data-autofocus": true, "data-testid": "entry-slug-input" }), _jsxs(Text, { size: "xs", c: "dimmed", children: ["Preview: ", _jsx("code", { children: slug })] }), _jsxs(Group, { justify: "flex-end", gap: "sm", children: [_jsx(Button, { variant: "subtle", onClick: onClose, disabled: isCreating, children: "Cancel" }), _jsx(Button, { onClick: handleCreate, loading: isCreating, disabled: !canCreate, "data-testid": "create-entry-submit", children: "Create" })] })] }) }));
81
+ const slugPath = previewPath?.(slug || 'your-slug', entryTypeName);
82
+ return (_jsx(Modal, { opened: isOpen, onClose: onClose, title: _jsxs(Text, { fw: 600, children: ["Add entry to ", collectionLabel] }), size: "md", closeOnClickOutside: !isCreating, closeOnEscape: !isCreating, children: noEntryTypes ? (_jsxs(Stack, { gap: "md", "data-testid": "create-entry-modal", children: [_jsxs(Alert, { color: "yellow", icon: _jsx(IconAlertCircle, { size: 16 }), "data-testid": "no-entry-types", children: [collectionLabel, " holds only collections, so there is no entry type to add."] }), _jsx(Group, { justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "subtle", onClick: onClose, children: "Close" }) })] })) : (_jsx("form", { onSubmit: handleSubmit, noValidate: true, children: _jsxs(Stack, { gap: "md", "data-testid": "create-entry-modal", children: [error && (_jsx(Alert, { icon: _jsx(IconAlertCircle, { size: 16 }), color: "red", variant: "light", children: error })), nothingToCreate && (_jsx(Alert, { color: "yellow", icon: _jsx(IconAlertCircle, { size: 16 }), "data-testid": "no-creatable-types", children: _jsx(UnavailableTypeMessage, { schemaRefs: skippedSchemaRefs }) })), entryTypes.length > 1 && (_jsx(Select, { label: "Entry type", value: entryTypeName, onChange: (value) => setEntryTypeName(value || ''), data: entryTypes.map((et) => ({
83
+ value: et.name,
84
+ label: et.label || et.name,
85
+ })), required: true, disabled: isCreating, searchable: true, placeholder: "Select an entry type" })), entryTypes.length === 1 && (_jsxs(Text, { size: "sm", c: "dimmed", children: ["Entry type: ", _jsx("strong", { children: typeLabel })] })), hasTitle && (_jsx(TextInput, { label: "Title", value: title, onChange: (e) => handleTitleChange(e.currentTarget.value), error: showErrors ? titleError : null, required: true, disabled: isCreating, "data-autofocus": true, "data-testid": "entry-title-input" })), _jsx(TextInput, { label: "Slug", description: slugPath ? `URL path: ${slugPath}` : 'Lowercase letters, numbers and hyphens', value: slug, onChange: (e) => handleSlugChange(e.currentTarget.value), error: showErrors ? slugError : null, placeholder: "my-entry-slug", required: true, disabled: isCreating, "data-autofocus": hasTitle ? undefined : true, "data-testid": "entry-slug-input" }), _jsxs(Group, { justify: "flex-end", gap: "sm", children: [_jsx(Button, { size: "sm", variant: "subtle", onClick: onClose, disabled: isCreating, children: "Cancel" }), _jsx(Button, { size: "sm", type: "submit", loading: isCreating, disabled: !entryTypeName, "data-testid": "create-entry-submit", children: "Add entry" })] })] }) })) }));
92
86
  }
@@ -1,10 +1,11 @@
1
1
  export interface NoEditPermissionNoticeProps {
2
- /** Logical path of the entry the user cannot edit. */
3
- entryPath: string;
2
+ /** Label of the entry the user cannot edit. */
3
+ entryLabel: string;
4
4
  }
5
5
  /**
6
6
  * Explains why an entry cannot be edited and whom to ask. An entry's `canEdit` is false only
7
- * when a path rule (or `defaultPathAccess`) denies edit, so that is the reason it gives; a
8
- * locked or protected branch shows its own banner in the header and a read-only form instead.
7
+ * when a path rule (or `defaultPathAccess`) denies edit, so that is the reason it gives. A locked
8
+ * or protected branch instead shows its banner in the header over a read-only form, the rule
9
+ * `contentReadOnly` in `Editor.tsx` holds.
9
10
  */
10
- export declare function NoEditPermissionNotice({ entryPath }: NoEditPermissionNoticeProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function NoEditPermissionNotice({ entryLabel }: NoEditPermissionNoticeProps): import("react/jsx-runtime").JSX.Element;
@@ -2,10 +2,11 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Text } from '@mantine/core';
3
3
  /**
4
4
  * Explains why an entry cannot be edited and whom to ask. An entry's `canEdit` is false only
5
- * when a path rule (or `defaultPathAccess`) denies edit, so that is the reason it gives; a
6
- * locked or protected branch shows its own banner in the header and a read-only form instead.
5
+ * when a path rule (or `defaultPathAccess`) denies edit, so that is the reason it gives. A locked
6
+ * or protected branch instead shows its banner in the header over a read-only form, the rule
7
+ * `contentReadOnly` in `Editor.tsx` holds.
7
8
  */
8
- export function NoEditPermissionNotice({ entryPath }) {
9
+ export function NoEditPermissionNotice({ entryLabel }) {
9
10
  return (_jsx("div", { role: "status", "data-testid": "no-edit-permission-notice", style: {
10
11
  display: 'flex',
11
12
  alignItems: 'center',
@@ -13,5 +14,5 @@ export function NoEditPermissionNotice({ entryPath }) {
13
14
  height: '100%',
14
15
  padding: '0 var(--mantine-spacing-md)',
15
16
  textAlign: 'center',
16
- }, children: _jsxs(Text, { size: "sm", c: "dimmed", children: ["You don't have edit access to \"", entryPath, "\". Ask a CanopyCMS admin to grant it in Manage Permissions."] }) }));
17
+ }, children: _jsxs(Text, { size: "sm", c: "dimmed", children: ["You don't have edit access to \"", entryLabel, "\". Ask a CanopyCMS admin to grant it in \"Manage permissions\"."] }) }));
17
18
  }
@@ -0,0 +1,22 @@
1
+ /** @internal Exported for tests. Marks an overlay Mod+P may open over: the palette's own closing dialog. */
2
+ export declare const QUICK_OPEN_REPLACES_ATTR = "data-quick-open-replaces";
3
+ /** @internal Exported for tests and stories. */
4
+ export interface QuickOpenEntry {
5
+ path: string;
6
+ label: string;
7
+ slug?: string;
8
+ collectionPath?: string;
9
+ collectionName?: string;
10
+ }
11
+ export interface QuickOpenPaletteProps {
12
+ opened: boolean;
13
+ onOpen: () => void;
14
+ onClose: () => void;
15
+ entries: ReadonlyArray<QuickOpenEntry>;
16
+ /** Display name for a collection heading; the collection's own name when absent, none when ''. */
17
+ collectionLabel?: (collectionPath: string) => string | undefined;
18
+ onSelect: (path: string) => void;
19
+ /** Names the element that takes focus after the palette closes, for a caller that unmounted the opener. */
20
+ returnFocusTo?: () => HTMLElement | null;
21
+ }
22
+ export declare function QuickOpenPalette({ opened, onOpen, onClose, entries, collectionLabel, onSelect, returnFocusTo, }: QuickOpenPaletteProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /** Mod+P palette: type part of an entry's title or slug, pick it with the keyboard or mouse. */
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ import { Combobox, Highlight, Modal, ScrollArea, Text, TextInput, useCombobox } from '@mantine/core';
6
+ import { useHotkeys } from '@mantine/hooks';
7
+ import { IconSearch } from '@tabler/icons-react';
8
+ import { filterEntries, groupEntriesByCollection } from '../entry-search.js';
9
+ import { modKeyLabel } from '../platform.js';
10
+ import { EDITOR_ACTIONS } from '../copy.js';
11
+ import { FOCUS_RETURN_ATTR, focusIfIdle } from '../utils/focus-return.js';
12
+ /** @internal Exported for tests. Marks an overlay Mod+P may open over: the palette's own closing dialog. */
13
+ export const QUICK_OPEN_REPLACES_ATTR = 'data-quick-open-replaces';
14
+ /**
15
+ * True while a modal or drawer (not a popover) is open that `QUICK_OPEN_REPLACES_ATTR` doesn't mark.
16
+ * Overlays stack in DOM order and the palette's portal precedes the editor's other dialogs, so it
17
+ * would open hidden under them and take the keyboard from the one in use.
18
+ */
19
+ const otherOverlayOpen = () => Array.from(document.querySelectorAll('[role="dialog"][aria-modal="true"]')).some((dialog) => !dialog.closest(`[${QUICK_OPEN_REPLACES_ATTR}]`));
20
+ /** Options rendered at once; the rest are reached by typing more. */
21
+ const MAX_OPTIONS = 50;
22
+ export function QuickOpenPalette({ opened, onOpen, onClose, entries, collectionLabel, onSelect, returnFocusTo, }) {
23
+ // `[]` ignores no tags and `true` fires from contenteditable, so the shortcut works from inside
24
+ // a text field or the MDX editor.
25
+ useHotkeys([
26
+ [
27
+ 'mod+P',
28
+ (event) => {
29
+ // Mantine's `mod` is Ctrl or ⌘ everywhere; on macOS Ctrl+P moves the caret up a line.
30
+ if (modKeyLabel() === '⌘' && !event.metaKey)
31
+ return;
32
+ // Keeps the browser's print dialog shut, even when the palette can't open.
33
+ event.preventDefault();
34
+ if (!otherOverlayOpen())
35
+ onOpen();
36
+ },
37
+ { preventDefault: false },
38
+ ],
39
+ ], [], true);
40
+ const [query, setQuery] = useState('');
41
+ const [wasOpened, setWasOpened] = useState(opened);
42
+ if (opened !== wasOpened) {
43
+ setWasOpened(opened);
44
+ if (!opened)
45
+ setQuery('');
46
+ }
47
+ // Always open: the options are the palette's body, not a dropdown below a field.
48
+ const combobox = useCombobox({ opened: true });
49
+ const { selectFirstOption } = combobox;
50
+ const matches = useMemo(() => filterEntries(entries, query), [entries, query]);
51
+ const shown = useMemo(() => matches.slice(0, MAX_OPTIONS), [matches]);
52
+ const groups = useMemo(() => groupEntriesByCollection(shown, collectionLabel), [shown, collectionLabel]);
53
+ // Enter right after typing opens the best match, so the first option is always armed. Deferred
54
+ // a tick: on opening, the modal's portal and the options list mount after this effect.
55
+ useEffect(() => {
56
+ if (!opened)
57
+ return;
58
+ const timer = setTimeout(selectFirstOption, 0);
59
+ return () => clearTimeout(timer);
60
+ }, [opened, query, selectFirstOption]);
61
+ const handleSubmit = (path) => {
62
+ onSelect(path);
63
+ onClose();
64
+ };
65
+ return (_jsx(Modal, { opened: opened, onClose: onClose, title: EDITOR_ACTIONS.openEntry, size: "lg", yOffset: "10vh", returnFocus: !returnFocusTo, onExitTransitionEnd: () => returnFocusTo && focusIfIdle(returnFocusTo()), [QUICK_OPEN_REPLACES_ATTR]: true, [FOCUS_RETURN_ATTR]: true, children: _jsxs(Combobox, { store: combobox, onOptionSubmit: handleSubmit, children: [_jsx(Combobox.EventsTarget, { withExpandedAttribute: true, "data-mantine-stop-propagation": false, children: _jsx(TextInput, { role: "combobox", "aria-label": "Search entries", "aria-autocomplete": "list", placeholder: "Type an entry title or slug", leftSection: _jsx(IconSearch, { size: 14, "aria-hidden": true }), value: query, onChange: (event) => setQuery(event.currentTarget.value), "data-autofocus": true, "data-testid": "quick-open-input" }) }), _jsx(Combobox.Options, { mt: "sm", "aria-label": "Entries", children: _jsx(ScrollArea.Autosize, { mah: 360, type: "scroll", scrollbars: "y", children: matches.length === 0 ? (_jsxs(Text, { size: "sm", c: "dimmed", p: "xs", "data-testid": "quick-open-empty", children: ["No entries match \u201C", query, "\u201D."] })) : (groups.map((group, index) => (_jsx(Combobox.Group, { label: group.heading || undefined, children: group.entries.map((entry) => (_jsxs(Combobox.Option, { value: entry.path, "data-testid": "quick-open-option", children: [_jsx(Highlight, { highlight: query.trim(), size: "sm", children: entry.label }), entry.slug && _jsx(Text, { size: "xs", children: entry.slug })] }, entry.path))) }, index)))) }) }), matches.length > MAX_OPTIONS && (_jsxs(Text, { size: "xs", c: "dimmed", mt: "xs", children: ["Showing ", MAX_OPTIONS, " of ", matches.length, ". Keep typing to narrow the list."] }))] }) }));
66
+ }
@@ -0,0 +1,11 @@
1
+ export interface ReadOnlyDraftNoticeProps {
2
+ /** The branch never becomes editable again: the protected base branch, or an archived one. */
3
+ permanentlyReadOnly: boolean;
4
+ /** Discards the entry's kept draft; the caller confirms first, since the draft is unsaved work. */
5
+ onDiscard: () => void;
6
+ }
7
+ /**
8
+ * Says that this entry has a draft the read-only form is not showing. The draft is kept until it
9
+ * is discarded, so it comes back as the editable value if the branch unlocks.
10
+ */
11
+ export declare function ReadOnlyDraftNotice({ permanentlyReadOnly, onDiscard }: ReadOnlyDraftNoticeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Alert, Button, Group, Stack, Text } from '@mantine/core';
3
+ import { IconAlertTriangle } from '@tabler/icons-react';
4
+ /**
5
+ * Says that this entry has a draft the read-only form is not showing. The draft is kept until it
6
+ * is discarded, so it comes back as the editable value if the branch unlocks.
7
+ */
8
+ export function ReadOnlyDraftNotice({ permanentlyReadOnly, onDiscard }) {
9
+ return (_jsx(Alert, { role: "status", icon: _jsx(IconAlertTriangle, { size: 16 }), color: "yellow", variant: "light", "data-testid": "read-only-draft-notice", children: _jsxs(Group, { justify: "space-between", align: "center", gap: "sm", wrap: "wrap", children: [_jsxs(Stack, { gap: 2, children: [_jsx(Text, { size: "sm", children: "You have unsaved changes from earlier on this read-only branch." }), _jsx(Text, { size: "sm", children: permanentlyReadOnly
10
+ ? "This branch won't become editable, so they can only be discarded."
11
+ : "They're kept on this device and come back when the branch is editable again." })] }), _jsx(Button, { variant: "default", onClick: onDiscard, children: "Discard changes" })] }) }));
12
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { type MenuItemProps } from '@mantine/core';
3
+ export interface ReasonedMenuItemProps extends MenuItemProps {
4
+ /** Why the item can't act now. Set, the item is blocked and shows this beneath its label. */
5
+ blockedReason?: string;
6
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
7
+ 'data-testid'?: string;
8
+ children: React.ReactNode;
9
+ }
10
+ /**
11
+ * A menu item that stays visible when it cannot act, with its reason beneath the label. Mantine's
12
+ * Menu drops a `disabled` or `data-disabled` item from its arrow-key order, hiding the reason from
13
+ * a keyboard user; a blocked item is `aria-disabled` instead and ignores its click.
14
+ */
15
+ export declare const ReasonedMenuItem: React.FC<ReasonedMenuItemProps>;
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Menu, Text } from '@mantine/core';
4
+ /**
5
+ * A menu item that stays visible when it cannot act, with its reason beneath the label. Mantine's
6
+ * Menu drops a `disabled` or `data-disabled` item from its arrow-key order, hiding the reason from
7
+ * a keyboard user; a blocked item is `aria-disabled` instead and ignores its click.
8
+ */
9
+ export const ReasonedMenuItem = ({ blockedReason, onClick, children, color, style, ...rest }) => (_jsxs(Menu.Item, { ...rest, color: blockedReason ? undefined : color, c: blockedReason ? 'dimmed' : rest.c, "aria-disabled": blockedReason ? true : undefined, closeMenuOnClick: blockedReason ? false : undefined, style: blockedReason ? { ...style, cursor: 'not-allowed' } : style, onClick: (event) => {
10
+ if (blockedReason) {
11
+ event.stopPropagation();
12
+ return;
13
+ }
14
+ onClick?.(event);
15
+ }, children: [children, blockedReason && (_jsx(Text, { size: "xs", c: "dimmed", component: "span", display: "block", style: { whiteSpace: 'normal' }, children: blockedReason }))] }));
@@ -0,0 +1,11 @@
1
+ import type { EntryReferencedBy } from '../../api/entries.js';
2
+ import type { LogicalPath } from '../../paths/types.js';
3
+ export interface ReferencedByListProps {
4
+ referencedBy: EntryReferencedBy;
5
+ /** Opens a referencing entry in the editor. */
6
+ onOpenEntry: (entryPath: LogicalPath) => void;
7
+ }
8
+ /** The delete dialog's message once the server has refused a delete over references. */
9
+ export declare function referencedDeleteMessage({ entries, hiddenCount }: EntryReferencedBy): string;
10
+ /** The entries a delete would leave pointing at nothing, as far as the user may see them. */
11
+ export declare function ReferencedByList({ referencedBy, onOpenEntry }: ReferencedByListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Anchor, Box, Stack, Text } from '@mantine/core';
4
+ // `anywhere`, unlike `break-word`, also lowers the min-content width, so an unbroken
5
+ // title or slug narrows to the dialog instead of widening it into a horizontal scroll.
6
+ const wrapAnywhere = { overflowWrap: 'anywhere' };
7
+ const pluralEntries = (n) => `${n} ${n === 1 ? 'entry' : 'entries'}`;
8
+ /** The delete dialog's message once the server has refused a delete over references. */
9
+ export function referencedDeleteMessage({ entries, hiddenCount }) {
10
+ const count = entries.length + hiddenCount;
11
+ return (`This entry is referenced by ${count} other ${count === 1 ? 'entry' : 'entries'}. ` +
12
+ `Deleting it leaves ${count === 1 ? 'that reference' : 'those references'} pointing at ` +
13
+ 'nothing. This cannot be undone.');
14
+ }
15
+ /** The entries a delete would leave pointing at nothing, as far as the user may see them. */
16
+ export function ReferencedByList({ referencedBy, onOpenEntry }) {
17
+ const { entries, hiddenCount } = referencedBy;
18
+ // Not Mantine's `List`: its `nowrap` column `inline-flex` items cannot narrow to the dialog.
19
+ return (_jsxs(Stack, { component: "ul", gap: 6, m: 0, p: 0, style: { listStyle: 'none' },
20
+ // WebKit drops the list role from a `ul` styled `list-style: none`.
21
+ role: "list", "data-testid": "referenced-by-list", children: [entries.map((entry) => {
22
+ const via = [
23
+ ...(entry.fields.length > 0 ? [entry.fields.join(', ')] : []),
24
+ ...(entry.links.length > 0 ? [`linked from ${entry.links.join(', ')}`] : []),
25
+ ].join('; ');
26
+ return (_jsxs(Box, { component: "li", children: [_jsx(Anchor, { component: "button", type: "button", size: "sm", ta: "start", style: wrapAnywhere, onClick: () => onOpenEntry(entry.entryPath), children: entry.title }), _jsxs(Text, { size: "xs", c: "dimmed", style: wrapAnywhere, children: ["(", via, ")"] })] }, entry.entryPath));
27
+ }), hiddenCount > 0 && (_jsx(Box, { component: "li", children: _jsxs(Text, { size: "sm", c: "dimmed", children: [entries.length > 0 ? 'and ' : '', pluralEntries(hiddenCount), " you can't view"] }) }))] }));
28
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { type TextProps } from '@mantine/core';
3
+ export interface RelativeTimeProps extends Pick<TextProps, 'size' | 'c' | 'fs'> {
4
+ /** An ISO timestamp. */
5
+ iso: string;
6
+ /** Text before the label, such as "Updated ". */
7
+ prefix?: string;
8
+ }
9
+ /** A relative label; the absolute date is a tooltip, and visually hidden text for a screen reader. */
10
+ export declare const RelativeTime: React.FC<RelativeTimeProps>;