@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.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +27 -15
- package/packages/bundled-features/src/audit/feature.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
- package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
- package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
- package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
- package/packages/bundled-features/src/config/feature.ts +6 -6
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
- package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
- package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
- package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/index.ts +4 -0
- package/packages/bundled-features/src/data-retention/feature.ts +1 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
- package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
- package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +2 -2
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
- package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
- package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
- package/packages/bundled-features/src/form-draft/feature.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
- package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
- package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
- package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
- package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
- package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
- package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
- package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +12 -0
- package/packages/bundled-features/src/sessions/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/screens.ts +8 -4
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
- package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
- package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
- package/packages/bundled-features/src/shared/index.ts +15 -0
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
- package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
- package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +18 -0
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
- package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
- package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
- package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
- package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
- package/packages/bundled-features/src/tenant/changes.json +19 -0
- package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
- package/packages/bundled-features/src/tenant/index.ts +5 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
- package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
- package/packages/bundled-features/src/tenant/seeding.ts +65 -62
- package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
- package/packages/bundled-features/src/tenant/testing.ts +77 -0
- package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
- package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
- package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
- package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
- package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
- package/packages/bundled-features/src/user/index.ts +2 -0
- package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
- package/packages/bundled-features/src/user/principal-status.ts +1 -1
- package/packages/bundled-features/src/user/seeding.ts +24 -6
- package/packages/bundled-features/src/user/testing.ts +22 -0
- package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
- package/packages/bundled-features/src/user-data-rights/README.md +1 -1
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
- package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
- package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
- package/samples/apps/cap-billing-demo/package.json +1 -1
- package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
- package/samples/apps/config-demo/src/app/server.ts +1 -1
- package/samples/apps/styleguide/src/app/server.ts +2 -2
- package/samples/apps/ui-walkthrough/package.json +1 -1
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/use-all-bundled/package.json +1 -1
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/user-data-rights-demo/README.md +1 -1
- package/samples/apps/user-data-rights-demo/package.json +1 -1
- package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
- package/samples/recipes/api-worker-split/README.md +2 -1
- package/samples/recipes/api-worker-split/src/feature.ts +2 -1
- package/samples/recipes/cross-feature-events/README.md +5 -5
- package/samples/recipes/cross-feature-events/package.json +1 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
- package/samples/recipes/event-sourcing/README.md +5 -5
- package/samples/recipes/event-sourcing/package.json +1 -1
- package/samples/recipes/event-sourcing/src/feature.ts +15 -15
- package/samples/recipes/files-post-processing/README.md +1 -1
- package/samples/recipes/files-post-processing/package.json +1 -1
- package/samples/recipes/files-post-processing/src/feature.ts +6 -4
- package/samples/recipes/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
- package/samples/recipes/managed-config/README.md +3 -3
- package/samples/recipes/search/README.md +1 -1
- package/samples/recipes/search/src/feature.ts +3 -3
- package/samples/recipes/secrets-demo/README.md +1 -1
- package/samples/recipes/secrets-demo/src/feature.ts +5 -2
- package/samples/recipes/state-machine/src/events.ts +1 -1
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import type {
|
|
3
|
+
BeforeTransactionContext,
|
|
3
4
|
BeforeTransactionResult,
|
|
4
5
|
ConfigAccessor,
|
|
5
6
|
HandlerContext,
|
|
@@ -100,7 +101,7 @@ function isAttachmentKey(links: Readonly<Record<string, LinkedResourceEntry>>, k
|
|
|
100
101
|
// so both receive the target tenant's db and identity.
|
|
101
102
|
function uploadToTenant(
|
|
102
103
|
args: {
|
|
103
|
-
readonly ctx:
|
|
104
|
+
readonly ctx: PrepareContext;
|
|
104
105
|
readonly user: SessionUser;
|
|
105
106
|
readonly tenantId: TenantId;
|
|
106
107
|
},
|
|
@@ -110,7 +111,7 @@ function uploadToTenant(
|
|
|
110
111
|
return uploadFileBytesBeforeTransaction(
|
|
111
112
|
{
|
|
112
113
|
db: requireForTenant(ctx, tenantId).db,
|
|
113
|
-
files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId),
|
|
114
|
+
services: { files: fileContextForTenant(ctx, ctx.user.tenantId, tenantId) },
|
|
114
115
|
registry: ctx.registry,
|
|
115
116
|
user: { ...user, tenantId },
|
|
116
117
|
},
|
|
@@ -119,7 +120,7 @@ function uploadToTenant(
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
type TenantScope = {
|
|
122
|
-
readonly ctx:
|
|
123
|
+
readonly ctx: PrepareContext;
|
|
123
124
|
readonly user: SessionUser;
|
|
124
125
|
readonly tenantId: TenantId;
|
|
125
126
|
};
|
|
@@ -133,7 +134,7 @@ function createFileRefFromPrepared(scope: TenantScope, upload: UploadedFileBytes
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
async function linkImage(args: {
|
|
136
|
-
readonly ctx:
|
|
137
|
+
readonly ctx: PrepareContext;
|
|
137
138
|
readonly user: SessionUser;
|
|
138
139
|
readonly tenantId: TenantId;
|
|
139
140
|
readonly row: TemplateResourceRow;
|
|
@@ -197,7 +198,7 @@ type ImageTarget = {
|
|
|
197
198
|
|
|
198
199
|
// Runs before the upload (reject early, store nothing) and again in the transaction on the fresh row.
|
|
199
200
|
async function checkImageTarget(args: {
|
|
200
|
-
readonly ctx:
|
|
201
|
+
readonly ctx: PrepareContext;
|
|
201
202
|
readonly tenantId: TenantId;
|
|
202
203
|
readonly templateId?: string | undefined;
|
|
203
204
|
readonly key?: string | undefined;
|
|
@@ -220,7 +221,7 @@ async function checkImageTarget(args: {
|
|
|
220
221
|
// beforeTransaction half: normalizes the image and uploads it to the target tenant's storage.
|
|
221
222
|
// The caller is responsible for authorization; the function trusts tenantId.
|
|
222
223
|
export async function prepareTemplateResourceBeforeTransaction(args: {
|
|
223
|
-
readonly ctx:
|
|
224
|
+
readonly ctx: PrepareContext;
|
|
224
225
|
readonly user: SessionUser;
|
|
225
226
|
readonly tenantId: TenantId;
|
|
226
227
|
readonly bytes: Uint8Array;
|
|
@@ -260,6 +261,10 @@ export async function prepareTemplateResourceBeforeTransaction(args: {
|
|
|
260
261
|
};
|
|
261
262
|
}
|
|
262
263
|
|
|
264
|
+
// The prepare phase runs before the transaction, so it must not rely on anything the in-transaction
|
|
265
|
+
// context adds (events, dispatch.write).
|
|
266
|
+
export type PrepareContext = HandlerContext | BeforeTransactionContext;
|
|
267
|
+
|
|
263
268
|
export type Release = () => Promise<void>;
|
|
264
269
|
|
|
265
270
|
// Tries every release, so one failing delete does not strand the other uploads; the first rejection is rethrown.
|
|
@@ -271,13 +276,13 @@ export async function releaseAll(releases: readonly Release[]): Promise<void> {
|
|
|
271
276
|
|
|
272
277
|
// Failure paths: a failing release must not replace the upload error the caller needs to see.
|
|
273
278
|
export async function releaseAfterFailure(
|
|
274
|
-
ctx:
|
|
279
|
+
ctx: PrepareContext,
|
|
275
280
|
releases: readonly Release[],
|
|
276
281
|
): Promise<void> {
|
|
277
282
|
try {
|
|
278
283
|
await releaseAll(releases);
|
|
279
284
|
} catch (releaseError) {
|
|
280
|
-
ctx.log?.warn("template upload rollback: releasing stored files failed", {
|
|
285
|
+
ctx.obs.log?.warn("template upload rollback: releasing stored files failed", {
|
|
281
286
|
error: releaseError instanceof Error ? releaseError.message : String(releaseError),
|
|
282
287
|
});
|
|
283
288
|
}
|
|
@@ -308,7 +313,7 @@ function sourceUnreadable(): WriteResult<never> {
|
|
|
308
313
|
// Undefined: the fileRef row exists but its bytes cannot be read, which must not look like a
|
|
309
314
|
// deleted file. The provider is chosen by the tenant of the fileRef, not by the caller's.
|
|
310
315
|
async function readCopySource(
|
|
311
|
-
ctx:
|
|
316
|
+
ctx: PrepareContext,
|
|
312
317
|
fileRefId: string,
|
|
313
318
|
): Promise<{ contentType: string; bytes: Uint8Array } | null | undefined> {
|
|
314
319
|
try {
|
|
@@ -320,7 +325,7 @@ async function readCopySource(
|
|
|
320
325
|
});
|
|
321
326
|
return found === null ? null : { contentType: found.contentType, bytes: found.content };
|
|
322
327
|
} catch (cause) {
|
|
323
|
-
ctx.log?.warn("template copy: source file could not be read", {
|
|
328
|
+
ctx.obs.log?.warn("template copy: source file could not be read", {
|
|
324
329
|
fileRefId,
|
|
325
330
|
errorClass: cause instanceof Error ? cause.name : typeof cause,
|
|
326
331
|
});
|
|
@@ -333,7 +338,7 @@ async function readCopySource(
|
|
|
333
338
|
// attachments. An entry whose source file is gone is left out: it could not be resolved at render
|
|
334
339
|
// time either. A failed or throwing upload releases the ones before it.
|
|
335
340
|
export async function prepareLinkedResourceCopiesBeforeTransaction(args: {
|
|
336
|
-
readonly ctx:
|
|
341
|
+
readonly ctx: PrepareContext;
|
|
337
342
|
readonly user: SessionUser;
|
|
338
343
|
readonly tenantId: TenantId;
|
|
339
344
|
readonly resources: Readonly<Record<string, LinkedResourceEntry>>;
|
|
@@ -525,7 +530,7 @@ type AttachmentTarget = {
|
|
|
525
530
|
|
|
526
531
|
// Runs before the upload (reject early, store nothing) and again in the transaction on the fresh row.
|
|
527
532
|
async function checkAttachmentTarget(args: {
|
|
528
|
-
readonly ctx:
|
|
533
|
+
readonly ctx: PrepareContext;
|
|
529
534
|
readonly tenantId: TenantId;
|
|
530
535
|
readonly templateId: string;
|
|
531
536
|
readonly key: string;
|
|
@@ -550,7 +555,7 @@ async function checkAttachmentTarget(args: {
|
|
|
550
555
|
// beforeTransaction half: validates a PDF/PNG/JPEG and uploads it unchanged to the target tenant's
|
|
551
556
|
// storage. With templateId the template is checked first; a bundle import has none yet.
|
|
552
557
|
export async function prepareTemplateAttachmentBeforeTransaction(args: {
|
|
553
|
-
readonly ctx:
|
|
558
|
+
readonly ctx: PrepareContext;
|
|
554
559
|
readonly user: SessionUser;
|
|
555
560
|
readonly tenantId: TenantId;
|
|
556
561
|
readonly bytes: Uint8Array;
|
|
@@ -27,16 +27,16 @@ type FileRefRow = {
|
|
|
27
27
|
// Reads the file behind a template resource. Null when the fileRef is unknown
|
|
28
28
|
// to the caller's tenant or has no bytes.
|
|
29
29
|
export async function readTemplateResource(
|
|
30
|
-
ctx: Pick<HandlerContext, "db" | "
|
|
30
|
+
ctx: Pick<HandlerContext, "db" | "services" | "user">,
|
|
31
31
|
fileRefId: string,
|
|
32
32
|
): Promise<TemplateResourceFile | null> {
|
|
33
|
-
if (!ctx.files) return null;
|
|
33
|
+
if (!ctx.services.files) return null;
|
|
34
34
|
const row = await fetchOne<FileRefRow>(ctx.db, fileRefsTable, {
|
|
35
35
|
id: fileRefId,
|
|
36
36
|
isDeleted: false,
|
|
37
37
|
});
|
|
38
38
|
if (!row) return null;
|
|
39
|
-
const handle = ctx.files.refFromFileRef(row);
|
|
39
|
+
const handle = ctx.services.files.refFromFileRef(row);
|
|
40
40
|
if (!(await handle.exists())) return null;
|
|
41
41
|
return { fileName: row.fileName, contentType: row.mimeType, bytes: await handle.read() };
|
|
42
42
|
}
|
|
@@ -44,7 +44,7 @@ export async function readTemplateResource(
|
|
|
44
44
|
// Replaces the whole key map of one row with files that are already stored (bundle import,
|
|
45
45
|
// copy-locale). Single keys go through storePreparedTemplateResource and resource-remove.
|
|
46
46
|
export async function attachTemplateResources(
|
|
47
|
-
ctx: Pick<HandlerContext, "
|
|
47
|
+
ctx: Pick<HandlerContext, "tenants">,
|
|
48
48
|
args: {
|
|
49
49
|
readonly user: SessionUser;
|
|
50
50
|
readonly tenantId: TenantId;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// No bun:test here — callers register cases with their own test runner.
|
|
4
4
|
|
|
5
5
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
6
|
-
import {
|
|
6
|
+
import { seedRowViaEventStore } from "@cosmicdrift/kumiko-testing/helpers";
|
|
7
7
|
import type { ResolveRequest, TemplateResource } from "./api.ts";
|
|
8
8
|
import { TemplateNotFoundError } from "./api.ts";
|
|
9
9
|
import { templateResourcesTable } from "./table.ts";
|
|
@@ -50,7 +50,7 @@ type SeedTemplateArgs = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
async function seedTemplate(db: DbConnection, args: SeedTemplateArgs): Promise<void> {
|
|
53
|
-
await
|
|
53
|
+
await seedRowViaEventStore(db, templateResourcesTable, {
|
|
54
54
|
tenantId: args.tenantId,
|
|
55
55
|
slug: args.slug,
|
|
56
56
|
kind: args.kind,
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { usePrimitives, VariableChips } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import { AlignCenter, AlignJustify, AlignLeft, AlignRight } from "lucide-react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { insertAndCloseOf, type PanelModel } from "./document-insert-model.ts";
|
|
5
|
+
import { DOCUMENT_TEXT_ALIGNMENTS, type DocumentTextAlignment } from "./document-schema.ts";
|
|
6
|
+
import { type PanelKind, variableLabelOf } from "./document-tiptap-helpers.ts";
|
|
7
|
+
|
|
8
|
+
const ALIGNMENT_ICONS: Record<DocumentTextAlignment, ReactNode> = {
|
|
9
|
+
left: <AlignLeft size={16} />,
|
|
10
|
+
center: <AlignCenter size={16} />,
|
|
11
|
+
right: <AlignRight size={16} />,
|
|
12
|
+
justify: <AlignJustify size={16} />,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ALIGNMENT_LABEL_KEYS: Record<DocumentTextAlignment, string> = {
|
|
16
|
+
left: "template-resolver.documentEditor.alignLeft",
|
|
17
|
+
center: "template-resolver.documentEditor.alignCenter",
|
|
18
|
+
right: "template-resolver.documentEditor.alignRight",
|
|
19
|
+
justify: "template-resolver.documentEditor.alignJustify",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type OptionSelectProps = {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly names: readonly string[];
|
|
26
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
27
|
+
readonly value: string;
|
|
28
|
+
readonly onChange: (name: string) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function NameSelect({ id, label, names, labels, value, onChange }: OptionSelectProps): ReactNode {
|
|
32
|
+
const { Field, Input } = usePrimitives();
|
|
33
|
+
return (
|
|
34
|
+
<Field id={id} label={label}>
|
|
35
|
+
<Input
|
|
36
|
+
kind="select"
|
|
37
|
+
id={id}
|
|
38
|
+
name={id}
|
|
39
|
+
value={value}
|
|
40
|
+
onChange={onChange}
|
|
41
|
+
display="dropdown"
|
|
42
|
+
options={names.map((name) => ({ value: name, label: variableLabelOf(name, labels) }))}
|
|
43
|
+
/>
|
|
44
|
+
</Field>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderLinkFields(m: PanelModel): ReactNode {
|
|
49
|
+
const { t, Field, Input } = m;
|
|
50
|
+
return (
|
|
51
|
+
<Field id={`${m.idPrefix}-href`} label={t("template-resolver.documentEditor.linkHref")}>
|
|
52
|
+
<Input
|
|
53
|
+
kind="text"
|
|
54
|
+
id={`${m.idPrefix}-href`}
|
|
55
|
+
name="href"
|
|
56
|
+
value={m.href}
|
|
57
|
+
onChange={m.setHref}
|
|
58
|
+
placeholder={t("template-resolver.documentEditor.linkHrefPlaceholder")}
|
|
59
|
+
/>
|
|
60
|
+
</Field>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderPlaceholderFields(m: PanelModel): ReactNode {
|
|
65
|
+
return (
|
|
66
|
+
!m.noVariables && (
|
|
67
|
+
<div className="flex flex-wrap gap-1">
|
|
68
|
+
<VariableChips
|
|
69
|
+
variables={m.selectable}
|
|
70
|
+
labels={m.variableLabels}
|
|
71
|
+
onInsert={(name) => insertAndCloseOf(m)({ type: "variable", attrs: { name } })}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderAlignFields(m: PanelModel): ReactNode {
|
|
79
|
+
const { t, Button, editor } = m;
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex flex-wrap gap-1">
|
|
82
|
+
{DOCUMENT_TEXT_ALIGNMENTS.map((alignment) => (
|
|
83
|
+
<Button
|
|
84
|
+
key={alignment}
|
|
85
|
+
type="button"
|
|
86
|
+
variant="secondary"
|
|
87
|
+
size="sm"
|
|
88
|
+
onClick={() => {
|
|
89
|
+
editor
|
|
90
|
+
.chain()
|
|
91
|
+
.focus()
|
|
92
|
+
.updateAttributes("paragraph", { textAlign: alignment })
|
|
93
|
+
.updateAttributes("heading", { textAlign: alignment })
|
|
94
|
+
.run();
|
|
95
|
+
m.onClose();
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
{ALIGNMENT_ICONS[alignment]}
|
|
99
|
+
{t(ALIGNMENT_LABEL_KEYS[alignment])}
|
|
100
|
+
</Button>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderImageFields(m: PanelModel): ReactNode {
|
|
107
|
+
const { t, Field, Input, idPrefix } = m;
|
|
108
|
+
return (
|
|
109
|
+
<>
|
|
110
|
+
<Field id={`${idPrefix}-resource`} label={t("template-resolver.documentEditor.resourceKey")}>
|
|
111
|
+
<Input
|
|
112
|
+
kind="text"
|
|
113
|
+
id={`${idPrefix}-resource`}
|
|
114
|
+
name="resource"
|
|
115
|
+
value={m.resource}
|
|
116
|
+
onChange={m.setResource}
|
|
117
|
+
/>
|
|
118
|
+
</Field>
|
|
119
|
+
<Field id={`${idPrefix}-alt`} label={t("template-resolver.documentEditor.imageAlt")}>
|
|
120
|
+
<Input kind="text" id={`${idPrefix}-alt`} name="alt" value={m.alt} onChange={m.setAlt} />
|
|
121
|
+
</Field>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderButtonFields(m: PanelModel): ReactNode {
|
|
127
|
+
const { t, Field, Input, idPrefix } = m;
|
|
128
|
+
return (
|
|
129
|
+
!m.noVariables && (
|
|
130
|
+
<>
|
|
131
|
+
<NameSelect
|
|
132
|
+
id={`${idPrefix}-target`}
|
|
133
|
+
label={t("template-resolver.documentEditor.buttonTarget")}
|
|
134
|
+
names={m.selectable}
|
|
135
|
+
labels={m.variableLabels}
|
|
136
|
+
value={m.selectedName}
|
|
137
|
+
onChange={m.setChosenName}
|
|
138
|
+
/>
|
|
139
|
+
<Field id={`${idPrefix}-text`} label={t("template-resolver.documentEditor.buttonText")}>
|
|
140
|
+
<Input
|
|
141
|
+
kind="text"
|
|
142
|
+
id={`${idPrefix}-text`}
|
|
143
|
+
name="text"
|
|
144
|
+
value={m.buttonText}
|
|
145
|
+
onChange={m.setButtonText}
|
|
146
|
+
/>
|
|
147
|
+
</Field>
|
|
148
|
+
</>
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderRepeatFields(m: PanelModel): ReactNode {
|
|
154
|
+
return (
|
|
155
|
+
!m.noVariables && (
|
|
156
|
+
<NameSelect
|
|
157
|
+
id={`${m.idPrefix}-list`}
|
|
158
|
+
label={m.t("template-resolver.documentEditor.repeatList")}
|
|
159
|
+
names={m.selectable}
|
|
160
|
+
labels={m.variableLabels}
|
|
161
|
+
value={m.selectedName}
|
|
162
|
+
onChange={m.setChosenName}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function renderImagesFields(m: PanelModel): ReactNode {
|
|
169
|
+
return (
|
|
170
|
+
!m.noVariables && (
|
|
171
|
+
<NameSelect
|
|
172
|
+
id={`${m.idPrefix}-images-variable`}
|
|
173
|
+
label={m.t("template-resolver.documentEditor.imagesVariable")}
|
|
174
|
+
names={m.selectable}
|
|
175
|
+
labels={m.variableLabels}
|
|
176
|
+
value={m.selectedName}
|
|
177
|
+
onChange={m.setChosenName}
|
|
178
|
+
/>
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderStepsFields(m: PanelModel): ReactNode {
|
|
184
|
+
const { t, Field, Input, idPrefix } = m;
|
|
185
|
+
return (
|
|
186
|
+
!m.noVariables && (
|
|
187
|
+
<>
|
|
188
|
+
<NameSelect
|
|
189
|
+
id={`${idPrefix}-steps-variable`}
|
|
190
|
+
label={t("template-resolver.documentEditor.stepsVariable")}
|
|
191
|
+
names={m.selectable}
|
|
192
|
+
labels={m.variableLabels}
|
|
193
|
+
value={m.selectedName}
|
|
194
|
+
onChange={m.setChosenName}
|
|
195
|
+
/>
|
|
196
|
+
<Field
|
|
197
|
+
id={`${idPrefix}-steps-list`}
|
|
198
|
+
label={t("template-resolver.documentEditor.stepsList")}
|
|
199
|
+
>
|
|
200
|
+
<Input
|
|
201
|
+
kind="text"
|
|
202
|
+
id={`${idPrefix}-steps-list`}
|
|
203
|
+
name="steps"
|
|
204
|
+
value={m.stepsText}
|
|
205
|
+
onChange={m.setStepsText}
|
|
206
|
+
/>
|
|
207
|
+
</Field>
|
|
208
|
+
</>
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const PANEL_FIELDS: Readonly<Record<PanelKind, (m: PanelModel) => ReactNode>> = {
|
|
214
|
+
link: renderLinkFields,
|
|
215
|
+
placeholder: renderPlaceholderFields,
|
|
216
|
+
align: renderAlignFields,
|
|
217
|
+
image: renderImageFields,
|
|
218
|
+
button: renderButtonFields,
|
|
219
|
+
repeat: renderRepeatFields,
|
|
220
|
+
images: renderImagesFields,
|
|
221
|
+
steps: renderStepsFields,
|
|
222
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import type { Editor, JSONContent } from "@tiptap/core";
|
|
3
|
+
import {
|
|
4
|
+
isAllowedDocumentLinkHref,
|
|
5
|
+
isDocumentPlaceholderHref,
|
|
6
|
+
isDocumentReferenceName,
|
|
7
|
+
isDocumentResourceKey,
|
|
8
|
+
} from "./document-schema.ts";
|
|
9
|
+
import { parseDocumentStepsInput } from "./document-steps.ts";
|
|
10
|
+
import { type PanelKind, repeatTableContent } from "./document-tiptap-helpers.ts";
|
|
11
|
+
|
|
12
|
+
type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
|
|
13
|
+
type Primitives = ReturnType<typeof usePrimitives>;
|
|
14
|
+
|
|
15
|
+
export type PanelModel = {
|
|
16
|
+
readonly t: Translate;
|
|
17
|
+
readonly Button: Primitives["Button"];
|
|
18
|
+
readonly Field: Primitives["Field"];
|
|
19
|
+
readonly Input: Primitives["Input"];
|
|
20
|
+
readonly editor: Editor;
|
|
21
|
+
readonly kind: PanelKind;
|
|
22
|
+
readonly idPrefix: string;
|
|
23
|
+
readonly variableLabels: Readonly<Record<string, string>>;
|
|
24
|
+
readonly onClose: () => void;
|
|
25
|
+
readonly href: string;
|
|
26
|
+
readonly setHref: (value: string) => void;
|
|
27
|
+
readonly resource: string;
|
|
28
|
+
readonly setResource: (value: string) => void;
|
|
29
|
+
readonly alt: string;
|
|
30
|
+
readonly setAlt: (value: string) => void;
|
|
31
|
+
readonly buttonText: string;
|
|
32
|
+
readonly setButtonText: (value: string) => void;
|
|
33
|
+
readonly stepsText: string;
|
|
34
|
+
readonly setStepsText: (value: string) => void;
|
|
35
|
+
readonly setChosenName: (value: string) => void;
|
|
36
|
+
readonly setError: (value: string | null) => void;
|
|
37
|
+
readonly selectable: readonly string[];
|
|
38
|
+
readonly selectedName: string;
|
|
39
|
+
readonly noVariables: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ChosenNames = {
|
|
43
|
+
readonly selectable: readonly string[];
|
|
44
|
+
readonly selectedName: string;
|
|
45
|
+
readonly noVariables: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function chosenNamesOf(
|
|
49
|
+
kind: PanelKind,
|
|
50
|
+
variables: readonly string[],
|
|
51
|
+
chosenName: string,
|
|
52
|
+
): ChosenNames {
|
|
53
|
+
const referenceNames = variables.filter(isDocumentReferenceName);
|
|
54
|
+
const buttonTargets = referenceNames.filter((name) => isDocumentPlaceholderHref(`{{${name}}}`));
|
|
55
|
+
const selectable = kind === "button" ? buttonTargets : referenceNames;
|
|
56
|
+
const selectedName = selectable.includes(chosenName) ? chosenName : (selectable[0] ?? "");
|
|
57
|
+
const needsVariables =
|
|
58
|
+
kind === "button" ||
|
|
59
|
+
kind === "repeat" ||
|
|
60
|
+
kind === "placeholder" ||
|
|
61
|
+
kind === "steps" ||
|
|
62
|
+
kind === "images";
|
|
63
|
+
return { selectable, selectedName, noVariables: needsVariables && selectable.length === 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function insertAndCloseOf(m: PanelModel): (content: JSONContent) => void {
|
|
67
|
+
return (content) => {
|
|
68
|
+
m.editor.chain().focus().insertContent(content).run();
|
|
69
|
+
m.onClose();
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function submitLinkOf(m: PanelModel): () => void {
|
|
74
|
+
const { editor, href, t } = m;
|
|
75
|
+
return () => {
|
|
76
|
+
if (!isAllowedDocumentLinkHref(href)) {
|
|
77
|
+
m.setError(t("template-resolver.documentEditor.invalidLink"));
|
|
78
|
+
// skip: invalid input is reported through the panel error
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const chain = editor.chain().focus();
|
|
82
|
+
if (!editor.state.selection.empty) chain.setLink({ href }).run();
|
|
83
|
+
else if (editor.isActive("link")) chain.extendMarkRange("link").setLink({ href }).run();
|
|
84
|
+
else if (isDocumentPlaceholderHref(href))
|
|
85
|
+
// Placeholders are never inserted as {{name}} text; the link shows the variable's value.
|
|
86
|
+
chain
|
|
87
|
+
.insertContent({
|
|
88
|
+
type: "variable",
|
|
89
|
+
attrs: { name: href.slice(2, -2) },
|
|
90
|
+
marks: [{ type: "link", attrs: { href } }],
|
|
91
|
+
})
|
|
92
|
+
.run();
|
|
93
|
+
else
|
|
94
|
+
chain
|
|
95
|
+
.insertContent({ type: "text", text: href, marks: [{ type: "link", attrs: { href } }] })
|
|
96
|
+
.run();
|
|
97
|
+
m.onClose();
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function submitImageOf(m: PanelModel): () => void {
|
|
102
|
+
const { resource, alt, t } = m;
|
|
103
|
+
return () => {
|
|
104
|
+
if (!isDocumentResourceKey(resource)) {
|
|
105
|
+
m.setError(t("template-resolver.documentEditor.invalidResourceKey"));
|
|
106
|
+
// skip: invalid input is reported through the panel error
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (/[<>]/.test(alt)) {
|
|
110
|
+
m.setError(t("template-resolver.documentEditor.invalidImageAlt"));
|
|
111
|
+
// skip: invalid input is reported through the panel error
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
insertAndCloseOf(m)({ type: "resource", attrs: { resource, alt: alt || null } });
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function submitStepsOf(m: PanelModel): () => void {
|
|
119
|
+
return () => {
|
|
120
|
+
const steps = parseDocumentStepsInput(m.stepsText);
|
|
121
|
+
if (steps === null) {
|
|
122
|
+
m.setError(m.t("template-resolver.documentEditor.invalidSteps"));
|
|
123
|
+
// skip: invalid input is reported through the panel error
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
insertAndCloseOf(m)({ type: "steps", attrs: { variable: m.selectedName, steps } });
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Variable-bound kinds offer no insert while there is nothing to bind to.
|
|
131
|
+
const INSERT_HANDLERS: Partial<Record<PanelKind, (m: PanelModel) => () => void>> = {
|
|
132
|
+
link: submitLinkOf,
|
|
133
|
+
image: submitImageOf,
|
|
134
|
+
button: (m) => () =>
|
|
135
|
+
insertAndCloseOf(m)({
|
|
136
|
+
type: "button",
|
|
137
|
+
attrs: { href: `{{${m.selectedName}}}` },
|
|
138
|
+
content: m.buttonText ? [{ type: "text", text: m.buttonText }] : [],
|
|
139
|
+
}),
|
|
140
|
+
repeat: (m) => () => insertAndCloseOf(m)(repeatTableContent(m.selectedName)),
|
|
141
|
+
steps: submitStepsOf,
|
|
142
|
+
images: (m) => () => insertAndCloseOf(m)({ type: "images", attrs: { variable: m.selectedName } }),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const KINDS_WITHOUT_VARIABLE_GUARD: readonly PanelKind[] = ["link", "image"];
|
|
146
|
+
|
|
147
|
+
export function panelActionProps(m: PanelModel): {
|
|
148
|
+
onInsert?: () => void;
|
|
149
|
+
onRemoveLink?: () => void;
|
|
150
|
+
} {
|
|
151
|
+
const { editor, kind } = m;
|
|
152
|
+
const build = INSERT_HANDLERS[kind];
|
|
153
|
+
const insertAllowed = KINDS_WITHOUT_VARIABLE_GUARD.includes(kind) || !m.noVariables;
|
|
154
|
+
return {
|
|
155
|
+
...(build !== undefined && insertAllowed && { onInsert: build(m) }),
|
|
156
|
+
...(kind === "link" &&
|
|
157
|
+
editor.isActive("link") && {
|
|
158
|
+
onRemoveLink: () => {
|
|
159
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
160
|
+
m.onClose();
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import type { Editor } from "@tiptap/core";
|
|
3
|
+
import { type ReactNode, useState } from "react";
|
|
4
|
+
import { PANEL_FIELDS } from "./document-insert-fields.tsx";
|
|
5
|
+
import { chosenNamesOf, type PanelModel, panelActionProps } from "./document-insert-model.ts";
|
|
6
|
+
import type { PanelKind } from "./document-tiptap-helpers.ts";
|
|
7
|
+
|
|
8
|
+
function PanelActions({
|
|
9
|
+
onInsert,
|
|
10
|
+
onRemoveLink,
|
|
11
|
+
onCancel,
|
|
12
|
+
}: {
|
|
13
|
+
readonly onInsert?: () => void;
|
|
14
|
+
readonly onRemoveLink?: () => void;
|
|
15
|
+
readonly onCancel: () => void;
|
|
16
|
+
}): ReactNode {
|
|
17
|
+
const { Button } = usePrimitives();
|
|
18
|
+
const t = useTranslation();
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex gap-2">
|
|
21
|
+
{onInsert && (
|
|
22
|
+
<Button type="button" size="sm" onClick={onInsert}>
|
|
23
|
+
{t("template-resolver.documentEditor.insert")}
|
|
24
|
+
</Button>
|
|
25
|
+
)}
|
|
26
|
+
{onRemoveLink && (
|
|
27
|
+
<Button type="button" size="sm" variant="danger-ghost" onClick={onRemoveLink}>
|
|
28
|
+
{t("template-resolver.documentEditor.removeLink")}
|
|
29
|
+
</Button>
|
|
30
|
+
)}
|
|
31
|
+
<Button type="button" size="sm" variant="secondary" onClick={onCancel}>
|
|
32
|
+
{t("template-resolver.documentEditor.cancel")}
|
|
33
|
+
</Button>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type PanelProps = {
|
|
39
|
+
readonly editor: Editor;
|
|
40
|
+
readonly editorId: string;
|
|
41
|
+
readonly kind: PanelKind;
|
|
42
|
+
readonly variables: readonly string[];
|
|
43
|
+
readonly variableLabels: Readonly<Record<string, string>>;
|
|
44
|
+
readonly onClose: () => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function InsertPanel({
|
|
48
|
+
editor,
|
|
49
|
+
editorId,
|
|
50
|
+
kind,
|
|
51
|
+
variables,
|
|
52
|
+
variableLabels,
|
|
53
|
+
onClose,
|
|
54
|
+
}: PanelProps): ReactNode {
|
|
55
|
+
const { Button, Field, Input } = usePrimitives();
|
|
56
|
+
const t = useTranslation();
|
|
57
|
+
const [href, setHref] = useState(() => {
|
|
58
|
+
const current = editor.getAttributes("link")["href"];
|
|
59
|
+
return typeof current === "string" ? current : "";
|
|
60
|
+
});
|
|
61
|
+
const [resource, setResource] = useState("");
|
|
62
|
+
const [alt, setAlt] = useState("");
|
|
63
|
+
const [buttonText, setButtonText] = useState(() =>
|
|
64
|
+
t("template-resolver.documentEditor.buttonDefaultText"),
|
|
65
|
+
);
|
|
66
|
+
const [stepsText, setStepsText] = useState(() =>
|
|
67
|
+
t("template-resolver.documentEditor.stepsDefault"),
|
|
68
|
+
);
|
|
69
|
+
const [chosenName, setChosenName] = useState("");
|
|
70
|
+
const [error, setError] = useState<string | null>(null);
|
|
71
|
+
|
|
72
|
+
const model: PanelModel = {
|
|
73
|
+
t,
|
|
74
|
+
Button,
|
|
75
|
+
Field,
|
|
76
|
+
Input,
|
|
77
|
+
editor,
|
|
78
|
+
kind,
|
|
79
|
+
idPrefix: `${editorId}-panel`,
|
|
80
|
+
variableLabels,
|
|
81
|
+
onClose,
|
|
82
|
+
href,
|
|
83
|
+
setHref,
|
|
84
|
+
resource,
|
|
85
|
+
setResource,
|
|
86
|
+
alt,
|
|
87
|
+
setAlt,
|
|
88
|
+
buttonText,
|
|
89
|
+
setButtonText,
|
|
90
|
+
stepsText,
|
|
91
|
+
setStepsText,
|
|
92
|
+
setChosenName,
|
|
93
|
+
setError,
|
|
94
|
+
...chosenNamesOf(kind, variables, chosenName),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className="flex flex-col gap-2 border-b border-input bg-muted/40 p-2">
|
|
99
|
+
{model.noVariables && (
|
|
100
|
+
<p className="text-sm text-muted-foreground">
|
|
101
|
+
{t("template-resolver.documentEditor.noVariables")}
|
|
102
|
+
</p>
|
|
103
|
+
)}
|
|
104
|
+
{PANEL_FIELDS[kind](model)}
|
|
105
|
+
{error !== null && (
|
|
106
|
+
<p role="alert" className="text-sm text-destructive">
|
|
107
|
+
{error}
|
|
108
|
+
</p>
|
|
109
|
+
)}
|
|
110
|
+
<div className="flex flex-wrap gap-2">
|
|
111
|
+
<PanelActions onCancel={onClose} {...panelActionProps(model)} />
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|