@elytracms/studio 0.0.93 → 0.0.94

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 (481) hide show
  1. package/convex/analytics-cost.test.ts +817 -0
  2. package/convex/auth.ts +5 -2
  3. package/convex/authority.ts +35 -11
  4. package/convex/contract-parity.test.ts +7 -0
  5. package/convex/delivery.ts +12 -9
  6. package/convex/guard.ts +9 -7
  7. package/convex/http-routes.test.ts +18 -8
  8. package/convex/http.ts +23 -17
  9. package/convex/ingest.test.ts +326 -0
  10. package/convex/ingest.ts +281 -0
  11. package/convex/invites.ts +13 -5
  12. package/convex/members.ts +10 -4
  13. package/convex/record-functions.ts +325 -0
  14. package/convex/records.test.ts +156 -0
  15. package/convex/redirect-migration.test.ts +205 -0
  16. package/convex/redirect-revalidate.test.ts +5 -1
  17. package/convex/redirects.ts +120 -79
  18. package/convex/role-policy.test.ts +315 -0
  19. package/convex/roles.ts +83 -0
  20. package/convex/schema.ts +30 -0
  21. package/convex/triggers.ts +21 -1
  22. package/dist/addon.d.ts +71 -0
  23. package/dist/addon.js +67 -0
  24. package/dist/addon.js.map +1 -0
  25. package/dist/components/account/account-settings.js +2 -2
  26. package/dist/components/account/account-settings.js.map +1 -1
  27. package/dist/components/account/credential-panel.js +2 -2
  28. package/dist/components/account/credential-panel.js.map +1 -1
  29. package/dist/components/account/self-account-schema.d.ts +6 -15
  30. package/dist/components/account/self-account-schema.js +52 -34
  31. package/dist/components/account/self-account-schema.js.map +1 -1
  32. package/dist/components/account/self-account-view-source.d.ts +1 -1
  33. package/dist/components/account/self-account-view-source.js +6 -4
  34. package/dist/components/account/self-account-view-source.js.map +1 -1
  35. package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
  36. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  37. package/dist/components/ai/ai-chat-panel.js +6 -6
  38. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  39. package/dist/components/ai/ai-cms-panel.js +2 -2
  40. package/dist/components/ai/ai-cms-panel.js.map +1 -1
  41. package/dist/components/ai/ai-panel-toggle.js +4 -4
  42. package/dist/components/ai/ai-panel-toggle.js.map +1 -1
  43. package/dist/components/ai/chat-ui.js +2 -2
  44. package/dist/components/ai/chat-ui.js.map +1 -1
  45. package/dist/components/ai/proposal-card.d.ts +2 -2
  46. package/dist/components/ai/proposal-card.js +2 -2
  47. package/dist/components/ai/proposal-card.js.map +1 -1
  48. package/dist/components/ai/proposal-issues.js +2 -2
  49. package/dist/components/ai/proposal-issues.js.map +1 -1
  50. package/dist/components/ai/translate-document-action.js +2 -2
  51. package/dist/components/ai/translate-document-action.js.map +1 -1
  52. package/dist/components/ai/translate-document-dialog.js +6 -6
  53. package/dist/components/ai/translate-document-dialog.js.map +1 -1
  54. package/dist/components/ai/translate-skipped.d.ts +1 -1
  55. package/dist/components/ai/translate-skipped.js +2 -2
  56. package/dist/components/ai/translate-skipped.js.map +1 -1
  57. package/dist/components/app-views/default-modules.d.ts +50 -2
  58. package/dist/components/app-views/default-modules.js +65 -20
  59. package/dist/components/app-views/default-modules.js.map +1 -1
  60. package/dist/components/app-views/module.d.ts +53 -3
  61. package/dist/components/app-views/module.js +14 -7
  62. package/dist/components/app-views/module.js.map +1 -1
  63. package/dist/components/app-views/sidebar.js +1 -1
  64. package/dist/components/app-views/sidebar.js.map +1 -1
  65. package/dist/components/app-views/studio-config.d.ts +11 -2
  66. package/dist/components/app-views/studio-config.js.map +1 -1
  67. package/dist/components/assets/asset-detail.js +2 -2
  68. package/dist/components/assets/asset-detail.js.map +1 -1
  69. package/dist/components/assets/asset-grid.js +2 -2
  70. package/dist/components/assets/asset-grid.js.map +1 -1
  71. package/dist/components/assets/asset-thumb.js +3 -3
  72. package/dist/components/assets/asset-thumb.js.map +1 -1
  73. package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
  74. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  75. package/dist/components/assets/assets-module.js +1 -1
  76. package/dist/components/assets/assets-module.js.map +1 -1
  77. package/dist/components/assets/assets-store.d.ts +2 -2
  78. package/dist/components/assets/assets-store.js +6 -6
  79. package/dist/components/assets/assets-store.js.map +1 -1
  80. package/dist/components/assets/assets-view-preference.js +1 -1
  81. package/dist/components/assets/assets-view-preference.js.map +1 -1
  82. package/dist/components/assets/replace-file.js +1 -1
  83. package/dist/components/assets/replace-file.js.map +1 -1
  84. package/dist/components/auth-context.d.ts +1 -1
  85. package/dist/components/auth-context.js +6 -6
  86. package/dist/components/auth-context.js.map +1 -1
  87. package/dist/components/builder/blocks-model.d.ts +1 -1
  88. package/dist/components/builder/blocks-model.js +3 -3
  89. package/dist/components/builder/blocks-model.js.map +1 -1
  90. package/dist/components/builder/blocks-panel.d.ts +1 -1
  91. package/dist/components/builder/blocks-panel.js +4 -4
  92. package/dist/components/builder/blocks-panel.js.map +1 -1
  93. package/dist/components/builder/builder-store.js +2 -2
  94. package/dist/components/builder/builder-store.js.map +1 -1
  95. package/dist/components/builder/canvas-breakpoints.js +1 -1
  96. package/dist/components/builder/canvas-breakpoints.js.map +1 -1
  97. package/dist/components/builder/canvas-dnd.d.ts +2 -2
  98. package/dist/components/builder/canvas-dnd.js +1 -1
  99. package/dist/components/builder/canvas-dnd.js.map +1 -1
  100. package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
  101. package/dist/components/builder/canvas-inline-editing.js.map +1 -1
  102. package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
  103. package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
  104. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  105. package/dist/components/builder/canvas-render-context.js +4 -4
  106. package/dist/components/builder/canvas-render-context.js.map +1 -1
  107. package/dist/components/builder/canvas-theme-notice.js +2 -2
  108. package/dist/components/builder/canvas-theme-notice.js.map +1 -1
  109. package/dist/components/builder/canvas.js +3 -3
  110. package/dist/components/builder/canvas.js.map +1 -1
  111. package/dist/components/builder/clipboard-actions.d.ts +2 -2
  112. package/dist/components/builder/clipboard-actions.js +4 -4
  113. package/dist/components/builder/clipboard-actions.js.map +1 -1
  114. package/dist/components/builder/composition-field-editor-lazy.js +1 -1
  115. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  116. package/dist/components/builder/composition-field-editor.d.ts +1 -1
  117. package/dist/components/builder/composition-field-editor.js +3 -3
  118. package/dist/components/builder/composition-field-editor.js.map +1 -1
  119. package/dist/components/builder/drag-session.d.ts +1 -1
  120. package/dist/components/builder/drag-session.js.map +1 -1
  121. package/dist/components/builder/fixture-thumbnail.js +1 -1
  122. package/dist/components/builder/fixture-thumbnail.js.map +1 -1
  123. package/dist/components/builder/insert-context.d.ts +1 -1
  124. package/dist/components/builder/insert-context.js +3 -3
  125. package/dist/components/builder/insert-context.js.map +1 -1
  126. package/dist/components/builder/layout-inspector.js +1 -1
  127. package/dist/components/builder/layout-inspector.js.map +1 -1
  128. package/dist/components/builder/node-title.js +1 -1
  129. package/dist/components/builder/node-title.js.map +1 -1
  130. package/dist/components/builder/page-tree.d.ts +1 -1
  131. package/dist/components/builder/page-tree.js +5 -5
  132. package/dist/components/builder/page-tree.js.map +1 -1
  133. package/dist/components/builder/placement-refusal.js +3 -3
  134. package/dist/components/builder/placement-refusal.js.map +1 -1
  135. package/dist/components/builder/property-inspector.js +4 -4
  136. package/dist/components/builder/property-inspector.js.map +1 -1
  137. package/dist/components/builder/reuse-context.js +4 -4
  138. package/dist/components/builder/reuse-context.js.map +1 -1
  139. package/dist/components/builder/reuse.js +1 -1
  140. package/dist/components/builder/reuse.js.map +1 -1
  141. package/dist/components/builder/rich-text-toolbar.js +1 -1
  142. package/dist/components/builder/rich-text-toolbar.js.map +1 -1
  143. package/dist/components/builder/run-inspector.js +1 -1
  144. package/dist/components/builder/run-inspector.js.map +1 -1
  145. package/dist/components/builder/selection-overlay.js +3 -3
  146. package/dist/components/builder/selection-overlay.js.map +1 -1
  147. package/dist/components/builder/spacing-handles.d.ts +2 -2
  148. package/dist/components/builder/spacing-handles.js +3 -3
  149. package/dist/components/builder/spacing-handles.js.map +1 -1
  150. package/dist/components/cms/asset-field.js +2 -2
  151. package/dist/components/cms/asset-field.js.map +1 -1
  152. package/dist/components/cms/asset-picker.js +2 -2
  153. package/dist/components/cms/asset-picker.js.map +1 -1
  154. package/dist/components/cms/bulk-actions.d.ts +1 -1
  155. package/dist/components/cms/bulk-actions.js +2 -2
  156. package/dist/components/cms/bulk-actions.js.map +1 -1
  157. package/dist/components/cms/bulk-bar.d.ts +2 -2
  158. package/dist/components/cms/bulk-bar.js +2 -2
  159. package/dist/components/cms/bulk-bar.js.map +1 -1
  160. package/dist/components/cms/cell-edit.d.ts +1 -1
  161. package/dist/components/cms/cell-edit.js +1 -1
  162. package/dist/components/cms/cell-edit.js.map +1 -1
  163. package/dist/components/cms/cell.d.ts +1 -1
  164. package/dist/components/cms/cell.js +3 -3
  165. package/dist/components/cms/cell.js.map +1 -1
  166. package/dist/components/cms/cms-index.js +2 -2
  167. package/dist/components/cms/cms-index.js.map +1 -1
  168. package/dist/components/cms/cms-labels.js +2 -2
  169. package/dist/components/cms/cms-labels.js.map +1 -1
  170. package/dist/components/cms/cms-store.d.ts +2 -2
  171. package/dist/components/cms/cms-store.js +10 -10
  172. package/dist/components/cms/cms-store.js.map +1 -1
  173. package/dist/components/cms/collection-table.js +7 -7
  174. package/dist/components/cms/collection-table.js.map +1 -1
  175. package/dist/components/cms/detail-editor.d.ts +5 -2
  176. package/dist/components/cms/detail-editor.js +20 -14
  177. package/dist/components/cms/detail-editor.js.map +1 -1
  178. package/dist/components/cms/details-pane.js +1 -1
  179. package/dist/components/cms/details-pane.js.map +1 -1
  180. package/dist/components/cms/doc-state.js +1 -1
  181. package/dist/components/cms/doc-state.js.map +1 -1
  182. package/dist/components/cms/document-diff-view.js +5 -5
  183. package/dist/components/cms/document-diff-view.js.map +1 -1
  184. package/dist/components/cms/document-editor-context.js +1 -1
  185. package/dist/components/cms/document-editor-context.js.map +1 -1
  186. package/dist/components/cms/document-staging.js +1 -1
  187. package/dist/components/cms/document-staging.js.map +1 -1
  188. package/dist/components/cms/document-url-path.js +2 -2
  189. package/dist/components/cms/document-url-path.js.map +1 -1
  190. package/dist/components/cms/duplicate-document.d.ts +1 -1
  191. package/dist/components/cms/duplicate-document.js.map +1 -1
  192. package/dist/components/cms/editable-cell.js +3 -3
  193. package/dist/components/cms/editable-cell.js.map +1 -1
  194. package/dist/components/cms/field-collaboration.d.ts +1 -1
  195. package/dist/components/cms/field-collaboration.js +1 -1
  196. package/dist/components/cms/field-collaboration.js.map +1 -1
  197. package/dist/components/cms/field-connection.d.ts +1 -1
  198. package/dist/components/cms/field-connection.js +2 -2
  199. package/dist/components/cms/field-connection.js.map +1 -1
  200. package/dist/components/cms/field-control-lib.js +1 -1
  201. package/dist/components/cms/field-control-lib.js.map +1 -1
  202. package/dist/components/cms/field-control.js +4 -4
  203. package/dist/components/cms/field-control.js.map +1 -1
  204. package/dist/components/cms/field-renderer.d.ts +1 -1
  205. package/dist/components/cms/field-renderer.js +2 -2
  206. package/dist/components/cms/field-renderer.js.map +1 -1
  207. package/dist/components/cms/filter-builder.d.ts +1 -1
  208. package/dist/components/cms/filter-builder.js +3 -3
  209. package/dist/components/cms/filter-builder.js.map +1 -1
  210. package/dist/components/cms/gate-validation.js +1 -1
  211. package/dist/components/cms/gate-validation.js.map +1 -1
  212. package/dist/components/cms/import-export.js +2 -2
  213. package/dist/components/cms/import-export.js.map +1 -1
  214. package/dist/components/cms/issue-copy.js +1 -1
  215. package/dist/components/cms/issue-copy.js.map +1 -1
  216. package/dist/components/cms/issues-pane.js +3 -3
  217. package/dist/components/cms/issues-pane.js.map +1 -1
  218. package/dist/components/cms/object-array-field.js +2 -2
  219. package/dist/components/cms/object-array-field.js.map +1 -1
  220. package/dist/components/cms/prop-fields.js +2 -2
  221. package/dist/components/cms/prop-fields.js.map +1 -1
  222. package/dist/components/cms/reference-confirm.d.ts +1 -1
  223. package/dist/components/cms/reference-confirm.js +2 -2
  224. package/dist/components/cms/reference-confirm.js.map +1 -1
  225. package/dist/components/cms/relation-picker.js +2 -2
  226. package/dist/components/cms/relation-picker.js.map +1 -1
  227. package/dist/components/cms/rich-text/block-style.js +1 -1
  228. package/dist/components/cms/rich-text/block-style.js.map +1 -1
  229. package/dist/components/cms/rich-text/component-embed.js +4 -4
  230. package/dist/components/cms/rich-text/component-embed.js.map +1 -1
  231. package/dist/components/cms/rich-text/editor.js +3 -3
  232. package/dist/components/cms/rich-text/editor.js.map +1 -1
  233. package/dist/components/cms/rich-text/embed-picker.js +3 -3
  234. package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
  235. package/dist/components/cms/rich-text/embed-props.js +3 -3
  236. package/dist/components/cms/rich-text/embed-props.js.map +1 -1
  237. package/dist/components/cms/rich-text/extensions.js +1 -1
  238. package/dist/components/cms/rich-text/extensions.js.map +1 -1
  239. package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
  240. package/dist/components/cms/rich-text/font-size-control.js +2 -2
  241. package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
  242. package/dist/components/cms/rich-text/lazy.js +1 -1
  243. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  244. package/dist/components/cms/rich-text/link-control.js +2 -2
  245. package/dist/components/cms/rich-text/link-control.js.map +1 -1
  246. package/dist/components/cms/rich-text/slash-command.js +3 -3
  247. package/dist/components/cms/rich-text/slash-command.js.map +1 -1
  248. package/dist/components/cms/rich-text/toolbar.js +1 -1
  249. package/dist/components/cms/rich-text/toolbar.js.map +1 -1
  250. package/dist/components/cms/rich-text/unknown-node.js +2 -2
  251. package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
  252. package/dist/components/cms/rich-text-preview.js +2 -2
  253. package/dist/components/cms/rich-text-preview.js.map +1 -1
  254. package/dist/components/cms/row-actions.d.ts +1 -1
  255. package/dist/components/cms/row-actions.js.map +1 -1
  256. package/dist/components/cms/scalar-value-editor.d.ts +1 -1
  257. package/dist/components/cms/scalar-value-editor.js +2 -2
  258. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  259. package/dist/components/cms/use-discard-controller.d.ts +1 -1
  260. package/dist/components/cms/use-discard-controller.js +1 -1
  261. package/dist/components/cms/use-discard-controller.js.map +1 -1
  262. package/dist/components/cms/use-document-draft.d.ts +7 -1
  263. package/dist/components/cms/use-document-draft.js +16 -2
  264. package/dist/components/cms/use-document-draft.js.map +1 -1
  265. package/dist/components/cms/use-document-versions.js +1 -1
  266. package/dist/components/cms/use-document-versions.js.map +1 -1
  267. package/dist/components/cms/use-field-focus.d.ts +1 -1
  268. package/dist/components/cms/use-field-focus.js +2 -2
  269. package/dist/components/cms/use-field-focus.js.map +1 -1
  270. package/dist/components/cms/use-live-url.d.ts +1 -1
  271. package/dist/components/cms/use-live-url.js +3 -3
  272. package/dist/components/cms/use-live-url.js.map +1 -1
  273. package/dist/components/cms/use-publish-controller.d.ts +1 -1
  274. package/dist/components/cms/use-publish-controller.js +1 -1
  275. package/dist/components/cms/use-publish-controller.js.map +1 -1
  276. package/dist/components/cms/use-published-perspective.d.ts +1 -1
  277. package/dist/components/cms/use-published-perspective.js.map +1 -1
  278. package/dist/components/cms/use-restore-version.d.ts +1 -1
  279. package/dist/components/cms/use-restore-version.js +1 -1
  280. package/dist/components/cms/use-restore-version.js.map +1 -1
  281. package/dist/components/cms/version-compare-lazy.js +1 -1
  282. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  283. package/dist/components/cms/version-compare.js +1 -1
  284. package/dist/components/cms/version-compare.js.map +1 -1
  285. package/dist/components/cms/versions-pane.js +3 -3
  286. package/dist/components/cms/versions-pane.js.map +1 -1
  287. package/dist/components/cms/view-bar.js +3 -3
  288. package/dist/components/cms/view-bar.js.map +1 -1
  289. package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
  290. package/dist/components/collection-view/cms-collection-view-source.js +2 -2
  291. package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
  292. package/dist/components/collection-view/collection-view-context.d.ts +1 -1
  293. package/dist/components/collection-view/collection-view-context.js +1 -1
  294. package/dist/components/collection-view/collection-view-context.js.map +1 -1
  295. package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
  296. package/dist/components/collection-view/collection-view-surface.js +18 -0
  297. package/dist/components/collection-view/collection-view-surface.js.map +1 -0
  298. package/dist/components/components/component-detail.js +10 -10
  299. package/dist/components/components/component-detail.js.map +1 -1
  300. package/dist/components/components/component-preview.d.ts +2 -2
  301. package/dist/components/components/component-preview.js +5 -5
  302. package/dist/components/components/component-preview.js.map +1 -1
  303. package/dist/components/components/component-status.d.ts +1 -1
  304. package/dist/components/components/component-status.js +1 -1
  305. package/dist/components/components/component-status.js.map +1 -1
  306. package/dist/components/components/components-index.js +2 -2
  307. package/dist/components/components/components-index.js.map +1 -1
  308. package/dist/components/components/field-inspector.js +1 -1
  309. package/dist/components/components/field-inspector.js.map +1 -1
  310. package/dist/components/components/registry-browser.js +3 -3
  311. package/dist/components/components/registry-browser.js.map +1 -1
  312. package/dist/components/components/taxonomy.d.ts +1 -1
  313. package/dist/components/components/taxonomy.js.map +1 -1
  314. package/dist/components/components/transfer-dialog.js +3 -3
  315. package/dist/components/components/transfer-dialog.js.map +1 -1
  316. package/dist/components/current-user-menu.js +1 -1
  317. package/dist/components/current-user-menu.js.map +1 -1
  318. package/dist/components/delivery-health-banner.js +2 -2
  319. package/dist/components/delivery-health-banner.js.map +1 -1
  320. package/dist/components/deployment-setup-gate.js +3 -3
  321. package/dist/components/deployment-setup-gate.js.map +1 -1
  322. package/dist/components/dev-login-badge.js +2 -2
  323. package/dist/components/dev-login-badge.js.map +1 -1
  324. package/dist/components/guards.js +1 -1
  325. package/dist/components/guards.js.map +1 -1
  326. package/dist/components/inspector/document-panes.d.ts +54 -0
  327. package/dist/components/inspector/document-panes.js +41 -0
  328. package/dist/components/inspector/document-panes.js.map +1 -0
  329. package/dist/components/inspector/inspector-sidebar.js +1 -1
  330. package/dist/components/inspector/inspector-sidebar.js.map +1 -1
  331. package/dist/components/nav-issues-registry.d.ts +53 -0
  332. package/dist/components/nav-issues-registry.js +69 -0
  333. package/dist/components/nav-issues-registry.js.map +1 -0
  334. package/dist/components/nav-issues.js +4 -4
  335. package/dist/components/nav-issues.js.map +1 -1
  336. package/dist/components/operation-error.d.ts +1 -1
  337. package/dist/components/operation-error.js +4 -4
  338. package/dist/components/operation-error.js.map +1 -1
  339. package/dist/components/playground-badge.js +2 -2
  340. package/dist/components/playground-badge.js.map +1 -1
  341. package/dist/components/project-list.js +3 -3
  342. package/dist/components/project-list.js.map +1 -1
  343. package/dist/components/project-session.d.ts +2 -2
  344. package/dist/components/project-session.js +5 -5
  345. package/dist/components/project-session.js.map +1 -1
  346. package/dist/components/project-switcher.js +2 -2
  347. package/dist/components/project-switcher.js.map +1 -1
  348. package/dist/components/rail-store-status.js +1 -1
  349. package/dist/components/rail-store-status.js.map +1 -1
  350. package/dist/components/rail-view-source.d.ts +6 -5
  351. package/dist/components/rail-view-source.js +4 -3
  352. package/dist/components/rail-view-source.js.map +1 -1
  353. package/dist/components/records/record-lens.d.ts +51 -0
  354. package/dist/components/records/record-lens.js +156 -0
  355. package/dist/components/records/record-lens.js.map +1 -0
  356. package/dist/components/records/record-rail.d.ts +86 -0
  357. package/dist/components/records/record-rail.js +241 -0
  358. package/dist/components/records/record-rail.js.map +1 -0
  359. package/dist/components/redirects/redirects-schema.js +1 -1
  360. package/dist/components/redirects/redirects-schema.js.map +1 -1
  361. package/dist/components/references/references-panel.js +2 -2
  362. package/dist/components/references/references-panel.js.map +1 -1
  363. package/dist/components/section-nav.js +27 -11
  364. package/dist/components/section-nav.js.map +1 -1
  365. package/dist/components/section-state.js +2 -2
  366. package/dist/components/section-state.js.map +1 -1
  367. package/dist/components/section-surface.js +2 -2
  368. package/dist/components/section-surface.js.map +1 -1
  369. package/dist/components/session-expired-overlay.js +3 -3
  370. package/dist/components/session-expired-overlay.js.map +1 -1
  371. package/dist/components/settings-form.js +1 -1
  372. package/dist/components/settings-form.js.map +1 -1
  373. package/dist/components/sign-in-needed-badge.js +1 -1
  374. package/dist/components/sign-in-needed-badge.js.map +1 -1
  375. package/dist/components/sign-in-view.d.ts +2 -2
  376. package/dist/components/sign-in-view.js +5 -5
  377. package/dist/components/sign-in-view.js.map +1 -1
  378. package/dist/components/studio-context.js +3 -3
  379. package/dist/components/studio-context.js.map +1 -1
  380. package/dist/components/studio-project-screen.js +30 -3
  381. package/dist/components/studio-project-screen.js.map +1 -1
  382. package/dist/components/studio-shell.js +6 -5
  383. package/dist/components/studio-shell.js.map +1 -1
  384. package/dist/components/theme-context.d.ts +1 -1
  385. package/dist/components/theme-context.js +2 -2
  386. package/dist/components/theme-context.js.map +1 -1
  387. package/dist/components/use-hydration-phase.d.ts +1 -1
  388. package/dist/components/use-hydration-phase.js +1 -1
  389. package/dist/components/use-hydration-phase.js.map +1 -1
  390. package/dist/components/users/invite-user-bar.d.ts +27 -0
  391. package/dist/components/users/invite-user-bar.js +25 -9
  392. package/dist/components/users/invite-user-bar.js.map +1 -1
  393. package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
  394. package/dist/components/users/role-policy-drift-issues.js +71 -0
  395. package/dist/components/users/role-policy-drift-issues.js.map +1 -0
  396. package/dist/components/users/users-collection-view-source.d.ts +1 -1
  397. package/dist/components/users/users-collection-view-source.js +4 -2
  398. package/dist/components/users/users-collection-view-source.js.map +1 -1
  399. package/dist/components/users/users-module.js +1 -1
  400. package/dist/components/users/users-module.js.map +1 -1
  401. package/dist/components/users/users-schema.d.ts +10 -1
  402. package/dist/components/users/users-schema.js +45 -25
  403. package/dist/components/users/users-schema.js.map +1 -1
  404. package/dist/components/users/users-store.js +3 -3
  405. package/dist/components/users/users-store.js.map +1 -1
  406. package/dist/components/workspace-projects.d.ts +1 -1
  407. package/dist/components/workspace-projects.js +1 -1
  408. package/dist/components/workspace-projects.js.map +1 -1
  409. package/dist/lib/ai/index.d.ts +1 -1
  410. package/dist/lib/ai/index.js +1 -1
  411. package/dist/lib/ai/index.js.map +1 -1
  412. package/dist/lib/ai/node-handler.d.ts +1 -1
  413. package/dist/lib/ai/node-handler.js.map +1 -1
  414. package/dist/lib/ai/translate-document.js +1 -1
  415. package/dist/lib/ai/translate-document.js.map +1 -1
  416. package/dist/lib/clipboard/paste-issues.js +1 -1
  417. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  418. package/dist/lib/clipboard/paste-target.js +2 -2
  419. package/dist/lib/clipboard/paste-target.js.map +1 -1
  420. package/dist/lib/cms/published-documents.js +1 -1
  421. package/dist/lib/cms/published-documents.js.map +1 -1
  422. package/dist/lib/cms-views/view-model.d.ts +12 -12
  423. package/dist/lib/cms-views/view-model.js +2 -2
  424. package/dist/lib/cms-views/view-model.js.map +1 -1
  425. package/dist/lib/collection-view/collection-view-source.d.ts +7 -2
  426. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  427. package/dist/lib/data/content-session-refresh.d.ts +1 -1
  428. package/dist/lib/data/content-session-refresh.js.map +1 -1
  429. package/dist/lib/data/playground-unload-guard.d.ts +1 -1
  430. package/dist/lib/data/playground-unload-guard.js +1 -1
  431. package/dist/lib/data/playground-unload-guard.js.map +1 -1
  432. package/dist/lib/engine-version.d.ts +1 -1
  433. package/dist/lib/engine-version.js +1 -1
  434. package/dist/lib/engine-version.js.map +1 -1
  435. package/dist/lib/i18n/i18n.js +1 -1
  436. package/dist/lib/i18n/i18n.js.map +1 -1
  437. package/dist/lib/i18n/messages.de.js +4 -0
  438. package/dist/lib/i18n/messages.de.js.map +1 -1
  439. package/dist/lib/i18n/messages.en.d.ts +4 -0
  440. package/dist/lib/i18n/messages.en.js +8 -0
  441. package/dist/lib/i18n/messages.en.js.map +1 -1
  442. package/dist/lib/operations/project-runtime-config.d.ts +9 -0
  443. package/dist/lib/operations/project-runtime-config.js +13 -0
  444. package/dist/lib/operations/project-runtime-config.js.map +1 -1
  445. package/dist/lib/operations/studio-operations.d.ts +9 -1
  446. package/dist/lib/operations/studio-operations.js +6 -1
  447. package/dist/lib/operations/studio-operations.js.map +1 -1
  448. package/dist/lib/persistence/convex-adapter.d.ts +16 -3
  449. package/dist/lib/persistence/convex-adapter.js +78 -47
  450. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  451. package/dist/lib/persistence/index.js +14 -1
  452. package/dist/lib/persistence/index.js.map +1 -1
  453. package/dist/lib/persona/labels.js +1 -1
  454. package/dist/lib/persona/labels.js.map +1 -1
  455. package/dist/lib/query/operations-query.d.ts +1 -1
  456. package/dist/lib/query/operations-query.js +1 -1
  457. package/dist/lib/query/operations-query.js.map +1 -1
  458. package/dist/lib/references/reference-model.js +1 -1
  459. package/dist/lib/references/reference-model.js.map +1 -1
  460. package/dist/lib/references/references-store.js +1 -1
  461. package/dist/lib/references/references-store.js.map +1 -1
  462. package/dist/lib/workspace/index.d.ts +6 -0
  463. package/dist/lib/workspace/index.js +8 -0
  464. package/dist/lib/workspace/index.js.map +1 -1
  465. package/dist/mount.d.ts +28 -0
  466. package/dist/mount.js +27 -0
  467. package/dist/mount.js.map +1 -0
  468. package/dist/router/root.js +10 -10
  469. package/dist/router/root.js.map +1 -1
  470. package/dist/ui/badge.d.ts +1 -1
  471. package/dist/ui/button.d.ts +2 -2
  472. package/package.json +15 -11
  473. package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
  474. package/dist/components/redirects/redirects-collection-view-source.js +0 -146
  475. package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
  476. package/dist/components/redirects/redirects-module.d.ts +0 -21
  477. package/dist/components/redirects/redirects-module.js +0 -40
  478. package/dist/components/redirects/redirects-module.js.map +0 -1
  479. package/dist/components/redirects/redirects-store.d.ts +0 -68
  480. package/dist/components/redirects/redirects-store.js +0 -132
  481. package/dist/components/redirects/redirects-store.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport { stableStringify } from '@elytracms/core/cms-core'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { humanizeName } from '@elytracms/studio/lib/utils'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/**\n * Key-order-insensitive structural equality — a redundant \"save\" must not write. The\n * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).\n * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a\n * real edit that a tolerant reader would read as \"the same\".\n */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
1
+ {"version":3,"file":"embed-props.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/embed-props.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Lock } from 'lucide-react'\nimport { isStaticProp, staticProp } from '@elytracms/core/project-graph'\nimport { stableStringify } from '@elytracms/core/cms-core'\nimport type { ComponentNode, JsonValue } from '@elytracms/core/project-graph'\nimport type { ComponentManifest, PropField } from '@elytracms/core/component-registry'\nimport {\n Badge,\n ExpandableSurface,\n Field,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '../../../lib/i18n'\nimport { humanizeName } from '../../../lib/utils'\nimport { componentDisplayName } from '../../../lib/persona/labels'\nimport { PropValueControl } from '../prop-fields'\nimport { visiblePropFields } from '../prop-visibility'\n\n/**\n * Prop editing for rich-text component embeds (EC-314 phase 4; stage surface\n * EC-430/AD-D).\n *\n * An embedded component is a full `ComponentNode` stored verbatim at\n * `componentEmbed.attrs.node`. The {@link EmbedPropsStage} edits that node's *props*\n * through the SAME EC-190 \"props-as-fields\" control stack the canvas inspector and\n * `blocks` field use ({@link PropValueControl}) — asset props open the library\n * picker, relation props the document picker, etc. — never a parallel form system.\n *\n * One surface, reachable everywhere (ratified AD-D, 2026-07-16): \"Edit settings\" on\n * an embed card opens the centered stage (`ExpandableSurface variant=\"stage\"\n * preset=\"form\"`) identically from the CANVAS tab and the form view. It replaced\n * BOTH halves of the 0.0.41 fork — the selection-driven sidebar swap (which only\n * rendered in the form view, leaving the canvas tab's \"Edit settings\" dead) and the\n * modal fallback.\n *\n * Value contract (unchanged from the 0.0.41 sidebar inspector): write-back is\n * **immediate per prop change** — each edit commits through `onChange` (one\n * ProseMirror `updateAttributes` transaction upstream, undoable), guarded by\n * {@link resolveEmbedWrite} so an unchanged write never dirties the field. There is\n * no draft+Apply and no save/cancel of its own: the stage is the same value flow\n * relocated, lightly dismissible (Escape / backdrop / header close) per the\n * forms-never-modal spirit. The dialog fork's draft+Apply semantics died with it.\n *\n * `blocks`-typed props (nested composition) are NOT edited here — that needs the\n * canvas, not a form. They are excluded from the form and surfaced as a note.\n */\n\n/** Set a prop to a fresh static value, preserving identity, slots, and sibling props. */\nexport function setComponentNodeProp(\n node: ComponentNode,\n name: string,\n value: JsonValue,\n): ComponentNode {\n return { ...node, props: { ...node.props, [name]: staticProp(value) } }\n}\n\n/** Remove a prop (revert to the manifest default), preserving everything else. */\nexport function clearComponentNodeProp(node: ComponentNode, name: string): ComponentNode {\n const props = { ...node.props }\n delete props[name]\n return { ...node, props }\n}\n\n/**\n * Key-order-insensitive structural equality — a redundant \"save\" must not write. The\n * core `stableStringify` (EC-586: one serialization; this used to be a studio copy).\n * Deliberately STRUCTURAL, not `semanticValueEqual`: a no-op guard must never swallow a\n * real edit that a tolerant reader would read as \"the same\".\n */\nexport function componentNodesEqual(a: ComponentNode, b: ComponentNode): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\n/**\n * The no-dirty guard for immediate per-prop write-back (0.0.41 sidebar inspector,\n * inherited verbatim by the EC-430 stage form): the node to commit for an edit, or\n * `undefined` when it is a structural no-op so an unchanged write never dirties the\n * field ({@link componentNodesEqual}), applied per keystroke.\n */\nexport function resolveEmbedWrite(current: ComponentNode, next: ComponentNode): ComponentNode | undefined {\n return componentNodesEqual(next, current) ? undefined : next\n}\n\n/** The manifest's `blocks` (composition) props — excluded from the form, shown as a note. */\nexport function blocksEmbedProps(manifest: ComponentManifest): Array<[string, PropField]> {\n return Object.entries(manifest.props).filter(([, spec]) => spec.type === 'blocks')\n}\n\n/**\n * A lean one-line summary of an embed's static scalar props (`Label: value`,\n * up to three), for the card. Skips unset props and structured values (refs /\n * objects / arrays) — those are edited in the stage form, not summarised.\n */\nexport function embedPropsSummary(\n node: ComponentNode,\n manifest?: ComponentManifest,\n): string | undefined {\n const parts: string[] = []\n for (const [name, value] of Object.entries(node.props ?? {})) {\n if (!isStaticProp(value)) continue\n const raw = value.value\n if (raw === null || raw === undefined || typeof raw === 'object') continue\n const label = manifest?.props[name]?.form?.label\n const labelText = label ? localizedLabel(label, name) : humanizeName(name)\n parts.push(`${labelText}: ${String(raw)}`)\n if (parts.length >= 3) break\n }\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\nexport interface EmbedStageFormProps {\n /** The embedded component node whose props are being edited (the STORED node). */\n node: ComponentNode\n /** Its manifest — drives which prop fields render. */\n manifest: ComponentManifest\n /**\n * Commit an edited node. Upstream writes it through one ProseMirror\n * `updateAttributes` transaction (undoable); the no-dirty guard has already been\n * applied here, so this only fires on a real structural change.\n */\n onChange: (node: ComponentNode) => void\n}\n\n/**\n * The embed prop form body — pure of surface chrome so it smoke-renders in the node\n * test env (the stage itself portals and renders `null` without a DOM).\n *\n * EC-393 parity: rows filter through the SAME {@link visiblePropFields} core the\n * canvas property inspector uses — a predicate-hidden row (`form.hidden` over the\n * sibling prop values) unmounts/remounts live as its driver prop flips, because the\n * committed node re-renders this form. EC-395 parity: `form.readOnly` renders the\n * document form's Lock badge next to the label and disables the delegated control\n * (`PropValueControl` reads the same flag); `form.control` and `form.description`\n * route through the same delegation.\n */\nexport function EmbedStageForm({ node, manifest, onChange }: EmbedStageFormProps) {\n const visible = visiblePropFields(manifest, node)\n const blocks = blocksEmbedProps(manifest)\n\n function set(name: string, value: JsonValue) {\n const next = resolveEmbedWrite(node, setComponentNodeProp(node, name, value))\n if (next) onChange(next)\n }\n function clear(name: string) {\n const next = resolveEmbedWrite(node, clearComponentNodeProp(node, name))\n if (next) onChange(next)\n }\n\n return (\n <Stack gap=\"4\">\n {visible.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\">\n {translate('richText.embed.noProps')}\n </Text>\n ) : (\n visible.map(([name, spec]) => {\n const id = `embed-${node.id}-${name}`\n return (\n <Field\n key={name}\n label={\n spec.form?.readOnly === true ? (\n // EC-395: the SAME read-only badge the document form and the canvas\n // prop inspector render next to a locked label — identical icon,\n // i18n key, and `Badge` variant, never a bespoke treatment.\n <Row gap=\"2\">\n <span>{localizedLabel(spec.form?.label, name)}</span>\n <Badge variant=\"outline\">\n <Lock className=\"size-3\" /> {translate('cms.editor.readOnlyBadge')}\n </Badge>\n </Row>\n ) : (\n localizedLabel(spec.form?.label, name)\n )\n }\n htmlFor={id}\n hint={localizedLabel(spec.form?.description) || undefined}\n >\n <PropValueControl\n id={id}\n name={name}\n spec={spec}\n value={node.props?.[name]}\n onSet={(v) => set(name, v)}\n onClear={() => clear(name)}\n />\n </Field>\n )\n })\n )}\n {blocks.length > 0 ? (\n <>\n <Separator />\n <Text size=\"xs\" tone=\"subtle\">\n {translate('richText.embed.blocksNote')}\n </Text>\n </>\n ) : null}\n </Stack>\n )\n}\n\nexport interface EmbedPropsStageProps extends EmbedStageFormProps {\n /** Dismiss request (Escape / backdrop / header close). The caller owns the open state. */\n onDismiss: () => void\n}\n\n/**\n * The stage-body focus handoff (a module-level constant so React never re-invokes it\n * on re-renders — a per-keystroke re-invocation would churn the guard). The stage\n * opens from a card INSIDE a focused TipTap editor, and prosemirror-view\n * preventDefaults every Escape while its contenteditable holds focus\n * (`capturekeys.ts` — its \"dangerous default\" backdrop map), which the surface's\n * EC-392 bridge reads as \"handled by a layer above\" — so Escape would never dismiss,\n * and stray typing would land in the covered document. Moving focus into the stage\n * on mount hands Escape to the layer stack; the `focusin` guard keeps it there,\n * because ProseMirror re-asserts focus on its editable off deferred click handling\n * and window-refocus events. The guard is NOT a focus trap: it only reclaims from a\n * ProseMirror editable OUTSIDE the stage (unreachable to the user behind the\n * backdrop — only programmatic steals hit this), never from the stage's own controls\n * (incl. nested rich-text prop editors, `el.contains`) or a picker dialog above.\n * Per-caller policy, deliberately NOT in ExpandableSurface: the rich-text prose\n * stage RELOCATES the editor, so there the editor keeping focus is the point.\n * React 19 ref-cleanup unregisters the guard when the stage unmounts.\n */\nconst focusStageBody = (el: HTMLDivElement | null) => {\n if (!el) return undefined\n el.focus()\n const guard = () => {\n const active = document.activeElement\n if (el.contains(active)) return\n if (active instanceof HTMLElement && active.closest('.ProseMirror')) el.focus()\n }\n document.addEventListener('focusin', guard)\n return () => document.removeEventListener('focusin', guard)\n}\n\n/**\n * The embed prop inspector on the centered stage (EC-430/AD-D) — mounted by the\n * embed card's \"Edit settings\" from ANY rich-text surface (form view, canvas-tab\n * prop inspector, or inside the expanded rich-text stage — a later mount rides the\n * EC-392 layer stack above it, and pickers opened from these controls stack above\n * this stage in turn). Hook-free by construction so the geometry contract is\n * pinnable by direct invocation in the node test env.\n */\nexport function EmbedPropsStage({ node, manifest, onChange, onDismiss }: EmbedPropsStageProps) {\n return (\n <ExpandableSurface\n variant=\"stage\"\n preset=\"form\"\n title={translate('richText.embed.editTitle', {\n name: componentDisplayName(node.componentId, manifest),\n })}\n closeLabel={translate('common.close')}\n onDismiss={onDismiss}\n >\n <div\n ref={focusStageBody}\n tabIndex={-1}\n className=\"min-h-0 flex-1 overflow-y-auto p-6 outline-none\"\n >\n <EmbedStageForm node={node} manifest={manifest} onChange={onChange} />\n </div>\n </ExpandableSurface>\n )\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import StarterKit from '@tiptap/starter-kit';
2
2
  import { Placeholder } from '@tiptap/extensions';
3
- import { translate } from '@elytracms/studio/lib/i18n';
3
+ import { translate } from '../../../lib/i18n/index.js';
4
4
  import { RefLink } from './ref-link.js';
5
5
  import { FontSize } from './font-size.js';
6
6
  import { AssetImage } from './asset-image.js';
@@ -1 +1 @@
1
- {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA2C,EAAE;IACnF,OAAO;QACL,UAAU,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxC,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC5E,QAAQ;QACR,UAAU;QACV,cAAc;QACd,kBAAkB;QAClB,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzE,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KACxC,CAAA;AACH,CAAC","sourcesContent":["import StarterKit from '@tiptap/starter-kit'\nimport { Placeholder } from '@tiptap/extensions'\nimport type { Extensions } from '@tiptap/react'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RefLink } from './ref-link'\nimport { FontSize } from './font-size'\nimport { AssetImage } from './asset-image'\nimport { ComponentEmbed } from './component-embed'\nimport { UnknownNodeCarrier } from './unknown-node'\nimport { createSlashCommand } from './slash-command'\nimport type { SlashCommandOptions } from './slash-command'\n\n/**\n * The TipTap extension set for the CMS rich-text editor (EC-314).\n *\n * Deliberately matches the storage vocabulary (`@elytracms/core/rich-text` schema)\n * one-for-one so every editor document round-trips through `parseRichTextValue` /\n * the delivery renderer with no format change:\n *\n * - **StarterKit** supplies `doc/paragraph/text/heading/bulletList/orderedList/\n * listItem/blockquote/codeBlock/hardBreak/horizontalRule` and the marks\n * `bold/italic/code/strike/underline` — all ProseMirror-standard names identical\n * to storage. Its bundled `link` is disabled and replaced by {@link RefLink} so\n * internal-document links can carry a structured `ref` (EC-290). Headings allow\n * all six levels so existing `h4`–`h6` content is preserved even though the\n * toolbar surfaces only 1–3.\n * - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying\n * atoms (asset image, embedded registry component) so existing content of those\n * kinds survives a round-trip verbatim rather than being dropped by the schema.\n * - {@link UnknownNodeCarrier} (EC-547) is the editor-only opaque atom the\n * storage⇄editor projection wraps any OTHER stored node type into, so the\n * closed ProseMirror schema can never drop a node the flow editor does not\n * natively edit — the EC-500 D3 round-trip law.\n * - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run\n * round-trips (an undeclared mark would be silently dropped on load).\n *\n * No `allow` gating lives here — that governs *insertion* (the embed picker) and\n * *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.\n *\n * `slash` wires the Notion-style `/` menu to the editor's shared image/embed pickers\n * (omit an action to hide that slash item).\n */\nexport function buildRichTextExtensions(options: { slash?: SlashCommandOptions } = {}): Extensions {\n return [\n StarterKit.configure({\n link: false,\n heading: { levels: [1, 2, 3, 4, 5, 6] },\n }),\n RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),\n FontSize,\n AssetImage,\n ComponentEmbed,\n UnknownNodeCarrier,\n Placeholder.configure({ placeholder: translate('richText.placeholder') }),\n createSlashCommand(options.slash ?? {}),\n ]\n}\n"]}
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAA2C,EAAE;IACnF,OAAO;QACL,UAAU,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE;SACxC,CAAC;QACF,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC5E,QAAQ;QACR,UAAU;QACV,cAAc;QACd,kBAAkB;QAClB,WAAW,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzE,kBAAkB,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;KACxC,CAAA;AACH,CAAC","sourcesContent":["import StarterKit from '@tiptap/starter-kit'\nimport { Placeholder } from '@tiptap/extensions'\nimport type { Extensions } from '@tiptap/react'\nimport { translate } from '../../../lib/i18n'\nimport { RefLink } from './ref-link'\nimport { FontSize } from './font-size'\nimport { AssetImage } from './asset-image'\nimport { ComponentEmbed } from './component-embed'\nimport { UnknownNodeCarrier } from './unknown-node'\nimport { createSlashCommand } from './slash-command'\nimport type { SlashCommandOptions } from './slash-command'\n\n/**\n * The TipTap extension set for the CMS rich-text editor (EC-314).\n *\n * Deliberately matches the storage vocabulary (`@elytracms/core/rich-text` schema)\n * one-for-one so every editor document round-trips through `parseRichTextValue` /\n * the delivery renderer with no format change:\n *\n * - **StarterKit** supplies `doc/paragraph/text/heading/bulletList/orderedList/\n * listItem/blockquote/codeBlock/hardBreak/horizontalRule` and the marks\n * `bold/italic/code/strike/underline` — all ProseMirror-standard names identical\n * to storage. Its bundled `link` is disabled and replaced by {@link RefLink} so\n * internal-document links can carry a structured `ref` (EC-290). Headings allow\n * all six levels so existing `h4`–`h6` content is preserved even though the\n * toolbar surfaces only 1–3.\n * - {@link AssetImage} / {@link ComponentEmbed} declare the two reference-carrying\n * atoms (asset image, embedded registry component) so existing content of those\n * kinds survives a round-trip verbatim rather than being dropped by the schema.\n * - {@link UnknownNodeCarrier} (EC-547) is the editor-only opaque atom the\n * storage⇄editor projection wraps any OTHER stored node type into, so the\n * closed ProseMirror schema can never drop a node the flow editor does not\n * natively edit — the EC-500 D3 round-trip law.\n * - {@link FontSize} declares the semantic font-size mark (EC-411) so a sized run\n * round-trips (an undeclared mark would be silently dropped on load).\n *\n * No `allow` gating lives here — that governs *insertion* (the embed picker) and\n * *validation* (`validateRichTextEmbeds`), not which nodes the schema tolerates.\n *\n * `slash` wires the Notion-style `/` menu to the editor's shared image/embed pickers\n * (omit an action to hide that slash item).\n */\nexport function buildRichTextExtensions(options: { slash?: SlashCommandOptions } = {}): Extensions {\n return [\n StarterKit.configure({\n link: false,\n heading: { levels: [1, 2, 3, 4, 5, 6] },\n }),\n RefLink.configure({ openOnClick: false, autolink: true, linkOnPaste: true }),\n FontSize,\n AssetImage,\n ComponentEmbed,\n UnknownNodeCarrier,\n Placeholder.configure({ placeholder: translate('richText.placeholder') }),\n createSlashCommand(options.slash ?? {}),\n ]\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { Editor } from '@tiptap/react';
2
2
  import type { RichTextFontSize } from '@elytracms/core/rich-text';
3
- import type { MessageKey } from '@elytracms/studio/lib/i18n';
3
+ import type { MessageKey } from '../../../lib/i18n/index.js';
4
4
  /**
5
5
  * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the
6
6
  * markless *normal* step — selecting it REMOVES the mark rather than storing a value.
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ALargeSmall, Check, ChevronDown } from 'lucide-react';
3
3
  import { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text';
4
4
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@elytracms/studio/ui';
5
- import { cn } from '@elytracms/studio/lib/utils';
6
- import { translate } from '@elytracms/studio/lib/i18n';
5
+ import { cn } from '../../../lib/utils.js';
6
+ import { translate } from '../../../lib/i18n/index.js';
7
7
  import { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar.js';
8
8
  /** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */
9
9
  export const FONT_SIZE_PRESETS = [
@@ -1 +1 @@
1
- {"version":3,"file":"font-size-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/font-size-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAA;AAWxD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC1D,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;IACpD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;CACrD,CAAA;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,MAAsB,EAAE,UAAmC;IACxF,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAA;AACnC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAA6B;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAA;IACpC,IAAI,IAAI;QAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;;QACvD,KAAK,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,CAAA;AACjD,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAwB;IAC1E,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,4BAA4B,gBAC3B,SAAS,CAAC,yBAAyB,CAAC,EAChD,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAC3C,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAEtC,KAAC,WAAW,IAAC,SAAS,EAAC,UAAU,GAAG,EACpC,KAAC,WAAW,IAAC,SAAS,EAAC,mBAAmB,GAAG,IACtC,GACW,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YACpD,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBAChC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;oBACjD,OAAO,CACL,MAAC,gBAAgB,IAEf,QAAQ,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,aAExD,KAAC,KAAK,IAAC,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,GAAI,EAAC,GAAG,EAC/D,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAJtB,MAAM,CAAC,IAAI,IAAI,QAAQ,CAKX,CACpB,CAAA;gBACH,CAAC,CAAC,GACkB,IACT,CAChB,CAAA;AACH,CAAC","sourcesContent":["import { ALargeSmall, Check, ChevronDown } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text'\nimport type { RichTextFontSize } from '@elytracms/core/rich-text'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport type { MessageKey } from '@elytracms/studio/lib/i18n'\nimport { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar'\n\n/**\n * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the\n * markless *normal* step — selecting it REMOVES the mark rather than storing a value.\n */\nexport interface FontSizePreset {\n size: RichTextFontSize | null\n labelKey: MessageKey\n}\n\n/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */\nexport const FONT_SIZE_PRESETS: readonly FontSizePreset[] = [\n { size: 'sm', labelKey: 'richText.fontSize.small' },\n { size: null, labelKey: 'richText.fontSize.normal' },\n { size: 'lg', labelKey: 'richText.fontSize.large' },\n { size: 'xl', labelKey: 'richText.fontSize.xlarge' },\n]\n\n/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */\nexport function isPresetActive(preset: FontSizePreset, activeSize: RichTextFontSize | null): boolean {\n return preset.size === activeSize\n}\n\n/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */\nexport function applyFontSizePreset(editor: Editor, size: RichTextFontSize | null): void {\n const chain = editor.chain().focus()\n if (size) chain.setMark(FONT_SIZE_MARK_TYPE, { size }).run()\n else chain.unsetMark(FONT_SIZE_MARK_TYPE).run()\n}\n\ninterface FontSizeControlProps {\n editor: Editor\n /** The active semantic step (from the toolbar state), or null for normal. */\n activeSize: RichTextFontSize | null\n}\n\n/**\n * The rich-text font-size control (EC-411). Width-stable under the EC-418 contract\n * (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box\n * as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels\n * live only in the opened menu, never on the strip, so the control never resizes with\n * selection. The trigger keeps an accessible name via an `aria-label`.\n */\nexport function FontSizeControl({ editor, activeSize }: FontSizeControlProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n aria-label={translate('richText.fontSize.label')}\n title={translate('richText.fontSize.label')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <ALargeSmall className=\"size-3.5\" />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {FONT_SIZE_PRESETS.map((preset) => {\n const active = isPresetActive(preset, activeSize)\n return (\n <DropdownMenuItem\n key={preset.size ?? 'normal'}\n onSelect={() => applyFontSizePreset(editor, preset.size)}\n >\n <Check className={cn('size-3.5', !active && 'invisible')} />{' '}\n {translate(preset.labelKey)}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
1
+ {"version":3,"file":"font-size-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/font-size-control.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAE/D,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,OAAO,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAA;AAWxD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,iBAAiB,GAA8B;IAC1D,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;IACpD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,EAAE;IACnD,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,0BAA0B,EAAE;CACrD,CAAA;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,MAAsB,EAAE,UAAmC;IACxF,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAA;AACnC,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAA6B;IAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAA;IACpC,IAAI,IAAI;QAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA;;QACvD,KAAK,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,CAAA;AACjD,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAwB;IAC1E,OAAO,CACL,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,4BAA4B,gBAC3B,SAAS,CAAC,yBAAyB,CAAC,EAChD,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAC3C,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAEtC,KAAC,WAAW,IAAC,SAAS,EAAC,UAAU,GAAG,EACpC,KAAC,WAAW,IAAC,SAAS,EAAC,mBAAmB,GAAG,IACtC,GACW,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YACpD,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBAChC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;oBACjD,OAAO,CACL,MAAC,gBAAgB,IAEf,QAAQ,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,aAExD,KAAC,KAAK,IAAC,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,GAAI,EAAC,GAAG,EAC/D,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAJtB,MAAM,CAAC,IAAI,IAAI,QAAQ,CAKX,CACpB,CAAA;gBACH,CAAC,CAAC,GACkB,IACT,CAChB,CAAA;AACH,CAAC","sourcesContent":["import { ALargeSmall, Check, ChevronDown } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { FONT_SIZE_MARK_TYPE } from '@elytracms/core/rich-text'\nimport type { RichTextFontSize } from '@elytracms/core/rich-text'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../../lib/utils'\nimport { translate } from '../../../lib/i18n'\nimport type { MessageKey } from '../../../lib/i18n'\nimport { RICH_TEXT_MENU_TRIGGER_CLASS } from './toolbar'\n\n/**\n * A font-size preset in the toolbar's dropdown (EC-411). `size: null` is the\n * markless *normal* step — selecting it REMOVES the mark rather than storing a value.\n */\nexport interface FontSizePreset {\n size: RichTextFontSize | null\n labelKey: MessageKey\n}\n\n/** The semantic scale the toolbar offers, ascending: small · normal · large · x-large. */\nexport const FONT_SIZE_PRESETS: readonly FontSizePreset[] = [\n { size: 'sm', labelKey: 'richText.fontSize.small' },\n { size: null, labelKey: 'richText.fontSize.normal' },\n { size: 'lg', labelKey: 'richText.fontSize.large' },\n { size: 'xl', labelKey: 'richText.fontSize.xlarge' },\n]\n\n/** Whether a preset is the currently-active step (normal ⇔ no mark ⇔ activeSize null). */\nexport function isPresetActive(preset: FontSizePreset, activeSize: RichTextFontSize | null): boolean {\n return preset.size === activeSize\n}\n\n/** Apply a preset on a focused chain: set the mark for a step, or unset it for normal. */\nexport function applyFontSizePreset(editor: Editor, size: RichTextFontSize | null): void {\n const chain = editor.chain().focus()\n if (size) chain.setMark(FONT_SIZE_MARK_TYPE, { size }).run()\n else chain.unsetMark(FONT_SIZE_MARK_TYPE).run()\n}\n\ninterface FontSizeControlProps {\n editor: Editor\n /** The active semantic step (from the toolbar state), or null for normal. */\n activeSize: RichTextFontSize | null\n}\n\n/**\n * The rich-text font-size control (EC-411). Width-stable under the EC-418 contract\n * (AD-4): the trigger shows a fixed-width icon (+ ChevronDown) in the SAME fixed box\n * as the block-style trigger ({@link RICH_TEXT_MENU_TRIGGER_CLASS}) — the size labels\n * live only in the opened menu, never on the strip, so the control never resizes with\n * selection. The trigger keeps an accessible name via an `aria-label`.\n */\nexport function FontSizeControl({ editor, activeSize }: FontSizeControlProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n aria-label={translate('richText.fontSize.label')}\n title={translate('richText.fontSize.label')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <ALargeSmall className=\"size-3.5\" />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {FONT_SIZE_PRESETS.map((preset) => {\n const active = isPresetActive(preset, activeSize)\n return (\n <DropdownMenuItem\n key={preset.size ?? 'normal'}\n onSelect={() => applyFontSizePreset(editor, preset.size)}\n >\n <Check className={cn('size-3.5', !active && 'invisible')} />{' '}\n {translate(preset.labelKey)}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Suspense, lazy } from 'react';
3
- import { translate } from '@elytracms/studio/lib/i18n';
3
+ import { translate } from '../../../lib/i18n/index.js';
4
4
  /**
5
5
  * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAC1E,CAAA;AAED,SAAS,sBAAsB;IAC7B,OAAO,CACL,cACE,SAAS,EAAC,0FAA0F,EACpG,IAAI,EAAC,QAAQ,eACH,MAAM,YAEhB,eAAM,SAAS,EAAC,SAAS,YAAE,SAAS,CAAC,kBAAkB,CAAC,GAAQ,GAC5D,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgD;IAC7E,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,sBAAsB,KAAG,YAC5C,KAAC,kBAAkB,OAAK,KAAK,GAAI,GACxB,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport type { ComponentProps } from 'react'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).\n *\n * The TipTap editor + its ProseMirror runtime and extension set is a large\n * dependency, and `field-control` sits on the critical path of *every* CMS view.\n * Importing the editor statically pulled all of TipTap into the studio's main\n * route chunk even for documents with no `richText` field on screen. Splitting it\n * behind {@link lazy} moves the whole editor subtree into an on-demand chunk that\n * only downloads when a `richText` field actually renders — the field control's\n * seam is unchanged (it still imports `RichTextEditor` from `./rich-text`).\n *\n * The fallback mirrors the loaded editor's outer frame (same border/rounding + a\n * toolbar-and-body min-height) so the swap-in is layout-jank-free, and carries an\n * accessible loading label from the message catalog.\n */\nconst RichTextEditorImpl = lazy(() =>\n import('./editor').then((module) => ({ default: module.RichTextEditor })),\n)\n\nfunction RichTextEditorFallback() {\n return (\n <div\n className=\"ec-rte min-h-[7.5rem] animate-pulse rounded-md border border-input bg-muted/20 shadow-sm\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span className=\"sr-only\">{translate('richText.loading')}</span>\n </div>\n )\n}\n\nexport function RichTextEditor(props: ComponentProps<typeof RichTextEditorImpl>) {\n return (\n <Suspense fallback={<RichTextEditorFallback />}>\n <RichTextEditorImpl {...props} />\n </Suspense>\n )\n}\n"]}
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAC1E,CAAA;AAED,SAAS,sBAAsB;IAC7B,OAAO,CACL,cACE,SAAS,EAAC,0FAA0F,EACpG,IAAI,EAAC,QAAQ,eACH,MAAM,YAEhB,eAAM,SAAS,EAAC,SAAS,YAAE,SAAS,CAAC,kBAAkB,CAAC,GAAQ,GAC5D,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAgD;IAC7E,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,sBAAsB,KAAG,YAC5C,KAAC,kBAAkB,OAAK,KAAK,GAAI,GACxB,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport type { ComponentProps } from 'react'\nimport { translate } from '../../../lib/i18n'\n\n/**\n * Lazy boundary for the CMS rich-text editor (EC-322 load-time pass).\n *\n * The TipTap editor + its ProseMirror runtime and extension set is a large\n * dependency, and `field-control` sits on the critical path of *every* CMS view.\n * Importing the editor statically pulled all of TipTap into the studio's main\n * route chunk even for documents with no `richText` field on screen. Splitting it\n * behind {@link lazy} moves the whole editor subtree into an on-demand chunk that\n * only downloads when a `richText` field actually renders — the field control's\n * seam is unchanged (it still imports `RichTextEditor` from `./rich-text`).\n *\n * The fallback mirrors the loaded editor's outer frame (same border/rounding + a\n * toolbar-and-body min-height) so the swap-in is layout-jank-free, and carries an\n * accessible loading label from the message catalog.\n */\nconst RichTextEditorImpl = lazy(() =>\n import('./editor').then((module) => ({ default: module.RichTextEditor })),\n)\n\nfunction RichTextEditorFallback() {\n return (\n <div\n className=\"ec-rte min-h-[7.5rem] animate-pulse rounded-md border border-input bg-muted/20 shadow-sm\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span className=\"sr-only\">{translate('richText.loading')}</span>\n </div>\n )\n}\n\nexport function RichTextEditor(props: ComponentProps<typeof RichTextEditorImpl>) {\n return (\n <Suspense fallback={<RichTextEditorFallback />}>\n <RichTextEditorImpl {...props} />\n </Suspense>\n )\n}\n"]}
@@ -3,8 +3,8 @@ import { useMemo, useState } from 'react';
3
3
  import { FileText, Link2, Trash2 } from 'lucide-react';
4
4
  import { referenceNode } from '@elytracms/core/cms-core';
5
5
  import { Button, Input, Popover, PopoverContent, PopoverTrigger, Row, Separator, Stack, Text, } from '@elytracms/studio/ui';
6
- import { cn } from '@elytracms/studio/lib/utils';
7
- import { translate } from '@elytracms/studio/lib/i18n';
6
+ import { cn } from '../../../lib/utils.js';
7
+ import { translate } from '../../../lib/i18n/index.js';
8
8
  const MAX_RESULTS = 40;
9
9
  /**
10
10
  * The rich-text link control (EC-314): set/clear a link on the selection, either as
@@ -1 +1 @@
1
- {"version":3,"file":"link-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/link-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FileText, Link2, Trash2 } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { referenceNode } from '@elytracms/core/cms-core'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/** A pickable internal-link destination: a document plus its display labels. */\nexport interface LinkTarget {\n ref: DocumentRef\n label: string\n collectionLabel: string\n}\n\ninterface LinkControlProps {\n editor: Editor\n /** Documents linkable across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n}\n\nconst MAX_RESULTS = 40\n\n/**\n * The rich-text link control (EC-314): set/clear a link on the selection, either as\n * an external URL (a literal `href`) or an internal document — which writes a\n * structured `attrs.ref` (`referenceNode`, EC-290) so the link stays indexed like a\n * relation and resolves to a live path in delivery. Applying an empty URL clears the\n * link, so the one control both sets and removes.\n */\nexport function LinkControl({ editor, targets }: LinkControlProps) {\n const [open, setOpen] = useState(false)\n const [href, setHref] = useState('')\n const [query, setQuery] = useState('')\n // Null-safe like the toolbar's state selector: a destroyed editor (concurrent\n // teardown / lazy-boundary unmount) still renders this control for a frame.\n const isLink = !editor.isDestroyed && editor.isActive('link')\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n const matched = q\n ? targets.filter((t) => t.label.toLowerCase().includes(q) || t.collectionLabel.toLowerCase().includes(q))\n : targets\n return matched.slice(0, MAX_RESULTS)\n }, [targets, query])\n\n function onOpenChange(next: boolean) {\n if (next) {\n const current = editor.getAttributes('link').href\n setHref(typeof current === 'string' ? current : '')\n setQuery('')\n }\n setOpen(next)\n }\n\n function applyExternal() {\n const url = href.trim()\n const chain = editor.chain().focus().extendMarkRange('link')\n if (url) chain.setLink({ href: url }).run()\n else chain.unsetLink().run()\n setOpen(false)\n }\n\n function applyDocument(target: LinkTarget) {\n editor\n .chain()\n .focus()\n .extendMarkRange('link')\n .setMark('link', { href: null, ref: referenceNode(target.ref) })\n .run()\n setOpen(false)\n }\n\n function remove() {\n editor.chain().focus().extendMarkRange('link').unsetLink().run()\n setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant={isLink ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-label={translate('richText.link.add')}\n aria-pressed={isLink}\n title={translate('richText.link.add')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <Link2 className=\"size-3.5\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-80\">\n <Stack gap=\"2\">\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.external')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Input\n autoFocus\n value={href}\n placeholder={translate('richText.link.externalPlaceholder')}\n onChange={(e) => setHref(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n applyExternal()\n }\n }}\n />\n <Button type=\"button\" size=\"sm\" onClick={applyExternal}>\n {translate('richText.link.apply')}\n </Button>\n </Row>\n\n {targets.length > 0 ? (\n <>\n <Separator className=\"my-0.5\" />\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.document')}\n </Text>\n <Input\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-52 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.link.documentEmpty')}\n </Text>\n ) : (\n results.map((target) => (\n <button\n key={`${target.ref.collection}:${target.ref.id}`}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => applyDocument(target)}\n className={cn(\n 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <FileText className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{target.label}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{target.collectionLabel}</span>\n </button>\n ))\n )}\n </Stack>\n </>\n ) : null}\n\n {isLink ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"self-start\" onClick={remove}>\n <Trash2 className=\"size-3.5\" /> {translate('richText.link.remove')}\n </Button>\n ) : null}\n </Stack>\n </PopoverContent>\n </Popover>\n )\n}\n"]}
1
+ {"version":3,"file":"link-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/link-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FileText, Link2, Trash2 } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { referenceNode } from '@elytracms/core/cms-core'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../../lib/utils'\nimport { translate } from '../../../lib/i18n'\n\n/** A pickable internal-link destination: a document plus its display labels. */\nexport interface LinkTarget {\n ref: DocumentRef\n label: string\n collectionLabel: string\n}\n\ninterface LinkControlProps {\n editor: Editor\n /** Documents linkable across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n}\n\nconst MAX_RESULTS = 40\n\n/**\n * The rich-text link control (EC-314): set/clear a link on the selection, either as\n * an external URL (a literal `href`) or an internal document — which writes a\n * structured `attrs.ref` (`referenceNode`, EC-290) so the link stays indexed like a\n * relation and resolves to a live path in delivery. Applying an empty URL clears the\n * link, so the one control both sets and removes.\n */\nexport function LinkControl({ editor, targets }: LinkControlProps) {\n const [open, setOpen] = useState(false)\n const [href, setHref] = useState('')\n const [query, setQuery] = useState('')\n // Null-safe like the toolbar's state selector: a destroyed editor (concurrent\n // teardown / lazy-boundary unmount) still renders this control for a frame.\n const isLink = !editor.isDestroyed && editor.isActive('link')\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n const matched = q\n ? targets.filter((t) => t.label.toLowerCase().includes(q) || t.collectionLabel.toLowerCase().includes(q))\n : targets\n return matched.slice(0, MAX_RESULTS)\n }, [targets, query])\n\n function onOpenChange(next: boolean) {\n if (next) {\n const current = editor.getAttributes('link').href\n setHref(typeof current === 'string' ? current : '')\n setQuery('')\n }\n setOpen(next)\n }\n\n function applyExternal() {\n const url = href.trim()\n const chain = editor.chain().focus().extendMarkRange('link')\n if (url) chain.setLink({ href: url }).run()\n else chain.unsetLink().run()\n setOpen(false)\n }\n\n function applyDocument(target: LinkTarget) {\n editor\n .chain()\n .focus()\n .extendMarkRange('link')\n .setMark('link', { href: null, ref: referenceNode(target.ref) })\n .run()\n setOpen(false)\n }\n\n function remove() {\n editor.chain().focus().extendMarkRange('link').unsetLink().run()\n setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant={isLink ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-label={translate('richText.link.add')}\n aria-pressed={isLink}\n title={translate('richText.link.add')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <Link2 className=\"size-3.5\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-80\">\n <Stack gap=\"2\">\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.external')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Input\n autoFocus\n value={href}\n placeholder={translate('richText.link.externalPlaceholder')}\n onChange={(e) => setHref(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n applyExternal()\n }\n }}\n />\n <Button type=\"button\" size=\"sm\" onClick={applyExternal}>\n {translate('richText.link.apply')}\n </Button>\n </Row>\n\n {targets.length > 0 ? (\n <>\n <Separator className=\"my-0.5\" />\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.document')}\n </Text>\n <Input\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-52 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.link.documentEmpty')}\n </Text>\n ) : (\n results.map((target) => (\n <button\n key={`${target.ref.collection}:${target.ref.id}`}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => applyDocument(target)}\n className={cn(\n 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <FileText className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{target.label}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{target.collectionLabel}</span>\n </button>\n ))\n )}\n </Stack>\n </>\n ) : null}\n\n {isLink ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"self-start\" onClick={remove}>\n <Trash2 className=\"size-3.5\" /> {translate('richText.link.remove')}\n </Button>\n ) : null}\n </Stack>\n </PopoverContent>\n </Popover>\n )\n}\n"]}
@@ -3,9 +3,9 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
3
3
  import { Extension, ReactRenderer } from '@tiptap/react';
4
4
  import Suggestion from '@tiptap/suggestion';
5
5
  import { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react';
6
- import { translate } from '@elytracms/studio/lib/i18n';
7
- import { componentDisplayName } from '@elytracms/studio/lib/persona/labels';
8
- import { cn } from '@elytracms/studio/lib/utils';
6
+ import { translate } from '../../../lib/i18n/index.js';
7
+ import { componentDisplayName } from '../../../lib/persona/labels.js';
8
+ import { cn } from '../../../lib/utils.js';
9
9
  import { BLOCK_COMMANDS } from './block-commands.js';
10
10
  /** The command list, gated by which media actions + components the editor provided. */
11
11
  export function buildSlashItems(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"slash-command.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/slash-command.tsx"],"names":[],"mappings":"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","sourcesContent":["import { forwardRef, useImperativeHandle, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport { Extension, ReactRenderer } from '@tiptap/react'\nimport type { Editor, Range } from '@tiptap/react'\nimport Suggestion from '@tiptap/suggestion'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { BLOCK_COMMANDS } from './block-commands'\n\n/**\n * Notion-style slash menu (EC-314): typing `/` at the caret opens a filterable\n * command palette that inserts block-level content — headings, lists, quote, code,\n * divider — and, when available, an image or a registry component.\n *\n * 0.0.41: the embed path shows the permitted components **directly** as their own\n * items under a \"Components\" group (allow-gated exactly like the picker), rather than\n * a single \"Embed component\" item that opened a separate picker dialog — the\n * product-owner feedback that the extra step was unintuitive. Selecting a component\n * inserts it with manifest defaults and selects it, so the sidebar inspector opens.\n * Built on TipTap's `Suggestion` utility; the menu is a React overlay at the caret.\n */\n\n/** How the slash menu inserts a chosen component (owned by the editor). */\nexport interface SlashEmbedOptions {\n /** The permitted component manifests (already `allow`-gated by the field). */\n components: ComponentManifest[]\n /** Insert `manifest` at `range` (deletes the `/query`, inserts + selects). */\n insert: (editor: Editor, range: Range, manifest: ComponentManifest) => void\n}\n\nexport interface SlashCommandOptions {\n /** Open the asset picker (omitted ⇒ no `/image` item). */\n onImage?: () => void\n /** The embeddable components + inserter (omitted/empty ⇒ no component items). */\n embed?: SlashEmbedOptions\n}\n\nexport interface SlashItem {\n key: string\n title: string\n keywords: string\n icon: LucideIcon\n /** Optional group label — consecutive same-group items share a header. */\n group?: string\n run: (editor: Editor, range: Range) => void\n}\n\n/** The command list, gated by which media actions + components the editor provided. */\nexport function buildSlashItems(options: SlashCommandOptions): SlashItem[] {\n const block = (run: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) =>\n (editor: Editor, range: Range) => run(editor.chain().focus().deleteRange(range)).run()\n\n const items: SlashItem[] = [\n { key: 'text', title: translate('richText.slash.text'), keywords: 'paragraph text plain', icon: Pilcrow, run: block((c) => c.setParagraph()) },\n { key: 'h1', title: translate('richText.heading', { level: 1 }), keywords: 'heading title h1', icon: Heading1, run: block((c) => c.toggleHeading({ level: 1 })) },\n { key: 'h2', title: translate('richText.heading', { level: 2 }), keywords: 'heading subtitle h2', icon: Heading2, run: block((c) => c.toggleHeading({ level: 2 })) },\n { key: 'h3', title: translate('richText.heading', { level: 3 }), keywords: 'heading h3', icon: Heading3, run: block((c) => c.toggleHeading({ level: 3 })) },\n // The shared table (EC-328) — bulletList/orderedList/blockquote/codeBlock/divider,\n // the SAME descriptors `RichTextToolbar` derives its buttons from.\n ...BLOCK_COMMANDS.map((cmd) => ({\n key: cmd.key,\n title: translate(cmd.labelKey),\n keywords: cmd.keywords,\n icon: cmd.icon,\n run: block(cmd.chain),\n })),\n ]\n if (options.onImage) {\n items.push({\n key: 'image',\n title: translate('richText.image.add'),\n keywords: 'image picture photo media',\n icon: ImagePlus,\n run: (editor, range) => {\n editor.chain().focus().deleteRange(range).run()\n options.onImage?.()\n },\n })\n }\n const embed = options.embed\n if (embed) {\n const group = translate('richText.slash.componentsGroup')\n for (const manifest of embed.components) {\n const title = componentDisplayName(manifest.id, manifest)\n items.push({\n key: `embed:${manifest.id}`,\n title,\n keywords: `${title} ${manifest.id} component embed block`.toLowerCase(),\n icon: Blocks,\n group,\n run: (editor, range) => embed.insert(editor, range, manifest),\n })\n }\n }\n return items\n}\n\n/** Filter items by a typed query (title or keyword substring) — empty query passes all. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase()\n if (!q) return items\n return items.filter((item) => item.title.toLowerCase().includes(q) || item.keywords.includes(q))\n}\n\n/** Imperative handle the suggestion render loop calls for keyboard navigation. */\ninterface SlashMenuHandle {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\nconst SlashMenu = forwardRef<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>>((props, ref) => {\n const [selected, setSelected] = useState(0)\n // Reset the highlight DURING RENDER when `items` changes, not in a post-render\n // Effect (EC-328 fix): `@tiptap/suggestion`'s `createSuggestionView.update()`\n // dispatches a NEW `items` array on nearly every keystroke, so an\n // effect-based reset landed one render late — a frame where `selected` was\n // briefly stale/out-of-range against the freshly filtered list. Comparing\n // against the previous reference here (React's documented \"adjust state\n // during render\" idiom) resets it in the SAME render that receives the new\n // `items`, before anything paints.\n const [prevItems, setPrevItems] = useState(props.items)\n if (props.items !== prevItems) {\n setPrevItems(props.items)\n setSelected(0)\n }\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n const count = props.items.length\n if (count === 0) return false\n if (event.key === 'ArrowUp') {\n setSelected((s) => (s + count - 1) % count)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelected((s) => (s + 1) % count)\n return true\n }\n if (event.key === 'Enter') {\n const item = props.items[selected]\n if (item) props.command(item)\n return true\n }\n return false\n },\n }))\n\n if (props.items.length === 0) {\n return (\n <div className=\"ec-rte-slash\">\n <span className=\"px-2 py-1.5 text-xs text-muted-foreground\">\n {translate('richText.slash.empty')}\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"ec-rte-slash\" role=\"menu\">\n {props.items.map((item, i) => {\n const Icon = item.icon\n // A group header precedes the first item whose group differs from its\n // predecessor (the block-formatting items are ungrouped; components share one).\n const showHeader = item.group != null && item.group !== props.items[i - 1]?.group\n return (\n <div key={item.key}>\n {showHeader ? (\n <span className=\"block px-2 pt-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wide text-muted-foreground\">\n {item.group}\n </span>\n ) : null}\n <button\n type=\"button\"\n role=\"menuitem\"\n onMouseEnter={() => setSelected(i)}\n onMouseDown={(e) => {\n e.preventDefault()\n props.command(item)\n }}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm outline-none',\n i === selected ? 'bg-muted' : 'hover:bg-muted/60',\n )}\n >\n <Icon className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{item.title}</span>\n </button>\n </div>\n )\n })}\n </div>\n )\n})\nSlashMenu.displayName = 'SlashMenu'\n\n/** Position a floating element under the caret, flipping above near the viewport edge. */\nfunction place(el: HTMLElement, rect: DOMRect | null | undefined): void {\n if (!rect) return\n const margin = 8\n const height = el.offsetHeight || 280\n const below = rect.bottom + 4\n const flip = below + height > window.innerHeight - margin && rect.top - height > margin\n el.style.position = 'fixed'\n el.style.left = `${Math.min(rect.left, window.innerWidth - 260)}px`\n el.style.top = flip ? `${rect.top - height - 4}px` : `${below}px`\n el.style.zIndex = '60'\n}\n\n/** The suggestion config (everything but the `editor`, which the extension injects). */\nfunction slashSuggestion(options: SlashCommandOptions): Omit<SuggestionOptions<SlashItem, SlashItem>, 'editor'> {\n return {\n char: '/',\n startOfLine: false,\n items: ({ query }) => filterSlashItems(buildSlashItems(options), query),\n command: ({ editor, range, props }) => props.run(editor, range),\n render: () => {\n let renderer: ReactRenderer<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>> | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props) => {\n renderer = new ReactRenderer(SlashMenu as ComponentType<SuggestionProps<SlashItem, SlashItem>>, {\n props,\n editor: props.editor,\n })\n wrapper = document.createElement('div')\n wrapper.appendChild(renderer.element)\n document.body.appendChild(wrapper)\n place(wrapper, props.clientRect?.())\n },\n onUpdate: (props) => {\n renderer?.updateProps(props)\n if (wrapper) place(wrapper, props.clientRect?.())\n },\n onKeyDown: (props) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n wrapper = null\n return true\n }\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n wrapper = null\n renderer?.destroy()\n renderer = null\n },\n }\n },\n }\n}\n\n/** Build the slash-command extension bound to the editor's shared media pickers. */\nexport function createSlashCommand(options: SlashCommandOptions) {\n return Extension.create<SlashCommandOptions>({\n name: 'slashCommand',\n addOptions: () => options,\n addProseMirrorPlugins() {\n return [Suggestion({ editor: this.editor, ...slashSuggestion(this.options) })]\n },\n })\n}\n"]}
1
+ {"version":3,"file":"slash-command.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/slash-command.tsx"],"names":[],"mappings":"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","sourcesContent":["import { forwardRef, useImperativeHandle, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport { Extension, ReactRenderer } from '@tiptap/react'\nimport type { Editor, Range } from '@tiptap/react'\nimport Suggestion from '@tiptap/suggestion'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { translate } from '../../../lib/i18n'\nimport { componentDisplayName } from '../../../lib/persona/labels'\nimport { cn } from '../../../lib/utils'\nimport { BLOCK_COMMANDS } from './block-commands'\n\n/**\n * Notion-style slash menu (EC-314): typing `/` at the caret opens a filterable\n * command palette that inserts block-level content — headings, lists, quote, code,\n * divider — and, when available, an image or a registry component.\n *\n * 0.0.41: the embed path shows the permitted components **directly** as their own\n * items under a \"Components\" group (allow-gated exactly like the picker), rather than\n * a single \"Embed component\" item that opened a separate picker dialog — the\n * product-owner feedback that the extra step was unintuitive. Selecting a component\n * inserts it with manifest defaults and selects it, so the sidebar inspector opens.\n * Built on TipTap's `Suggestion` utility; the menu is a React overlay at the caret.\n */\n\n/** How the slash menu inserts a chosen component (owned by the editor). */\nexport interface SlashEmbedOptions {\n /** The permitted component manifests (already `allow`-gated by the field). */\n components: ComponentManifest[]\n /** Insert `manifest` at `range` (deletes the `/query`, inserts + selects). */\n insert: (editor: Editor, range: Range, manifest: ComponentManifest) => void\n}\n\nexport interface SlashCommandOptions {\n /** Open the asset picker (omitted ⇒ no `/image` item). */\n onImage?: () => void\n /** The embeddable components + inserter (omitted/empty ⇒ no component items). */\n embed?: SlashEmbedOptions\n}\n\nexport interface SlashItem {\n key: string\n title: string\n keywords: string\n icon: LucideIcon\n /** Optional group label — consecutive same-group items share a header. */\n group?: string\n run: (editor: Editor, range: Range) => void\n}\n\n/** The command list, gated by which media actions + components the editor provided. */\nexport function buildSlashItems(options: SlashCommandOptions): SlashItem[] {\n const block = (run: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) =>\n (editor: Editor, range: Range) => run(editor.chain().focus().deleteRange(range)).run()\n\n const items: SlashItem[] = [\n { key: 'text', title: translate('richText.slash.text'), keywords: 'paragraph text plain', icon: Pilcrow, run: block((c) => c.setParagraph()) },\n { key: 'h1', title: translate('richText.heading', { level: 1 }), keywords: 'heading title h1', icon: Heading1, run: block((c) => c.toggleHeading({ level: 1 })) },\n { key: 'h2', title: translate('richText.heading', { level: 2 }), keywords: 'heading subtitle h2', icon: Heading2, run: block((c) => c.toggleHeading({ level: 2 })) },\n { key: 'h3', title: translate('richText.heading', { level: 3 }), keywords: 'heading h3', icon: Heading3, run: block((c) => c.toggleHeading({ level: 3 })) },\n // The shared table (EC-328) — bulletList/orderedList/blockquote/codeBlock/divider,\n // the SAME descriptors `RichTextToolbar` derives its buttons from.\n ...BLOCK_COMMANDS.map((cmd) => ({\n key: cmd.key,\n title: translate(cmd.labelKey),\n keywords: cmd.keywords,\n icon: cmd.icon,\n run: block(cmd.chain),\n })),\n ]\n if (options.onImage) {\n items.push({\n key: 'image',\n title: translate('richText.image.add'),\n keywords: 'image picture photo media',\n icon: ImagePlus,\n run: (editor, range) => {\n editor.chain().focus().deleteRange(range).run()\n options.onImage?.()\n },\n })\n }\n const embed = options.embed\n if (embed) {\n const group = translate('richText.slash.componentsGroup')\n for (const manifest of embed.components) {\n const title = componentDisplayName(manifest.id, manifest)\n items.push({\n key: `embed:${manifest.id}`,\n title,\n keywords: `${title} ${manifest.id} component embed block`.toLowerCase(),\n icon: Blocks,\n group,\n run: (editor, range) => embed.insert(editor, range, manifest),\n })\n }\n }\n return items\n}\n\n/** Filter items by a typed query (title or keyword substring) — empty query passes all. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase()\n if (!q) return items\n return items.filter((item) => item.title.toLowerCase().includes(q) || item.keywords.includes(q))\n}\n\n/** Imperative handle the suggestion render loop calls for keyboard navigation. */\ninterface SlashMenuHandle {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\nconst SlashMenu = forwardRef<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>>((props, ref) => {\n const [selected, setSelected] = useState(0)\n // Reset the highlight DURING RENDER when `items` changes, not in a post-render\n // Effect (EC-328 fix): `@tiptap/suggestion`'s `createSuggestionView.update()`\n // dispatches a NEW `items` array on nearly every keystroke, so an\n // effect-based reset landed one render late — a frame where `selected` was\n // briefly stale/out-of-range against the freshly filtered list. Comparing\n // against the previous reference here (React's documented \"adjust state\n // during render\" idiom) resets it in the SAME render that receives the new\n // `items`, before anything paints.\n const [prevItems, setPrevItems] = useState(props.items)\n if (props.items !== prevItems) {\n setPrevItems(props.items)\n setSelected(0)\n }\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n const count = props.items.length\n if (count === 0) return false\n if (event.key === 'ArrowUp') {\n setSelected((s) => (s + count - 1) % count)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelected((s) => (s + 1) % count)\n return true\n }\n if (event.key === 'Enter') {\n const item = props.items[selected]\n if (item) props.command(item)\n return true\n }\n return false\n },\n }))\n\n if (props.items.length === 0) {\n return (\n <div className=\"ec-rte-slash\">\n <span className=\"px-2 py-1.5 text-xs text-muted-foreground\">\n {translate('richText.slash.empty')}\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"ec-rte-slash\" role=\"menu\">\n {props.items.map((item, i) => {\n const Icon = item.icon\n // A group header precedes the first item whose group differs from its\n // predecessor (the block-formatting items are ungrouped; components share one).\n const showHeader = item.group != null && item.group !== props.items[i - 1]?.group\n return (\n <div key={item.key}>\n {showHeader ? (\n <span className=\"block px-2 pt-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wide text-muted-foreground\">\n {item.group}\n </span>\n ) : null}\n <button\n type=\"button\"\n role=\"menuitem\"\n onMouseEnter={() => setSelected(i)}\n onMouseDown={(e) => {\n e.preventDefault()\n props.command(item)\n }}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm outline-none',\n i === selected ? 'bg-muted' : 'hover:bg-muted/60',\n )}\n >\n <Icon className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{item.title}</span>\n </button>\n </div>\n )\n })}\n </div>\n )\n})\nSlashMenu.displayName = 'SlashMenu'\n\n/** Position a floating element under the caret, flipping above near the viewport edge. */\nfunction place(el: HTMLElement, rect: DOMRect | null | undefined): void {\n if (!rect) return\n const margin = 8\n const height = el.offsetHeight || 280\n const below = rect.bottom + 4\n const flip = below + height > window.innerHeight - margin && rect.top - height > margin\n el.style.position = 'fixed'\n el.style.left = `${Math.min(rect.left, window.innerWidth - 260)}px`\n el.style.top = flip ? `${rect.top - height - 4}px` : `${below}px`\n el.style.zIndex = '60'\n}\n\n/** The suggestion config (everything but the `editor`, which the extension injects). */\nfunction slashSuggestion(options: SlashCommandOptions): Omit<SuggestionOptions<SlashItem, SlashItem>, 'editor'> {\n return {\n char: '/',\n startOfLine: false,\n items: ({ query }) => filterSlashItems(buildSlashItems(options), query),\n command: ({ editor, range, props }) => props.run(editor, range),\n render: () => {\n let renderer: ReactRenderer<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>> | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props) => {\n renderer = new ReactRenderer(SlashMenu as ComponentType<SuggestionProps<SlashItem, SlashItem>>, {\n props,\n editor: props.editor,\n })\n wrapper = document.createElement('div')\n wrapper.appendChild(renderer.element)\n document.body.appendChild(wrapper)\n place(wrapper, props.clientRect?.())\n },\n onUpdate: (props) => {\n renderer?.updateProps(props)\n if (wrapper) place(wrapper, props.clientRect?.())\n },\n onKeyDown: (props) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n wrapper = null\n return true\n }\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n wrapper = null\n renderer?.destroy()\n renderer = null\n },\n }\n },\n }\n}\n\n/** Build the slash-command extension bound to the editor's shared media pickers. */\nexport function createSlashCommand(options: SlashCommandOptions) {\n return Extension.create<SlashCommandOptions>({\n name: 'slashCommand',\n addOptions: () => options,\n addProseMirrorPlugins() {\n return [Suggestion({ editor: this.editor, ...slashSuggestion(this.options) })]\n },\n })\n}\n"]}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEditorState } from '@tiptap/react';
3
3
  import { Blocks, Bold, ChevronDown, Code, ImagePlus, Italic, Maximize2, Minimize2, Redo2, Strikethrough, Underline, Undo2, } from 'lucide-react';
4
4
  import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Toolbar, ToolbarGroup, ToolbarSeparator, } from '@elytracms/studio/ui';
5
- import { translate } from '@elytracms/studio/lib/i18n';
5
+ import { translate } from '../../../lib/i18n/index.js';
6
6
  import { ToolbarButton } from './toolbar-button.js';
7
7
  import { LinkControl } from './link-control.js';
8
8
  import { toolbarStateFromEditor } from './toolbar-state.js';
@@ -1 +1 @@
1
- {"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/toolbar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEditorState } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport {\n Blocks,\n Bold,\n ChevronDown,\n Code,\n ImagePlus,\n Italic,\n Maximize2,\n Minimize2,\n Redo2,\n Strikethrough,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Toolbar,\n ToolbarGroup,\n ToolbarSeparator,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { ToolbarButton } from './toolbar-button'\nimport { LinkControl } from './link-control'\nimport type { LinkTarget } from './link-control'\nimport { toolbarStateFromEditor } from './toolbar-state'\nimport { BLOCK_COMMANDS, isBlockCommandActive } from './block-commands'\nimport { BLOCK_STYLES, BlockStyleGlyph, blockStyleLabel } from './block-style'\nimport { FontSizeControl } from './font-size-control'\n\n/**\n * The toolbar container classes (EC-391). The load-bearing part is the height\n * contract: `h-auto min-h-10` (NOT a fixed `h-10`/`h-11`) so that when the many\n * controls wrap at narrow inspector widths the strip GROWS and reserves its real\n * layout height — the editable content always starts below it. A fixed height plus\n * `flex-wrap` was the bug: wrapped rows overflowed the locked box and painted over\n * the prose, so the buttons were un-hittable. `py-1` only bites once wrapped (a\n * single row is governed by `min-h-10` + `items-center`), padding the stacked rows.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_TOOLBAR_CLASS =\n 'h-auto min-h-10 flex-wrap gap-0.5 rounded-none border-x-0 border-t-0 bg-muted/30 px-1.5 py-1'\n\n/**\n * The block-style dropdown trigger's class (EC-418). The load-bearing part is the\n * FIXED width (`w-12`): the trigger shows the active style's icon glyph (not its\n * variable-width localized label — \"Absatz\" ≪ \"Überschrift 2\"), so caret movement\n * across Absatz/H1/H2/H3 can never change the trigger's width and reflow the wrapped\n * strip beneath the cursor (the reported German-locale defect). `justify-between`\n * seats the glyph and the ChevronDown in that fixed box. Every other control on the\n * strip is already a fixed `size-7` square (`ToolbarButton`, `LinkControl`) whose\n * active state only recolors (a `secondary` border sits inside the box-border square,\n * never resizing it) — so this trigger was the sole selection-variable width.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_MENU_TRIGGER_CLASS = 'h-7 w-12 justify-between gap-1 px-2'\n\ninterface RichTextToolbarProps {\n editor: Editor\n /** Internal-link destinations across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n /** Open the shared image picker (omitted ⇒ no image button). */\n onOpenImage?: () => void\n /** Open the shared component-embed picker (omitted ⇒ no embed button). */\n onOpenEmbed?: () => void\n /** EC-388: whether the editor is currently in its expanded (sheet) surface. */\n expanded?: boolean\n /** EC-388: toggle the expanded editing surface (omitted ⇒ no expand affordance). */\n onToggleExpand?: () => void\n}\n\n/**\n * The CMS rich-text toolbar (EC-314) — a compact, Notion-grade control strip that\n * reads its active/enabled state reactively from the editor (`useEditorState`) so a\n * pressed control always mirrors the caret. Every button prevents the default\n * mousedown so clicking it never steals the editor selection.\n */\nexport function RichTextToolbar({\n editor,\n targets,\n onOpenImage,\n onOpenEmbed,\n expanded = false,\n onToggleExpand,\n}: RichTextToolbarProps) {\n // The selector delegates to a pure, NULL-SAFE projection: `useEditorState` runs\n // it through an external store that React can fire against a destroyed/pre-init\n // editor (concurrent teardown, the EC-322 lazy boundary), where `editor.can()`\n // would throw on TipTap v3's nulled `commandManager`. `toolbarStateFromEditor`\n // returns the disabled shape in that window instead of crashing the toolbar.\n const state = useEditorState({\n editor,\n selector: ({ editor }) => toolbarStateFromEditor(editor),\n })\n\n return (\n <Toolbar className={RICH_TEXT_TOOLBAR_CLASS}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n // The visible label moved into the opened menu (EC-418) — the trigger keeps\n // an accessible name for the ACTIVE style from the same catalog keys.\n aria-label={blockStyleLabel(state.headingLevel)}\n onMouseDown={(e) => e.preventDefault()}\n >\n <BlockStyleGlyph headingLevel={state.headingLevel} />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {BLOCK_STYLES.map((style) => (\n <DropdownMenuItem\n key={style.headingLevel ?? 'paragraph'}\n onSelect={() => style.chain(editor.chain().focus()).run()}\n >\n <BlockStyleGlyph headingLevel={style.headingLevel} />{' '}\n {blockStyleLabel(style.headingLevel)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <FontSizeControl editor={editor} activeSize={state.fontSize} />\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bold}\n label={translate('richText.bold')}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.italic}\n label={translate('richText.italic')}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.underline}\n label={translate('richText.underline')}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.strike}\n label={translate('richText.strike')}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.code}\n label={translate('richText.code')}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n {/* The shared table (EC-328) — the SAME descriptors `buildSlashItems`\n derives its bulletList/orderedList/blockquote/codeBlock/divider items from. */}\n {BLOCK_COMMANDS.map((cmd) => (\n <ToolbarButton\n key={cmd.key}\n active={isBlockCommandActive(cmd, state)}\n label={translate(cmd.labelKey)}\n onClick={() => cmd.chain(editor.chain().focus()).run()}\n >\n <cmd.icon className=\"size-3.5\" />\n </ToolbarButton>\n ))}\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <LinkControl editor={editor} targets={targets} />\n {onOpenImage ? (\n <ToolbarButton label={translate('richText.image.add')} onClick={onOpenImage}>\n <ImagePlus className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n {onOpenEmbed ? (\n <ToolbarButton label={translate('richText.embed.add')} onClick={onOpenEmbed}>\n <Blocks className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n </ToolbarGroup>\n\n <div className=\"flex-1\" />\n\n <ToolbarGroup>\n <ToolbarButton\n active={false}\n disabled={!state.canUndo}\n label={translate('richText.undo')}\n onClick={() => editor.chain().focus().undo().run()}\n >\n <Undo2 className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n disabled={!state.canRedo}\n label={translate('richText.redo')}\n onClick={() => editor.chain().focus().redo().run()}\n >\n <Redo2 className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n {/* EC-388: the expand/collapse affordance — moves with the toolbar into the\n expanded sheet (where it reads as \"collapse\") and back to the inline\n editor (where it reads as \"expand\"). Same editor instance, same value\n flow; no separate save/cancel. */}\n {onToggleExpand ? (\n <>\n <ToolbarSeparator />\n <ToolbarGroup>\n <ToolbarButton\n active={expanded}\n label={\n expanded ? translate('richText.expand.collapse') : translate('richText.expand.expand')\n }\n onClick={onToggleExpand}\n >\n {expanded ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </ToolbarButton>\n </ToolbarGroup>\n </>\n ) : null}\n </Toolbar>\n )\n}\n"]}
1
+ {"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/toolbar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEditorState } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport {\n Blocks,\n Bold,\n ChevronDown,\n Code,\n ImagePlus,\n Italic,\n Maximize2,\n Minimize2,\n Redo2,\n Strikethrough,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Toolbar,\n ToolbarGroup,\n ToolbarSeparator,\n} from '@elytracms/studio/ui'\nimport { translate } from '../../../lib/i18n'\nimport { ToolbarButton } from './toolbar-button'\nimport { LinkControl } from './link-control'\nimport type { LinkTarget } from './link-control'\nimport { toolbarStateFromEditor } from './toolbar-state'\nimport { BLOCK_COMMANDS, isBlockCommandActive } from './block-commands'\nimport { BLOCK_STYLES, BlockStyleGlyph, blockStyleLabel } from './block-style'\nimport { FontSizeControl } from './font-size-control'\n\n/**\n * The toolbar container classes (EC-391). The load-bearing part is the height\n * contract: `h-auto min-h-10` (NOT a fixed `h-10`/`h-11`) so that when the many\n * controls wrap at narrow inspector widths the strip GROWS and reserves its real\n * layout height — the editable content always starts below it. A fixed height plus\n * `flex-wrap` was the bug: wrapped rows overflowed the locked box and painted over\n * the prose, so the buttons were un-hittable. `py-1` only bites once wrapped (a\n * single row is governed by `min-h-10` + `items-center`), padding the stacked rows.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_TOOLBAR_CLASS =\n 'h-auto min-h-10 flex-wrap gap-0.5 rounded-none border-x-0 border-t-0 bg-muted/30 px-1.5 py-1'\n\n/**\n * The block-style dropdown trigger's class (EC-418). The load-bearing part is the\n * FIXED width (`w-12`): the trigger shows the active style's icon glyph (not its\n * variable-width localized label — \"Absatz\" ≪ \"Überschrift 2\"), so caret movement\n * across Absatz/H1/H2/H3 can never change the trigger's width and reflow the wrapped\n * strip beneath the cursor (the reported German-locale defect). `justify-between`\n * seats the glyph and the ChevronDown in that fixed box. Every other control on the\n * strip is already a fixed `size-7` square (`ToolbarButton`, `LinkControl`) whose\n * active state only recolors (a `secondary` border sits inside the box-border square,\n * never resizing it) — so this trigger was the sole selection-variable width.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_MENU_TRIGGER_CLASS = 'h-7 w-12 justify-between gap-1 px-2'\n\ninterface RichTextToolbarProps {\n editor: Editor\n /** Internal-link destinations across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n /** Open the shared image picker (omitted ⇒ no image button). */\n onOpenImage?: () => void\n /** Open the shared component-embed picker (omitted ⇒ no embed button). */\n onOpenEmbed?: () => void\n /** EC-388: whether the editor is currently in its expanded (sheet) surface. */\n expanded?: boolean\n /** EC-388: toggle the expanded editing surface (omitted ⇒ no expand affordance). */\n onToggleExpand?: () => void\n}\n\n/**\n * The CMS rich-text toolbar (EC-314) — a compact, Notion-grade control strip that\n * reads its active/enabled state reactively from the editor (`useEditorState`) so a\n * pressed control always mirrors the caret. Every button prevents the default\n * mousedown so clicking it never steals the editor selection.\n */\nexport function RichTextToolbar({\n editor,\n targets,\n onOpenImage,\n onOpenEmbed,\n expanded = false,\n onToggleExpand,\n}: RichTextToolbarProps) {\n // The selector delegates to a pure, NULL-SAFE projection: `useEditorState` runs\n // it through an external store that React can fire against a destroyed/pre-init\n // editor (concurrent teardown, the EC-322 lazy boundary), where `editor.can()`\n // would throw on TipTap v3's nulled `commandManager`. `toolbarStateFromEditor`\n // returns the disabled shape in that window instead of crashing the toolbar.\n const state = useEditorState({\n editor,\n selector: ({ editor }) => toolbarStateFromEditor(editor),\n })\n\n return (\n <Toolbar className={RICH_TEXT_TOOLBAR_CLASS}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n // The visible label moved into the opened menu (EC-418) — the trigger keeps\n // an accessible name for the ACTIVE style from the same catalog keys.\n aria-label={blockStyleLabel(state.headingLevel)}\n onMouseDown={(e) => e.preventDefault()}\n >\n <BlockStyleGlyph headingLevel={state.headingLevel} />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {BLOCK_STYLES.map((style) => (\n <DropdownMenuItem\n key={style.headingLevel ?? 'paragraph'}\n onSelect={() => style.chain(editor.chain().focus()).run()}\n >\n <BlockStyleGlyph headingLevel={style.headingLevel} />{' '}\n {blockStyleLabel(style.headingLevel)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <FontSizeControl editor={editor} activeSize={state.fontSize} />\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bold}\n label={translate('richText.bold')}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.italic}\n label={translate('richText.italic')}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.underline}\n label={translate('richText.underline')}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.strike}\n label={translate('richText.strike')}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.code}\n label={translate('richText.code')}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n {/* The shared table (EC-328) — the SAME descriptors `buildSlashItems`\n derives its bulletList/orderedList/blockquote/codeBlock/divider items from. */}\n {BLOCK_COMMANDS.map((cmd) => (\n <ToolbarButton\n key={cmd.key}\n active={isBlockCommandActive(cmd, state)}\n label={translate(cmd.labelKey)}\n onClick={() => cmd.chain(editor.chain().focus()).run()}\n >\n <cmd.icon className=\"size-3.5\" />\n </ToolbarButton>\n ))}\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <LinkControl editor={editor} targets={targets} />\n {onOpenImage ? (\n <ToolbarButton label={translate('richText.image.add')} onClick={onOpenImage}>\n <ImagePlus className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n {onOpenEmbed ? (\n <ToolbarButton label={translate('richText.embed.add')} onClick={onOpenEmbed}>\n <Blocks className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n </ToolbarGroup>\n\n <div className=\"flex-1\" />\n\n <ToolbarGroup>\n <ToolbarButton\n active={false}\n disabled={!state.canUndo}\n label={translate('richText.undo')}\n onClick={() => editor.chain().focus().undo().run()}\n >\n <Undo2 className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n disabled={!state.canRedo}\n label={translate('richText.redo')}\n onClick={() => editor.chain().focus().redo().run()}\n >\n <Redo2 className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n {/* EC-388: the expand/collapse affordance — moves with the toolbar into the\n expanded sheet (where it reads as \"collapse\") and back to the inline\n editor (where it reads as \"expand\"). Same editor instance, same value\n flow; no separate save/cancel. */}\n {onToggleExpand ? (\n <>\n <ToolbarSeparator />\n <ToolbarGroup>\n <ToolbarButton\n active={expanded}\n label={\n expanded ? translate('richText.expand.collapse') : translate('richText.expand.expand')\n }\n onClick={onToggleExpand}\n >\n {expanded ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </ToolbarButton>\n </ToolbarGroup>\n </>\n ) : null}\n </Toolbar>\n )\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react';
3
3
  import { FileQuestion } from 'lucide-react';
4
- import { translate } from '@elytracms/studio/lib/i18n';
5
- import { cn } from '@elytracms/studio/lib/utils';
4
+ import { translate } from '../../../lib/i18n/index.js';
5
+ import { cn } from '../../../lib/utils.js';
6
6
  /**
7
7
  * The generic unknown-node carrier (EC-547, per EC-500 D3).
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"unknown-node.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/unknown-node.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAA;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IAEf,aAAa;QACX,2EAA2E;QAC3E,yEAAyE;QACzE,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAA;IACrD,CAAC;IAED,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,sBAAsB,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,cAAc,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;IAED,WAAW;QACT,OAAO,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC/C,CAAC;CACF,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAA;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;IAC9D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAwB;IAChE,OAAO,CACL,kCACmB,QAAQ,EACzB,IAAI,EAAC,MAAM,EACX,eAAe,EAAE,KAAK,EACtB,SAAS,EAAC,+HAA+H,aAEzI,KAAC,YAAY,IAAC,SAAS,EAAC,iBAAiB,GAAG,EAC5C,gBAAM,SAAS,EAAC,yBAAyB,aACtC,SAAS,CAAC,0BAA0B,CAAC,OAAE,eAAM,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAQ,IAChF,IACH,CACP,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAsB;IAC7D,OAAO,CACL,KAAC,eAAe,8BAEd,SAAS,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,IAAI,gCAAgC,CAAC,YAEnE,KAAC,eAAe,IAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAI,GAC/C,CACnB,CAAA;AACH,CAAC","sourcesContent":["import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react'\nimport type { ReactNodeViewProps } from '@tiptap/react'\nimport { FileQuestion } from 'lucide-react'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * The generic unknown-node carrier (EC-547, per EC-500 D3).\n *\n * A ProseMirror schema is CLOSED: any node type it does not declare is silently\n * dropped on load, and the first keystroke persists the loss. That makes the\n * flow editor a data shredder for any stored node it does not natively edit —\n * exactly what the converged block sequence must survive (the anti-Gutenberg\n * round-trip law: a projection may only rewrite nodes it natively edits; every\n * other node survives byte-for-byte).\n *\n * So the storage⇄editor projection ({@link ../value}) wraps every node whose\n * type is outside the editor's native vocabulary into this block atom on the\n * way IN — the ORIGINAL node rides verbatim in the single JSON `node` attr\n * (the `componentEmbed` pattern; in-repo precedent #2 is the dom-editor's\n * `PRESERVE_NODE_ATTR` verbatim-JSON carry) — and unwraps it byte-for-byte on\n * the way OUT. The NodeView renders a visibly-labelled read-only block naming\n * the carried type, matching the server renderer's unknown fallback\n * (`rich-text/render.tsx`: \"Unknown content block: <type>\") so both surfaces\n * speak the same vocabulary. Never a silent drop, never a crash.\n *\n * The carrier exists ONLY in the editor document — it is never written to\n * storage (the projection-out replaces it with its carried payload), so a\n * stored node that happened to reuse this type name would itself be wrapped on\n * the way in and restored untouched on the way out: misinterpretation is\n * structurally impossible, not policed.\n *\n * **Two declared, deliberate limits (EC-550, measured and DECLINED for now):**\n *\n * 1. *Unknown MARKS still shred.* A foreign mark type on a text run inside a\n * natively-edited node is dropped by ProseMirror's closed mark schema on\n * load, and the first keystroke persists the loss (foreign marks inside a\n * CARRIED node survive in its payload). Declined because the failure is\n * bounded — the text itself survives, only the annotation drops — and the\n * mark vocabulary is fixed and global today, so no writer can produce a\n * foreign mark short of hand-written JSON. A mark-preserving projection\n * (marks copied per-run to a carrier attr) is the design if a real producer\n * ever lands (registry-extensible marks, external/AI writers).\n * 2. *Inline-position unknowns relocate.* This carrier is a BLOCK atom; a\n * foreign node inside a paragraph's inline content is wrapped as a block and\n * ProseMirror's fitting moves it out of the inline position — it survives\n * byte-for-byte but changes position on save. Declined for the same reason:\n * there is no inline foreign-node producer today (the engine's own inline\n * vocabulary is closed), and the fix needs a second inline carrier plus a\n * position-aware projection. Revisit alongside (1).\n */\n\n/** The editor-only node type of the opaque carrier. Never a storage type. */\nexport const UNKNOWN_NODE_CARRIER_TYPE = 'unknownCarrier'\n\nexport const UnknownNodeCarrier = Node.create({\n name: UNKNOWN_NODE_CARRIER_TYPE,\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addAttributes() {\n // `node` holds the carried original node JSON verbatim; storage/JSON only,\n // never rendered into markup (same convention as ComponentEmbed's attr).\n return { node: { default: null, rendered: false } }\n },\n\n parseHTML() {\n return [{ tag: 'div[data-ec-carrier]' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes(HTMLAttributes, { 'data-ec-carrier': '' })]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(UnknownNodeView)\n },\n})\n\n/**\n * The carried node's `type` for the visible label; `'unknown'` when the payload\n * is not even node-shaped (the carrier is total — it can hold any JSON).\n */\nexport function carriedNodeType(carried: unknown): string {\n if (typeof carried === 'object' && carried !== null && !Array.isArray(carried)) {\n const type = (carried as { type?: unknown }).type\n if (typeof type === 'string' && type.length > 0) return type\n }\n return 'unknown'\n}\n\n/**\n * The presentational carrier block — a labelled, read-only card naming the\n * carried node type. Kept free of TipTap context so it renders + asserts\n * standalone (the house `renderToStaticMarkup` smoke pattern). The label rides\n * the studio message catalog (`richText.carrier.unknown`, EC-550) and matches\n * the server renderer's unknown-node fallback vocabulary (`data-unknown-node` /\n * \"Unknown content block: <type>\" — deliberately unlocalized there: delivery\n * has no viewer-locale catalog).\n */\nexport function UnknownNodeCard({ nodeType }: { nodeType: string }) {\n return (\n <div\n data-ec-carrier={nodeType}\n role=\"note\"\n contentEditable={false}\n className=\"flex items-center gap-2.5 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2.5 text-sm text-muted-foreground\"\n >\n <FileQuestion className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate\">\n {translate('richText.carrier.unknown')} <span className=\"font-mono\">{nodeType}</span>\n </span>\n </div>\n )\n}\n\nfunction UnknownNodeView({ node, selected }: ReactNodeViewProps) {\n return (\n <NodeViewWrapper\n data-drag-handle\n className={cn('my-2', selected && 'rounded-md ring-2 ring-ring/50')}\n >\n <UnknownNodeCard nodeType={carriedNodeType(node.attrs.node)} />\n </NodeViewWrapper>\n )\n}\n"]}
1
+ {"version":3,"file":"unknown-node.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/unknown-node.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAA;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IAEf,aAAa;QACX,2EAA2E;QAC3E,yEAAyE;QACzE,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAA;IACrD,CAAC;IAED,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,sBAAsB,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,cAAc,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;IAED,WAAW;QACT,OAAO,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC/C,CAAC;CACF,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAA;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;IAC9D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAwB;IAChE,OAAO,CACL,kCACmB,QAAQ,EACzB,IAAI,EAAC,MAAM,EACX,eAAe,EAAE,KAAK,EACtB,SAAS,EAAC,+HAA+H,aAEzI,KAAC,YAAY,IAAC,SAAS,EAAC,iBAAiB,GAAG,EAC5C,gBAAM,SAAS,EAAC,yBAAyB,aACtC,SAAS,CAAC,0BAA0B,CAAC,OAAE,eAAM,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAQ,IAChF,IACH,CACP,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAsB;IAC7D,OAAO,CACL,KAAC,eAAe,8BAEd,SAAS,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,IAAI,gCAAgC,CAAC,YAEnE,KAAC,eAAe,IAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAI,GAC/C,CACnB,CAAA;AACH,CAAC","sourcesContent":["import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react'\nimport type { ReactNodeViewProps } from '@tiptap/react'\nimport { FileQuestion } from 'lucide-react'\nimport { translate } from '../../../lib/i18n'\nimport { cn } from '../../../lib/utils'\n\n/**\n * The generic unknown-node carrier (EC-547, per EC-500 D3).\n *\n * A ProseMirror schema is CLOSED: any node type it does not declare is silently\n * dropped on load, and the first keystroke persists the loss. That makes the\n * flow editor a data shredder for any stored node it does not natively edit —\n * exactly what the converged block sequence must survive (the anti-Gutenberg\n * round-trip law: a projection may only rewrite nodes it natively edits; every\n * other node survives byte-for-byte).\n *\n * So the storage⇄editor projection ({@link ../value}) wraps every node whose\n * type is outside the editor's native vocabulary into this block atom on the\n * way IN — the ORIGINAL node rides verbatim in the single JSON `node` attr\n * (the `componentEmbed` pattern; in-repo precedent #2 is the dom-editor's\n * `PRESERVE_NODE_ATTR` verbatim-JSON carry) — and unwraps it byte-for-byte on\n * the way OUT. The NodeView renders a visibly-labelled read-only block naming\n * the carried type, matching the server renderer's unknown fallback\n * (`rich-text/render.tsx`: \"Unknown content block: <type>\") so both surfaces\n * speak the same vocabulary. Never a silent drop, never a crash.\n *\n * The carrier exists ONLY in the editor document — it is never written to\n * storage (the projection-out replaces it with its carried payload), so a\n * stored node that happened to reuse this type name would itself be wrapped on\n * the way in and restored untouched on the way out: misinterpretation is\n * structurally impossible, not policed.\n *\n * **Two declared, deliberate limits (EC-550, measured and DECLINED for now):**\n *\n * 1. *Unknown MARKS still shred.* A foreign mark type on a text run inside a\n * natively-edited node is dropped by ProseMirror's closed mark schema on\n * load, and the first keystroke persists the loss (foreign marks inside a\n * CARRIED node survive in its payload). Declined because the failure is\n * bounded — the text itself survives, only the annotation drops — and the\n * mark vocabulary is fixed and global today, so no writer can produce a\n * foreign mark short of hand-written JSON. A mark-preserving projection\n * (marks copied per-run to a carrier attr) is the design if a real producer\n * ever lands (registry-extensible marks, external/AI writers).\n * 2. *Inline-position unknowns relocate.* This carrier is a BLOCK atom; a\n * foreign node inside a paragraph's inline content is wrapped as a block and\n * ProseMirror's fitting moves it out of the inline position — it survives\n * byte-for-byte but changes position on save. Declined for the same reason:\n * there is no inline foreign-node producer today (the engine's own inline\n * vocabulary is closed), and the fix needs a second inline carrier plus a\n * position-aware projection. Revisit alongside (1).\n */\n\n/** The editor-only node type of the opaque carrier. Never a storage type. */\nexport const UNKNOWN_NODE_CARRIER_TYPE = 'unknownCarrier'\n\nexport const UnknownNodeCarrier = Node.create({\n name: UNKNOWN_NODE_CARRIER_TYPE,\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addAttributes() {\n // `node` holds the carried original node JSON verbatim; storage/JSON only,\n // never rendered into markup (same convention as ComponentEmbed's attr).\n return { node: { default: null, rendered: false } }\n },\n\n parseHTML() {\n return [{ tag: 'div[data-ec-carrier]' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes(HTMLAttributes, { 'data-ec-carrier': '' })]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(UnknownNodeView)\n },\n})\n\n/**\n * The carried node's `type` for the visible label; `'unknown'` when the payload\n * is not even node-shaped (the carrier is total — it can hold any JSON).\n */\nexport function carriedNodeType(carried: unknown): string {\n if (typeof carried === 'object' && carried !== null && !Array.isArray(carried)) {\n const type = (carried as { type?: unknown }).type\n if (typeof type === 'string' && type.length > 0) return type\n }\n return 'unknown'\n}\n\n/**\n * The presentational carrier block — a labelled, read-only card naming the\n * carried node type. Kept free of TipTap context so it renders + asserts\n * standalone (the house `renderToStaticMarkup` smoke pattern). The label rides\n * the studio message catalog (`richText.carrier.unknown`, EC-550) and matches\n * the server renderer's unknown-node fallback vocabulary (`data-unknown-node` /\n * \"Unknown content block: <type>\" — deliberately unlocalized there: delivery\n * has no viewer-locale catalog).\n */\nexport function UnknownNodeCard({ nodeType }: { nodeType: string }) {\n return (\n <div\n data-ec-carrier={nodeType}\n role=\"note\"\n contentEditable={false}\n className=\"flex items-center gap-2.5 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2.5 text-sm text-muted-foreground\"\n >\n <FileQuestion className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate\">\n {translate('richText.carrier.unknown')} <span className=\"font-mono\">{nodeType}</span>\n </span>\n </div>\n )\n}\n\nfunction UnknownNodeView({ node, selected }: ReactNodeViewProps) {\n return (\n <NodeViewWrapper\n data-drag-handle\n className={cn('my-2', selected && 'rounded-md ring-2 ring-ring/50')}\n >\n <UnknownNodeCard nodeType={carriedNodeType(node.attrs.node)} />\n </NodeViewWrapper>\n )\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { parseRichTextValue, renderRichTextValue } from '@elytracms/core/rich-text';
3
3
  import { Badge, Text } from '@elytracms/studio/ui';
4
- import { translate } from '@elytracms/studio/lib/i18n';
5
- import { cn } from '@elytracms/studio/lib/utils';
4
+ import { translate } from '../../lib/i18n/index.js';
5
+ import { cn } from '../../lib/utils.js';
6
6
  /**
7
7
  * Compact read-only rich-text rendering for review surfaces (EC-379).
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text-preview.js","sourceRoot":"","sources":["../../../src/components/cms/rich-text-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAGnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAEhD;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG;IAC/B,iDAAiD;IACjD,uCAAuC;IACvC,qCAAqC;IACrC,+DAA+D;IAC/D,qFAAqF;IACrF,iHAAiH;IACjH,0CAA0C;IAC1C,0GAA0G;IAC1G,oCAAoC;IACpC,sBAAsB;CACvB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX,kFAAkF;AAClF,SAAS,SAAS,CAAC,EAAE,IAAI,EAA2B;IAClD,OAAO,CACL,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,0BAA0B,YAC1D,IAAI,CAAC,WAAW,GACX,CACT,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,SAAS,EAA0C;IAC1F,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,KAAK,GACD,CACR,CAAA;QACH,CAAC;QACD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,yBAAyB,CAAC,GAChC,CACR,CAAA;IACH,CAAC;IACD,OAAO,mBAAmB,CAAC,MAAM,CAAC,KAAK,EAAE;QACvC,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC;QAClD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAC,SAAS,IAAC,IAAI,EAAE,IAAI,GAAI;KACjD,CAAC,CAAA;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,WAAW;IACX,SAAS;IACT,MAAM;IACN,WAAW;IACX,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;CACb,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IACtE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,CAAC,KAA0C,EAAW,EAAE,CACzE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAChB,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAC3B,CAAA;IACH,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,OAAgB,CAAC,CAAA;AACnD,CAAC","sourcesContent":["import { parseRichTextValue, renderRichTextValue } from '@elytracms/core/rich-text'\nimport type { RichTextNode } from '@elytracms/core/rich-text'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { Badge, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\n\n/**\n * Compact read-only rich-text rendering for review surfaces (EC-379).\n *\n * The AI proposal diff (and any other change-review lane) must show the editor\n * the ACTUAL prose they are approving — never a blind \"Content changed.\" note.\n * This renders a stored rich-text value through the engine's server-safe\n * read-only renderer (`@elytracms/core/rich-text`) with modest, compact styles;\n * component embeds render as an inspectable component-id chip (the diff reviews\n * content, not layout), and unknown nodes keep the renderer's visible fallback.\n *\n * Self-contained by design: EC-382 (proposal-diff layout) restyles the diff\n * chrome around it without touching this component's contract.\n */\n\n/** Compact prose styles applied to the renderer's `div.rich-text` root. */\nconst RICH_TEXT_PREVIEW_STYLES = [\n 'text-sm leading-relaxed break-words space-y-1.5',\n '[&_h1]:text-base [&_h1]:font-semibold',\n '[&_h2]:text-sm [&_h2]:font-semibold',\n '[&_:is(h3,h4,h5,h6)]:text-sm [&_:is(h3,h4,h5,h6)]:font-medium',\n '[&_:is(ul,ol)]:pl-5 [&_:is(ul,ol)]:space-y-0.5 [&_ul]:list-disc [&_ol]:list-decimal',\n '[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-2 [&_blockquote]:text-muted-foreground',\n '[&_a]:underline [&_a]:underline-offset-2',\n '[&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-2 [&_code]:font-mono [&_code]:text-xs',\n '[&_img]:max-w-full [&_img]:rounded',\n '[&_hr]:border-border',\n].join(' ')\n\n/** The inspectable chip a `componentEmbed` renders as inside a review preview. */\nfunction EmbedChip({ node }: { node: ComponentNode }) {\n return (\n <Badge variant=\"outline\" className=\"font-mono text-[0.65rem]\">\n {node.componentId}\n </Badge>\n )\n}\n\n/**\n * Render a stored rich-text value as compact read-only prose. A legacy plain\n * string stays visible as text; any other unparseable value degrades to the\n * generic object note — visible, never silent, never a crash.\n */\nexport function RichTextPreview({ value, className }: { value: unknown; className?: string }) {\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) {\n if (typeof value === 'string') {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {value}\n </Text>\n )\n }\n return (\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.version.objectValue')}\n </Text>\n )\n }\n return renderRichTextValue(parsed.value, {\n className: cn(RICH_TEXT_PREVIEW_STYLES, className),\n renderEmbed: (node) => <EmbedChip node={node} />,\n })\n}\n\n/** Node types that carry no content of their own — an empty scaffold of these is \"empty\". */\nconst PROSE_SCAFFOLD_TYPES = new Set([\n 'paragraph',\n 'heading',\n 'text',\n 'hardBreak',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n])\n\n/**\n * Whether a rich-text value is EMPTY for review purposes: unset, an empty doc,\n * or nothing but blank prose scaffolding. A doc carrying any atom (image, embed,\n * custom node) or non-blank text is content — it must be shown, not skipped.\n */\nexport function isRichTextValueEmpty(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) return false\n const hasContent = (nodes: readonly RichTextNode[] | undefined): boolean =>\n (nodes ?? []).some(\n (node) =>\n (typeof node.text === 'string' && node.text.trim().length > 0) ||\n !PROSE_SCAFFOLD_TYPES.has(node.type) ||\n hasContent(node.content),\n )\n return !hasContent(parsed.value.content as never)\n}\n"]}
1
+ {"version":3,"file":"rich-text-preview.js","sourceRoot":"","sources":["../../../src/components/cms/rich-text-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAGnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG;IAC/B,iDAAiD;IACjD,uCAAuC;IACvC,qCAAqC;IACrC,+DAA+D;IAC/D,qFAAqF;IACrF,iHAAiH;IACjH,0CAA0C;IAC1C,0GAA0G;IAC1G,oCAAoC;IACpC,sBAAsB;CACvB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX,kFAAkF;AAClF,SAAS,SAAS,CAAC,EAAE,IAAI,EAA2B;IAClD,OAAO,CACL,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,0BAA0B,YAC1D,IAAI,CAAC,WAAW,GACX,CACT,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,SAAS,EAA0C;IAC1F,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,KAAK,GACD,CACR,CAAA;QACH,CAAC;QACD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,yBAAyB,CAAC,GAChC,CACR,CAAA;IACH,CAAC;IACD,OAAO,mBAAmB,CAAC,MAAM,CAAC,KAAK,EAAE;QACvC,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC;QAClD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAC,SAAS,IAAC,IAAI,EAAE,IAAI,GAAI;KACjD,CAAC,CAAA;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,WAAW;IACX,SAAS;IACT,MAAM;IACN,WAAW;IACX,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;CACb,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IACtE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,CAAC,KAA0C,EAAW,EAAE,CACzE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAChB,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAC3B,CAAA;IACH,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,OAAgB,CAAC,CAAA;AACnD,CAAC","sourcesContent":["import { parseRichTextValue, renderRichTextValue } from '@elytracms/core/rich-text'\nimport type { RichTextNode } from '@elytracms/core/rich-text'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { Badge, Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { cn } from '../../lib/utils'\n\n/**\n * Compact read-only rich-text rendering for review surfaces (EC-379).\n *\n * The AI proposal diff (and any other change-review lane) must show the editor\n * the ACTUAL prose they are approving — never a blind \"Content changed.\" note.\n * This renders a stored rich-text value through the engine's server-safe\n * read-only renderer (`@elytracms/core/rich-text`) with modest, compact styles;\n * component embeds render as an inspectable component-id chip (the diff reviews\n * content, not layout), and unknown nodes keep the renderer's visible fallback.\n *\n * Self-contained by design: EC-382 (proposal-diff layout) restyles the diff\n * chrome around it without touching this component's contract.\n */\n\n/** Compact prose styles applied to the renderer's `div.rich-text` root. */\nconst RICH_TEXT_PREVIEW_STYLES = [\n 'text-sm leading-relaxed break-words space-y-1.5',\n '[&_h1]:text-base [&_h1]:font-semibold',\n '[&_h2]:text-sm [&_h2]:font-semibold',\n '[&_:is(h3,h4,h5,h6)]:text-sm [&_:is(h3,h4,h5,h6)]:font-medium',\n '[&_:is(ul,ol)]:pl-5 [&_:is(ul,ol)]:space-y-0.5 [&_ul]:list-disc [&_ol]:list-decimal',\n '[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-2 [&_blockquote]:text-muted-foreground',\n '[&_a]:underline [&_a]:underline-offset-2',\n '[&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-2 [&_code]:font-mono [&_code]:text-xs',\n '[&_img]:max-w-full [&_img]:rounded',\n '[&_hr]:border-border',\n].join(' ')\n\n/** The inspectable chip a `componentEmbed` renders as inside a review preview. */\nfunction EmbedChip({ node }: { node: ComponentNode }) {\n return (\n <Badge variant=\"outline\" className=\"font-mono text-[0.65rem]\">\n {node.componentId}\n </Badge>\n )\n}\n\n/**\n * Render a stored rich-text value as compact read-only prose. A legacy plain\n * string stays visible as text; any other unparseable value degrades to the\n * generic object note — visible, never silent, never a crash.\n */\nexport function RichTextPreview({ value, className }: { value: unknown; className?: string }) {\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) {\n if (typeof value === 'string') {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {value}\n </Text>\n )\n }\n return (\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.version.objectValue')}\n </Text>\n )\n }\n return renderRichTextValue(parsed.value, {\n className: cn(RICH_TEXT_PREVIEW_STYLES, className),\n renderEmbed: (node) => <EmbedChip node={node} />,\n })\n}\n\n/** Node types that carry no content of their own — an empty scaffold of these is \"empty\". */\nconst PROSE_SCAFFOLD_TYPES = new Set([\n 'paragraph',\n 'heading',\n 'text',\n 'hardBreak',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n])\n\n/**\n * Whether a rich-text value is EMPTY for review purposes: unset, an empty doc,\n * or nothing but blank prose scaffolding. A doc carrying any atom (image, embed,\n * custom node) or non-blank text is content — it must be shown, not skipped.\n */\nexport function isRichTextValueEmpty(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) return false\n const hasContent = (nodes: readonly RichTextNode[] | undefined): boolean =>\n (nodes ?? []).some(\n (node) =>\n (typeof node.text === 'string' && node.text.trim().length > 0) ||\n !PROSE_SCAFFOLD_TYPES.has(node.type) ||\n hasContent(node.content),\n )\n return !hasContent(parsed.value.content as never)\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { CollectionViewCapabilities } from '@elytracms/studio/lib/collection-view/collection-view-source';
1
+ import type { CollectionViewCapabilities } from '../../lib/collection-view/collection-view-source.js';
2
2
  /**
3
3
  * EC-316 — the right-edge row `⋮` menu's action vocabulary: the SAME bulk-action
4
4
  * set the toolbar's selection actions offer (`bulk-actions.ts`'s `BulkAction`),