@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.363.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/admin-shell/feature.d.ts +3 -0
- package/dist/admin-shell/feature.js +6 -1
- package/dist/agent-tools/agent-manifest.js +2 -1
- package/dist/agent-tools/filter-ops.d.ts +1 -1
- package/dist/agent-tools/filter-ops.js +1 -3
- package/dist/agent-tools/tool-dispatch.js +2 -1
- package/dist/auth-email-password/email-templates.d.ts +4 -26
- package/dist/auth-email-password/email-templates.js +9 -101
- package/dist/auth-email-password/feature.js +24 -6
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
- package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
- package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
- package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -2
- package/dist/auth-email-password/handlers/token-request-handler.js +0 -1
- package/dist/auth-email-password/index.d.ts +2 -2
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
- package/dist/auth-email-password/magic-link-mail.js +19 -14
- package/dist/auth-email-password/mail-notifications.d.ts +32 -0
- package/dist/auth-email-password/mail-notifications.js +116 -0
- package/dist/auth-email-password/signup-activation.d.ts +1 -1
- package/dist/auth-email-password/signup-activation.js +2 -3
- package/dist/auth-foundation/types.js +2 -8
- package/dist/auth-mfa/feature.js +4 -4
- package/dist/auth-mfa/hmac-token-codec.js +4 -4
- package/dist/auth-mfa/mfa-challenge-token.js +3 -3
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
- package/dist/auth-mfa/mfa-setup-token.js +8 -8
- package/dist/billing-foundation/constants.d.ts +3 -3
- package/dist/billing-foundation/constants.js +5 -4
- package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +27 -32
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
- package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +20 -10
- package/dist/billing-foundation/consumer-protection/termination-mail.js +10 -21
- package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
- package/dist/billing-foundation/consumer-protection/termination-request.js +2 -2
- package/dist/billing-foundation/handlers/process-event.write.js +2 -2
- package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
- package/dist/billing-foundation/subscription-tier-sync.js +3 -4
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
- package/dist/cap-overview/web/cap-usage-cell.js +5 -4
- package/dist/channel-email/email-channel.d.ts +2 -2
- package/dist/channel-email/email-channel.js +89 -26
- package/dist/channel-email/feature.js +3 -3
- package/dist/channel-email/index.d.ts +1 -1
- package/dist/channel-email/index.js +1 -1
- package/dist/channel-email/pii-guard.js +10 -0
- package/dist/channel-email/smtp-transport.js +13 -0
- package/dist/channel-email/types.d.ts +8 -0
- package/dist/config/handlers/set.write.js +1 -2
- package/dist/custom-fields/lib/value-schema.js +10 -11
- package/dist/custom-fields/wire-user-data-rights.js +7 -6
- package/dist/data-retention/index.d.ts +1 -0
- package/dist/data-retention/index.js +1 -0
- package/dist/data-retention/run-retention-cleanup.js +6 -6
- package/dist/delivery/attachment-ref.d.ts +39 -0
- package/dist/delivery/attachment-ref.js +64 -0
- package/dist/delivery/channel-context.d.ts +8 -4
- package/dist/delivery/channel-context.js +76 -2
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +1 -0
- package/dist/delivery/delivery-service.d.ts +4 -1
- package/dist/delivery/delivery-service.js +38 -32
- package/dist/delivery/feature.js +1 -1
- package/dist/delivery/index.d.ts +5 -2
- package/dist/delivery/index.js +4 -1
- package/dist/delivery/jobs.js +25 -5
- package/dist/delivery/notify-factory.js +4 -1
- package/dist/delivery/testing.d.ts +4 -1
- package/dist/delivery/types.d.ts +20 -14
- package/dist/delivery/unsubscribe.js +4 -1
- package/dist/derivatives-sharp/render.js +1 -18
- package/dist/document-ingest-foundation/pages.js +4 -4
- package/dist/feature-toggles/toggle-sync-signal.js +4 -4
- package/dist/file-foundation/index.d.ts +1 -0
- package/dist/file-foundation/index.js +1 -0
- package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
- package/dist/file-foundation/read-file-ref-content.js +49 -0
- package/dist/folders/web/folder-manager.js +3 -2
- package/dist/form-draft/db/queries/cleanup.js +4 -4
- package/dist/form-draft/db/queries/draft-count.js +2 -2
- package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
- package/dist/form-draft/release-file-refs.js +3 -2
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
- package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-state.js +7 -7
- package/dist/inbound-mail-foundation/types.js +4 -4
- package/dist/inbound-mail-foundation/watch-supervisor.js +22 -13
- package/dist/inbound-provider-imap/imap-client.js +4 -3
- package/dist/ledger/recurring.js +1 -3
- package/dist/managed-pages/branding.d.ts +2 -0
- package/dist/managed-pages/branding.js +24 -2
- package/dist/managed-pages/feature.js +1 -0
- package/dist/managed-pages/i18n.js +1 -0
- package/dist/managed-pages/screens/branding-screen.js +8 -1
- package/dist/page-render/branding.d.ts +1 -0
- package/dist/page-render/branding.js +1 -0
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/feature.js +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -1
- package/dist/personal-access-tokens/revoke-for-user.js +2 -1
- package/dist/renderer-foundation/api.d.ts +13 -13
- package/dist/renderer-foundation/api.js +1 -1
- package/dist/renderer-foundation/constants.js +2 -4
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/renderer-foundation/index.d.ts +2 -1
- package/dist/renderer-foundation/index.js +1 -0
- package/dist/renderer-foundation/types.d.ts +92 -32
- package/dist/renderer-pdf/feature.js +7 -1
- package/dist/sessions/db/queries/cleanup.js +2 -2
- package/dist/sessions/feature.js +1 -1
- package/dist/sessions/handlers/list.query.js +2 -1
- package/dist/shared/attachment-file-name.d.ts +3 -0
- package/dist/shared/attachment-file-name.js +35 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/is-tenant-db.js +1 -1
- package/dist/shared/mail-template-body.d.ts +4 -0
- package/dist/shared/mail-template-body.js +17 -0
- package/dist/shared/sharp-input-limits.d.ts +6 -0
- package/dist/shared/sharp-input-limits.js +22 -0
- package/dist/shared/signup-handover.js +2 -2
- package/dist/tags/handlers/assign-tag.write.js +3 -1
- package/dist/tags/web/tag-chip.js +2 -0
- package/dist/template-resolver/api.d.ts +30 -2
- package/dist/template-resolver/api.js +40 -5
- package/dist/template-resolver/compile-document-body.d.ts +8 -0
- package/dist/template-resolver/compile-document-body.js +163 -0
- package/dist/template-resolver/constants.d.ts +13 -4
- package/dist/template-resolver/constants.js +30 -10
- package/dist/template-resolver/document-sanitizer.d.ts +32 -0
- package/dist/template-resolver/document-sanitizer.js +201 -0
- package/dist/template-resolver/document-schema.d.ts +21 -0
- package/dist/template-resolver/document-schema.js +296 -0
- package/dist/template-resolver/feature.d.ts +18 -0
- package/dist/template-resolver/feature.js +50 -6
- package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
- package/dist/template-resolver/handlers/bundle-import.js +147 -0
- package/dist/template-resolver/handlers/collection-set.write.js +25 -2
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
- package/dist/template-resolver/handlers/copy-locale.write.js +116 -0
- package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
- package/dist/template-resolver/handlers/diff-to-system.query.js +64 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +68 -0
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +3 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
- package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
- package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
- package/dist/template-resolver/handlers/layout-source.query.d.ts +26 -0
- package/dist/template-resolver/handlers/layout-source.query.js +64 -0
- package/dist/template-resolver/handlers/list.query.d.ts +4 -1
- package/dist/template-resolver/handlers/list.query.js +5 -7
- package/dist/template-resolver/handlers/overview.query.d.ts +59 -0
- package/dist/template-resolver/handlers/overview.query.js +165 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
- package/dist/template-resolver/handlers/preview.query.js +24 -0
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
- package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
- package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-shared.js +14 -0
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
- package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
- package/dist/template-resolver/handlers/send-test.write.d.ts +11 -0
- package/dist/template-resolver/handlers/send-test.write.js +81 -0
- package/dist/template-resolver/handlers/set.write.d.ts +4 -1
- package/dist/template-resolver/handlers/set.write.js +25 -3
- package/dist/template-resolver/handlers/shared.d.ts +9 -5
- package/dist/template-resolver/handlers/shared.js +6 -2
- package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
- package/dist/template-resolver/handlers/template-gates.js +131 -0
- package/dist/template-resolver/handlers/template-write.d.ts +45 -0
- package/dist/template-resolver/handlers/template-write.js +109 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +17 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +48 -0
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +12 -0
- package/dist/template-resolver/handlers/toggle-status.write.js +27 -11
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-tenant.write.js +10 -61
- package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
- package/dist/template-resolver/handlers/usage.query.js +15 -0
- package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
- package/dist/template-resolver/handlers/validate.query.js +80 -0
- package/dist/template-resolver/index.d.ts +18 -1
- package/dist/template-resolver/index.js +16 -1
- package/dist/template-resolver/layout-regions.d.ts +23 -0
- package/dist/template-resolver/layout-regions.js +125 -0
- package/dist/template-resolver/linked-resources.d.ts +17 -0
- package/dist/template-resolver/linked-resources.js +75 -0
- package/dist/template-resolver/preview-display-html.d.ts +7 -0
- package/dist/template-resolver/preview-display-html.js +27 -0
- package/dist/template-resolver/print-document.d.ts +1 -0
- package/dist/template-resolver/print-document.js +55 -0
- package/dist/template-resolver/qualified-names.d.ts +30 -0
- package/dist/template-resolver/qualified-names.js +30 -0
- package/dist/template-resolver/record-mapping.d.ts +2 -0
- package/dist/template-resolver/record-mapping.js +21 -0
- package/dist/template-resolver/resource-image.d.ts +15 -0
- package/dist/template-resolver/resource-image.js +34 -0
- package/dist/template-resolver/seeding.d.ts +14 -1
- package/dist/template-resolver/seeding.js +105 -15
- package/dist/template-resolver/store-template-resource.d.ts +64 -0
- package/dist/template-resolver/store-template-resource.js +264 -0
- package/dist/template-resolver/table.d.ts +47 -8
- package/dist/template-resolver/table.js +11 -0
- package/dist/template-resolver/template-bundle.d.ts +36 -0
- package/dist/template-resolver/template-bundle.js +60 -0
- package/dist/template-resolver/template-diff.d.ts +7 -0
- package/dist/template-resolver/template-diff.js +68 -0
- package/dist/template-resolver/template-meta.d.ts +60 -0
- package/dist/template-resolver/template-meta.js +101 -0
- package/dist/template-resolver/template-preview.d.ts +86 -0
- package/dist/template-resolver/template-preview.js +374 -0
- package/dist/template-resolver/template-resources.d.ts +14 -0
- package/dist/template-resolver/template-resources.js +42 -0
- package/dist/template-resolver/template-usage.d.ts +12 -0
- package/dist/template-resolver/template-usage.js +30 -0
- package/dist/template-resolver/template-validation.d.ts +22 -0
- package/dist/template-resolver/template-validation.js +172 -0
- package/dist/template-resolver/testing.js +5 -5
- package/dist/template-resolver/user-content-table.d.ts +14 -8
- package/dist/template-resolver/web/client-plugin.d.ts +1 -0
- package/dist/template-resolver/web/client-plugin.js +9 -6
- package/dist/template-resolver/web/document-editor-extensions.d.ts +15 -0
- package/dist/template-resolver/web/document-editor-extensions.js +107 -0
- package/dist/template-resolver/web/document-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-editor.js +11 -0
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +376 -0
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +33 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +15 -0
- package/dist/template-resolver/web/editor-handle.d.ts +3 -0
- package/dist/template-resolver/web/editor-handle.js +24 -0
- package/dist/template-resolver/web/i18n.js +76 -0
- package/dist/template-resolver/web/index.d.ts +4 -0
- package/dist/template-resolver/web/index.js +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +15 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
- package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-text.js +34 -0
- package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
- package/dist/template-resolver/web/use-stable-by-key.js +13 -0
- package/dist/tenant/handlers/team-list.query.js +3 -2
- package/dist/tenant/seeding.js +31 -6
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
- package/dist/user/handlers/detail.query.js +2 -1
- package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
- package/dist/user-data-rights/email-templates.d.ts +1 -29
- package/dist/user-data-rights/email-templates.js +5 -106
- package/dist/user-data-rights/feature.js +53 -35
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
- package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
- package/dist/user-data-rights/lib/default-mailers.js +100 -48
- package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
- package/dist/user-data-rights/mail-notifications.d.ts +13 -0
- package/dist/user-data-rights/mail-notifications.js +118 -0
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
- package/dist/user-profile/web/profile-screen.js +2 -1
- package/dist/waitlist/feature.js +10 -0
- package/dist/waitlist/handlers/submit.write.js +3 -4
- package/dist/waitlist/mail.d.ts +19 -6
- package/dist/waitlist/mail.js +50 -14
- package/package.json +28 -17
- package/src/admin-shell/changes.json +6 -0
- package/src/auth-email-password/changes.json +14 -0
- package/src/billing-foundation/changes.json +7 -0
- package/src/channel-email/changes.json +19 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +48 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-foundation/changes.json +39 -0
- package/src/template-resolver/changes.json +118 -0
- package/src/user-data-rights/changes.json +7 -0
- package/src/waitlist/changes.json +7 -0
- package/dist/renderer-simple/feature.d.ts +0 -10
- package/dist/renderer-simple/feature.js +0 -42
- package/dist/renderer-simple/index.d.ts +0 -3
- package/dist/renderer-simple/index.js +0 -2
- package/dist/renderer-simple/mail-blocks.d.ts +0 -21
- package/dist/renderer-simple/mail-blocks.js +0 -107
- package/dist/renderer-simple/resolve-variables.d.ts +0 -7
- package/dist/renderer-simple/resolve-variables.js +0 -47
- package/dist/renderer-simple/simple-renderer.d.ts +0 -24
- package/dist/renderer-simple/simple-renderer.js +0 -200
- package/src/renderer-simple/changes.json +0 -27
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "GDPR default mails are r.notification declarations, render*Email is gone",
|
|
6
|
+
"detail": "export-ready, export-failed, deletion-requested and deletion-executed are declared as trigger-less notifications (user-data-rights:notify:<name>) with variables and localized templates built from the GDPR translations, so a tenant can override the text. The default export-ready, export-failed and deletion-requested mails send through ctx.notify to the user (export mails with immediate so the plain magic-link token never lands in a job payload). deletion-executed renders the seeded template through the mail renderer and sends over the tenant mail transport without a delivery log, because the user is already gone. App-provided send*Email callbacks still win. The defaults now need delivery with channel-email; a boot warning is logged once when mail-foundation and a mail transport are mounted without them. New exports are GdprMailNotificationTypes, gdprMailLocalizedTemplate, gdprMailTemplateLocale, registerGdprMails and formatGdprMailTimestamp.",
|
|
7
|
+
"migration": "renderExportReadyEmail, renderExportFailedEmail, renderDeletionRequestedEmail, renderDeletionExecutedEmail and RenderedEmail are removed from user-data-rights/email-templates; the translations stay. An app that mounts it without includeBundled needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html at boot (composeRendererStack() from dev-server mounts all of them), plus a delivery with the channel-email feature; apps without the template tables need a schema migration (`kumiko schema generate`). Delivery preferences that referenced the mails use the new user-data-rights:notify:<name> types."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.362.0",
|
|
4
11
|
"type": "fix",
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Waitlist confirmation and admin notice use template variables",
|
|
6
|
+
"detail": "The confirmation and admin-notice mails are r.notification declarations (waitlist:notify:confirmation, waitlist:notify:admin-notice) with variables and localized templates. The notify data is the variable object; the confirmation wording is computed in code and passed as variables. The admin notice subject is the submitter name.",
|
|
7
|
+
"migration": "Update delivery preferences and tests that referenced the old types or read the sections data. An app that mounts it without includeBundled needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html at boot (composeRendererStack() from dev-server mounts all of them), plus a delivery with the channel-email feature; apps without the template tables need a schema migration (`kumiko schema generate`)."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.342.0",
|
|
4
11
|
"type": "improvement",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import type { NotificationRenderer } from "../delivery/index.js";
|
|
3
|
-
import { type RendererContext, type RenderRequest, type RenderResponse } from "../renderer-foundation/index.js";
|
|
4
|
-
import { type MailBranding } from "./simple-renderer.js";
|
|
5
|
-
export declare function adaptToFoundation(req: RenderRequest, ctx: RendererContext, renderer?: NotificationRenderer): Promise<RenderResponse>;
|
|
6
|
-
export type RendererSimpleOptions = {
|
|
7
|
-
/** Branding applied to every notification mail, including the framework auth mails. */
|
|
8
|
-
readonly mailBranding?: MailBranding;
|
|
9
|
-
};
|
|
10
|
-
export declare function createRendererSimpleFeature(options?: RendererSimpleOptions): FeatureDefinition;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import { RENDERER_EXTENSION, RendererError, } from "../renderer-foundation/index.js";
|
|
3
|
-
import { resolveNotificationVariables } from "./resolve-variables.js";
|
|
4
|
-
import { createSimpleRenderer, simpleRenderer } from "./simple-renderer.js";
|
|
5
|
-
// Adapter: simpleRenderer.render hat `Promise<string>`-Signatur (Legacy
|
|
6
|
-
// NotificationRenderer-Contract), renderer-foundation erwartet
|
|
7
|
-
// `Promise<RenderResponse>` mit discriminated union. Mapper bewahrt
|
|
8
|
-
// die simpleRenderer-Implementierung (Template-Strings → HTML mit
|
|
9
|
-
// Inline-CSS) und packt sie in den RendererPlugin-Contract.
|
|
10
|
-
//
|
|
11
|
-
// Exported damit der Adapter-Pfad direkt testbar ist (unit-test).
|
|
12
|
-
export async function adaptToFoundation(req, ctx, renderer = simpleRenderer) {
|
|
13
|
-
if (req.kind !== "notification") {
|
|
14
|
-
// Defensiver Guard — Foundation wählt Plugins nur für matching kinds,
|
|
15
|
-
// dieser Pfad sollte unter normalen Umständen nie erreicht werden.
|
|
16
|
-
throw new RendererError(`renderer-simple supports only kind="notification", got "${req.kind}"`, "invalid_payload");
|
|
17
|
-
}
|
|
18
|
-
const variables = await resolveNotificationVariables(req, ctx);
|
|
19
|
-
const input = { template: req.payload.template ?? "", variables, locale: req.payload.locale };
|
|
20
|
-
const html = await renderer.render(input);
|
|
21
|
-
const text = renderer.renderText ? await renderer.renderText(input) : undefined;
|
|
22
|
-
return text === undefined ? { kind: "notification", html } : { kind: "notification", html, text };
|
|
23
|
-
}
|
|
24
|
-
export function createRendererSimpleFeature(options) {
|
|
25
|
-
const renderer = options?.mailBranding
|
|
26
|
-
? createSimpleRenderer(options.mailBranding)
|
|
27
|
-
: simpleRenderer;
|
|
28
|
-
return defineFeature("renderer-simple", (r) => {
|
|
29
|
-
r.describe('Default renderer plugin for `kind="notification"`: takes a structured `EmailTemplateData` variable map (with `header`, `sections[]` of text/button objects, and optional `footer`; falls back to `title`/`body` if no structured fields are present) and returns rendered HTML with inline CSS. Requires `renderer-foundation`; sufficient for plain notification emails \u2014 swap it for `renderer-mail-html` if you need MJML/Markdown layouts.');
|
|
30
|
-
r.uiHints({
|
|
31
|
-
displayLabel: "Renderer \u00b7 Simple",
|
|
32
|
-
category: "notifications",
|
|
33
|
-
recommended: false,
|
|
34
|
-
});
|
|
35
|
-
r.requires("renderer-foundation");
|
|
36
|
-
r.optionalRequires("template-resolver");
|
|
37
|
-
r.useExtension(RENDERER_EXTENSION, "simple", {
|
|
38
|
-
kinds: ["notification"],
|
|
39
|
-
render: (req, ctx) => adaptToFoundation(req, ctx, renderer),
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { createRendererSimpleFeature, type RendererSimpleOptions } from "./feature.js";
|
|
2
|
-
export type { MailBadge, MailChip, MailStage, MailStageState, MailTone } from "./mail-blocks.js";
|
|
3
|
-
export { createSimpleRenderer, type LocalizedText, type MailBranding, simpleRenderer, } from "./simple-renderer.js";
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type MailTone = "neutral" | "info" | "success" | "warning" | "danger";
|
|
2
|
-
export type MailStageState = "done" | "current" | "upcoming";
|
|
3
|
-
export type MailBadge = {
|
|
4
|
-
readonly label: string;
|
|
5
|
-
readonly tone?: MailTone;
|
|
6
|
-
};
|
|
7
|
-
export type MailStage = {
|
|
8
|
-
readonly label: string;
|
|
9
|
-
readonly state: MailStageState;
|
|
10
|
-
};
|
|
11
|
-
export type MailChip = {
|
|
12
|
-
readonly label: string;
|
|
13
|
-
readonly tone?: MailTone;
|
|
14
|
-
};
|
|
15
|
-
export declare function parseBadge(value: unknown): MailBadge | undefined;
|
|
16
|
-
export declare function renderBadge(badge: MailBadge): string;
|
|
17
|
-
export declare function renderChips(chips: unknown): string;
|
|
18
|
-
export declare function renderStages(stages: unknown, primaryColor: string): string;
|
|
19
|
-
export declare function badgeText(badge: MailBadge): string;
|
|
20
|
-
export declare function stagesText(stages: unknown): string;
|
|
21
|
-
export declare function chipsText(chips: unknown): string;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
|
-
// Literal types (as const) keep the colors compile-time known, so they can go into style
|
|
3
|
-
// attributes without escaping.
|
|
4
|
-
const TONE_COLORS = {
|
|
5
|
-
neutral: { bg: "#f3f4f6", fg: "#374151" },
|
|
6
|
-
info: { bg: "#dbeafe", fg: "#1e40af" },
|
|
7
|
-
success: { bg: "#dcfce7", fg: "#166534" },
|
|
8
|
-
warning: { bg: "#fef3c7", fg: "#92400e" },
|
|
9
|
-
danger: { bg: "#fee2e2", fg: "#991b1b" },
|
|
10
|
-
};
|
|
11
|
-
const STAGE_STATES = ["done", "current", "upcoming"];
|
|
12
|
-
const STAGE_DONE_COLOR = "#16a34a";
|
|
13
|
-
const STAGE_UPCOMING_BG = "#e5e7eb";
|
|
14
|
-
const STAGE_UPCOMING_FG = "#9ca3af";
|
|
15
|
-
function isRecord(value) {
|
|
16
|
-
return typeof value === "object" && value !== null;
|
|
17
|
-
}
|
|
18
|
-
function isMailTone(value) {
|
|
19
|
-
return typeof value === "string" && Object.hasOwn(TONE_COLORS, value);
|
|
20
|
-
}
|
|
21
|
-
function toneColors(tone) {
|
|
22
|
-
return TONE_COLORS[isMailTone(tone) ? tone : "neutral"];
|
|
23
|
-
}
|
|
24
|
-
function isStageState(value) {
|
|
25
|
-
return STAGE_STATES.some((state) => state === value);
|
|
26
|
-
}
|
|
27
|
-
// Template data is runtime data (variables of a stored template): entries that do not match are
|
|
28
|
-
// skipped instead of failing the whole mail.
|
|
29
|
-
function isStage(value) {
|
|
30
|
-
return isRecord(value) && typeof value["label"] === "string" && isStageState(value["state"]);
|
|
31
|
-
}
|
|
32
|
-
function isChip(value) {
|
|
33
|
-
return isRecord(value) && typeof value["label"] === "string";
|
|
34
|
-
}
|
|
35
|
-
export function parseBadge(value) {
|
|
36
|
-
return isChip(value) && value.label !== "" ? value : undefined;
|
|
37
|
-
}
|
|
38
|
-
function validStages(value) {
|
|
39
|
-
return Array.isArray(value) ? value.filter(isStage) : [];
|
|
40
|
-
}
|
|
41
|
-
function validChips(value) {
|
|
42
|
-
return Array.isArray(value) ? value.filter(isChip) : [];
|
|
43
|
-
}
|
|
44
|
-
function pill(chip, margin) {
|
|
45
|
-
const { bg, fg } = toneColors(chip.tone);
|
|
46
|
-
return `<span style="display:inline-block;margin:${margin};padding:2px 10px;border-radius:999px;background:${bg};color:${fg};font-size:12px;font-weight:600;line-height:18px">${escapeHtml(chip.label)}</span>`;
|
|
47
|
-
}
|
|
48
|
-
export function renderBadge(badge) {
|
|
49
|
-
return `<p style="margin:0 0 12px">${pill(badge, "0")}</p>`;
|
|
50
|
-
}
|
|
51
|
-
export function renderChips(chips) {
|
|
52
|
-
const valid = validChips(chips);
|
|
53
|
-
if (valid.length === 0)
|
|
54
|
-
return "";
|
|
55
|
-
return `<p style="margin:0 0 16px">${valid.map((chip) => pill(chip, "0 6px 6px 0")).join("")}</p>`;
|
|
56
|
-
}
|
|
57
|
-
function stageMarker(stage, position, primaryColor) {
|
|
58
|
-
const base = "display:inline-block;width:24px;height:24px;line-height:24px;border-radius:12px;text-align:center;font-size:12px;font-weight:700";
|
|
59
|
-
const primary = escapeHtmlAttr(primaryColor);
|
|
60
|
-
if (stage.state === "done") {
|
|
61
|
-
return `<span style="${base};background:${STAGE_DONE_COLOR};color:#fff">✓</span>`;
|
|
62
|
-
}
|
|
63
|
-
if (stage.state === "current") {
|
|
64
|
-
return `<span style="${base};background:#fff;border:2px solid ${primary};line-height:20px;width:20px;height:20px;color:${primary}">${position}</span>`;
|
|
65
|
-
}
|
|
66
|
-
return `<span style="${base};background:${STAGE_UPCOMING_BG};color:${STAGE_UPCOMING_FG}">${position}</span>`;
|
|
67
|
-
}
|
|
68
|
-
function stageCell(stage, position, primaryColor) {
|
|
69
|
-
const labelColor = stage.state === "upcoming" ? STAGE_UPCOMING_FG : "#111";
|
|
70
|
-
const weight = stage.state === "current" ? 700 : 400;
|
|
71
|
-
return `<td align="center" valign="top" style="padding:0 4px;font-family:sans-serif">${stageMarker(stage, position, primaryColor)}<div style="margin:4px 0 0;font-size:12px;font-weight:${weight};color:${labelColor}">${escapeHtml(stage.label)}</div></td>`;
|
|
72
|
-
}
|
|
73
|
-
function stageConnector(previous) {
|
|
74
|
-
const color = previous.state === "done" ? STAGE_DONE_COLOR : STAGE_UPCOMING_BG;
|
|
75
|
-
return `<td valign="top" style="padding:11px 0 0"><div style="width:24px;height:2px;line-height:2px;font-size:2px;background:${color}"> </div></td>`;
|
|
76
|
-
}
|
|
77
|
-
export function renderStages(stages, primaryColor) {
|
|
78
|
-
const valid = validStages(stages);
|
|
79
|
-
if (valid.length === 0)
|
|
80
|
-
return "";
|
|
81
|
-
const cellsHtml = valid
|
|
82
|
-
.map((stage, index) => {
|
|
83
|
-
const previous = valid[index - 1];
|
|
84
|
-
const connector = previous ? stageConnector(previous) : "";
|
|
85
|
-
return `${connector}${stageCell(stage, index + 1, primaryColor)}`;
|
|
86
|
-
})
|
|
87
|
-
.join("");
|
|
88
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 16px"><tr>${cellsHtml}</tr></table>`;
|
|
89
|
-
}
|
|
90
|
-
const STAGE_TEXT_MARKER = {
|
|
91
|
-
done: "✓",
|
|
92
|
-
current: "▶",
|
|
93
|
-
upcoming: "○",
|
|
94
|
-
};
|
|
95
|
-
export function badgeText(badge) {
|
|
96
|
-
return `[${badge.label}]`;
|
|
97
|
-
}
|
|
98
|
-
export function stagesText(stages) {
|
|
99
|
-
return validStages(stages)
|
|
100
|
-
.map((stage) => `${STAGE_TEXT_MARKER[stage.state]} ${stage.label}`)
|
|
101
|
-
.join(" → ");
|
|
102
|
-
}
|
|
103
|
-
export function chipsText(chips) {
|
|
104
|
-
return validChips(chips)
|
|
105
|
-
.map((chip) => chip.label)
|
|
106
|
-
.join(" · ");
|
|
107
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { RendererContext, RenderRequest } from "../renderer-foundation/index.js";
|
|
2
|
-
type NotificationRequest = Extract<RenderRequest, {
|
|
3
|
-
kind: "notification";
|
|
4
|
-
}>;
|
|
5
|
-
/** Merge template-resolver content (plain JSON EmailTemplateData) with runtime variables. */
|
|
6
|
-
export declare function resolveNotificationVariables(req: NotificationRequest, ctx: RendererContext): Promise<Readonly<Record<string, unknown>>>;
|
|
7
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { createTemplateResolverApi, FALLBACK_LOCALE, TemplateNotFoundError, } from "../template-resolver/index.js";
|
|
2
|
-
/** Merge template-resolver content (plain JSON EmailTemplateData) with runtime variables. */
|
|
3
|
-
export async function resolveNotificationVariables(req, ctx) {
|
|
4
|
-
const variables = req.payload.variables ?? {};
|
|
5
|
-
const slug = req.payload.template?.trim();
|
|
6
|
-
if (!slug || req.payload.content || !ctx.db || !ctx.registry.features.has("template-resolver")) {
|
|
7
|
-
return variables;
|
|
8
|
-
}
|
|
9
|
-
try {
|
|
10
|
-
const api = createTemplateResolverApi(ctx.db);
|
|
11
|
-
const locale = req.payload.locale ?? FALLBACK_LOCALE;
|
|
12
|
-
const resolved = await api.resolveTemplate({
|
|
13
|
-
tenantId: ctx.tenantId,
|
|
14
|
-
slug,
|
|
15
|
-
kind: "notification",
|
|
16
|
-
locale,
|
|
17
|
-
});
|
|
18
|
-
if (resolved.contentFormat === "plain") {
|
|
19
|
-
const base = parsePlainTemplateContent(resolved.content);
|
|
20
|
-
return { ...base, ...variables };
|
|
21
|
-
}
|
|
22
|
-
// body always wins from the resolved template for rendered formats — a runtime
|
|
23
|
-
// "variables.body" override would clobber the actual rendered content.
|
|
24
|
-
return { ...variables, body: resolved.content };
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
if (err instanceof TemplateNotFoundError) {
|
|
28
|
-
return variables;
|
|
29
|
-
}
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function parsePlainTemplateContent(content) {
|
|
34
|
-
const trimmed = content.trim();
|
|
35
|
-
if (!trimmed)
|
|
36
|
-
return {};
|
|
37
|
-
try {
|
|
38
|
-
const parsed = JSON.parse(trimmed);
|
|
39
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
40
|
-
return parsed;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// ponytail: non-JSON plain content becomes a single body section downstream
|
|
45
|
-
}
|
|
46
|
-
return { body: content };
|
|
47
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { NotificationRenderer } from "../delivery/index.js";
|
|
2
|
-
export type MailBranding = {
|
|
3
|
-
/** Shown as text when there is no logo, and as the logo alt text. */
|
|
4
|
-
readonly productName?: string;
|
|
5
|
-
/** Hex color (#rgb, #rrggbb, #rrggbbaa); used for buttons and the header accent. */
|
|
6
|
-
readonly primaryColor?: string;
|
|
7
|
-
/** Absolute http(s) URL; anything else is dropped. Mutually exclusive with `logoPath`. */
|
|
8
|
-
readonly logoUrl?: string;
|
|
9
|
-
/** Absolute path on `baseUrl` (e.g. "/logo.png"); use PNG/JPEG, mail clients block SVG. */
|
|
10
|
-
readonly logoPath?: string;
|
|
11
|
-
/** Absolute http(s) origin that `logoPath` is resolved against; same base as `auth.mail.baseUrl`. */
|
|
12
|
-
readonly baseUrl?: string;
|
|
13
|
-
/** Locale tried after the exact and language-part match when a localized field has no entry. */
|
|
14
|
-
readonly defaultLocale?: string;
|
|
15
|
-
readonly footerText?: LocalizedText;
|
|
16
|
-
/** Absolute http(s) URLs only; string URLs that are not are dropped, localized ones fail at boot. */
|
|
17
|
-
readonly footerLinks?: readonly {
|
|
18
|
-
readonly label: LocalizedText;
|
|
19
|
-
readonly url: LocalizedText;
|
|
20
|
-
}[];
|
|
21
|
-
};
|
|
22
|
-
export type LocalizedText = string | Readonly<Record<string, string>>;
|
|
23
|
-
export declare function createSimpleRenderer(branding?: MailBranding): NotificationRenderer;
|
|
24
|
-
export declare const simpleRenderer: NotificationRenderer;
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
|
-
import { renderSafeMarkdown } from "../page-render/index.js";
|
|
3
|
-
import { badgeText, chipsText, parseBadge, renderBadge, renderChips, renderStages, stagesText, } from "./mail-blocks.js";
|
|
4
|
-
const DEFAULT_PRIMARY_COLOR = "#2563eb";
|
|
5
|
-
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
6
|
-
function isHttpUrl(value) {
|
|
7
|
-
try {
|
|
8
|
-
const { protocol } = new URL(value);
|
|
9
|
-
return protocol === "http:" || protocol === "https:";
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function languagePart(locale) {
|
|
16
|
-
return locale.split("-")[0] ?? locale;
|
|
17
|
-
}
|
|
18
|
-
function pickByLocale(entries, locale) {
|
|
19
|
-
// Own keys only: a locale like "constructor" must not hit Object.prototype.
|
|
20
|
-
const ownEntry = (key) => Object.hasOwn(entries, key) ? entries[key] : undefined;
|
|
21
|
-
return ownEntry(locale) ?? ownEntry(languagePart(locale));
|
|
22
|
-
}
|
|
23
|
-
function resolveLocalized(value, locale, defaultLocale) {
|
|
24
|
-
if (value === undefined || typeof value === "string")
|
|
25
|
-
return value;
|
|
26
|
-
return ((locale !== undefined ? pickByLocale(value, locale) : undefined) ??
|
|
27
|
-
(defaultLocale !== undefined ? pickByLocale(value, defaultLocale) : undefined) ??
|
|
28
|
-
Object.values(value)[0]);
|
|
29
|
-
}
|
|
30
|
-
function assertLocalizedUrlsAreHttp(branding) {
|
|
31
|
-
for (const link of branding.footerLinks ?? []) {
|
|
32
|
-
if (typeof link.url === "string")
|
|
33
|
-
continue;
|
|
34
|
-
for (const [locale, url] of Object.entries(link.url)) {
|
|
35
|
-
if (!isHttpUrl(url)) {
|
|
36
|
-
throw new Error(`createSimpleRenderer: footerLinks url for locale "${locale}" must be an absolute http(s) URL`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function resolveLogoUrl(branding) {
|
|
42
|
-
if (!branding)
|
|
43
|
-
return undefined;
|
|
44
|
-
const { logoUrl, logoPath, baseUrl } = branding;
|
|
45
|
-
if (logoPath === undefined) {
|
|
46
|
-
return logoUrl && isHttpUrl(logoUrl) ? logoUrl : undefined;
|
|
47
|
-
}
|
|
48
|
-
if (logoUrl !== undefined) {
|
|
49
|
-
throw new Error("createSimpleRenderer: logoPath and logoUrl are mutually exclusive");
|
|
50
|
-
}
|
|
51
|
-
if (baseUrl === undefined || !isHttpUrl(baseUrl)) {
|
|
52
|
-
throw new Error("createSimpleRenderer: logoPath requires an absolute http(s) baseUrl");
|
|
53
|
-
}
|
|
54
|
-
if (!/^\/(?![/\\])/.test(logoPath) || logoPath.includes("\\")) {
|
|
55
|
-
throw new Error('createSimpleRenderer: logoPath must start with a single "/"');
|
|
56
|
-
}
|
|
57
|
-
const resolved = new URL(logoPath, baseUrl);
|
|
58
|
-
if (resolved.origin !== new URL(baseUrl).origin) {
|
|
59
|
-
throw new Error("createSimpleRenderer: logoPath must stay on the baseUrl origin");
|
|
60
|
-
}
|
|
61
|
-
return resolved.href;
|
|
62
|
-
}
|
|
63
|
-
function resolvePrimaryColor(branding) {
|
|
64
|
-
const color = branding?.primaryColor;
|
|
65
|
-
return color && HEX_COLOR_PATTERN.test(color) ? color : DEFAULT_PRIMARY_COLOR;
|
|
66
|
-
}
|
|
67
|
-
function renderBrandingHeader(branding, primaryColor, logoUrl) {
|
|
68
|
-
if (!branding)
|
|
69
|
-
return "";
|
|
70
|
-
if (!logoUrl && !branding.productName)
|
|
71
|
-
return "";
|
|
72
|
-
const logo = logoUrl
|
|
73
|
-
? `<img src="${escapeHtmlAttr(logoUrl)}" alt="${escapeHtmlAttr(branding.productName ?? "")}" style="display:block;max-height:40px;max-width:200px;border:0" />`
|
|
74
|
-
: `<span style="font-size:18px;font-weight:700;color:${escapeHtmlAttr(primaryColor)}">${escapeHtml(branding.productName ?? "")}</span>`;
|
|
75
|
-
return `<div style="margin:0 0 24px;padding:0 0 16px;border-bottom:3px solid ${escapeHtmlAttr(primaryColor)}">${logo}</div>`;
|
|
76
|
-
}
|
|
77
|
-
function resolveBrandingFooter(branding, locale) {
|
|
78
|
-
const pick = (value) => resolveLocalized(value, locale, branding.defaultLocale);
|
|
79
|
-
const links = (branding.footerLinks ?? []).flatMap((link) => {
|
|
80
|
-
const url = pick(link.url);
|
|
81
|
-
if (url === undefined || !isHttpUrl(url))
|
|
82
|
-
return [];
|
|
83
|
-
return [{ label: pick(link.label) ?? "", url }];
|
|
84
|
-
});
|
|
85
|
-
return { footerText: pick(branding.footerText), links };
|
|
86
|
-
}
|
|
87
|
-
function renderBrandingFooter(branding, locale) {
|
|
88
|
-
if (!branding)
|
|
89
|
-
return "";
|
|
90
|
-
const { footerText, links: resolvedLinks } = resolveBrandingFooter(branding, locale);
|
|
91
|
-
const links = resolvedLinks.map((link) => `<a href="${escapeHtmlAttr(link.url)}" style="color:#999">${escapeHtml(link.label)}</a>`);
|
|
92
|
-
const footerPartsHtml = [footerText ? escapeHtml(footerText) : "", links.join(" · ")].filter((part) => part !== "");
|
|
93
|
-
if (footerPartsHtml.length === 0)
|
|
94
|
-
return "";
|
|
95
|
-
const footerHtml = footerPartsHtml.join("<br />");
|
|
96
|
-
return `<p style="margin:16px 0 0;color:#999;font-size:12px">${footerHtml}</p>`;
|
|
97
|
-
}
|
|
98
|
-
function brandingFooterLines(branding, locale) {
|
|
99
|
-
if (!branding)
|
|
100
|
-
return [];
|
|
101
|
-
const { footerText, links } = resolveBrandingFooter(branding, locale);
|
|
102
|
-
return [
|
|
103
|
-
...(footerText ? [footerText] : []),
|
|
104
|
-
...links.map((link) => (link.label ? `${link.label}: ${link.url}` : link.url)),
|
|
105
|
-
];
|
|
106
|
-
}
|
|
107
|
-
function templateContent(variables) {
|
|
108
|
-
const data = variables; // @cast-boundary render-helper
|
|
109
|
-
// Without structured fields, title + body become the header and a single text section.
|
|
110
|
-
return {
|
|
111
|
-
badge: parseBadge(data.badge),
|
|
112
|
-
header: data.header ?? data.title,
|
|
113
|
-
sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
|
|
114
|
-
footer: data.footer,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
// Markdown stays as written: its source is already readable plain text.
|
|
118
|
-
function sectionText(section) {
|
|
119
|
-
if ("text" in section)
|
|
120
|
-
return section.text;
|
|
121
|
-
if ("heading" in section)
|
|
122
|
-
return section.heading;
|
|
123
|
-
if ("markdown" in section)
|
|
124
|
-
return section.markdown;
|
|
125
|
-
if ("button" in section)
|
|
126
|
-
return `${section.button.label}: ${section.button.url}`;
|
|
127
|
-
if ("stages" in section)
|
|
128
|
-
return stagesText(section.stages);
|
|
129
|
-
if ("chips" in section)
|
|
130
|
-
return chipsText(section.chips);
|
|
131
|
-
return "";
|
|
132
|
-
}
|
|
133
|
-
function renderSection(section, primaryColor) {
|
|
134
|
-
if ("text" in section) {
|
|
135
|
-
return `<p style="margin:0 0 16px;color:#333;font-size:14px;line-height:1.5">${escapeHtml(section.text)}</p>`;
|
|
136
|
-
}
|
|
137
|
-
if ("heading" in section) {
|
|
138
|
-
return `<h2 style="margin:24px 0 12px;color:#111;font-size:16px;font-weight:600">${escapeHtml(section.heading)}</h2>`;
|
|
139
|
-
}
|
|
140
|
-
if ("markdown" in section) {
|
|
141
|
-
const markdownHtml = renderSafeMarkdown(section.markdown);
|
|
142
|
-
return `<div style="color:#333;font-size:14px;line-height:1.5">${markdownHtml}</div>`;
|
|
143
|
-
}
|
|
144
|
-
if ("button" in section) {
|
|
145
|
-
return `<p style="margin:0 0 16px"><a href="${escapeHtml(section.button.url)}" style="display:inline-block;padding:10px 24px;background:${escapeHtmlAttr(primaryColor)};color:#fff;text-decoration:none;border-radius:4px;font-size:14px">${escapeHtml(section.button.label)}</a></p>`;
|
|
146
|
-
}
|
|
147
|
-
if ("stages" in section)
|
|
148
|
-
return renderStages(section.stages, primaryColor);
|
|
149
|
-
if ("chips" in section)
|
|
150
|
-
return renderChips(section.chips);
|
|
151
|
-
return "";
|
|
152
|
-
}
|
|
153
|
-
// Simple Renderer: turns structured email template data into HTML with inline CSS.
|
|
154
|
-
// No external dependencies, no template engine — just string concatenation.
|
|
155
|
-
export function createSimpleRenderer(branding) {
|
|
156
|
-
const primaryColor = resolvePrimaryColor(branding);
|
|
157
|
-
const logoUrl = resolveLogoUrl(branding);
|
|
158
|
-
if (branding)
|
|
159
|
-
assertLocalizedUrlsAreHttp(branding);
|
|
160
|
-
return {
|
|
161
|
-
name: "simple",
|
|
162
|
-
async render(input) {
|
|
163
|
-
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
164
|
-
const parts = [];
|
|
165
|
-
parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
|
|
166
|
-
parts.push('<div style="max-width:600px;margin:0 auto;padding:24px">');
|
|
167
|
-
parts.push(renderBrandingHeader(branding, primaryColor, logoUrl));
|
|
168
|
-
if (badge)
|
|
169
|
-
parts.push(renderBadge(badge));
|
|
170
|
-
if (header) {
|
|
171
|
-
parts.push(`<h1 style="margin:0 0 24px;color:#111;font-size:20px;font-weight:600">${escapeHtml(header)}</h1>`);
|
|
172
|
-
}
|
|
173
|
-
if (sections) {
|
|
174
|
-
for (const section of sections) {
|
|
175
|
-
parts.push(renderSection(section, primaryColor));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (footer) {
|
|
179
|
-
parts.push(`<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(footer)}</p>`);
|
|
180
|
-
}
|
|
181
|
-
parts.push(renderBrandingFooter(branding, input.locale));
|
|
182
|
-
parts.push("</div></body></html>");
|
|
183
|
-
return parts.join("");
|
|
184
|
-
},
|
|
185
|
-
async renderText(input) {
|
|
186
|
-
const { badge, header, sections, footer } = templateContent(input.variables);
|
|
187
|
-
const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
|
|
188
|
-
return [
|
|
189
|
-
badge ? badgeText(badge) : undefined,
|
|
190
|
-
header,
|
|
191
|
-
...(sections ?? []).map(sectionText),
|
|
192
|
-
footer,
|
|
193
|
-
brandingFooter,
|
|
194
|
-
]
|
|
195
|
-
.filter((block) => block !== undefined && block !== "")
|
|
196
|
-
.join("\n\n");
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
export const simpleRenderer = createSimpleRenderer();
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"version": "0.352.0",
|
|
4
|
-
"type": "improvement",
|
|
5
|
-
"title": "Badge, stages track and chips for the simple mail renderer",
|
|
6
|
-
"detail": "Use badge, { stages } and { chips } in the template variables; the notification RenderResponse carries the matching plain text in text."
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
"version": "0.344.0",
|
|
10
|
-
"type": "improvement",
|
|
11
|
-
"title": "Heading and Markdown sections in the simple mail renderer",
|
|
12
|
-
"detail": "`createSimpleRenderer` accepts two more section kinds: `{ heading: string }` (rendered as an escaped `<h2>`) and `{ markdown: string }` (rendered with the hardened Markdown renderer; raw HTML is escaped and unsafe link targets are replaced by `#`). Existing section kinds render as before."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"version": "0.341.0",
|
|
16
|
-
"type": "improvement",
|
|
17
|
-
"title": "Localized mail branding, locale through delivery, logoPath",
|
|
18
|
-
"detail": "`ctx.notify` accepts `locale`, and the locale now travels through the delivery jobs (`delivery.render`, `delivery.send`) to `NotificationRenderer.render`, where `RendererInput.locale` is available. Jobs queued before this release have no locale and keep working. The email channel and `renderer-simple` pass it on; the auth mails and the consumer-protection mails set it to the language they already translate their content into. This lands in the same release as the `NotifyOptions` change from #3508.\n`MailBranding` for `renderer-simple` can now be localized: `footerText`, `footerLinks[].label` and `footerLinks[].url` take a plain string or a map of locale to string. The renderer picks the exact locale, then the language part (`de-AT` falls back to `de`), then the new optional `MailBranding.defaultLocale`, then the first entry. Every URL in a map must be an absolute http(s) URL, otherwise `createSimpleRenderer` throws at boot. Without a locale and with plain strings the HTML is unchanged.\n`MailBranding` also gets `logoPath` and `baseUrl`, so an app can point at a raster logo on its own origin (`logoPath: \"/logo.png\"`, `baseUrl` the same base as `auth.mail.baseUrl`). `logoPath` and `logoUrl` exclude each other, `logoPath` needs a valid `baseUrl`, must start with a single `/` and must stay on the base origin; violations throw at boot. Use PNG or JPEG, because mail clients block SVG."
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"version": "0.339.0",
|
|
22
|
-
"type": "improvement",
|
|
23
|
-
"title": "createRendererSimpleFeature({ mailBranding }) brands every notification mail, including the auth mails, with product name or logo, primary color and footer text and links",
|
|
24
|
-
"detail": "renderer-simple accepts mailBranding for all framework notification mails",
|
|
25
|
-
"migration": "No code change needed."
|
|
26
|
-
}
|
|
27
|
-
]
|