@cosmicdrift/kumiko-renderer-mail-html 0.373.0 → 0.377.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.
@@ -4,6 +4,8 @@ import Handlebars from "handlebars";
4
4
  export type DocumentFormat = {
5
5
  readonly locale: string;
6
6
  readonly currency: string;
7
+ /** Tenant zone for `{{date x time=true}}`; absent = UTC. */
8
+ readonly timeZone?: string;
7
9
  };
8
10
  export declare function createDocumentHandlebars(format: DocumentFormat): typeof Handlebars;
9
11
  export declare function compileDocumentSource(hbs: typeof Handlebars, source: string, options?: {
@@ -1,11 +1,14 @@
1
1
  import { BRANDING_QN } from "@cosmicdrift/kumiko-bundled-features/managed-pages";
2
2
  import { isSafeHttpsUrl } from "@cosmicdrift/kumiko-bundled-features/page-render";
3
3
  import { collectDocumentResourceKeys, flattenParagraphsToLineBreaks, isHexColor, sanitizeDocumentHtml, TemplateNotFoundError, } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
4
+ import { decryptTenantNames, TENANT_FEATURE, tenantTable, } from "@cosmicdrift/kumiko-bundled-features/tenant";
4
5
  import { selectMany } from "@cosmicdrift/kumiko-framework/db";
5
6
  import { isUuid, SYSTEM_TENANT_ID, TENANT_CURRENCY_CONFIG_KEY, } from "@cosmicdrift/kumiko-framework/engine";
6
7
  import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
7
8
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
8
- import { DOCUMENT_FALLBACK_CURRENCY, formatDocumentDate, formatDocumentMoney, formatDocumentPercent, } from "@cosmicdrift/kumiko-headless";
9
+ import { TENANT_TIMEZONE_CONFIG_KEY } from "@cosmicdrift/kumiko-framework/pipeline";
10
+ import { isValidIanaTimeZone } from "@cosmicdrift/kumiko-framework/time";
11
+ import { DOCUMENT_FALLBACK_CURRENCY, formatDocumentDate, formatDocumentMoney, formatDocumentPercent, } from "@cosmicdrift/kumiko-renderer/headless";
9
12
  import Handlebars from "handlebars";
10
13
  // Handlebars instance, template resolution and resource lookup shared by the
11
14
  // mail and PDF renderers, so both format money/dates and find images the same way.
@@ -14,7 +17,10 @@ const SAFE_LINK_PROTOCOL = /^(https?:|mailto:)/i;
14
17
  export function createDocumentHandlebars(format) {
15
18
  const hbs = Handlebars.create();
16
19
  hbs.registerHelper("money", (value) => formatDocumentMoney(value, format.currency, format.locale));
17
- hbs.registerHelper("date", (value) => formatDocumentDate(value, format.locale));
20
+ hbs.registerHelper("date", (value, options) => formatDocumentDate(value, format.locale, {
21
+ time: options?.hash?.["time"] === true,
22
+ ...(format.timeZone !== undefined && { timeZone: format.timeZone }),
23
+ }));
18
24
  hbs.registerHelper("percent", (value) => formatDocumentPercent(value, format.locale));
19
25
  hbs.registerHelper("richText", (value, options) => {
20
26
  if (typeof value !== "string")
@@ -79,10 +85,23 @@ async function readConfigText(ctx, key) {
79
85
  // "Oct 1, 2026". The currency is a tenant fact and still comes from its settings.
80
86
  export async function readDocumentFormat(ctx, templateLocale) {
81
87
  const currency = await readConfigText(ctx, TENANT_CURRENCY_CONFIG_KEY);
82
- return { locale: templateLocale, currency: currency ?? DOCUMENT_FALLBACK_CURRENCY };
88
+ const zone = await readConfigText(ctx, TENANT_TIMEZONE_CONFIG_KEY);
89
+ return {
90
+ locale: templateLocale,
91
+ currency: currency ?? DOCUMENT_FALLBACK_CURRENCY,
92
+ ...(zone !== undefined && isValidIanaTimeZone(zone) && { timeZone: zone }),
93
+ };
83
94
  }
95
+ // Without managed-pages there is no branding title; the tenant record's own name is the fallback.
84
96
  export async function readTenantName(ctx) {
85
- return (await readConfigText(ctx, BRANDING_QN.title)) ?? "";
97
+ const branded = await readConfigText(ctx, BRANDING_QN.title);
98
+ if (branded !== undefined)
99
+ return branded;
100
+ if (ctx.registry.getFeature(TENANT_FEATURE) === undefined)
101
+ return "";
102
+ const rows = await selectMany(ctx.db, tenantTable, { id: ctx.tenantId });
103
+ const [row] = await decryptTenantNames(rows, "renderer-mail-html:tenant-name");
104
+ return typeof row?.name === "string" ? row.name : "";
86
105
  }
87
106
  export async function readBrandingAccentColor(ctx) {
88
107
  const value = await readConfigText(ctx, BRANDING_QN.accentColor);
@@ -1,5 +1,5 @@
1
1
  import { isHexColor, MAIL_TONES, } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
2
- import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
2
+ import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-renderer/headless";
3
3
  import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
4
4
  const FIXED_TONES = {
5
5
  info: { bar: "#7C8DB5", fg: "#3E4C70", soft: "#EEF1F7" },
@@ -1,4 +1,4 @@
1
- import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
1
+ import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-renderer/headless";
2
2
  import { mailChromeText } from "./mail-strings.js";
3
3
  const SAFE_FOOTER_URL = /^(https?:|mailto:)/i;
4
4
  const BAND_LINK_COLOR = "#4A515E";
@@ -1,7 +1,7 @@
1
1
  // Renders a structured (html) mail layout: the tenant-editable regions go into
2
2
  // a fixed in-code MJML frame, so a tenant layout never contributes MJML source.
3
3
  import { compileDocumentBodyToHandlebars, isHexColor, mailLayoutAppearance, readLayoutRegions, } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
4
- import { escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
4
+ import { escapeHtmlAttr } from "@cosmicdrift/kumiko-renderer/headless";
5
5
  import { headerRowHtml } from "./mail-chrome.js";
6
6
  // The logo of a mail layout and of a mail body is capped here; the editors show the same width.
7
7
  export const MAIL_IMAGE_MAX_WIDTH_PX = 180;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer-mail-html",
3
- "version": "0.373.0",
3
+ "version": "0.377.0",
4
4
  "description": "Renderer-Foundation plugin for branded HTML emails: mail-kind templates (document HTML + subject meta) rendered with isolated Handlebars into an MJML layout, plus plain text via html-to-text. Templates resolved from template-resolver.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -19,17 +19,17 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@cosmicdrift/kumiko-bundled-features": "0.373.0",
23
- "@cosmicdrift/kumiko-framework": "0.373.0",
24
- "@cosmicdrift/kumiko-headless": "0.373.0",
25
- "@cosmicdrift/kumiko-types": "0.373.0",
22
+ "@cosmicdrift/kumiko-bundled-features": "0.377.0",
23
+ "@cosmicdrift/kumiko-framework": "0.377.0",
24
+ "@cosmicdrift/kumiko-renderer": "0.377.0",
25
+ "@cosmicdrift/kumiko-types": "0.377.0",
26
26
  "handlebars": "^4.7.8",
27
27
  "html-to-text": "^9.0.5",
28
28
  "mjml": "^5.2.2"
29
29
  },
30
30
  "devDependencies": {
31
- "@cosmicdrift/kumiko-locale-de": "0.373.0",
32
- "@cosmicdrift/kumiko-testing": "0.373.0",
31
+ "@cosmicdrift/kumiko-locale-de": "0.377.0",
32
+ "@cosmicdrift/kumiko-testing": "0.377.0",
33
33
  "@types/html-to-text": "^9.0.4",
34
34
  "@types/mjml": "^5.0.0"
35
35
  },
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.377.0",
4
+ "type": "improvement",
5
+ "title": "Document dates can print the time in the tenant time zone",
6
+ "detail": "The date helper accepts time=true ({{date value time=true}}). Instants print date and time in the tenant's configured IANA zone (tenant timezone config, UTC without one), offset date-times keep their own wall clock, date-only values print no time. Without time=true nothing changes. formatDocumentDate takes an optional { time, timeZone } argument, DocumentFormat gets timeZone, and TENANT_TIMEZONE_CONFIG_KEY is exported from framework/pipeline."
7
+ },
8
+ {
9
+ "version": "0.377.0",
10
+ "type": "fix",
11
+ "title": "tenantName in the document context falls back to the tenant record",
12
+ "detail": "Without managed-pages there is no branding title, so tenantName was empty. It now falls back to the decrypted name of the tenant record when the tenant feature is mounted."
13
+ },
2
14
  {
3
15
  "version": "0.367.0",
4
16
  "type": "improvement",