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
@@ -4,20 +4,38 @@
4
4
  * Converts a single entry's data + schema fields to clean markdown.
5
5
  * Handles all CanopyCMS field types including nested objects and blocks.
6
6
  */
7
+ import { RESTRICTED_REFERENCE_MARKER } from '../entry-schema.js';
7
8
  import { flattenGroupFields } from '../utils/flatten-group-fields.js';
9
+ import { resolveEntryTitle } from '../utils/title-field.js';
8
10
  import { stripMdxImports } from './strip-mdx.js';
9
11
  import { applyComponentTransforms } from './transform-components.js';
12
+ const DEFAULT_REFERENCE_RENDERING = {
13
+ title: (target) => resolveEntryTitle(target, { slug: typeof target.slug === 'string' ? target.slug : undefined }),
14
+ url: (target) => typeof target.urlPath === 'string' && target.urlPath ? target.urlPath : undefined,
15
+ };
16
+ /**
17
+ * The entry's `title` as frontmatter carries it. Only a scalar qualifies: a `reference` field
18
+ * named `title` holds an object and renders as a field instead.
19
+ */
20
+ export function frontmatterTitle(data) {
21
+ const { title } = data;
22
+ return (typeof title === 'string' || typeof title === 'number') && title !== ''
23
+ ? String(title)
24
+ : undefined;
25
+ }
10
26
  /**
11
27
  * Convert an entry to clean markdown suitable for AI consumption.
12
28
  *
13
29
  * For MD/MDX entries: renders frontmatter fields as metadata, appends body verbatim.
14
30
  * For JSON entries: full schema-driven conversion of all fields.
15
31
  */
16
- export function entryToMarkdown(entry, config) {
32
+ export function entryToMarkdown(entry, config, references = DEFAULT_REFERENCE_RENDERING) {
33
+ const opts = { config, references };
17
34
  const parts = [];
35
+ const title = frontmatterTitle(entry.data);
18
36
  parts.push('---');
19
- if (entry.data.title) {
20
- parts.push(`title: ${yamlValue(String(entry.data.title))}`);
37
+ if (title !== undefined) {
38
+ parts.push(`title: ${yamlValue(title)}`);
21
39
  }
22
40
  parts.push(`slug: ${yamlValue(entry.slug)}`);
23
41
  parts.push(`collection: ${yamlValue(entry.collection)}`);
@@ -26,13 +44,13 @@ export function entryToMarkdown(entry, config) {
26
44
  parts.push('');
27
45
  // Fields already in frontmatter — skip from body rendering to avoid duplication
28
46
  const skipFields = new Set();
29
- if (entry.data.title)
47
+ if (title !== undefined)
30
48
  skipFields.add('title');
31
49
  if (entry.format === 'md' || entry.format === 'mdx') {
32
- parts.push(...renderMarkdownEntry(entry, config, skipFields));
50
+ parts.push(...renderMarkdownEntry(entry, opts, skipFields));
33
51
  }
34
52
  else {
35
- parts.push(...renderJsonEntry(entry, config, skipFields));
53
+ parts.push(...renderJsonEntry(entry, opts, skipFields));
36
54
  }
37
55
  // Markdown appended by an entry transform (e.g. a folded-in sibling artifact). Computed once
38
56
  // upstream, so it flows into the per-entry file, the collection all.md, and bundles alike.
@@ -45,27 +63,39 @@ export function entryToMarkdown(entry, config) {
45
63
  /**
46
64
  * Render a MD/MDX entry: metadata fields as a section, body verbatim.
47
65
  */
48
- function renderMarkdownEntry(entry, config, skipFields) {
66
+ function renderMarkdownEntry(entry, opts, skipFields) {
67
+ const { config } = opts;
49
68
  const parts = [];
50
69
  // Render frontmatter fields (excluding body-like fields and already-rendered fields)
51
70
  const bodyFieldTypes = new Set(['markdown', 'mdx']);
52
71
  const metadataFields = flattenGroupFields(entry.fields).filter((f) => !bodyFieldTypes.has(f.type) && !skipFields.has(f.name));
72
+ // Blocks of output are separated by exactly one blank line, which is what Prettier leaves.
73
+ const endBlock = () => {
74
+ if (parts.length > 0 && parts[parts.length - 1] !== '')
75
+ parts.push('');
76
+ };
53
77
  for (const field of metadataFields) {
54
78
  const value = entry.data[field.name];
55
79
  if (value === undefined || value === null)
56
80
  continue;
57
- const transformed = applyFieldTransform(entry, field, value, config);
81
+ // A structured field has no one-line form, so it gets a section as in a JSON entry.
82
+ const transformed = field.type === 'object' || field.type === 'block'
83
+ ? renderField(field, value, 2, entry, opts)
84
+ : applyFieldTransform(entry, field, value, config);
58
85
  if (transformed !== undefined) {
59
- parts.push(transformed);
60
- parts.push('');
86
+ if (transformed) {
87
+ endBlock();
88
+ parts.push(transformed);
89
+ parts.push('');
90
+ }
61
91
  continue;
62
92
  }
63
- const label = field.label || field.name;
64
- parts.push(`**${label}:** ${formatInlineValue(field, value)}`);
65
- }
66
- if (parts.length > 0) {
67
- parts.push('');
93
+ const inline = formatInlineValue(field, value, opts.references);
94
+ if (inline === '' && field.type === 'reference')
95
+ continue;
96
+ parts.push(`**${field.label || field.name}:** ${inline}`);
68
97
  }
98
+ endBlock();
69
99
  if (entry.body) {
70
100
  let body = entry.format === 'mdx' ? stripMdxImports(entry.body) : entry.body;
71
101
  if (config?.componentTransforms && Object.keys(config.componentTransforms).length > 0) {
@@ -83,7 +113,7 @@ function renderMarkdownEntry(entry, config, skipFields) {
83
113
  /**
84
114
  * Render a JSON entry: full schema-driven conversion of all fields.
85
115
  */
86
- function renderJsonEntry(entry, config, skipFields) {
116
+ function renderJsonEntry(entry, opts, skipFields) {
87
117
  const parts = [];
88
118
  for (const field of flattenGroupFields(entry.fields)) {
89
119
  if (skipFields.has(field.name))
@@ -91,7 +121,7 @@ function renderJsonEntry(entry, config, skipFields) {
91
121
  const value = entry.data[field.name];
92
122
  if (value === undefined || value === null)
93
123
  continue;
94
- const rendered = renderField(field, value, 2, entry, config);
124
+ const rendered = renderField(field, value, 2, entry, opts);
95
125
  if (rendered) {
96
126
  parts.push(rendered);
97
127
  parts.push('');
@@ -104,8 +134,8 @@ function renderJsonEntry(entry, config, skipFields) {
104
134
  *
105
135
  * @param depth - Heading depth (2 = ##, 3 = ###, etc.)
106
136
  */
107
- function renderField(field, value, depth, entry, config) {
108
- const transformed = applyFieldTransform(entry, field, value, config);
137
+ function renderField(field, value, depth, entry, opts) {
138
+ const transformed = applyFieldTransform(entry, field, value, opts.config);
109
139
  if (transformed !== undefined) {
110
140
  return transformed;
111
141
  }
@@ -117,7 +147,7 @@ function renderField(field, value, depth, entry, config) {
117
147
  // already agrees with is what keeps a regenerated bundle diff-free.
118
148
  const descriptionLine = 'description' in field && field.description ? `\n\n_${field.description}_` : '';
119
149
  if ('list' in field && field.list && Array.isArray(value)) {
120
- return renderListField(field, value, depth, label, heading, descriptionLine, entry, config);
150
+ return renderListField(field, value, depth, label, heading, descriptionLine, entry, opts);
121
151
  }
122
152
  switch (field.type) {
123
153
  case 'string':
@@ -136,11 +166,11 @@ function renderField(field, value, depth, entry, config) {
136
166
  case 'select':
137
167
  return renderSelectField(field, value, heading, label, descriptionLine);
138
168
  case 'reference':
139
- return renderReferenceField(value, heading, label, descriptionLine);
169
+ return renderReferenceField(field, value, heading, label, descriptionLine, opts.references);
140
170
  case 'object':
141
- return renderObjectField(field, value, depth, heading, label, descriptionLine, entry, config);
171
+ return renderObjectField(field, value, depth, heading, label, descriptionLine, entry, opts);
142
172
  case 'block':
143
- return renderBlockField(field, value, depth, heading, label, descriptionLine, entry, config);
173
+ return renderBlockField(field, value, depth, heading, label, descriptionLine, entry, opts);
144
174
  default:
145
175
  return `${heading} ${label}${descriptionLine}\n\n${String(value)}`;
146
176
  }
@@ -148,7 +178,7 @@ function renderField(field, value, depth, entry, config) {
148
178
  /**
149
179
  * Render a list field (field with list: true).
150
180
  */
151
- function renderListField(field, values, depth, label, heading, descriptionLine, entry, config) {
181
+ function renderListField(field, values, depth, label, heading, descriptionLine, entry, opts) {
152
182
  if (values.length === 0)
153
183
  return '';
154
184
  // Arrays of flat records (object items whose subfields are all single-line scalars) render far
@@ -156,7 +186,7 @@ function renderListField(field, values, depth, label, heading, descriptionLine,
156
186
  // headings. Genuinely nested items (subfields that are lists/objects/blocks/long text) keep the
157
187
  // heading-per-item fallback below.
158
188
  if (field.type === 'object' && isFlatObjectList(field)) {
159
- return renderObjectListTable(field, values, label, heading, descriptionLine);
189
+ return renderObjectListTable(field, values, label, heading, descriptionLine, opts.references);
160
190
  }
161
191
  const isComplex = field.type === 'object' || field.type === 'block';
162
192
  if (isComplex) {
@@ -171,7 +201,7 @@ function renderListField(field, values, depth, label, heading, descriptionLine,
171
201
  const v = item[f.name];
172
202
  if (v === undefined || v === null)
173
203
  return '';
174
- return renderField(f, v, depth + 2, entry, config);
204
+ return renderField(f, v, depth + 2, entry, opts);
175
205
  })
176
206
  .filter(Boolean);
177
207
  return `${itemHeading} ${itemLabel}\n\n${subFields.join('\n\n')}`;
@@ -181,8 +211,12 @@ function renderListField(field, values, depth, label, heading, descriptionLine,
181
211
  .filter(Boolean);
182
212
  return `${heading} ${label}${descriptionLine}\n\n${items.join('\n\n')}`;
183
213
  }
184
- const items = values.map((v) => `- ${formatInlineValue(field, v)}`).join('\n');
185
- return `${heading} ${label}${descriptionLine}\n\n${items}`;
214
+ const items = values
215
+ .map((v) => formatInlineValue(field, v, opts.references))
216
+ .filter((item) => item !== '' || field.type !== 'reference');
217
+ if (items.length === 0)
218
+ return '';
219
+ return `${heading} ${label}${descriptionLine}\n\n${items.map((item) => `- ${item}`).join('\n')}`;
186
220
  }
187
221
  /** Subfield types that render to a single line and so fit cleanly in a table cell. */
188
222
  const FLAT_CELL_TYPES = new Set([
@@ -213,7 +247,7 @@ function isFlatObjectList(field) {
213
247
  * Render an array of flat object records as a GFM table. Columns are the subfields in schema order;
214
248
  * absent values become empty cells; pipes and newlines in cells are escaped.
215
249
  */
216
- function renderObjectListTable(field, values, label, heading, descriptionLine) {
250
+ function renderObjectListTable(field, values, label, heading, descriptionLine, references) {
217
251
  const columns = field.fields;
218
252
  const headerRow = `| ${columns.map((f) => escapeTableCell(f.label || f.name)).join(' | ')} |`;
219
253
  const separatorRow = `| ${columns.map(() => '---').join(' | ')} |`;
@@ -223,7 +257,7 @@ function renderObjectListTable(field, values, label, heading, descriptionLine) {
223
257
  const v = record[f.name];
224
258
  if (v === undefined || v === null)
225
259
  return '';
226
- return escapeTableCell(formatCellValue(f, v));
260
+ return escapeTableCell(formatCellValue(f, v, references));
227
261
  });
228
262
  return `| ${cells.join(' | ')} |`;
229
263
  });
@@ -231,12 +265,12 @@ function renderObjectListTable(field, values, label, heading, descriptionLine) {
231
265
  return `${heading} ${label}${descriptionLine}\n\n${table}`;
232
266
  }
233
267
  /** Render a single scalar subfield value to its one-line table-cell form. */
234
- function formatCellValue(field, value) {
268
+ function formatCellValue(field, value, references) {
235
269
  switch (field.type) {
236
270
  case 'boolean':
237
271
  return value ? 'Yes' : 'No';
238
272
  case 'reference':
239
- return formatReference(value);
273
+ return formatReference(field, value, references);
240
274
  case 'select':
241
275
  return Array.isArray(value)
242
276
  ? value.map((v) => resolveSelectLabel(field, v)).join(', ')
@@ -283,12 +317,14 @@ function resolveSelectLabel(field, value) {
283
317
  }
284
318
  return strValue;
285
319
  }
286
- function renderReferenceField(value, heading, label, descriptionLine) {
287
- if (Array.isArray(value)) {
288
- const items = value.map((v) => `- ${formatReference(v)}`).join('\n');
289
- return `${heading} ${label}${descriptionLine}\n\n${items}`;
290
- }
291
- return `${heading} ${label}${descriptionLine}\n\n${formatReference(value)}`;
320
+ function renderReferenceField(field, value, heading, label, descriptionLine, references) {
321
+ const items = (Array.isArray(value) ? value : [value])
322
+ .map((v) => formatReference(field, v, references))
323
+ .filter(Boolean);
324
+ if (items.length === 0)
325
+ return '';
326
+ const body = Array.isArray(value) ? items.map((item) => `- ${item}`).join('\n') : items[0];
327
+ return `${heading} ${label}${descriptionLine}\n\n${body}`;
292
328
  }
293
329
  function isImageValueLike(value) {
294
330
  return (typeof value === 'object' &&
@@ -297,8 +333,8 @@ function isImageValueLike(value) {
297
333
  typeof value.src === 'string');
298
334
  }
299
335
  /**
300
- * Sanitizes `alt` for the `[...]` span of `![alt](src)`: unescaped, a crafted alt like
301
- * `x](/a) [pwn](https://evil.com)` injects a second attacker-chosen link/image. Strips
336
+ * Sanitizes text for the `[...]` span of `![alt](src)` or `[title](url)`: unescaped, a crafted
337
+ * value like `x](/a) [pwn](https://evil.com)` injects a second attacker-chosen link/image. Strips
302
338
  * (rather than escapes) `[`, `]`, backslash, and newlines — losing a stray bracket from
303
339
  * descriptive text is harmless, and escaping would break under `escapeTableCell`'s blind
304
340
  * backslash-doubling once this lands in a table cell (formatCellValue's image case).
@@ -339,20 +375,41 @@ function formatImageMarkdown(value, altFallback) {
339
375
  return `![${sanitizeMarkdownAltText(alt)}](${encodeMarkdownLinkDestination(value.src)})`;
340
376
  }
341
377
  /**
342
- * References may be resolved (objects with data) or unresolved (string IDs).
378
+ * One reference value as inline markdown: a target the export shows becomes `[title](url)`, its
379
+ * page, then its markdown copy labeled as such, so a reader can tell the two apart; one it does
380
+ * not becomes its id with a marker, so the copy shows the break instead of hiding it. An
381
+ * unavailable target renders from its id alone, whatever else it carries, so no title can leak.
382
+ * A bare string is an id that resolution never replaced.
343
383
  */
344
- function formatReference(value) {
345
- if (typeof value === 'object' && value !== null) {
346
- const ref = value;
347
- // Resolved reference — use title, name, or slug
348
- const display = ref.title || ref.name || ref.slug || ref.id;
349
- if (display)
350
- return String(display);
384
+ function formatReference(field, value, references) {
385
+ // `''` is what the editor saves when a reference is cleared: no value, not a broken one.
386
+ if (value === '')
387
+ return '';
388
+ if (typeof value === 'string')
389
+ return `(missing entry ${sanitizeMarkdownAltText(value)})`;
390
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
391
+ return '(missing entry)';
392
+ const target = value;
393
+ const id = typeof target.id === 'string' ? sanitizeMarkdownAltText(target.id) : '';
394
+ if (target[RESTRICTED_REFERENCE_MARKER] === true) {
395
+ const marker = target.reason === 'missing' ? 'missing entry' : 'unavailable entry';
396
+ return id ? `(${marker} ${id})` : `(${marker})`;
351
397
  }
352
- // Unresolved — raw ID or string
353
- return String(value);
398
+ const displayField = field.displayField;
399
+ const displayValue = displayField ? target[displayField] : undefined;
400
+ const title = sanitizeMarkdownAltText(typeof displayValue === 'string' && displayValue.trim()
401
+ ? displayValue
402
+ : references.title(target)) || id;
403
+ const url = references.url(target);
404
+ if (url === false)
405
+ return title;
406
+ const page = url ? `[${title}](${encodeMarkdownLinkDestination(url)})` : title;
407
+ const markdownUrl = references.markdownUrl?.(target);
408
+ return markdownUrl
409
+ ? `${page} ([markdown version](${encodeMarkdownLinkDestination(markdownUrl)}))`
410
+ : page;
354
411
  }
355
- function renderObjectField(field, value, depth, heading, label, descriptionLine, entry, config) {
412
+ function renderObjectField(field, value, depth, heading, label, descriptionLine, entry, opts) {
356
413
  if (typeof value !== 'object' || value === null) {
357
414
  return `${heading} ${label}${descriptionLine}\n\n${String(value)}`;
358
415
  }
@@ -362,7 +419,7 @@ function renderObjectField(field, value, depth, heading, label, descriptionLine,
362
419
  const v = obj[f.name];
363
420
  if (v === undefined || v === null)
364
421
  return '';
365
- return renderField(f, v, depth + 1, entry, config);
422
+ return renderField(f, v, depth + 1, entry, opts);
366
423
  })
367
424
  .filter(Boolean);
368
425
  if (subFields.length === 0)
@@ -372,7 +429,7 @@ function renderObjectField(field, value, depth, heading, label, descriptionLine,
372
429
  /**
373
430
  * Render a block field (array of typed block items).
374
431
  */
375
- function renderBlockField(field, value, depth, heading, label, descriptionLine, entry, config) {
432
+ function renderBlockField(field, value, depth, heading, label, descriptionLine, entry, opts) {
376
433
  if (!Array.isArray(value))
377
434
  return '';
378
435
  const items = value
@@ -394,7 +451,7 @@ function renderBlockField(field, value, depth, heading, label, descriptionLine,
394
451
  const v = blockItem[f.name] ?? blockItem.value?.[f.name];
395
452
  if (v === undefined || v === null)
396
453
  return '';
397
- return renderField(f, v, depth + 2, entry, config);
454
+ return renderField(f, v, depth + 2, entry, opts);
398
455
  })
399
456
  .filter(Boolean);
400
457
  if (blockFields.length === 0)
@@ -420,11 +477,17 @@ function applyFieldTransform(entry, field, value, config) {
420
477
  /**
421
478
  * Format a value for inline display (metadata lines, list items).
422
479
  */
423
- function formatInlineValue(field, value) {
480
+ function formatInlineValue(field, value, references) {
424
481
  if (field.type === 'boolean')
425
482
  return value ? 'Yes' : 'No';
426
- if (field.type === 'reference')
427
- return formatReference(value);
483
+ if (field.type === 'reference') {
484
+ return Array.isArray(value)
485
+ ? value
486
+ .map((v) => formatReference(field, v, references))
487
+ .filter(Boolean)
488
+ .join(', ')
489
+ : formatReference(field, value, references);
490
+ }
428
491
  // Without this, an `image` field falls through to `String(value)` below,
429
492
  // which stringifies the structured `{ src, alt }` value object as the
430
493
  // useless literal text "[object Object]" - hit by MD/MDX frontmatter
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Reference fields in AI content: resolving them, hiding targets the export leaves out, and the
3
+ * title and URL a resolved one renders with.
4
+ *
5
+ * A reference renders as a link to its target, never as the target's content, so a shared block
6
+ * referenced by forty pages appears once in the export rather than forty times.
7
+ */
8
+ import type { ContentStore } from '../content-store.js';
9
+ import { type ContentIdIndex } from '../content-id-index.js';
10
+ import type { FieldConfig, FlatSchemaItem } from '../config/index.js';
11
+ import { type EntryLinkUrlResolver } from '../entry-link-resolver.js';
12
+ import type { ReferenceRendering } from './json-to-markdown.js';
13
+ import type { AIEntry, AIReferenceLinkFn, AIResolvedReference, AIReferenceValue } from './types.js';
14
+ export type ReferenceTargetResolver = (id: string) => Promise<AIReferenceValue>;
15
+ /**
16
+ * Resolve reference ids to their targets, reading each target once.
17
+ *
18
+ * Create one per `generateAIContent` call and never keep it: the route handler is long-lived in
19
+ * prod, and a memo held across calls would serve a target as it was when first read.
20
+ * `resolveReferenceTarget` embeds no body, which keeps the target's content out of the export,
21
+ * and resolves an id naming no readable entry to a `MissingReference`, which keeps the id.
22
+ */
23
+ export declare function createReferenceTargetResolver(store: ContentStore): ReferenceTargetResolver;
24
+ /**
25
+ * A copy of `data` with every reference id replaced by its target, or by a `MissingReference`
26
+ * when the id names no readable entry, so the id survives into the output.
27
+ * A `list: true` field resolves each non-empty string element; any other value, including the
28
+ * `''` of a cleared reference, is left as stored.
29
+ */
30
+ export declare function resolveReferenceFields(data: Record<string, unknown>, fields: readonly FieldConfig[], resolveTarget: ReferenceTargetResolver): Promise<Record<string, unknown>>;
31
+ /**
32
+ * Replace, in place, every resolved target that is not itself in this export with an
33
+ * {@link AIExcludedReference}, so no title, URL or field of an excluded entry reaches the
34
+ * output. Runs before each round of entry transforms and rendering, so their final output never
35
+ * carries such a target. Returns whether it replaced anything.
36
+ */
37
+ export declare function maskUnexportedTargets(entry: AIEntry, exported: {
38
+ has(id: string): boolean;
39
+ }): boolean;
40
+ /** A target's page link as `referenceLink` decided it: a URL, `false` for none, or the default. */
41
+ export type ReferenceLinkLookup = (id: string) => string | false | undefined;
42
+ /** What `referenceLink` is told about one exported target. */
43
+ export interface ReferenceLinkSubject {
44
+ target: AIResolvedReference;
45
+ body: string | undefined;
46
+ }
47
+ /**
48
+ * Ask `referenceLink` once for each exported target the export's entries reference or link from
49
+ * their bodies, in entry order, and return the answers, each URL encoded by
50
+ * {@link encodeLinkUrl}. `subjectOf` builds a target from the export's own read of it, so the
51
+ * callback never sees a target outside the export set, and the body it is shown reaches no linking
52
+ * entry's output. A throw
53
+ * or a return other than a non-empty string, `false` or `undefined` is logged and becomes
54
+ * `false`, so a broken callback withholds a link rather than advertising a page the site may not
55
+ * have. A target whose `entryLinkUrl` throws is left undecided, unasked, so every link to it fails
56
+ * and is reported as it is without the hook.
57
+ */
58
+ export declare function decideReferenceLinks(referenceLink: AIReferenceLinkFn, entries: ReadonlyArray<{
59
+ entry: AIEntry;
60
+ body?: string;
61
+ }>, subjectOf: (id: string) => ReferenceLinkSubject | undefined, entryLinkUrl: EntryLinkUrlResolver | undefined): Promise<ReferenceLinkLookup>;
62
+ /**
63
+ * Title and URLs for a resolved target. The title is `resolveEntryTitle` against the target's own
64
+ * entry type, the chain the entries API labels an entry with. The page URL is `linkFor`'s
65
+ * decision, else {@link defaultPageUrl}; body `entry:` links follow both, so the two kinds of link
66
+ * in one document agree. The markdown copy is the file
67
+ * this export wrote for the target (`files`: content id to output path), under `mountPath`.
68
+ */
69
+ export declare function createReferenceRendering(idIndex: ContentIdIndex, flatSchema: readonly FlatSchemaItem[], entryLinkUrl: EntryLinkUrlResolver | undefined, markdownCopies: {
70
+ mountPath: string;
71
+ files: ReadonlyMap<string, string>;
72
+ }, linkFor?: ReferenceLinkLookup): ReferenceRendering;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Reference fields in AI content: resolving them, hiding targets the export leaves out, and the
3
+ * title and URL a resolved one renders with.
4
+ *
5
+ * A reference renders as a link to its target, never as the target's content, so a shared block
6
+ * referenced by forty pages appears once in the export rather than forty times.
7
+ */
8
+ import path from 'node:path';
9
+ import { getDefaultEntryType } from '../content-store.js';
10
+ import { extractEntryTypeFromFilename } from '../content-id-index.js';
11
+ import { RESTRICTED_REFERENCE_MARKER } from '../entry-schema.js';
12
+ import { extractEntryLinkIds } from '../entry-link-resolver.js';
13
+ import { trimSlashes } from '../paths/normalize.js';
14
+ import { getErrorMessage } from '../utils/error.js';
15
+ import { flattenGroupFields } from '../utils/flatten-group-fields.js';
16
+ import { resolveEntryTitle } from '../utils/title-field.js';
17
+ import { traverseFields } from '../validation/field-traversal.js';
18
+ /**
19
+ * Resolve reference ids to their targets, reading each target once.
20
+ *
21
+ * Create one per `generateAIContent` call and never keep it: the route handler is long-lived in
22
+ * prod, and a memo held across calls would serve a target as it was when first read.
23
+ * `resolveReferenceTarget` embeds no body, which keeps the target's content out of the export,
24
+ * and resolves an id naming no readable entry to a `MissingReference`, which keeps the id.
25
+ */
26
+ export function createReferenceTargetResolver(store) {
27
+ const reads = new Map();
28
+ return async (id) => {
29
+ let read = reads.get(id);
30
+ if (!read) {
31
+ read = store.resolveReferenceTarget(id);
32
+ reads.set(id, read);
33
+ }
34
+ // Each occurrence gets its own copy, since masking and adopter transforms may mutate it.
35
+ return structuredClone(await read);
36
+ };
37
+ }
38
+ /** Each reference field's holding record, at every depth: objects, object lists, blocks, groups. */
39
+ function referenceSlots(fields, data) {
40
+ return traverseFields(fields, data, () => [], '', ({ fields: containerFields, data: record }) => flattenGroupFields(containerFields)
41
+ .filter((field) => field.type === 'reference')
42
+ .map((field) => ({ record, field })));
43
+ }
44
+ /**
45
+ * A copy of `data` with every reference id replaced by its target, or by a `MissingReference`
46
+ * when the id names no readable entry, so the id survives into the output.
47
+ * A `list: true` field resolves each non-empty string element; any other value, including the
48
+ * `''` of a cleared reference, is left as stored.
49
+ */
50
+ export async function resolveReferenceFields(data, fields, resolveTarget) {
51
+ const resolved = structuredClone(data);
52
+ await Promise.all(referenceSlots(fields, resolved).map(async ({ record, field }) => {
53
+ const value = record[field.name];
54
+ if (typeof value === 'string' && value) {
55
+ record[field.name] = await resolveTarget(value);
56
+ }
57
+ else if (field.list && Array.isArray(value)) {
58
+ record[field.name] = await Promise.all(value.map((id) => (typeof id === 'string' && id ? resolveTarget(id) : id)));
59
+ }
60
+ }));
61
+ return resolved;
62
+ }
63
+ function isShownTarget(value) {
64
+ return (typeof value === 'object' &&
65
+ value !== null &&
66
+ !Array.isArray(value) &&
67
+ typeof value.id === 'string' &&
68
+ value[RESTRICTED_REFERENCE_MARKER] !== true);
69
+ }
70
+ /**
71
+ * Replace, in place, every resolved target that is not itself in this export with an
72
+ * {@link AIExcludedReference}, so no title, URL or field of an excluded entry reaches the
73
+ * output. Runs before each round of entry transforms and rendering, so their final output never
74
+ * carries such a target. Returns whether it replaced anything.
75
+ */
76
+ export function maskUnexportedTargets(entry, exported) {
77
+ let masked = false;
78
+ const mask = (value) => {
79
+ if (!isShownTarget(value) || exported.has(value.id))
80
+ return value;
81
+ masked = true;
82
+ return { id: value.id, unavailable: true, reason: 'excluded' };
83
+ };
84
+ for (const { record, field } of referenceSlots(entry.fields, entry.data)) {
85
+ const value = record[field.name];
86
+ if (value === undefined)
87
+ continue;
88
+ record[field.name] = Array.isArray(value) ? value.map(mask) : mask(value);
89
+ }
90
+ return masked;
91
+ }
92
+ /**
93
+ * The page URL a resolved target links by default: `entryLinkUrl`'s, else its `urlPath`, the rule
94
+ * body `entry:` links use.
95
+ */
96
+ function defaultPageUrl(target, entryLinkUrl) {
97
+ const { id, slug, collection, urlPath } = target;
98
+ if (entryLinkUrl &&
99
+ typeof id === 'string' &&
100
+ typeof slug === 'string' &&
101
+ typeof collection === 'string') {
102
+ return entryLinkUrl({ collection, slug, id });
103
+ }
104
+ return typeof urlPath === 'string' && urlPath ? urlPath : undefined;
105
+ }
106
+ /**
107
+ * Percent-encode every character that would end a link destination, an autolink or an HTML
108
+ * attribute value early (whitespace, controls, quotes, angle brackets and parentheses) or escape
109
+ * the character after it (a backslash). Body links splice the URL into all three as it is.
110
+ */
111
+ function encodeLinkUrl(url) {
112
+ let out = '';
113
+ for (const char of url) {
114
+ const code = char.charCodeAt(0);
115
+ out +=
116
+ code <= 0x20 || code === 0x7f || `"'<>()\\`.includes(char)
117
+ ? `%${code.toString(16).toUpperCase().padStart(2, '0')}`
118
+ : char;
119
+ }
120
+ return out;
121
+ }
122
+ /**
123
+ * Ask `referenceLink` once for each exported target the export's entries reference or link from
124
+ * their bodies, in entry order, and return the answers, each URL encoded by
125
+ * {@link encodeLinkUrl}. `subjectOf` builds a target from the export's own read of it, so the
126
+ * callback never sees a target outside the export set, and the body it is shown reaches no linking
127
+ * entry's output. A throw
128
+ * or a return other than a non-empty string, `false` or `undefined` is logged and becomes
129
+ * `false`, so a broken callback withholds a link rather than advertising a page the site may not
130
+ * have. A target whose `entryLinkUrl` throws is left undecided, unasked, so every link to it fails
131
+ * and is reported as it is without the hook.
132
+ */
133
+ export async function decideReferenceLinks(referenceLink, entries, subjectOf, entryLinkUrl) {
134
+ const ids = new Set();
135
+ for (const { entry, body } of entries) {
136
+ for (const { record, field } of referenceSlots(entry.fields, entry.data)) {
137
+ const value = record[field.name];
138
+ for (const target of Array.isArray(value) ? value : [value]) {
139
+ if (isShownTarget(target))
140
+ ids.add(target.id);
141
+ }
142
+ }
143
+ for (const { id } of body ? extractEntryLinkIds(body) : [])
144
+ ids.add(id);
145
+ }
146
+ const decisions = new Map();
147
+ for (const id of ids) {
148
+ const subject = subjectOf(id);
149
+ if (!subject)
150
+ continue;
151
+ let defaultUrl;
152
+ try {
153
+ defaultUrl = defaultPageUrl(subject.target, entryLinkUrl);
154
+ }
155
+ catch {
156
+ continue;
157
+ }
158
+ let decision;
159
+ try {
160
+ decision = await referenceLink(subject.target, { body: subject.body, defaultUrl });
161
+ }
162
+ catch (err) {
163
+ console.warn(`AI content: referenceLink threw for entry ${id}:`, getErrorMessage(err));
164
+ decision = false;
165
+ }
166
+ if (typeof decision === 'string' && decision.trim()) {
167
+ decisions.set(id, encodeLinkUrl(decision.trim()));
168
+ }
169
+ else if (decision === false) {
170
+ decisions.set(id, false);
171
+ }
172
+ else if (decision !== undefined) {
173
+ console.warn(`AI content: referenceLink returned ${typeof decision === 'string' ? 'an empty string' : typeof decision} for entry ${id}; expected a URL, false or undefined`);
174
+ decisions.set(id, false);
175
+ }
176
+ }
177
+ return (id) => decisions.get(id);
178
+ }
179
+ /**
180
+ * Title and URLs for a resolved target. The title is `resolveEntryTitle` against the target's own
181
+ * entry type, the chain the entries API labels an entry with. The page URL is `linkFor`'s
182
+ * decision, else {@link defaultPageUrl}; body `entry:` links follow both, so the two kinds of link
183
+ * in one document agree. The markdown copy is the file
184
+ * this export wrote for the target (`files`: content id to output path), under `mountPath`.
185
+ */
186
+ export function createReferenceRendering(idIndex, flatSchema, entryLinkUrl, markdownCopies, linkFor) {
187
+ const mountSegment = trimSlashes(markdownCopies.mountPath);
188
+ const mount = mountSegment === '' ? '' : `/${mountSegment}`;
189
+ const entryTypeOf = (id) => {
190
+ const location = typeof id === 'string' ? idIndex.findById(id) : null;
191
+ if (!location?.collection)
192
+ return {};
193
+ const item = flatSchema.find((candidate) => candidate.logicalPath === location.collection);
194
+ if (!item)
195
+ return {};
196
+ if (item.type === 'entry-type')
197
+ return { schema: item.schema, label: item.label };
198
+ if (item.type !== 'collection')
199
+ return {};
200
+ const typeName = extractEntryTypeFromFilename(path.basename(location.relativePath));
201
+ const entries = item.entries;
202
+ const entryType = entries?.find((candidate) => candidate.name === typeName) ?? getDefaultEntryType(entries);
203
+ return { schema: entryType?.schema, label: entryType?.label };
204
+ };
205
+ return {
206
+ title: (target) => {
207
+ const { schema, label } = entryTypeOf(target.id);
208
+ return resolveEntryTitle(target, {
209
+ schema,
210
+ entryTypeLabel: label,
211
+ slug: typeof target.slug === 'string' ? target.slug : undefined,
212
+ });
213
+ },
214
+ url: (target) => {
215
+ const decided = typeof target.id === 'string' ? linkFor?.(target.id) : undefined;
216
+ return decided ?? defaultPageUrl(target, entryLinkUrl);
217
+ },
218
+ markdownUrl: (target) => {
219
+ const file = typeof target.id === 'string' ? markdownCopies.files.get(target.id) : undefined;
220
+ return file ? `${mount}/${file}` : undefined;
221
+ },
222
+ };
223
+ }