@cosmicdrift/kumiko-bundled-features 0.363.0 → 0.365.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/feature.js +1 -1
- package/dist/audit/handlers/list.query.js +9 -5
- package/dist/audit/run-escape-hatch-retention.d.ts +1 -0
- package/dist/audit/run-escape-hatch-retention.js +39 -12
- package/dist/auth-email-password/bootstrap.d.ts +9 -8
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- package/dist/auth-email-password/feature.js +2 -0
- package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -3
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +4 -5
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
- package/dist/auth-email-password/magic-link-mail.js +6 -3
- package/dist/auth-email-password/mail-i18n.d.ts +3 -0
- package/dist/auth-email-password/mail-i18n.js +10 -0
- package/dist/auth-email-password/web/auth-gate.js +16 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +15 -2
- package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +10 -6
- package/dist/billing-foundation/i18n.js +9 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +21 -7
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/book-cap-usage.js +8 -0
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/cap-reservation.js +7 -3
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/handlers/increment-rolling.write.d.ts +1 -0
- package/dist/cap-counter/handlers/increment-rolling.write.js +2 -0
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +3 -1
- package/dist/cap-overview/handlers/tenant-options.query.js +3 -1
- package/dist/channel-discord/discord-channel.js +3 -2
- package/dist/channel-email/email-channel.js +40 -23
- package/dist/channel-email/smtp-transport.js +6 -1
- package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/channel-slack/slack-channel.js +3 -2
- package/dist/channel-teams/teams-channel.js +3 -2
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +2 -2
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +30 -20
- package/dist/custom-fields/db/queries/retention.d.ts +1 -1
- package/dist/custom-fields/db/queries/retention.js +2 -1
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- package/dist/data-retention/run-retention-cleanup.js +4 -5
- package/dist/delivery/attachment-ref.d.ts +2 -0
- package/dist/delivery/attachment-ref.js +30 -1
- package/dist/delivery/channel-context.js +19 -1
- package/dist/delivery/delivery-service.js +71 -82
- package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/index.js +1 -1
- package/dist/delivery/jobs.js +43 -11
- package/dist/delivery/types.d.ts +7 -1
- package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
- package/dist/feature-toggles/toggle-runtime.d.ts +5 -3
- package/dist/feature-toggles/toggle-runtime.js +25 -12
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
- package/dist/file-foundation/read-file-ref-content.js +6 -4
- package/dist/files-tenant-data/hooks.js +4 -0
- package/dist/files-tenant-data/index.d.ts +1 -0
- package/dist/files-tenant-data/index.js +2 -1
- package/dist/form-draft/db/queries/owned-file-refs.d.ts +1 -1
- package/dist/form-draft/db/queries/owned-file-refs.js +9 -2
- package/dist/form-draft/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/discard.write.js +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
- package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +35 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- package/dist/managed-pages/handlers/set.write.d.ts +1 -1
- package/dist/metrics/filters.d.ts +16 -1
- package/dist/metrics/filters.js +27 -11
- package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
- package/dist/personal-access-tokens/revoke-for-user.js +3 -7
- package/dist/renderer-foundation/types.d.ts +2 -0
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/delete.write.js +3 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- package/dist/secrets/write-gate.d.ts +3 -1
- package/dist/secrets/write-gate.js +6 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/mail-template-body.d.ts +3 -0
- package/dist/shared/mail-template-body.js +10 -0
- package/dist/shared/resource-image-formats.d.ts +23 -0
- package/dist/shared/resource-image-formats.js +21 -0
- package/dist/shared/sharp-input-limits.js +7 -8
- package/dist/step-dispatcher/webhook-runner.js +2 -2
- package/dist/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- package/dist/template-resolver/api.js +3 -2
- package/dist/template-resolver/compile-document-body.d.ts +7 -0
- package/dist/template-resolver/compile-document-body.js +114 -21
- package/dist/template-resolver/compile-steps-block.d.ts +10 -0
- package/dist/template-resolver/compile-steps-block.js +45 -0
- package/dist/template-resolver/constants.d.ts +6 -3
- package/dist/template-resolver/constants.js +13 -2
- package/dist/template-resolver/document-sanitizer.js +3 -0
- package/dist/template-resolver/document-schema.d.ts +3 -2
- package/dist/template-resolver/document-schema.js +29 -4
- package/dist/template-resolver/document-steps.d.ts +17 -0
- package/dist/template-resolver/document-steps.js +93 -0
- package/dist/template-resolver/feature.d.ts +2 -0
- package/dist/template-resolver/feature.js +13 -1
- package/dist/template-resolver/handlers/bundle-import.d.ts +7 -1
- package/dist/template-resolver/handlers/bundle-import.js +63 -38
- package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-set.write.js +4 -3
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +95 -49
- package/dist/template-resolver/handlers/diff-to-system.query.js +5 -38
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle-system.write.js +16 -5
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle.write.js +11 -1
- package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
- package/dist/template-resolver/handlers/layout-source.query.js +46 -23
- package/dist/template-resolver/handlers/list.query.js +4 -1
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +9 -0
- package/dist/template-resolver/handlers/overview.query.js +60 -12
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +2 -2
- package/dist/template-resolver/handlers/resource-upload.write.js +36 -6
- package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
- package/dist/template-resolver/handlers/send-test.write.js +37 -10
- package/dist/template-resolver/handlers/set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/template-gates.js +13 -2
- package/dist/template-resolver/handlers/template-write.js +36 -4
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
- package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +11 -2
- package/dist/template-resolver/handlers/validate.query.js +4 -1
- package/dist/template-resolver/index.d.ts +5 -4
- package/dist/template-resolver/index.js +5 -4
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +36 -0
- package/dist/template-resolver/mail-constants.d.ts +2 -0
- package/dist/template-resolver/mail-constants.js +3 -0
- package/dist/template-resolver/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -0
- package/dist/template-resolver/qualified-names.d.ts +4 -0
- package/dist/template-resolver/qualified-names.js +4 -0
- package/dist/template-resolver/resource-image.d.ts +2 -9
- package/dist/template-resolver/resource-image.js +2 -11
- package/dist/template-resolver/seeding.js +23 -4
- package/dist/template-resolver/store-template-resource.d.ts +53 -9
- package/dist/template-resolver/store-template-resource.js +250 -97
- package/dist/template-resolver/table.d.ts +103 -0
- package/dist/template-resolver/table.js +9 -2
- package/dist/template-resolver/template-bundle.js +4 -0
- package/dist/template-resolver/template-draft.d.ts +54 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +35 -6
- package/dist/template-resolver/template-meta.js +67 -16
- package/dist/template-resolver/template-preview.d.ts +19 -2
- package/dist/template-resolver/template-preview.js +98 -55
- package/dist/template-resolver/template-resources.js +3 -4
- package/dist/template-resolver/template-usage.d.ts +5 -1
- package/dist/template-resolver/template-usage.js +14 -9
- package/dist/template-resolver/template-validation.js +13 -9
- package/dist/template-resolver/web/client-plugin.js +4 -2
- package/dist/template-resolver/web/document-editor-extensions.d.ts +5 -1
- package/dist/template-resolver/web/document-editor-extensions.js +76 -13
- package/dist/template-resolver/web/document-editor.d.ts +2 -1
- package/dist/template-resolver/web/document-editor.js +12 -2
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
- package/dist/template-resolver/web/document-tiptap-editor.js +173 -34
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +13 -5
- package/dist/template-resolver/web/document-toolbar-blocks.js +6 -0
- package/dist/template-resolver/web/i18n.js +24 -4
- package/dist/template-resolver/web/index.d.ts +2 -0
- package/dist/template-resolver/web/index.js +2 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +6 -2
- package/dist/template-resolver/web/placeholder-line-editor.js +9 -7
- package/dist/tenant/handlers/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update-member-roles.write.js +3 -6
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant/seeding.js +23 -24
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-handover/move-entity-graph.js +14 -4
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +1 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +8 -1
- package/dist/tenant-lifecycle/stages.js +3 -2
- package/dist/tier-engine/constants.d.ts +2 -0
- package/dist/tier-engine/constants.js +4 -0
- package/dist/tier-engine/entity.d.ts +15 -14
- package/dist/tier-engine/entity.js +4 -3
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +6 -1
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -4
- package/dist/tier-engine/index.d.ts +1 -1
- package/dist/tier-engine/index.js +1 -1
- package/dist/tier-engine/tier-resolver.d.ts +13 -13
- package/dist/user/constants.d.ts +1 -0
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/invited-global-roles.d.ts +2 -4
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -1
- package/dist/user-data-rights/email-templates.js +0 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- package/dist/user-data-rights/i18n.js +5 -0
- package/dist/user-data-rights/lib/update-user-lifecycle.d.ts +2 -0
- package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -2
- package/dist/user-data-rights/run-forget-cleanup.js +2 -2
- package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
- package/dist/waitlist/feature.js +1 -1
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/invite.write.js +21 -3
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.js +3 -16
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.js +29 -7
- package/dist/waitlist/i18n.js +9 -0
- package/dist/waitlist/lib.d.ts +2 -1
- package/dist/waitlist/lib.js +15 -0
- package/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/feature.js +1 -1
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +19 -10
- package/src/audit/changes.json +18 -0
- package/src/auth-email-password/changes.json +55 -0
- package/src/billing-foundation/changes.json +48 -0
- package/src/cap-counter/changes.json +19 -0
- package/src/cap-overview/changes.json +6 -0
- package/src/channel-email/changes.json +18 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/delivery/changes.json +30 -0
- package/src/document-ingest-foundation/changes.json +6 -0
- package/src/feature-toggles/changes.json +7 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +31 -0
- package/src/ledger/changes.json +7 -0
- package/src/metrics/changes.json +6 -0
- package/src/personal-access-tokens/changes.json +6 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +12 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +149 -0
- package/src/tenant/changes.json +6 -0
- package/src/tenant-lifecycle/changes.json +6 -0
- package/src/tier-engine/changes.json +18 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +12 -0
- package/src/user-profile/changes.json +1 -1
- package/src/waitlist/changes.json +18 -0
- package/src/workflow-runner/changes.json +6 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { isResourceImageContentType } from "../shared/index.js";
|
|
3
|
+
import { rewriteStepsBlock } from "./compile-steps-block.js";
|
|
2
4
|
import { withParsedDocument } from "./document-sanitizer.js";
|
|
3
5
|
import { DOCUMENT_TEXT_ALIGNMENTS, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.js";
|
|
4
6
|
// Private-use characters mark where a Handlebars expression goes. Author text
|
|
@@ -37,6 +39,8 @@ function valueExpression(path, def) {
|
|
|
37
39
|
return `{{money ${path}}}`;
|
|
38
40
|
if (def?.type === "date")
|
|
39
41
|
return `{{date ${path}}}`;
|
|
42
|
+
if (def?.type === "richText")
|
|
43
|
+
return `{{richText ${path}}}`;
|
|
40
44
|
return `{{${path}}}`;
|
|
41
45
|
}
|
|
42
46
|
function rewriteVariable(element, state) {
|
|
@@ -50,6 +54,33 @@ function pathAndDef(name, element, state) {
|
|
|
50
54
|
const { path, def } = resolveReference(name, rowScopeOf(element, state), state.variables);
|
|
51
55
|
return [path, def];
|
|
52
56
|
}
|
|
57
|
+
const VARIABLE_ONLY_BLOCK_SELECTOR = "p, h1, h2, h3, h4";
|
|
58
|
+
function soleVariableName(block) {
|
|
59
|
+
const names = new Set(Array.from(block.querySelectorAll("span[data-variable]")).map((span) => span.getAttribute("data-variable") ?? ""));
|
|
60
|
+
const [name] = [...names];
|
|
61
|
+
if (names.size !== 1 || name === undefined || !isDocumentReferenceName(name))
|
|
62
|
+
return null;
|
|
63
|
+
const withoutVariables = block.cloneNode(true);
|
|
64
|
+
for (const span of Array.from(withoutVariables.querySelectorAll("span[data-variable]"))) {
|
|
65
|
+
span.remove();
|
|
66
|
+
}
|
|
67
|
+
const hasStaticContent = (withoutVariables.textContent ?? "").trim() !== "" ||
|
|
68
|
+
withoutVariables.querySelector("img") !== null;
|
|
69
|
+
return hasStaticContent ? null : name;
|
|
70
|
+
}
|
|
71
|
+
// A line that holds nothing but one optional placeholder would otherwise leave
|
|
72
|
+
// an empty paragraph (a visible gap) when the value is missing. includeZero
|
|
73
|
+
// keeps an amount of 0.
|
|
74
|
+
function wrapVariableOnlyBlock(block, state) {
|
|
75
|
+
const name = soleVariableName(block);
|
|
76
|
+
// skip: a line with static content or several placeholders stays as it is
|
|
77
|
+
if (name === null)
|
|
78
|
+
return;
|
|
79
|
+
const [path] = pathAndDef(name, block, state);
|
|
80
|
+
const document = block.ownerDocument;
|
|
81
|
+
block.before(document.createTextNode(marker(state, `{{#if ${path} includeZero=true}}`)));
|
|
82
|
+
block.after(document.createTextNode(marker(state, "{{/if}}")));
|
|
83
|
+
}
|
|
53
84
|
function rewriteLink(element, state) {
|
|
54
85
|
const href = element.getAttribute("href") ?? "";
|
|
55
86
|
if (isDocumentPlaceholderHref(href)) {
|
|
@@ -88,12 +119,21 @@ function stripActiveContent(element) {
|
|
|
88
119
|
function neutralizeBraces(html) {
|
|
89
120
|
return html.replace(/[{}]/g, (brace) => (brace === "{" ? "{" : "}"));
|
|
90
121
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
const MAIL_BUTTON_STYLE = "display:inline-block;padding:10px 20px;border-radius:6px;background-color:#1f5468;color:#ffffff;font-weight:600;text-decoration:none";
|
|
123
|
+
const MAIL_TABLE_STYLE = "width:100%;border-collapse:collapse";
|
|
124
|
+
const MAIL_TABLE_CELL_STYLE = "padding:6px 12px;vertical-align:top;border-bottom:1px solid #e5e5e5";
|
|
125
|
+
const MAIL_TABLE_HEAD_STYLE = "font-size:12px;font-weight:600;color:#6b7280;text-align:left";
|
|
126
|
+
const MAIL_SMALL_STYLE = "font-size:13px;color:#4A515E";
|
|
127
|
+
const MAIL_HEADING_STYLES = {
|
|
128
|
+
h1: "margin:0 0 12px 0;font-size:30px;line-height:1.15;font-weight:700;letter-spacing:-0.035em;color:#0B0D12",
|
|
129
|
+
h2: "margin:0 0 12px 0;font-size:24px;line-height:1.2;font-weight:700;letter-spacing:-0.02em;color:#0B0D12",
|
|
130
|
+
h3: "margin:20px 0 8px 0;font-size:17px;line-height:1.3;font-weight:600;color:#0B0D12",
|
|
131
|
+
};
|
|
132
|
+
// Appended after an existing style (data-align) so the alignment survives.
|
|
133
|
+
function appendStyle(element, style) {
|
|
134
|
+
const existing = element.getAttribute("style");
|
|
135
|
+
element.setAttribute("style", existing === null ? style : `${existing};${style}`);
|
|
136
|
+
}
|
|
97
137
|
// Unique valid resource keys in document order.
|
|
98
138
|
export function collectDocumentResourceKeys(html) {
|
|
99
139
|
return withParsedDocument(html, (root) => {
|
|
@@ -107,7 +147,7 @@ export function collectDocumentResourceKeys(html) {
|
|
|
107
147
|
});
|
|
108
148
|
}
|
|
109
149
|
export function toResourceDataUri(contentType, bytes) {
|
|
110
|
-
if (!
|
|
150
|
+
if (!isResourceImageContentType(contentType)) {
|
|
111
151
|
throw new Error(`resource content type "${contentType}" cannot be embedded as an image`);
|
|
112
152
|
}
|
|
113
153
|
return `data:${contentType};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
@@ -118,7 +158,7 @@ function rewriteAlignment(element) {
|
|
|
118
158
|
if (isOneOf(DOCUMENT_TEXT_ALIGNMENTS, align))
|
|
119
159
|
element.setAttribute("style", `text-align:${align}`);
|
|
120
160
|
}
|
|
121
|
-
function rewriteResourceImage(element, resourceSrc) {
|
|
161
|
+
function rewriteResourceImage(element, resourceSrc, maxWidthPx, maxHeightPx) {
|
|
122
162
|
const key = element.getAttribute("data-resource") ?? "";
|
|
123
163
|
const src = isDocumentResourceKey(key) ? resourceSrc?.(key) : undefined;
|
|
124
164
|
if (src === undefined) {
|
|
@@ -128,6 +168,56 @@ function rewriteResourceImage(element, resourceSrc) {
|
|
|
128
168
|
}
|
|
129
169
|
element.setAttribute("src", src);
|
|
130
170
|
element.removeAttribute("data-resource");
|
|
171
|
+
const caps = [
|
|
172
|
+
...(maxWidthPx === undefined ? [] : [`max-width:${maxWidthPx}px`]),
|
|
173
|
+
...(maxHeightPx === undefined ? [] : [`max-height:${maxHeightPx}px;width:auto`]),
|
|
174
|
+
];
|
|
175
|
+
if (caps.length > 0)
|
|
176
|
+
element.setAttribute("style", `${caps.join(";")};height:auto`);
|
|
177
|
+
}
|
|
178
|
+
function stripScriptsAndActiveContent(root, options) {
|
|
179
|
+
for (const element of Array.from(root.querySelectorAll("script")))
|
|
180
|
+
element.remove();
|
|
181
|
+
for (const element of Array.from(root.querySelectorAll("img[data-resource]"))) {
|
|
182
|
+
rewriteResourceImage(element, options.resourceSrc, options.resourceImageMaxWidthPx, options.resourceImageMaxHeightPx);
|
|
183
|
+
}
|
|
184
|
+
for (const element of Array.from(root.querySelectorAll("*")))
|
|
185
|
+
stripActiveContent(element);
|
|
186
|
+
}
|
|
187
|
+
function applyAlignmentInlineStyles(root) {
|
|
188
|
+
for (const element of Array.from(root.querySelectorAll("[data-align]"))) {
|
|
189
|
+
rewriteAlignment(element);
|
|
190
|
+
}
|
|
191
|
+
for (const element of Array.from(root.querySelectorAll("[data-size]"))) {
|
|
192
|
+
element.removeAttribute("data-size");
|
|
193
|
+
appendStyle(element, MAIL_SMALL_STYLE);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function applyHeadingInlineStyles(root) {
|
|
197
|
+
for (const heading of Array.from(root.querySelectorAll("h1, h2, h3"))) {
|
|
198
|
+
const style = MAIL_HEADING_STYLES[heading.tagName.toLowerCase()];
|
|
199
|
+
if (style !== undefined)
|
|
200
|
+
appendStyle(heading, style);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function applyTableInlineStyles(root) {
|
|
204
|
+
for (const table of Array.from(root.querySelectorAll("table"))) {
|
|
205
|
+
appendStyle(table, MAIL_TABLE_STYLE);
|
|
206
|
+
}
|
|
207
|
+
for (const cell of Array.from(root.querySelectorAll("td, th"))) {
|
|
208
|
+
appendStyle(cell, MAIL_TABLE_CELL_STYLE);
|
|
209
|
+
}
|
|
210
|
+
for (const head of Array.from(root.querySelectorAll("th"))) {
|
|
211
|
+
appendStyle(head, MAIL_TABLE_HEAD_STYLE);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function rewriteLinks(root, state, options) {
|
|
215
|
+
for (const element of Array.from(root.querySelectorAll("a[href]"))) {
|
|
216
|
+
rewriteLink(element, state);
|
|
217
|
+
if (options.buttonAsInlineStyle === true && element.hasAttribute("data-button")) {
|
|
218
|
+
element.setAttribute("style", options.buttonInlineStyle ?? MAIL_BUTTON_STYLE);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
131
221
|
}
|
|
132
222
|
// Turns a sanitized document body into Handlebars source. Rows and links are
|
|
133
223
|
// rewritten in document order; list rows last so their placeholders still see
|
|
@@ -136,24 +226,27 @@ export function compileDocumentBodyToHandlebars(html, variables, options = {}) {
|
|
|
136
226
|
const state = { variables, expressions: [] };
|
|
137
227
|
const scrubbed = html.replaceAll(EXPRESSION_OPEN, "").replaceAll(EXPRESSION_CLOSE, "");
|
|
138
228
|
const compiled = withParsedDocument(scrubbed, (root) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
229
|
+
stripScriptsAndActiveContent(root, options);
|
|
230
|
+
if (options.alignmentAsInlineStyle === true)
|
|
231
|
+
applyAlignmentInlineStyles(root);
|
|
232
|
+
if (options.headingAsInlineStyle === true)
|
|
233
|
+
applyHeadingInlineStyles(root);
|
|
234
|
+
if (options.tableAsInlineStyle === true)
|
|
235
|
+
applyTableInlineStyles(root);
|
|
236
|
+
for (const element of Array.from(root.querySelectorAll("div[data-steps]"))) {
|
|
237
|
+
rewriteStepsBlock(element, {
|
|
238
|
+
expression: (source) => marker(state, source),
|
|
239
|
+
pathOf: (name, at) => pathAndDef(name, at, state)[0],
|
|
240
|
+
color: options.stepsColor,
|
|
241
|
+
});
|
|
143
242
|
}
|
|
144
|
-
for (const
|
|
145
|
-
|
|
146
|
-
if (options.alignmentAsInlineStyle === true) {
|
|
147
|
-
for (const element of Array.from(root.querySelectorAll("[data-align]"))) {
|
|
148
|
-
rewriteAlignment(element);
|
|
149
|
-
}
|
|
243
|
+
for (const block of Array.from(root.querySelectorAll(VARIABLE_ONLY_BLOCK_SELECTOR))) {
|
|
244
|
+
wrapVariableOnlyBlock(block, state);
|
|
150
245
|
}
|
|
151
246
|
for (const element of Array.from(root.querySelectorAll("span[data-variable]"))) {
|
|
152
247
|
rewriteVariable(element, state);
|
|
153
248
|
}
|
|
154
|
-
|
|
155
|
-
rewriteLink(element, state);
|
|
156
|
-
}
|
|
249
|
+
rewriteLinks(root, state, options);
|
|
157
250
|
for (const element of Array.from(root.querySelectorAll("tr[data-each]"))) {
|
|
158
251
|
rewriteRepeatRow(element, state);
|
|
159
252
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Element as ScanElement } from "happy-dom";
|
|
2
|
+
export declare const DEFAULT_STEPS_COLOR = "#1f5468";
|
|
3
|
+
export type StepsCompileHooks = {
|
|
4
|
+
/** Registers a Handlebars expression and returns the marker that stands for it. */
|
|
5
|
+
readonly expression: (source: string) => string;
|
|
6
|
+
/** Resolves a variable name to its Handlebars path at the element's position. */
|
|
7
|
+
readonly pathOf: (name: string, element: ScanElement) => string;
|
|
8
|
+
readonly color: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function rewriteStepsBlock(element: ScanElement, hooks: StepsCompileHooks): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isDocumentReferenceName } from "./document-schema.js";
|
|
2
|
+
import { readDocumentSteps } from "./document-steps.js";
|
|
3
|
+
import { isHexColor } from "./layout-appearance.js";
|
|
4
|
+
export const DEFAULT_STEPS_COLOR = "#1f5468";
|
|
5
|
+
const TODO_BAR_COLOR = "#ECEDEA";
|
|
6
|
+
const REACHED_LABEL_COLOR = "#0B0D12";
|
|
7
|
+
const TODO_LABEL_COLOR = "#5A6170";
|
|
8
|
+
// Inline-styled table in every output (mail, PDF, preview): mail clients ignore
|
|
9
|
+
// document CSS, and one rendering keeps the three outputs identical. The labels
|
|
10
|
+
// stay static text; only the reached/current switch is a Handlebars block, and
|
|
11
|
+
// its arguments are a validated path and validated keys.
|
|
12
|
+
export function rewriteStepsBlock(element, hooks) {
|
|
13
|
+
const name = element.getAttribute("data-steps") ?? "";
|
|
14
|
+
const steps = readDocumentSteps(element);
|
|
15
|
+
if (!isDocumentReferenceName(name) || steps === null) {
|
|
16
|
+
element.remove();
|
|
17
|
+
// skip: an invalid steps block renders nothing
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const path = hooks.pathOf(name, element);
|
|
21
|
+
const keys = steps.map((step) => step.key).join(",");
|
|
22
|
+
const color = hooks.color !== undefined && isHexColor(hooks.color) ? hooks.color : DEFAULT_STEPS_COLOR;
|
|
23
|
+
const when = (index, state, yes, no) => `${hooks.expression(`{{#stepIs ${path} ${index} "${keys}" "${state}"}}`)}${yes}${hooks.expression("{{else}}")}${no}${hooks.expression("{{/stepIs}}")}`;
|
|
24
|
+
const document = element.ownerDocument;
|
|
25
|
+
const table = document.createElement("table");
|
|
26
|
+
table.setAttribute("role", "presentation");
|
|
27
|
+
table.setAttribute("width", "100%");
|
|
28
|
+
table.setAttribute("cellpadding", "0");
|
|
29
|
+
table.setAttribute("cellspacing", "0");
|
|
30
|
+
table.setAttribute("style", "table-layout:fixed;border-collapse:collapse;margin:4px 0 24px 0");
|
|
31
|
+
const row = document.createElement("tr");
|
|
32
|
+
steps.forEach((step, index) => {
|
|
33
|
+
const cell = document.createElement("td");
|
|
34
|
+
cell.setAttribute("style", `padding:0 ${index === steps.length - 1 ? 0 : 4}px 0 0;vertical-align:top`);
|
|
35
|
+
const bar = document.createElement("div");
|
|
36
|
+
bar.setAttribute("style", `height:4px;line-height:4px;font-size:0;border-radius:2px;background-color:${when(index, "reached", color, TODO_BAR_COLOR)}`);
|
|
37
|
+
const label = document.createElement("div");
|
|
38
|
+
label.setAttribute("style", `padding-top:8px;font-size:12px;line-height:1.3;color:${when(index, "reached", REACHED_LABEL_COLOR, TODO_LABEL_COLOR)};font-weight:${when(index, "current", "600", "400")}`);
|
|
39
|
+
label.textContent = step.label;
|
|
40
|
+
cell.append(bar, label);
|
|
41
|
+
row.append(cell);
|
|
42
|
+
});
|
|
43
|
+
table.append(row);
|
|
44
|
+
element.replaceWith(table);
|
|
45
|
+
}
|
|
@@ -9,16 +9,19 @@ export type TemplateKind = (typeof TEMPLATE_KINDS)[number];
|
|
|
9
9
|
export declare const UPSERT_KINDS: readonly ["mail", "document-pdf", "image-snapshot", "ai-prompt", "mail-layout", "pdf-layout"];
|
|
10
10
|
export type UpsertKind = (typeof UPSERT_KINDS)[number];
|
|
11
11
|
export declare const TEXT_BLOCK_KIND: "text-block";
|
|
12
|
-
export declare const COLLECTION_REFUSED_KINDS: readonly
|
|
12
|
+
export declare const COLLECTION_REFUSED_KINDS: readonly ["mail", "mail-layout", "pdf-layout"];
|
|
13
|
+
export declare function isCollectionRefusedKind(kind: string): boolean;
|
|
13
14
|
export declare const CONTENT_FORMATS: readonly ["markdown", "mjml", "html", "plain"];
|
|
14
15
|
export type ContentFormat = (typeof CONTENT_FORMATS)[number];
|
|
15
16
|
export declare const TEMPLATE_SCOPES: readonly ["system", "tenant"];
|
|
16
17
|
export type TemplateScope = (typeof TEMPLATE_SCOPES)[number];
|
|
17
18
|
export declare const TEMPLATE_STATUSES: readonly ["draft", "active", "archived"];
|
|
18
19
|
export type TemplateStatus = (typeof TEMPLATE_STATUSES)[number];
|
|
19
|
-
export declare const
|
|
20
|
-
export type PdfPageFormat
|
|
20
|
+
export declare const ACTIVE_TEMPLATE_STATUS = "active";
|
|
21
|
+
export { PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, type PdfOrientation, type PdfPageFormat, } from "./pdf-constants.js";
|
|
22
|
+
export declare const HEX_COLOR_PATTERN: RegExp;
|
|
21
23
|
export declare const DEFAULT_MAIL_LAYOUT_SLUG = "default";
|
|
24
|
+
export declare const DEFAULT_PDF_LAYOUT_SLUG = "default";
|
|
22
25
|
export declare const MAIL_PREVIEW_MAX_BYTES: number;
|
|
23
26
|
export declare const TEST_MAIL_NOTIFICATION_TYPE = "template-resolver:test-mail";
|
|
24
27
|
export declare const TEST_MAIL_SUBJECT_PREFIX = "[TEST] ";
|
|
@@ -40,12 +40,23 @@ export const TEXT_BLOCK_KIND = "text-block";
|
|
|
40
40
|
// Kinds a content collection must not write: a tenant row of these kinds would
|
|
41
41
|
// shadow the system notification template or layout without the meta and
|
|
42
42
|
// placeholder gates that upsertTenant applies.
|
|
43
|
-
export const COLLECTION_REFUSED_KINDS = [
|
|
43
|
+
export const COLLECTION_REFUSED_KINDS = [
|
|
44
|
+
"mail",
|
|
45
|
+
"mail-layout",
|
|
46
|
+
"pdf-layout",
|
|
47
|
+
];
|
|
48
|
+
export function isCollectionRefusedKind(kind) {
|
|
49
|
+
return COLLECTION_REFUSED_KINDS.some((refused) => refused === kind);
|
|
50
|
+
}
|
|
44
51
|
export const CONTENT_FORMATS = ["markdown", "mjml", "html", "plain"];
|
|
45
52
|
export const TEMPLATE_SCOPES = ["system", "tenant"];
|
|
46
53
|
export const TEMPLATE_STATUSES = ["draft", "active", "archived"];
|
|
47
|
-
export const
|
|
54
|
+
export const ACTIVE_TEMPLATE_STATUS = "active";
|
|
55
|
+
export { PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, } from "./pdf-constants.js";
|
|
56
|
+
// Colors end up inside MJML attributes, so only a strict hex is ever written there.
|
|
57
|
+
export const HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
48
58
|
export const DEFAULT_MAIL_LAYOUT_SLUG = "default";
|
|
59
|
+
export const DEFAULT_PDF_LAYOUT_SLUG = "default";
|
|
49
60
|
// The mail transport enforces no size limit of its own; 10 MiB is a common provider ceiling.
|
|
50
61
|
export const MAIL_PREVIEW_MAX_BYTES = 10 * 1024 * 1024;
|
|
51
62
|
export const TEST_MAIL_NOTIFICATION_TYPE = "template-resolver:test-mail";
|
|
@@ -3,6 +3,7 @@ import { generateHTML, generateJSON } from "@tiptap/html/server";
|
|
|
3
3
|
import { Window } from "happy-dom";
|
|
4
4
|
import { DOCUMENT_HTML_KINDS } from "./constants.js";
|
|
5
5
|
import { documentExtensions, isAllowedDocumentLinkHref, isDocumentPlaceholderHref, isDocumentReferenceName, isDocumentResourceKey, } from "./document-schema.js";
|
|
6
|
+
import { isDocumentStepKey } from "./document-steps.js";
|
|
6
7
|
export function violation(code, detail, message) {
|
|
7
8
|
return { code, detail, message };
|
|
8
9
|
}
|
|
@@ -56,6 +57,8 @@ const REFERENCE_ATTRIBUTES = [
|
|
|
56
57
|
["data-variable", isDocumentReferenceName],
|
|
57
58
|
["data-each", isDocumentReferenceName],
|
|
58
59
|
["data-resource", isDocumentResourceKey],
|
|
60
|
+
["data-steps", isDocumentReferenceName],
|
|
61
|
+
["data-step", isDocumentStepKey],
|
|
59
62
|
];
|
|
60
63
|
function scanReferences(tag, element, out) {
|
|
61
64
|
for (const [attribute, isValid] of REFERENCE_ATTRIBUTES) {
|
|
@@ -3,14 +3,14 @@ export declare function isDocumentPlaceholderHref(href: string): boolean;
|
|
|
3
3
|
export declare function isAllowedDocumentLinkHref(href: string): boolean;
|
|
4
4
|
export declare function isDocumentReferenceName(value: string): boolean;
|
|
5
5
|
export declare function isDocumentResourceKey(value: string): boolean;
|
|
6
|
-
export declare const DOCUMENT_NODE_NAMES: readonly ["variable", "resource", "button", "divider", "pageBreak"];
|
|
6
|
+
export declare const DOCUMENT_NODE_NAMES: readonly ["variable", "resource", "button", "divider", "pageBreak", "steps"];
|
|
7
7
|
export declare const DocumentVariable: Node<any, any>;
|
|
8
8
|
export declare const DocumentResource: Node<any, any>;
|
|
9
9
|
export declare const DocumentButton: Node<any, any>;
|
|
10
10
|
export declare const DocumentDivider: Node<any, any>;
|
|
11
11
|
export declare const DocumentPageBreak: Node<any, any>;
|
|
12
|
+
export declare const DocumentSteps: Node<any, any>;
|
|
12
13
|
export declare const DocumentTableRow: Node<import("@tiptap/extension-table").TableRowOptions, any>;
|
|
13
|
-
export declare const DocumentRepeatRow: Node<import("@tiptap/extension-table").TableRowOptions, any>;
|
|
14
14
|
export declare const DocumentTable: Node<import("@tiptap/extension-table").TableOptions, any>;
|
|
15
15
|
export declare const DocumentTableCell: Node<import("@tiptap/extension-table").TableCellOptions, any>;
|
|
16
16
|
export declare const DocumentTableHeader: Node<import("@tiptap/extension-table").TableHeaderOptions, any>;
|
|
@@ -18,4 +18,5 @@ export declare const DocumentLink: import("@tiptap/core").Mark<import("@tiptap/e
|
|
|
18
18
|
export declare const DOCUMENT_TEXT_ALIGNMENTS: readonly ["left", "center", "right", "justify"];
|
|
19
19
|
export type DocumentTextAlignment = (typeof DOCUMENT_TEXT_ALIGNMENTS)[number];
|
|
20
20
|
export declare const DocumentTextAlign: Extension<any, any>;
|
|
21
|
+
export declare const DocumentTextSize: Extension<any, any>;
|
|
21
22
|
export declare const documentExtensions: Extensions;
|
|
@@ -8,12 +8,14 @@ import { Extension, Node } from "@tiptap/core";
|
|
|
8
8
|
import { Link } from "@tiptap/extension-link";
|
|
9
9
|
import { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
|
|
10
10
|
import StarterKit from "@tiptap/starter-kit";
|
|
11
|
-
|
|
11
|
+
import { createDocumentStepsNode } from "./document-steps.js";
|
|
12
|
+
const PLACEHOLDER_HREF = /^\{\{(.+)\}\}$/;
|
|
12
13
|
const REFERENCE_NAME = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
13
14
|
const RESOURCE_KEY = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
14
15
|
const ALLOWED_LINK_SCHEMES = ["https://", "http://", "mailto:", "tel:"];
|
|
15
16
|
export function isDocumentPlaceholderHref(href) {
|
|
16
|
-
|
|
17
|
+
const name = PLACEHOLDER_HREF.exec(href)?.[1];
|
|
18
|
+
return name !== undefined && isDocumentReferenceName(name);
|
|
17
19
|
}
|
|
18
20
|
const UNSAFE_URL_CHARACTERS = new Set(["<", ">", '"']);
|
|
19
21
|
const FORBIDDEN_REFERENCE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -62,6 +64,7 @@ export const DOCUMENT_NODE_NAMES = [
|
|
|
62
64
|
"button",
|
|
63
65
|
"divider",
|
|
64
66
|
"pageBreak",
|
|
67
|
+
"steps",
|
|
65
68
|
];
|
|
66
69
|
export const DocumentVariable = Node.create({
|
|
67
70
|
name: "variable",
|
|
@@ -120,7 +123,8 @@ export const DocumentResource = Node.create({
|
|
|
120
123
|
export const DocumentButton = Node.create({
|
|
121
124
|
name: "button",
|
|
122
125
|
group: "block",
|
|
123
|
-
|
|
126
|
+
// A placeholder in the label ("View {{pageName}}") must survive the round trip.
|
|
127
|
+
content: "(text | variable)*",
|
|
124
128
|
marks: "",
|
|
125
129
|
defining: true,
|
|
126
130
|
addAttributes() {
|
|
@@ -165,6 +169,7 @@ export const DocumentPageBreak = Node.create({
|
|
|
165
169
|
return ["div", { "data-page-break": "" }];
|
|
166
170
|
},
|
|
167
171
|
});
|
|
172
|
+
export const DocumentSteps = createDocumentStepsNode(isDocumentReferenceName);
|
|
168
173
|
// A repeat row is a table row carrying `each`: prosemirror-tables requires
|
|
169
174
|
// the tableRow node type, so a separate node type is not an option.
|
|
170
175
|
export const DocumentTableRow = TableRow.extend({
|
|
@@ -189,7 +194,6 @@ export const DocumentTableRow = TableRow.extend({
|
|
|
189
194
|
};
|
|
190
195
|
},
|
|
191
196
|
});
|
|
192
|
-
export const DocumentRepeatRow = DocumentTableRow;
|
|
193
197
|
export const DocumentTable = Table.extend({
|
|
194
198
|
renderHTML() {
|
|
195
199
|
return ["table", ["tbody", 0]];
|
|
@@ -261,6 +265,25 @@ export const DocumentTextAlign = Extension.create({
|
|
|
261
265
|
];
|
|
262
266
|
},
|
|
263
267
|
});
|
|
268
|
+
// Only "small" exists: the sender line above an address block. Serialized as data-size for the
|
|
269
|
+
// same reason as data-align.
|
|
270
|
+
export const DocumentTextSize = Extension.create({
|
|
271
|
+
name: "documentTextSize",
|
|
272
|
+
addGlobalAttributes() {
|
|
273
|
+
return [
|
|
274
|
+
{
|
|
275
|
+
types: ["paragraph"],
|
|
276
|
+
attributes: {
|
|
277
|
+
textSize: {
|
|
278
|
+
default: null,
|
|
279
|
+
parseHTML: (element) => element.getAttribute("data-size") === "small" ? "small" : null,
|
|
280
|
+
renderHTML: (attributes) => attributes["textSize"] === "small" ? { "data-size": "small" } : {},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
];
|
|
285
|
+
},
|
|
286
|
+
});
|
|
264
287
|
export const documentExtensions = [
|
|
265
288
|
StarterKit.configure({
|
|
266
289
|
blockquote: false,
|
|
@@ -292,5 +315,7 @@ export const documentExtensions = [
|
|
|
292
315
|
DocumentButton,
|
|
293
316
|
DocumentDivider,
|
|
294
317
|
DocumentPageBreak,
|
|
318
|
+
DocumentSteps,
|
|
295
319
|
DocumentTextAlign,
|
|
320
|
+
DocumentTextSize,
|
|
296
321
|
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Node } from "@tiptap/core";
|
|
2
|
+
export declare const DOCUMENT_STEPS_MAX = 8;
|
|
3
|
+
export type DocumentStep = {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function isDocumentStepKey(value: string): boolean;
|
|
8
|
+
export declare function documentStepKeyOf(label: string): string;
|
|
9
|
+
export declare function parseDocumentStepsInput(text: string): readonly DocumentStep[] | null;
|
|
10
|
+
export declare function readDocumentSteps(element: {
|
|
11
|
+
querySelectorAll(selector: string): ArrayLike<{
|
|
12
|
+
getAttribute(name: string): string | null;
|
|
13
|
+
textContent: string | null;
|
|
14
|
+
}>;
|
|
15
|
+
}): readonly DocumentStep[] | null;
|
|
16
|
+
export declare function readDocumentStepsAttribute(value: unknown): readonly DocumentStep[];
|
|
17
|
+
export declare function createDocumentStepsNode(isReferenceName: (value: string) => boolean): Node<any, any>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
// A progress row: fixed stages stored in the template, the reached stage read
|
|
3
|
+
// from a variable at render time. Universal like document-schema.ts.
|
|
4
|
+
import { Node } from "@tiptap/core";
|
|
5
|
+
// Keys end up as Handlebars string literals in the compiled body, so they are
|
|
6
|
+
// restricted to characters that cannot close or escape a literal.
|
|
7
|
+
const STEP_KEY = /^[A-Za-z0-9][A-Za-z0-9_-]{0,39}$/;
|
|
8
|
+
export const DOCUMENT_STEPS_MAX = 8;
|
|
9
|
+
const STEP_LABEL_MAX_LENGTH = 40;
|
|
10
|
+
export function isDocumentStepKey(value) {
|
|
11
|
+
return STEP_KEY.test(value);
|
|
12
|
+
}
|
|
13
|
+
export function documentStepKeyOf(label) {
|
|
14
|
+
return label
|
|
15
|
+
.normalize("NFKD")
|
|
16
|
+
.replace(/\p{M}/gu, "")
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
19
|
+
.replace(/^-+|-+$/g, "")
|
|
20
|
+
.slice(0, 40);
|
|
21
|
+
}
|
|
22
|
+
// Editor input: "value=Label" entries separated by commas; a bare label derives its value.
|
|
23
|
+
export function parseDocumentStepsInput(text) {
|
|
24
|
+
const steps = text
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((entry) => entry.trim())
|
|
27
|
+
.filter((entry) => entry !== "")
|
|
28
|
+
.map((entry) => {
|
|
29
|
+
const separator = entry.indexOf("=");
|
|
30
|
+
const label = (separator < 0 ? entry : entry.slice(separator + 1)).trim();
|
|
31
|
+
const key = separator < 0 ? documentStepKeyOf(entry) : entry.slice(0, separator).trim();
|
|
32
|
+
return { key, label: label.slice(0, STEP_LABEL_MAX_LENGTH) };
|
|
33
|
+
});
|
|
34
|
+
return areValidSteps(steps) ? steps : null;
|
|
35
|
+
}
|
|
36
|
+
function areValidSteps(steps) {
|
|
37
|
+
return (steps.length > 0 &&
|
|
38
|
+
steps.length <= DOCUMENT_STEPS_MAX &&
|
|
39
|
+
new Set(steps.map((step) => step.key)).size === steps.length &&
|
|
40
|
+
steps.every((step) => isDocumentStepKey(step.key) && step.label !== ""));
|
|
41
|
+
}
|
|
42
|
+
// Duplicate or invalid keys would make the reached stage ambiguous, so the whole
|
|
43
|
+
// list is refused rather than repaired.
|
|
44
|
+
export function readDocumentSteps(element) {
|
|
45
|
+
const steps = Array.from(element.querySelectorAll("span[data-step]")).map((span) => ({
|
|
46
|
+
key: span.getAttribute("data-step") ?? "",
|
|
47
|
+
label: (span.textContent ?? "").trim().slice(0, STEP_LABEL_MAX_LENGTH),
|
|
48
|
+
}));
|
|
49
|
+
return areValidSteps(steps) ? steps : null;
|
|
50
|
+
}
|
|
51
|
+
function isDocumentStep(step) {
|
|
52
|
+
return (typeof step === "object" &&
|
|
53
|
+
step !== null &&
|
|
54
|
+
"key" in step &&
|
|
55
|
+
"label" in step &&
|
|
56
|
+
typeof step.key === "string" &&
|
|
57
|
+
typeof step.label === "string");
|
|
58
|
+
}
|
|
59
|
+
// Node attrs are untyped JSON; anything that is not a step entry is dropped.
|
|
60
|
+
export function readDocumentStepsAttribute(value) {
|
|
61
|
+
return Array.isArray(value) ? value.filter(isDocumentStep) : [];
|
|
62
|
+
}
|
|
63
|
+
export function createDocumentStepsNode(isReferenceName) {
|
|
64
|
+
return Node.create({
|
|
65
|
+
name: "steps",
|
|
66
|
+
group: "block",
|
|
67
|
+
atom: true,
|
|
68
|
+
addAttributes() {
|
|
69
|
+
return { variable: { default: null }, steps: { default: [] } };
|
|
70
|
+
},
|
|
71
|
+
parseHTML() {
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
tag: "div[data-steps]",
|
|
75
|
+
getAttrs: (element) => {
|
|
76
|
+
const variable = element.getAttribute("data-steps");
|
|
77
|
+
const steps = readDocumentSteps(element);
|
|
78
|
+
if (variable === null || !isReferenceName(variable) || steps === null)
|
|
79
|
+
return false;
|
|
80
|
+
return { variable, steps };
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
},
|
|
85
|
+
renderHTML({ node }) {
|
|
86
|
+
return [
|
|
87
|
+
"div",
|
|
88
|
+
{ "data-steps": node.attrs["variable"] },
|
|
89
|
+
...readDocumentStepsAttribute(node.attrs["steps"]).map((step) => ["span", { "data-step": step.key }, step.label]),
|
|
90
|
+
];
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -20,6 +20,8 @@ export declare function createTemplateResolverFeature(opts?: TemplateResolverOpt
|
|
|
20
20
|
archive: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
21
21
|
publishSystem: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
22
22
|
archiveSystem: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
23
|
+
discardDraft: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
24
|
+
discardDraftSystem: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
23
25
|
resetToSystem: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
24
26
|
set: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
25
27
|
importBundle: import("@cosmicdrift/kumiko-types/handlers").HandlerRef;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSystemConfig, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { isCollectionRefusedKind } from "./constants.js";
|
|
2
3
|
import { bySlugQuery } from "./handlers/by-slug.query.js";
|
|
3
4
|
import { byTenantQuery } from "./handlers/by-tenant.query.js";
|
|
4
5
|
import { makeCollectionItemQuery } from "./handlers/collection-item.query.js";
|
|
@@ -6,6 +7,7 @@ import { makeCollectionListQuery } from "./handlers/collection-list.query.js";
|
|
|
6
7
|
import { makeCollectionSetWrite } from "./handlers/collection-set.write.js";
|
|
7
8
|
import { copyLocaleWrite } from "./handlers/copy-locale.write.js";
|
|
8
9
|
import { diffToSystemQuery } from "./handlers/diff-to-system.query.js";
|
|
10
|
+
import { discardDraftSystemWrite, discardDraftWrite } from "./handlers/discard-draft.write.js";
|
|
9
11
|
import { exportBundleQuery } from "./handlers/export-bundle.query.js";
|
|
10
12
|
import { findByIdQuery } from "./handlers/find-by-id.query.js";
|
|
11
13
|
import { importBundleWrite } from "./handlers/import-bundle.write.js";
|
|
@@ -28,13 +30,21 @@ import { usageQuery } from "./handlers/usage.query.js";
|
|
|
28
30
|
import { validateQuery } from "./handlers/validate.query.js";
|
|
29
31
|
import { templateResourceEntity } from "./table.js";
|
|
30
32
|
import { userContentEntryEntity } from "./user-content-table.js";
|
|
33
|
+
function assertCollectionKindsAllowed(collections) {
|
|
34
|
+
for (const collection of collections) {
|
|
35
|
+
if (isCollectionRefusedKind(collection.kind)) {
|
|
36
|
+
throw new Error(`template-resolver: collection "${collection.id}" uses kind "${collection.kind}", which collections may not write`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
31
40
|
export function createTemplateResolverFeature(opts = {}) {
|
|
32
41
|
const collections = opts.collections ?? [];
|
|
42
|
+
assertCollectionKindsAllowed(collections);
|
|
33
43
|
const hasUserOwned = collections.some((c) => c.ownership === "user");
|
|
34
44
|
return defineFeature("template-resolver", (r) => {
|
|
35
45
|
r.describe([
|
|
36
46
|
"Every piece of editable text lives here, in one entity: mail bodies, notification texts, PDF document templates, AI prompts and plain text blocks. What a record is used for is the `kind` (`notification`, `mail`, `mail-layout`, `document-pdf`, `pdf-layout`, `ai-prompt`, `text-block`, `snippet`, `image-snapshot`).",
|
|
37
|
-
"Reading is one call — `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })`. It walks four levels: tenant+locale, system+locale, tenant+fallback-locale, system+fallback-locale. A tenant overrides a system default by simply having its own record; no application code changes. Writing programmatically goes through the `upsertSystem`, `upsertTenant`, `publish` and `archive` write handlers; a SystemAdmin retires or restores a system default with `publishSystem` and `archiveSystem`, and a tenant drops its override with `resetToSystem`.",
|
|
47
|
+
"Reading is one call — `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })`. It walks four levels: tenant+locale, system+locale, tenant+fallback-locale, system+fallback-locale. A tenant overrides a system default by simply having its own record; no application code changes. Writing programmatically goes through the `upsertSystem`, `upsertTenant`, `publish` and `archive` write handlers; a SystemAdmin retires or restores a system default with `publishSystem` and `archiveSystem`, and a tenant drops its override with `resetToSystem`. Saving a draft over an active template keeps the active version served and stores the edit beside it (column `draft`); `publish` makes it the served version, `discardDraft` / `discardDraftSystem` throws it away.",
|
|
38
48
|
"Text an editor should be able to change belongs in a collection, declared at mount: `createTemplateResolverFeature({ collections: [{ id, kind, access: { roles }, nav }] })`. It appears in the navigation, and `access` is part of the mount because a bundled feature cannot know the host's roles. Each collection gets its own `<id>-list` / `<id>-item` / `<id>-set` handlers, so the dispatcher enforces the separation.",
|
|
39
49
|
"How a collection is edited follows from `contentFormat`: `plain` gives a text area, `rich` a small WYSIWYG (bold, italic, headings, lists, links), `markdown` a text area that stores markdown text (same insertable chips as `plain`). All three offer the collection's `variableSchema` as insertable chips and a preview rendered with sample data — an editor sees what `{{firstName}}` becomes without sending a mail. An app can register its own editor for a format; the last `clientFeature` that registers a format wins (a conflict logs a warning).",
|
|
40
50
|
'A collection is tenant-wide by default. With `ownership: "user"` every user keeps their own entries — mail signatures being the obvious case. Those rows live in the separate `user-content-entry` entity and count as user data, so mounting one also requires the `template-resolver-user-data` feature and a migration on the app side.',
|
|
@@ -67,6 +77,8 @@ export function createTemplateResolverFeature(opts = {}) {
|
|
|
67
77
|
archive: r.writeHandler(archiveWrite),
|
|
68
78
|
publishSystem: r.writeHandler(publishSystemWrite),
|
|
69
79
|
archiveSystem: r.writeHandler(archiveSystemWrite),
|
|
80
|
+
discardDraft: r.writeHandler(discardDraftWrite),
|
|
81
|
+
discardDraftSystem: r.writeHandler(discardDraftSystemWrite),
|
|
70
82
|
resetToSystem: r.writeHandler(resetToSystemWrite),
|
|
71
83
|
set: r.writeHandler(setWrite),
|
|
72
84
|
importBundle: r.writeHandler(importBundleWrite),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { HandlerContext, SessionUser, TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import type { BeforeTransactionResult, HandlerContext, SessionUser, TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { type FieldIssue } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
3
|
import type { TemplateWriteScope } from "../layout-regions.js";
|
|
4
|
+
import { type PreparedTemplateFile } from "../store-template-resource.js";
|
|
4
5
|
import type { TemplateBundle } from "../template-bundle.js";
|
|
5
6
|
export declare function targetExistsIssue(locale: string): FieldIssue;
|
|
6
7
|
type BundleImport = {
|
|
@@ -12,8 +13,13 @@ type BundleImport = {
|
|
|
12
13
|
readonly overwrite: boolean;
|
|
13
14
|
};
|
|
14
15
|
type BundleImportStatus = "draft" | "active";
|
|
16
|
+
export type PreparedBundleResources = ReadonlyMap<string, PreparedTemplateFile>;
|
|
17
|
+
export declare function prepareTemplateBundleBeforeTransaction(args: BundleImport & {
|
|
18
|
+
readonly status: BundleImportStatus;
|
|
19
|
+
}): Promise<BeforeTransactionResult<PreparedBundleResources>>;
|
|
15
20
|
export declare function importTemplateBundle(args: BundleImport & {
|
|
16
21
|
readonly status: BundleImportStatus;
|
|
22
|
+
readonly prepared: PreparedBundleResources;
|
|
17
23
|
}): Promise<WriteResult<{
|
|
18
24
|
ids: Record<string, string>;
|
|
19
25
|
}>>;
|