@cosmicdrift/kumiko-samples 0.389.0 → 0.390.1

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 (301) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +27 -15
  3. package/packages/bundled-features/src/audit/feature.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  6. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  7. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  8. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  9. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  17. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
  20. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  21. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
  22. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  23. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  24. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  25. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  26. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  27. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  28. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  29. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  30. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  31. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  32. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  33. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  34. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
  35. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  36. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  37. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  48. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  49. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  50. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  52. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  53. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  54. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  55. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  56. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  57. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  58. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
  59. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  60. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  61. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  62. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  63. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  64. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  65. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  66. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  67. package/packages/bundled-features/src/config/feature.ts +6 -6
  68. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  69. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  70. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  71. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  72. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  73. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  74. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  75. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
  76. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  77. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  78. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  79. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  80. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  81. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  82. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  83. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  84. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  85. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  86. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  87. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  88. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  89. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  90. package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
  91. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  93. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  97. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  98. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  99. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  100. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  101. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  102. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  103. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  104. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  105. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  106. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  107. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  108. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  109. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  110. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  111. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  112. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  113. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  114. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  115. package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
  116. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  117. package/packages/bundled-features/src/jobs/changes.json +6 -0
  118. package/packages/bundled-features/src/jobs/events.ts +1 -1
  119. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  120. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  121. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  122. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  125. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  126. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  127. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  128. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  129. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  130. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  131. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  132. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  133. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  136. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  137. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  138. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  139. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  140. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  141. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  142. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  143. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  144. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  145. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  146. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  147. package/packages/bundled-features/src/seo/feature.ts +1 -1
  148. package/packages/bundled-features/src/sessions/changes.json +12 -0
  149. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  150. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  151. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  155. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  157. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  158. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  159. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  160. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  161. package/packages/bundled-features/src/shared/index.ts +15 -0
  162. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  163. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  164. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  165. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  166. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  168. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  169. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  170. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  171. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  172. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  173. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  174. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  175. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  176. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  178. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  181. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  182. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  183. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  184. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  185. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  196. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  197. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  198. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  199. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  200. package/packages/bundled-features/src/tenant/changes.json +19 -0
  201. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  203. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  204. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  205. package/packages/bundled-features/src/tenant/index.ts +5 -0
  206. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  207. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  208. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  209. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  210. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  211. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  212. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  213. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  215. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  216. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  217. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  218. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  219. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  220. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  222. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  223. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  224. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  225. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  226. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  227. package/packages/bundled-features/src/user/index.ts +2 -0
  228. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  229. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  230. package/packages/bundled-features/src/user/seeding.ts +24 -6
  231. package/packages/bundled-features/src/user/testing.ts +22 -0
  232. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  234. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  235. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  236. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  237. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  241. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  242. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  243. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  244. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  245. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  247. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  248. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  249. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  250. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  251. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  252. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  253. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  254. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  255. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  256. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  257. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  258. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  259. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  260. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  261. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  262. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  263. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  264. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  265. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  266. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  267. package/samples/apps/cap-billing-demo/package.json +1 -1
  268. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  269. package/samples/apps/config-demo/src/app/server.ts +1 -1
  270. package/samples/apps/styleguide/src/app/server.ts +2 -2
  271. package/samples/apps/ui-walkthrough/package.json +1 -1
  272. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  273. package/samples/apps/use-all-bundled/package.json +1 -1
  274. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  275. package/samples/apps/user-data-rights-demo/README.md +1 -1
  276. package/samples/apps/user-data-rights-demo/package.json +1 -1
  277. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  278. package/samples/recipes/api-worker-split/README.md +2 -1
  279. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  280. package/samples/recipes/cross-feature-events/README.md +5 -5
  281. package/samples/recipes/cross-feature-events/package.json +1 -1
  282. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  283. package/samples/recipes/event-sourcing/README.md +5 -5
  284. package/samples/recipes/event-sourcing/package.json +1 -1
  285. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  286. package/samples/recipes/files-post-processing/README.md +1 -1
  287. package/samples/recipes/files-post-processing/package.json +1 -1
  288. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  289. package/samples/recipes/lane-routing/package.json +1 -1
  290. package/samples/recipes/legal-pages/README.md +2 -2
  291. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  292. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  293. package/samples/recipes/managed-config/README.md +3 -3
  294. package/samples/recipes/search/README.md +1 -1
  295. package/samples/recipes/search/src/feature.ts +3 -3
  296. package/samples/recipes/secrets-demo/README.md +1 -1
  297. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  298. package/samples/recipes/state-machine/src/events.ts +1 -1
  299. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  300. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  301. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -4,585 +4,14 @@
4
4
  // collection is rendered. Variables are inserted as `variable` nodes only,
5
5
  // never as `{{name}}` text, so the sanitizer's schema keeps them.
6
6
 
7
- import {
8
- type ActionMenuItemSpec,
9
- TextareaContentEditor,
10
- usePrimitives,
11
- useTranslation,
12
- VariableChips,
13
- } from "@cosmicdrift/kumiko-renderer";
14
- import { cn } from "@cosmicdrift/kumiko-renderer/web";
15
- import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
16
- import type { Editor, JSONContent } from "@tiptap/core";
17
- import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
18
- import { BubbleMenu } from "@tiptap/react/menus";
19
- import {
20
- AlignCenter,
21
- AlignJustify,
22
- AlignLeft,
23
- AlignRight,
24
- Bold as BoldIcon,
25
- Braces,
26
- ChevronDown,
27
- GalleryThumbnails,
28
- Image as ImageIcon,
29
- Italic as ItalicIcon,
30
- Link as LinkIcon,
31
- List,
32
- ListChecks,
33
- ListOrdered,
34
- Minus,
35
- MousePointerClick,
36
- Repeat,
37
- Scissors,
38
- Table as TableIcon,
39
- Underline as UnderlineIcon,
40
- } from "lucide-react";
41
- import { Fragment, type ReactNode, useEffect, useMemo, useState } from "react";
42
- import { createPortal } from "react-dom";
43
- import {
44
- createDocumentEditorExtensions,
45
- DOCUMENT_BLOCK_ATTRIBUTE,
46
- } from "./document-editor-extensions.ts";
47
- import {
48
- DOCUMENT_TEXT_ALIGNMENTS,
49
- type DocumentTextAlignment,
50
- isAllowedDocumentLinkHref,
51
- isDocumentPlaceholderHref,
52
- isDocumentReferenceName,
53
- isDocumentResourceKey,
54
- } from "./document-schema.ts";
55
- import { parseDocumentStepsInput } from "./document-steps.ts";
56
- import {
57
- type DocumentEditorProps,
58
- type DocumentResourcePreview,
59
- type DocumentToolbarBlock,
60
- MAIL_DOCUMENT_BLOCKS,
61
- } from "./document-toolbar-blocks.ts";
62
- import { createEditorHandle } from "./editor-handle.ts";
63
- import { useStableByKey } from "./use-stable-by-key.ts";
64
-
65
- const NO_LABELS: Readonly<Record<string, string>> = {};
66
- const NO_NAMES: readonly string[] = [];
67
- const NO_PREVIEWS: Readonly<Record<string, DocumentResourcePreview>> = {};
68
-
69
- function previewsKey(previews: Readonly<Record<string, DocumentResourcePreview>>): string {
70
- return Object.entries(previews)
71
- .map(([key, { src, caption }]) => `${key}|${src.length}|${src.slice(-24)}|${caption ?? ""}`)
72
- .join("\n");
73
- }
74
-
75
- type PanelKind =
76
- | "link"
77
- | "placeholder"
78
- | "image"
79
- | "button"
80
- | "repeat"
81
- | "align"
82
- | "steps"
83
- | "images";
84
-
85
- // Blocks that stay in the canvas toolbar; the rest moves into the overflow menu.
86
- const CANVAS_INLINE_BLOCKS: readonly DocumentToolbarBlock[] = ["image", "table"];
87
-
88
- const BLOCK_PANEL: Partial<Record<DocumentToolbarBlock, PanelKind>> = {
89
- image: "image",
90
- button: "button",
91
- repeat: "repeat",
92
- align: "align",
93
- steps: "steps",
94
- images: "images",
95
- };
96
-
97
- const ALIGNMENT_ICONS: Record<DocumentTextAlignment, ReactNode> = {
98
- left: <AlignLeft size={16} />,
99
- center: <AlignCenter size={16} />,
100
- right: <AlignRight size={16} />,
101
- justify: <AlignJustify size={16} />,
102
- };
103
-
104
- const ALIGNMENT_LABEL_KEYS: Record<DocumentTextAlignment, string> = {
105
- left: "template-resolver.documentEditor.alignLeft",
106
- center: "template-resolver.documentEditor.alignCenter",
107
- right: "template-resolver.documentEditor.alignRight",
108
- justify: "template-resolver.documentEditor.alignJustify",
109
- };
110
-
111
- type RepeatTableState = {
112
- readonly lists: readonly string[];
113
- readonly headerRepeats: boolean;
114
- readonly firstRowPos: number;
115
- };
116
-
117
- // print-document moves the leading rows flagged repeatHeader into thead, so the
118
- // toggle reads and writes the table's first row.
119
- function findRepeatTable(editor: Editor): RepeatTableState | null {
120
- const { $from } = editor.state.selection;
121
- for (let depth = $from.depth; depth > 0; depth--) {
122
- const node = $from.node(depth);
123
- if (node.type.name !== "table") continue;
124
- const lists: string[] = [];
125
- node.forEach((row) => {
126
- const each: unknown = row.attrs["each"];
127
- if (typeof each === "string" && each !== "" && !lists.includes(each)) lists.push(each);
128
- });
129
- if (lists.length === 0) return null;
130
- return {
131
- lists,
132
- headerRepeats: node.firstChild?.attrs["repeatHeader"] === true,
133
- firstRowPos: $from.start(depth),
134
- };
135
- }
136
- return null;
137
- }
138
-
139
- function toggleRepeatHeader(editor: Editor): void {
140
- const found = findRepeatTable(editor);
141
- if (found === null) return;
142
- editor
143
- .chain()
144
- .focus()
145
- .command(({ tr }) => {
146
- const row = tr.doc.nodeAt(found.firstRowPos);
147
- if (row === null) return false;
148
- tr.setNodeMarkup(found.firstRowPos, undefined, {
149
- ...row.attrs,
150
- repeatHeader: !found.headerRepeats,
151
- });
152
- return true;
153
- })
154
- .run();
155
- }
156
-
157
- const PARAGRAPH_STYLES = ["paragraph", "1", "2", "3"] as const;
158
- type ParagraphStyle = (typeof PARAGRAPH_STYLES)[number];
159
-
160
- const HEADING_LEVELS = { "1": 1, "2": 2, "3": 3 } as const;
161
-
162
- const PLACEHOLDER_HREF = /^\{\{([^{}]+)\}\}$/;
163
-
164
- function variableLabelOf(name: string, labels: Readonly<Record<string, string>>): string {
165
- return Object.hasOwn(labels, name) ? (labels[name] ?? name) : name;
166
- }
167
-
168
- function buttonTargetLabel(href: string, labels: Readonly<Record<string, string>>): string {
169
- const name = PLACEHOLDER_HREF.exec(href)?.[1];
170
- return name === undefined ? href : variableLabelOf(name, labels);
171
- }
172
-
173
- // The bubble menu registers its plugin once per mount, so a rebuilt editor needs a fresh mount.
174
- const editorMountKeys = new WeakMap<Editor, number>();
175
- let lastMountKey = 0;
176
- function mountKeyOf(editor: Editor): number {
177
- const known = editorMountKeys.get(editor);
178
- if (known !== undefined) return known;
179
- lastMountKey += 1;
180
- const created = lastMountKey;
181
- editorMountKeys.set(editor, created);
182
- return created;
183
- }
184
-
185
- // Board .hb: a small bar that straddles the block's top frame line on the right; above a table it sits fully
186
- // outside so the header row stays readable.
187
- const BUBBLE_BAR_CLASS =
188
- "flex w-max items-center gap-1 rounded border border-input bg-popover p-px text-xs [&_button]:h-5 [&_button]:px-1.5 [&_button]:text-[10px]";
189
-
190
- function bubbleOffset(editor: Editor): (args: { rects: { floating: { height: number } } }) => {
191
- mainAxis: number;
192
- crossAxis: number;
193
- } {
194
- return ({ rects }) => ({
195
- mainAxis: findRepeatTable(editor) !== null ? 2 : -rects.floating.height / 2,
196
- crossAxis: -8,
197
- });
198
- }
199
-
200
- const BUBBLE_MENU_PLUGIN_KEY = "documentBlockOptions";
201
-
202
- // The options bar belongs to the block frame, not to the caret inside it.
203
- function selectedBlockReference(editor: Editor): {
204
- getBoundingClientRect: () => DOMRect;
205
- getClientRects: () => DOMRect[];
206
- contextElement: Element;
207
- } | null {
208
- const { node } = editor.view.domAtPos(editor.state.selection.from);
209
- const start = node instanceof Element ? node : node.parentElement;
210
- const block = start?.closest(`[${DOCUMENT_BLOCK_ATTRIBUTE}], table`);
211
- if (block === null || block === undefined) return null;
212
- return {
213
- getBoundingClientRect: () => block.getBoundingClientRect(),
214
- getClientRects: () => [block.getBoundingClientRect()],
215
- contextElement: block,
216
- };
217
- }
218
-
219
- // The bubble is first measured before React has filled it, so it sits at the wrong place until a second pass.
220
- function repositionBubble(editor: Editor): void {
221
- if (editor.isDestroyed) return;
222
- editor.view.dispatch(editor.state.tr.setMeta(BUBBLE_MENU_PLUGIN_KEY, "updatePosition"));
223
- }
224
-
225
- function isParagraphStyle(value: string): value is ParagraphStyle {
226
- return isOneOf(PARAGRAPH_STYLES, value);
227
- }
228
-
229
- function emptyParagraph(): JSONContent {
230
- return { type: "paragraph" };
231
- }
232
-
233
- function repeatTableContent(listName: string): JSONContent {
234
- const headerRow: JSONContent = {
235
- type: "tableRow",
236
- content: [
237
- { type: "tableHeader", content: [emptyParagraph()] },
238
- { type: "tableHeader", content: [emptyParagraph()] },
239
- ],
240
- };
241
- const repeatRow: JSONContent = {
242
- type: "tableRow",
243
- attrs: { each: listName },
244
- content: [
245
- { type: "tableCell", content: [emptyParagraph()] },
246
- { type: "tableCell", content: [emptyParagraph()] },
247
- ],
248
- };
249
- return { type: "table", content: [headerRow, repeatRow] };
250
- }
251
-
252
- function Separator({ compact = false }: { readonly compact?: boolean }): ReactNode {
253
- return (
254
- <span aria-hidden="true" className={cn("h-5 w-px bg-border", compact ? "mx-0.5" : "mx-2")} />
255
- );
256
- }
257
-
258
- function PanelActions({
259
- onInsert,
260
- onRemoveLink,
261
- onCancel,
262
- }: {
263
- readonly onInsert?: () => void;
264
- readonly onRemoveLink?: () => void;
265
- readonly onCancel: () => void;
266
- }): ReactNode {
267
- const { Button } = usePrimitives();
268
- const t = useTranslation();
269
- return (
270
- <div className="flex gap-2">
271
- {onInsert && (
272
- <Button type="button" size="sm" onClick={onInsert}>
273
- {t("template-resolver.documentEditor.insert")}
274
- </Button>
275
- )}
276
- {onRemoveLink && (
277
- <Button type="button" size="sm" variant="danger-ghost" onClick={onRemoveLink}>
278
- {t("template-resolver.documentEditor.removeLink")}
279
- </Button>
280
- )}
281
- <Button type="button" size="sm" variant="secondary" onClick={onCancel}>
282
- {t("template-resolver.documentEditor.cancel")}
283
- </Button>
284
- </div>
285
- );
286
- }
287
-
288
- type OptionSelectProps = {
289
- readonly id: string;
290
- readonly label: string;
291
- readonly names: readonly string[];
292
- readonly labels: Readonly<Record<string, string>>;
293
- readonly value: string;
294
- readonly onChange: (name: string) => void;
295
- };
296
-
297
- function NameSelect({ id, label, names, labels, value, onChange }: OptionSelectProps): ReactNode {
298
- const { Field, Input } = usePrimitives();
299
- return (
300
- <Field id={id} label={label}>
301
- <Input
302
- kind="select"
303
- id={id}
304
- name={id}
305
- value={value}
306
- onChange={onChange}
307
- display="dropdown"
308
- options={names.map((name) => ({ value: name, label: variableLabelOf(name, labels) }))}
309
- />
310
- </Field>
311
- );
312
- }
313
-
314
- type PanelProps = {
315
- readonly editor: Editor;
316
- readonly editorId: string;
317
- readonly kind: PanelKind;
318
- readonly variables: readonly string[];
319
- readonly variableLabels: Readonly<Record<string, string>>;
320
- readonly onClose: () => void;
321
- };
322
-
323
- function InsertPanel({
324
- editor,
325
- editorId,
326
- kind,
327
- variables,
328
- variableLabels,
329
- onClose,
330
- }: PanelProps): ReactNode {
331
- const { Button, Field, Input } = usePrimitives();
332
- const t = useTranslation();
333
- const [href, setHref] = useState(() => {
334
- const current = editor.getAttributes("link")["href"];
335
- return typeof current === "string" ? current : "";
336
- });
337
- const [resource, setResource] = useState("");
338
- const [alt, setAlt] = useState("");
339
- const [buttonText, setButtonText] = useState(() =>
340
- t("template-resolver.documentEditor.buttonDefaultText"),
341
- );
342
- const [stepsText, setStepsText] = useState(() =>
343
- t("template-resolver.documentEditor.stepsDefault"),
344
- );
345
- const [chosenName, setChosenName] = useState("");
346
- const [error, setError] = useState<string | null>(null);
347
-
348
- const referenceNames = variables.filter(isDocumentReferenceName);
349
- const buttonTargets = referenceNames.filter((name) => isDocumentPlaceholderHref(`{{${name}}}`));
350
- const selectable = kind === "button" ? buttonTargets : referenceNames;
351
- const selectedName = selectable.includes(chosenName) ? chosenName : (selectable[0] ?? "");
352
-
353
- const insertAndClose = (content: JSONContent): void => {
354
- editor.chain().focus().insertContent(content).run();
355
- onClose();
356
- };
357
-
358
- const submitLink = (): void => {
359
- if (!isAllowedDocumentLinkHref(href)) {
360
- setError(t("template-resolver.documentEditor.invalidLink"));
361
- return;
362
- }
363
- const chain = editor.chain().focus();
364
- if (!editor.state.selection.empty) chain.setLink({ href }).run();
365
- else if (editor.isActive("link")) chain.extendMarkRange("link").setLink({ href }).run();
366
- else if (isDocumentPlaceholderHref(href))
367
- // Placeholders are never inserted as {{name}} text; the link shows the variable's value.
368
- chain
369
- .insertContent({
370
- type: "variable",
371
- attrs: { name: href.slice(2, -2) },
372
- marks: [{ type: "link", attrs: { href } }],
373
- })
374
- .run();
375
- else
376
- chain
377
- .insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] })
378
- .run();
379
- onClose();
380
- };
381
-
382
- const submitImage = (): void => {
383
- if (!isDocumentResourceKey(resource)) {
384
- setError(t("template-resolver.documentEditor.invalidResourceKey"));
385
- return;
386
- }
387
- if (/[<>]/.test(alt)) {
388
- setError(t("template-resolver.documentEditor.invalidImageAlt"));
389
- return;
390
- }
391
- insertAndClose({ type: "resource", attrs: { resource, alt: alt || null } });
392
- };
393
-
394
- const submitSteps = (): void => {
395
- const steps = parseDocumentStepsInput(stepsText);
396
- if (steps === null) {
397
- setError(t("template-resolver.documentEditor.invalidSteps"));
398
- return;
399
- }
400
- insertAndClose({ type: "steps", attrs: { variable: selectedName, steps } });
401
- };
402
-
403
- const needsVariables =
404
- kind === "button" ||
405
- kind === "repeat" ||
406
- kind === "placeholder" ||
407
- kind === "steps" ||
408
- kind === "images";
409
- const noVariables = needsVariables && selectable.length === 0;
410
- const idPrefix = `${editorId}-panel`;
411
-
412
- return (
413
- <div className="flex flex-col gap-2 border-b border-input bg-muted/40 p-2">
414
- {noVariables && (
415
- <p className="text-sm text-muted-foreground">
416
- {t("template-resolver.documentEditor.noVariables")}
417
- </p>
418
- )}
419
- {kind === "link" && (
420
- <Field id={`${idPrefix}-href`} label={t("template-resolver.documentEditor.linkHref")}>
421
- <Input
422
- kind="text"
423
- id={`${idPrefix}-href`}
424
- name="href"
425
- value={href}
426
- onChange={setHref}
427
- placeholder={t("template-resolver.documentEditor.linkHrefPlaceholder")}
428
- />
429
- </Field>
430
- )}
431
- {kind === "placeholder" && !noVariables && (
432
- <div className="flex flex-wrap gap-1">
433
- <VariableChips
434
- variables={selectable}
435
- labels={variableLabels}
436
- onInsert={(name) => insertAndClose({ type: "variable", attrs: { name } })}
437
- />
438
- </div>
439
- )}
440
- {kind === "align" && (
441
- <div className="flex flex-wrap gap-1">
442
- {DOCUMENT_TEXT_ALIGNMENTS.map((alignment) => (
443
- <Button
444
- key={alignment}
445
- type="button"
446
- variant="secondary"
447
- size="sm"
448
- onClick={() => {
449
- editor
450
- .chain()
451
- .focus()
452
- .updateAttributes("paragraph", { textAlign: alignment })
453
- .updateAttributes("heading", { textAlign: alignment })
454
- .run();
455
- onClose();
456
- }}
457
- >
458
- {ALIGNMENT_ICONS[alignment]}
459
- {t(ALIGNMENT_LABEL_KEYS[alignment])}
460
- </Button>
461
- ))}
462
- </div>
463
- )}
464
- {kind === "image" && (
465
- <>
466
- <Field
467
- id={`${idPrefix}-resource`}
468
- label={t("template-resolver.documentEditor.resourceKey")}
469
- >
470
- <Input
471
- kind="text"
472
- id={`${idPrefix}-resource`}
473
- name="resource"
474
- value={resource}
475
- onChange={setResource}
476
- />
477
- </Field>
478
- <Field id={`${idPrefix}-alt`} label={t("template-resolver.documentEditor.imageAlt")}>
479
- <Input kind="text" id={`${idPrefix}-alt`} name="alt" value={alt} onChange={setAlt} />
480
- </Field>
481
- </>
482
- )}
483
- {kind === "button" && !noVariables && (
484
- <>
485
- <NameSelect
486
- id={`${idPrefix}-target`}
487
- label={t("template-resolver.documentEditor.buttonTarget")}
488
- names={selectable}
489
- labels={variableLabels}
490
- value={selectedName}
491
- onChange={setChosenName}
492
- />
493
- <Field id={`${idPrefix}-text`} label={t("template-resolver.documentEditor.buttonText")}>
494
- <Input
495
- kind="text"
496
- id={`${idPrefix}-text`}
497
- name="text"
498
- value={buttonText}
499
- onChange={setButtonText}
500
- />
501
- </Field>
502
- </>
503
- )}
504
- {kind === "repeat" && !noVariables && (
505
- <NameSelect
506
- id={`${idPrefix}-list`}
507
- label={t("template-resolver.documentEditor.repeatList")}
508
- names={selectable}
509
- labels={variableLabels}
510
- value={selectedName}
511
- onChange={setChosenName}
512
- />
513
- )}
514
- {kind === "images" && !noVariables && (
515
- <NameSelect
516
- id={`${idPrefix}-images-variable`}
517
- label={t("template-resolver.documentEditor.imagesVariable")}
518
- names={selectable}
519
- labels={variableLabels}
520
- value={selectedName}
521
- onChange={setChosenName}
522
- />
523
- )}
524
- {kind === "steps" && !noVariables && (
525
- <>
526
- <NameSelect
527
- id={`${idPrefix}-steps-variable`}
528
- label={t("template-resolver.documentEditor.stepsVariable")}
529
- names={selectable}
530
- labels={variableLabels}
531
- value={selectedName}
532
- onChange={setChosenName}
533
- />
534
- <Field
535
- id={`${idPrefix}-steps-list`}
536
- label={t("template-resolver.documentEditor.stepsList")}
537
- >
538
- <Input
539
- kind="text"
540
- id={`${idPrefix}-steps-list`}
541
- name="steps"
542
- value={stepsText}
543
- onChange={setStepsText}
544
- />
545
- </Field>
546
- </>
547
- )}
548
- {error !== null && (
549
- <p role="alert" className="text-sm text-destructive">
550
- {error}
551
- </p>
552
- )}
553
- <div className="flex flex-wrap gap-2">
554
- <PanelActions
555
- onCancel={onClose}
556
- {...(kind === "link" && { onInsert: submitLink })}
557
- {...(kind === "link" &&
558
- editor.isActive("link") && {
559
- onRemoveLink: () => {
560
- editor.chain().focus().extendMarkRange("link").unsetLink().run();
561
- onClose();
562
- },
563
- })}
564
- {...(kind === "image" && { onInsert: submitImage })}
565
- {...(kind === "button" &&
566
- !noVariables && {
567
- onInsert: () =>
568
- insertAndClose({
569
- type: "button",
570
- attrs: { href: `{{${selectedName}}}` },
571
- content: buttonText ? [{ type: "text", text: buttonText }] : [],
572
- }),
573
- })}
574
- {...(kind === "repeat" &&
575
- !noVariables && { onInsert: () => insertAndClose(repeatTableContent(selectedName)) })}
576
- {...(kind === "steps" && !noVariables && { onInsert: submitSteps })}
577
- {...(kind === "images" &&
578
- !noVariables && {
579
- onInsert: () => insertAndClose({ type: "images", attrs: { variable: selectedName } }),
580
- })}
581
- />
582
- </div>
583
- </div>
584
- );
585
- }
7
+ import { TextareaContentEditor, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
8
+ import { type ReactNode, useEffect } from "react";
9
+ import { renderEditorContent } from "./document-tiptap-content.tsx";
10
+ import { buildTiptapContext } from "./document-tiptap-context.ts";
11
+ import { NO_LABELS, NO_NAMES, NO_PREVIEWS } from "./document-tiptap-helpers.ts";
12
+ import { renderHostedEditor, renderInlineEditor } from "./document-tiptap-layout.tsx";
13
+ import { type DocumentEditorProps, MAIL_DOCUMENT_BLOCKS } from "./document-toolbar-blocks.ts";
14
+ import { useDocumentTiptapModel } from "./use-document-tiptap-model.ts";
586
15
 
587
16
  export default function DocumentTiptapEditor({
588
17
  id,
@@ -603,9 +32,6 @@ export default function DocumentTiptapEditor({
603
32
  }: DocumentEditorProps): ReactNode {
604
33
  const t = useTranslation();
605
34
  const { ActionOverflowMenu, Button, Field, Input } = usePrimitives();
606
- const [openPanel, setOpenPanel] = useState<PanelKind | null>(null);
607
- const [canvasScroller, setCanvasScroller] = useState<HTMLElement | null>(null);
608
- const [showHtml, setShowHtml] = useState(false);
609
35
  const canvas = chrome === "canvas";
610
36
  const compact = toolbar === "compact" && !canvas;
611
37
  const hosted = toolbarHost !== undefined;
@@ -615,98 +41,21 @@ export default function DocumentTiptapEditor({
615
41
  ? configuredBlocks.filter((block) => block !== "images")
616
42
  : configuredBlocks;
617
43
 
618
- const placeholderLabel = t("template-resolver.documentEditor.placeholder");
619
- const imageLabel = t("template-resolver.documentEditor.image");
620
- const imageBlockLabel = t("template-resolver.documentEditor.imageBlock");
621
- const pageBreakLabel = t("template-resolver.documentEditor.pageBreak");
622
- const buttonLabel = t("template-resolver.documentEditor.button");
623
- const stepsLabel = t("template-resolver.documentEditor.steps");
624
- const imagesLabel = t("template-resolver.documentEditor.images");
625
- const stableLabels = useStableByKey(variableLabels, JSON.stringify(variableLabels));
626
- const stableSystem = useStableByKey(systemVariables, systemVariables.join("\n"));
627
- const stablePreviews = useStableByKey(resourcePreviews, previewsKey(resourcePreviews));
628
- const extensions = useMemo(
629
- () =>
630
- createDocumentEditorExtensions({
631
- placeholder: placeholderLabel,
632
- image: imageLabel,
633
- pageBreak: pageBreakLabel,
634
- button: buttonLabel,
635
- steps: stepsLabel,
636
- images: imagesLabel,
637
- imageBlock: imageBlockLabel,
638
- variableLabels: stableLabels,
639
- systemVariables: stableSystem,
640
- resourcePreviews: stablePreviews,
641
- blockLabels: canvas,
642
- }),
643
- [
644
- placeholderLabel,
645
- imageLabel,
646
- imageBlockLabel,
647
- pageBreakLabel,
648
- buttonLabel,
649
- stepsLabel,
650
- imagesLabel,
651
- stableLabels,
652
- stableSystem,
653
- stablePreviews,
654
- canvas,
655
- ],
656
- );
657
-
658
- // The node views are built from labels and previews, so a changed set
659
- // rebuilds the editor; its content is re-read from `value`.
660
- const editor = useEditor(
44
+ const model = useDocumentTiptapModel(
661
45
  {
662
- extensions,
663
- content: value,
664
- editable: !readOnly,
665
- // Suspense (DocumentContentEditor's lazy boundary) speculatively mounts
666
- // this component before committing it; immediate render would create an
667
- // editor instance during that throwaway pass.
668
- immediatelyRender: false,
669
- editorProps: { attributes: { id } },
670
- onCreate: ({ editor: created }) => onEditorReady?.(createEditorHandle(created)),
671
- onDestroy: () => onEditorReady?.(null),
672
- onUpdate: ({ editor: e }) => onChange(e.getHTML()),
46
+ id,
47
+ value,
48
+ onChange,
49
+ readOnly,
50
+ onEditorReady,
51
+ variableLabels,
52
+ systemVariables,
53
+ resourcePreviews,
54
+ canvas,
673
55
  },
674
- [extensions],
56
+ t,
675
57
  );
676
-
677
- // @tiptap/react v3 does not re-render on selection-only transactions, so
678
- // active states must come from a subscription.
679
- const activeState = useEditorState({
680
- editor,
681
- selector: ({ editor: e }) =>
682
- e === null
683
- ? null
684
- : {
685
- blockStyle: e.isActive("heading", { level: 1 })
686
- ? "1"
687
- : e.isActive("heading", { level: 2 })
688
- ? "2"
689
- : e.isActive("heading", { level: 3 })
690
- ? "3"
691
- : "paragraph",
692
- bold: e.isActive("bold"),
693
- italic: e.isActive("italic"),
694
- underline: e.isActive("underline"),
695
- selectedBlock: e.isActive("resource")
696
- ? {
697
- type: "resource" as const,
698
- detail: String(e.getAttributes("resource")["resource"]),
699
- }
700
- : e.isActive("button")
701
- ? { type: "button" as const, detail: String(e.getAttributes("button")["href"]) }
702
- : null,
703
- html: showHtml ? e.getHTML() : "",
704
- bulletList: e.isActive("bulletList"),
705
- orderedList: e.isActive("orderedList"),
706
- link: e.isActive("link"),
707
- repeatTable: findRepeatTable(e),
708
- },
709
- });
58
+ const { editor } = model;
710
59
 
711
60
  // The entry loads asynchronously, so `value` arrives after useEditor read
712
61
  // its initial content; the guard keeps in-flight typing from being clobbered.
@@ -733,434 +82,38 @@ export default function DocumentTiptapEditor({
733
82
  <TextareaContentEditor id={id} value={value} onChange={onChange} variables={[]} readOnly />
734
83
  );
735
84
 
736
- const togglePanel = (kind: PanelKind): void =>
737
- setOpenPanel((current) => (current === kind ? null : kind));
738
-
739
- const setBlockStyle = (style: string): void => {
740
- if (!isParagraphStyle(style)) return;
741
- const chain = editor.chain().focus();
742
- if (style === "paragraph") chain.setParagraph().run();
743
- else chain.setHeading({ level: HEADING_LEVELS[style] }).run();
744
- };
745
-
746
- const runBlock = (block: DocumentToolbarBlock): void => {
747
- const panel = BLOCK_PANEL[block];
748
- if (panel) {
749
- togglePanel(panel);
750
- return;
751
- }
752
- const chain = editor.chain().focus();
753
- if (block === "table") chain.insertTable({ rows: 3, cols: 2, withHeaderRow: true }).run();
754
- else if (block === "divider") chain.insertContent({ type: "divider" }).run();
755
- else if (block === "pageBreak") chain.insertContent({ type: "pageBreak" }).run();
756
- };
757
-
758
- const blockButtons: Record<DocumentToolbarBlock, { label: string; icon: ReactNode }> = {
759
- image: { label: imageLabel, icon: <ImageIcon size={16} /> },
760
- table: { label: t("template-resolver.documentEditor.table"), icon: <TableIcon size={16} /> },
761
- button: {
762
- label: t("template-resolver.documentEditor.button"),
763
- icon: <MousePointerClick size={16} />,
764
- },
765
- divider: { label: t("template-resolver.documentEditor.divider"), icon: <Minus size={16} /> },
766
- repeat: { label: t("template-resolver.documentEditor.repeat"), icon: <Repeat size={16} /> },
767
- pageBreak: { label: pageBreakLabel, icon: <Scissors size={16} /> },
768
- align: { label: t("template-resolver.documentEditor.align"), icon: <AlignLeft size={16} /> },
769
- steps: { label: stepsLabel, icon: <ListChecks size={16} /> },
770
- images: { label: imagesLabel, icon: <GalleryThumbnails size={16} /> },
771
- };
772
-
773
- const repeatTable = activeState?.repeatTable ?? null;
774
- const repeatListLabel =
775
- repeatTable !== null && repeatTable.lists.length === 1
776
- ? (variableLabels[repeatTable.lists[0] ?? ""] ?? repeatTable.lists[0])
777
- : undefined;
778
-
779
- const repeatBarLabel =
780
- repeatListLabel === undefined
781
- ? t("template-resolver.documentEditor.repeat")
782
- : t("template-resolver.documentEditor.repeatBarList", { list: repeatListLabel });
783
-
784
- const paragraphStyleLabel = t("template-resolver.documentEditor.paragraphStyle");
785
- const styleOptions = [
786
- { value: "paragraph", label: t("template-resolver.documentEditor.paragraph") },
787
- { value: "1", label: t("kumiko.contentEditor.heading1") },
788
- { value: "2", label: t("kumiko.contentEditor.heading2") },
789
- { value: "3", label: t("template-resolver.documentEditor.heading3") },
790
- ];
791
-
792
- // Canvas chrome is one flat row: unframed ghost buttons, glyph plus text for the longer names.
793
- const iconButton = (
794
- label: string,
795
- icon: ReactNode,
796
- isActive: boolean,
797
- onClick: () => void,
798
- text?: string,
799
- ): ReactNode =>
800
- canvas ? (
801
- <Button
802
- type="button"
803
- variant="ghost"
804
- size="sm"
805
- pressed={isActive}
806
- disabled={readOnly}
807
- onClick={onClick}
808
- ariaLabel={label}
809
- title={label}
810
- >
811
- {icon}
812
- {text}
813
- </Button>
814
- ) : (
815
- <Button
816
- type="button"
817
- variant={isActive ? "primary" : "secondary"}
818
- size="icon"
819
- pressed={isActive}
820
- disabled={readOnly}
821
- onClick={onClick}
822
- ariaLabel={label}
823
- title={label}
824
- >
825
- {icon}
826
- </Button>
827
- );
828
-
829
- const markContent = (letter: string, icon: ReactNode, className: string): ReactNode =>
830
- canvas ? <span className={className}>{letter}</span> : icon;
831
- const selectedBlock = activeState?.selectedBlock ?? null;
832
-
833
- const flatVariant = canvas ? "ghost" : "secondary";
834
-
835
- const labelledButton = (spec: {
836
- readonly key?: string;
837
- readonly label: string;
838
- readonly icon: ReactNode;
839
- readonly showIcon: boolean;
840
- readonly trailingIcon?: ReactNode;
841
- readonly expanded?: boolean;
842
- readonly onClick: () => void;
843
- }): ReactNode => {
844
- const shared = {
845
- type: "button",
846
- variant: flatVariant,
847
- disabled: readOnly,
848
- onClick: spec.onClick,
849
- ...(spec.expanded !== undefined && { expanded: spec.expanded }),
850
- } as const;
851
- return compact ? (
852
- <Button key={spec.key} {...shared} size="icon" ariaLabel={spec.label} title={spec.label}>
853
- {spec.icon}
854
- </Button>
855
- ) : (
856
- <Button key={spec.key} {...shared} size="sm">
857
- {spec.showIcon && spec.icon}
858
- {spec.label}
859
- {spec.trailingIcon}
860
- </Button>
861
- );
862
- };
863
-
864
- const editorContent = (
865
- <EditorContent
866
- editor={editor}
867
- className={cn(
868
- !canvas && "min-h-40 px-3 py-2 text-base outline-none md:text-sm",
869
- canvas && "text-sm",
870
- "[&_.ProseMirror-focused_.ProseMirror-selectednode]:ring-2 [&_.ProseMirror-focused_.ProseMirror-selectednode]:ring-primary",
871
- hosted
872
- ? "[&_.ProseMirror]:outline-none"
873
- : "[&_.ProseMirror]:min-h-40 [&_.ProseMirror]:outline-none",
874
- "[&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-6 [&_ol]:pl-6",
875
- "[&_h1]:text-xl [&_h1]:font-bold [&_h2]:text-lg [&_h2]:font-bold [&_h3]:text-base [&_h3]:font-bold",
876
- "[&_a]:underline [&_a]:text-primary",
877
- "[&_strong]:font-bold [&_em]:italic [&_u]:underline",
878
- canvas
879
- ? "[&_table]:mb-2 [&_table]:mt-6 [&_.ProseMirror>:first-child_table]:mt-0 [&_.ProseMirror>table:first-child]:mt-0 [&_td:last-child>p[data-size=small][data-align=right]]:whitespace-nowrap"
880
- : "[&_table]:my-2",
881
- "[&_table]:w-full [&_table]:border-collapse",
882
- "[&_td]:border-b [&_td]:border-border [&_td]:px-2 [&_td]:py-1.5 [&_td>p]:m-0 [&_th>p]:m-0",
883
- "[&_th]:border-b [&_th]:border-border [&_th]:px-2 [&_th]:py-1.5 [&_th]:text-left [&_th]:text-xs [&_th]:font-medium [&_th]:text-muted-foreground",
884
- "[&_tr[data-each]>td]:bg-muted/50",
885
- "[&_[data-align=left]]:text-left [&_[data-align=center]]:text-center [&_[data-align=right]]:text-right [&_[data-align=justify]]:text-justify",
886
- "[&_p[data-size=small]]:text-xs [&_p[data-size=small]]:text-muted-foreground",
887
- "[&_a[data-button]]:inline-block [&_a[data-button]]:rounded-full [&_a[data-button]]:bg-primary [&_a[data-button]]:px-4 [&_a[data-button]]:py-1.5 [&_a[data-button]]:text-primary-foreground [&_a[data-button]]:no-underline",
888
- // A chip in the button would be primary on primary; it inverts to stay readable.
889
- "[&_a[data-button]_[data-variable-chip]]:border-primary-foreground/40 [&_a[data-button]_[data-variable-chip]]:bg-primary-foreground/15 [&_a[data-button]_[data-variable-chip]]:text-primary-foreground",
890
- "[&_hr]:my-3 [&_hr]:border-input",
891
- "[&_.is-editor-empty]:before:pointer-events-none [&_.is-editor-empty]:before:float-left [&_.is-editor-empty]:before:h-0 [&_.is-editor-empty]:before:text-muted-foreground [&_.is-editor-empty]:before:content-[attr(data-placeholder)]",
892
- )}
893
- />
894
- );
895
-
896
- // The canvas toolbar never scrolls sideways: the rarer blocks and the HTML view sit in the menu.
897
- const menuBlocks =
898
- canvas && ActionOverflowMenu !== undefined
899
- ? blocks.filter((block) => !CANVAS_INLINE_BLOCKS.includes(block))
900
- : [];
901
- const inlineBlocks = blocks.filter((block) => !menuBlocks.includes(block));
902
- const overflowItems: readonly ActionMenuItemSpec[] = [
903
- ...menuBlocks.map((block) => ({
904
- id: block,
905
- label: blockButtons[block].label,
906
- disabled: readOnly,
907
- onSelect: () => runBlock(block),
908
- })),
909
- ...(canvas && !hosted
910
- ? [
911
- {
912
- id: "html",
913
- label: t("template-resolver.documentEditor.showHtml"),
914
- onSelect: () => setShowHtml((current) => !current),
915
- },
916
- ]
917
- : []),
918
- ];
919
-
920
- const toolbarBar = (
921
- <div
922
- className={cn(
923
- "flex items-center border-b border-input",
924
- canvas
925
- ? "min-h-9 flex-nowrap gap-0 overflow-hidden bg-background px-1.5 [&>*]:shrink-0 [&_button]:h-7 [&_button]:gap-1 [&_button]:px-1 [&_button]:text-xs"
926
- : "flex-wrap gap-1 p-1",
927
- )}
928
- >
929
- <div
930
- // kumiko-lint-ignore raw-classname the toolbar paragraph select sits flush in the editor toolbar; ComboboxInput has no flat trigger variant
931
- className={cn(
932
- !canvas && (compact ? "w-24" : "w-40"),
933
- canvas &&
934
- "w-auto shrink-0 [&_button[aria-haspopup=listbox]]:border-transparent [&_button[aria-haspopup=listbox]]:bg-transparent [&_button[aria-haspopup=listbox]]:shadow-none",
935
- )}
936
- >
937
- <Field id={`${id}-paragraph-style`} label={paragraphStyleLabel} hideLabel>
938
- <Input
939
- kind="select"
940
- id={`${id}-paragraph-style`}
941
- name="paragraph-style"
942
- value={activeState?.blockStyle ?? "paragraph"}
943
- onChange={setBlockStyle}
944
- options={styleOptions}
945
- display="dropdown"
946
- disabled={readOnly}
947
- />
948
- </Field>
949
- </div>
950
- <Separator compact={compact || canvas} />
951
- {iconButton(
952
- t("kumiko.contentEditor.bold"),
953
- markContent("B", <BoldIcon size={16} />, "font-bold"),
954
- activeState?.bold ?? false,
955
- () => editor.chain().focus().toggleBold().run(),
956
- )}
957
- {iconButton(
958
- t("kumiko.contentEditor.italic"),
959
- markContent("I", <ItalicIcon size={16} />, "italic"),
960
- activeState?.italic ?? false,
961
- () => editor.chain().focus().toggleItalic().run(),
962
- )}
963
- {iconButton(
964
- t("template-resolver.documentEditor.underline"),
965
- markContent("U", <UnderlineIcon size={16} />, "underline"),
966
- activeState?.underline ?? false,
967
- () => editor.chain().focus().toggleUnderline().run(),
968
- )}
969
- <Separator compact={compact || canvas} />
970
- {iconButton(
971
- t("kumiko.contentEditor.bulletList"),
972
- <List size={16} />,
973
- activeState?.bulletList ?? false,
974
- () => editor.chain().focus().toggleBulletList().run(),
975
- canvas ? undefined : t("template-resolver.documentEditor.list"),
976
- )}
977
- {iconButton(
978
- t("kumiko.contentEditor.orderedList"),
979
- <ListOrdered size={16} />,
980
- activeState?.orderedList ?? false,
981
- () => editor.chain().focus().toggleOrderedList().run(),
982
- )}
983
- {iconButton(
984
- t("template-resolver.documentEditor.link"),
985
- <LinkIcon size={16} />,
986
- activeState?.link ?? false,
987
- () => togglePanel("link"),
988
- )}
989
- <Separator compact={compact || canvas} />
990
- {labelledButton({
991
- label: t("template-resolver.documentEditor.insertPlaceholder"),
992
- icon: <Braces size={16} />,
993
- showIcon: true,
994
- trailingIcon: canvas ? <ChevronDown size={12} /> : undefined,
995
- expanded: openPanel === "placeholder",
996
- onClick: () => togglePanel("placeholder"),
997
- })}
998
- {inlineBlocks.length > 0 && <Separator compact={compact || canvas} />}
999
- {inlineBlocks.map((block) =>
1000
- canvas ? (
1001
- <Fragment key={block}>
1002
- {iconButton(blockButtons[block].label, blockButtons[block].icon, false, () =>
1003
- runBlock(block),
1004
- )}
1005
- </Fragment>
1006
- ) : (
1007
- labelledButton({
1008
- key: block,
1009
- label: blockButtons[block].label,
1010
- icon: blockButtons[block].icon,
1011
- showIcon: true,
1012
- onClick: () => runBlock(block),
1013
- })
1014
- ),
1015
- )}
1016
- {overflowItems.length > 0 && ActionOverflowMenu !== undefined && (
1017
- <span className="ml-auto shrink-0">
1018
- <ActionOverflowMenu
1019
- items={overflowItems}
1020
- label={t("template-resolver.documentEditor.more")}
1021
- testId={`${id}-toolbar-more`}
1022
- />
1023
- </span>
1024
- )}
1025
- </div>
1026
- );
1027
-
1028
- const bubbleMenu = (
1029
- <BubbleMenu
1030
- key={mountKeyOf(editor)}
1031
- pluginKey={BUBBLE_MENU_PLUGIN_KEY}
1032
- editor={editor}
1033
- options={{
1034
- strategy: "fixed",
1035
- placement: "top-end",
1036
- offset: bubbleOffset(editor),
1037
- scrollTarget: canvasScroller ?? window,
1038
- onShow: () => requestAnimationFrame(() => repositionBubble(editor)),
1039
- }}
1040
- getReferencedVirtualElement={() => selectedBlockReference(editor)}
1041
- shouldShow={({ editor: e, element }) =>
1042
- // A leading image is selected by default; the bubble belongs to a deliberate selection.
1043
- (e.isFocused || element.contains(document.activeElement)) &&
1044
- (e.isActive("resource") || e.isActive("button") || findRepeatTable(e) !== null)
1045
- }
1046
- >
1047
- {selectedBlock !== null ? (
1048
- <div className={BUBBLE_BAR_CLASS} data-testid="document-block-options">
1049
- <span className="px-1.5 text-[10px] text-muted-foreground">
1050
- {selectedBlock.type === "resource"
1051
- ? t("template-resolver.documentEditor.resourceKey")
1052
- : t("template-resolver.documentEditor.buttonTarget")}
1053
- {": "}
1054
- {selectedBlock.type === "button" ? (
1055
- <strong>{buttonTargetLabel(selectedBlock.detail, variableLabels)}</strong>
1056
- ) : (
1057
- <code>{selectedBlock.detail}</code>
1058
- )}
1059
- </span>
1060
- <Button
1061
- type="button"
1062
- size="sm"
1063
- variant="danger-ghost"
1064
- onClick={() => {
1065
- editor.chain().focus().deleteNode(selectedBlock.type).run();
1066
- }}
1067
- >
1068
- {t("template-resolver.documentEditor.removeBlock")}
1069
- </Button>
1070
- </div>
1071
- ) : (
1072
- repeatTable !== null && (
1073
- <div data-testid="document-repeat-bar" className={BUBBLE_BAR_CLASS}>
1074
- <span className="px-1.5 text-[10px] uppercase tracking-wide text-muted-foreground">
1075
- {repeatBarLabel}
1076
- </span>
1077
- <Button
1078
- type="button"
1079
- variant="secondary"
1080
- size="sm"
1081
- pressed={repeatTable.headerRepeats}
1082
- onClick={() => toggleRepeatHeader(editor)}
1083
- >
1084
- {t("template-resolver.documentEditor.repeatHeader")}
1085
- </Button>
1086
- </div>
1087
- )
1088
- )}
1089
- </BubbleMenu>
1090
- );
1091
-
1092
- const insertPanel = openPanel !== null && !readOnly && (
1093
- <InsertPanel
1094
- key={openPanel}
1095
- editor={editor}
1096
- editorId={id}
1097
- kind={openPanel}
1098
- variables={openPanel === "images" ? imageVariables : variables}
1099
- variableLabels={stableLabels}
1100
- onClose={() => setOpenPanel(null)}
1101
- />
1102
- );
85
+ const context = buildTiptapContext({
86
+ id,
87
+ t,
88
+ Button,
89
+ Field,
90
+ Input,
91
+ ActionOverflowMenu,
92
+ editor,
93
+ readOnly,
94
+ canvas,
95
+ compact,
96
+ hosted,
97
+ labels: model.labels,
98
+ activeState: model.activeState,
99
+ openPanel: model.openPanel,
100
+ setOpenPanel: model.setOpenPanel,
101
+ canvasScroller: model.canvasScroller,
102
+ setCanvasScroller: model.setCanvasScroller,
103
+ showHtml: model.showHtml,
104
+ setShowHtml: model.setShowHtml,
105
+ blocks,
106
+ variables,
107
+ imageVariables,
108
+ variableLabels,
109
+ stableLabels: model.stableLabels,
110
+ });
111
+ const editorContent = renderEditorContent(editor, canvas, hosted);
1103
112
 
1104
- if (hosted) {
1105
- return (
1106
- <>
1107
- {toolbarHost !== null &&
1108
- createPortal(
1109
- <>
1110
- {toolbarBar}
1111
- {insertPanel}
1112
- </>,
1113
- toolbarHost,
1114
- )}
1115
- {!readOnly && bubbleMenu}
1116
- {editorContent}
1117
- </>
1118
- );
1119
- }
113
+ if (hosted) return renderHostedEditor(context, toolbarHost, editorContent);
1120
114
 
1121
- return (
1122
- <div
1123
- className={cn(
1124
- canvas ? "flex min-h-0 flex-1 flex-col" : "rounded-md border border-input bg-transparent",
1125
- )}
1126
- >
1127
- {!hosted && toolbarBar}
1128
- {canvas && !readOnly && bubbleMenu}
1129
- {canvas && !hosted && showHtml && (
1130
- <section aria-label={t("template-resolver.documentEditor.htmlSource")}>
1131
- <pre className="h-40 overflow-auto whitespace-pre-wrap break-all border-b border-input bg-muted p-2 font-mono text-xs">
1132
- {activeState?.html ?? ""}
1133
- </pre>
1134
- </section>
1135
- )}
1136
- {!hosted && insertPanel}
1137
- {repeatTable !== null && !readOnly && !canvas && (
1138
- <div
1139
- data-testid="document-repeat-bar"
1140
- className="flex flex-wrap items-center gap-3 border-b border-input bg-muted/40 px-2 py-1 text-xs"
1141
- >
1142
- <span className="font-medium">
1143
- {repeatListLabel === undefined
1144
- ? t("template-resolver.documentEditor.repeat")
1145
- : t("template-resolver.documentEditor.repeatBarList", { list: repeatListLabel })}
1146
- </span>
1147
- <Button
1148
- type="button"
1149
- variant="secondary"
1150
- size="sm"
1151
- pressed={repeatTable.headerRepeats}
1152
- onClick={() => toggleRepeatHeader(editor)}
1153
- >
1154
- {t("template-resolver.documentEditor.repeatHeader")}
1155
- </Button>
1156
- </div>
1157
- )}
1158
- <div
1159
- ref={setCanvasScroller}
1160
- className={cn(canvas && "min-h-0 flex-1 overflow-auto bg-muted px-8 py-6")}
1161
- >
1162
- {canvas && renderPaper !== undefined ? renderPaper(editorContent) : editorContent}
1163
- </div>
1164
- </div>
115
+ return renderInlineEditor(
116
+ context,
117
+ canvas && renderPaper !== undefined ? renderPaper(editorContent) : editorContent,
1165
118
  );
1166
119
  }