vintasend-liquidjs 1.0.0-alpha7 → 1.0.0-alpha9

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.
@@ -17,7 +17,10 @@ export type LiquidTemplateRendererOptions = LiquidOptions & {
17
17
  *
18
18
  * The body is rendered with `outputEscape: 'escape'` unless `options.outputEscape` says otherwise,
19
19
  * so context values cannot inject markup into an HTML email; use `{{ value | raw }}` for values
20
- * that are trusted HTML. The subject is plain text and is never escaped; its whitespace, including
20
+ * that are trusted HTML. When template content carries a `preheader` (as managed templates do),
21
+ * it is rendered with the body's escaping and injected as a hidden element at the top of the body,
22
+ * which is what mail clients show as inbox preview text. The file-based `render` has no preheader.
23
+ * The subject is plain text and is never escaped; its whitespace, including
21
24
  * the trailing newline of a template file, is collapsed to single spaces.
22
25
  */
23
26
  export declare class LiquidEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> implements BaseEmailTemplateRenderer<Config> {
@@ -27,6 +30,8 @@ export declare class LiquidEmailTemplateRenderer<Config extends BaseNotification
27
30
  /** What this renderer produces, for log lines. */
28
31
  protected readonly templateKind: string;
29
32
  protected readonly subjectRequired: boolean;
33
+ /** Whether a preheader in template content is injected into the body. Off for plain text. */
34
+ protected readonly preheaderSupported: boolean;
30
35
  private readonly bodyEngine;
31
36
  private readonly subjectEngine;
32
37
  constructor(options?: LiquidTemplateRendererOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"liquid-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-email-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,EACzB,UAAU,EACV,0BAA0B,EAC1B,aAAa,EACb,oBAAoB,EACpB,UAAU,EACX,MAAM,WAAW,CAAC;AAKnB,MAAM,MAAM,6BAA6B,GAAG,aAAa,GAAG;IAC1D;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,2BAA2B,CAAC,MAAM,SAAS,0BAA0B,CAChF,YAAW,yBAAyB,CAAC,MAAM,CAAC;IAUhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,6BAA6B;IARrE,MAAM,EAAE,UAAU,GAAG,IAAI,CAAQ;IACjC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAiC;IACxE,kDAAkD;IAClD,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAW;IAClD,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAQ;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAER,OAAO,GAAE,6BAAkC;IAQ1E;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IA6B1F,yBAAyB,CAC7B,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,EACrC,eAAe,EAAE,oBAAoB,EACrC,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,aAAa,CAAC;IAsBzB,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI7C,OAAO,CAAC,oBAAoB;IAO5B;;;;OAIG;YACW,UAAU;CAiBzB;AAMD,qBAAa,kCAAkC,CAAC,MAAM,SAAS,0BAA0B;IACvF,MAAM,CAAC,OAAO,GAAE,6BAAkC,GAAG,2BAA2B,CAAC,MAAM,CAAC;CAGzF"}
1
+ {"version":3,"file":"liquid-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-email-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,EACzB,UAAU,EACV,0BAA0B,EAC1B,aAAa,EACb,oBAAoB,EACpB,UAAU,EACX,MAAM,WAAW,CAAC;AAKnB,MAAM,MAAM,6BAA6B,GAAG,aAAa,GAAG;IAC1D;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,2BAA2B,CAAC,MAAM,SAAS,0BAA0B,CAChF,YAAW,yBAAyB,CAAC,MAAM,CAAC;IAYhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,6BAA6B;IAVrE,MAAM,EAAE,UAAU,GAAG,IAAI,CAAQ;IACjC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAiC;IACxE,kDAAkD;IAClD,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAW;IAClD,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAQ;IACnD,6FAA6F;IAC7F,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAQ;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAA+B,OAAO,GAAE,6BAAkC,EAMzE;IAED;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAErC;IAEK,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B/F;IAEK,yBAAyB,CAC7B,YAAY,EAAE,eAAe,CAAC,MAAM,CAAC,EACrC,eAAe,EAAE,oBAAoB,EACrC,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,aAAa,CAAC,CA8BxB;IAED,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;IAED,OAAO,CAAC,oBAAoB;IAO5B;;;;OAIG;YACW,UAAU;CAiBzB;AA2BD,qBAAa,kCAAkC,CAAC,MAAM,SAAS,0BAA0B;IACvF,MAAM,CAAC,OAAO,GAAE,6BAAkC,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAEvF;CACF"}
@@ -10,7 +10,10 @@ import { log, logError, logId, logLabel } from 'vintasend';
10
10
  *
11
11
  * The body is rendered with `outputEscape: 'escape'` unless `options.outputEscape` says otherwise,
12
12
  * so context values cannot inject markup into an HTML email; use `{{ value | raw }}` for values
13
- * that are trusted HTML. The subject is plain text and is never escaped; its whitespace, including
13
+ * that are trusted HTML. When template content carries a `preheader` (as managed templates do),
14
+ * it is rendered with the body's escaping and injected as a hidden element at the top of the body,
15
+ * which is what mail clients show as inbox preview text. The file-based `render` has no preheader.
16
+ * The subject is plain text and is never escaped; its whitespace, including
14
17
  * the trailing newline of a template file, is collapsed to single spaces.
15
18
  */
16
19
  export class LiquidEmailTemplateRenderer {
@@ -21,6 +24,8 @@ export class LiquidEmailTemplateRenderer {
21
24
  /** What this renderer produces, for log lines. */
22
25
  this.templateKind = 'email';
23
26
  this.subjectRequired = true;
27
+ /** Whether a preheader in template content is injected into the body. Off for plain text. */
28
+ this.preheaderSupported = true;
24
29
  const { configureEngine, ...liquidOptions } = options;
25
30
  this.bodyEngine = new Liquid({ outputEscape: 'escape', ...liquidOptions });
26
31
  this.subjectEngine = new Liquid({ ...liquidOptions, outputEscape: undefined });
@@ -54,6 +59,7 @@ export class LiquidEmailTemplateRenderer {
54
59
  }
55
60
  async renderFromTemplateContent(notification, templateContent, context) {
56
61
  const { subject: subjectContent, body: bodyContent } = templateContent;
62
+ const preheaderContent = templateContent.preheader;
57
63
  if (!subjectContent && this.subjectRequired) {
58
64
  throw new Error('Subject template is required');
59
65
  }
@@ -62,7 +68,13 @@ export class LiquidEmailTemplateRenderer {
62
68
  const subject = subjectContent
63
69
  ? await this.renderPart('subject', null, notification, () => this.subjectEngine.parseAndRender(subjectContent, context))
64
70
  : '';
65
- return { subject: normalizeSubject(subject), body: this.normalizeBody(body) };
71
+ const preheader = this.preheaderSupported && preheaderContent?.trim()
72
+ ? await this.renderPart('preheader', null, notification, () => this.bodyEngine.parseAndRender(preheaderContent, context))
73
+ : '';
74
+ return {
75
+ subject: normalizeSubject(subject),
76
+ body: this.normalizeBody(injectPreheader(body, preheader)),
77
+ };
66
78
  }
67
79
  normalizeBody(body) {
68
80
  return body;
@@ -90,6 +102,24 @@ export class LiquidEmailTemplateRenderer {
90
102
  }
91
103
  }
92
104
  }
105
+ const PREHEADER_STYLE = 'display:none;font-size:1px;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;visibility:hidden;';
106
+ /**
107
+ * Put `preheader` (already escaped by the engine) in a hidden element at the top of the body, or
108
+ * straight after the opening `<body>` tag when the body is a full document. No-op when blank.
109
+ */
110
+ function injectPreheader(body, preheader) {
111
+ const text = preheader.replace(/\s+/g, ' ').trim();
112
+ if (!text) {
113
+ return body;
114
+ }
115
+ const element = `<div style="${PREHEADER_STYLE}">${text}</div>`;
116
+ const bodyTag = /<body\b[^>]*>/i.exec(body);
117
+ if (bodyTag === null) {
118
+ return element + body;
119
+ }
120
+ const end = bodyTag.index + bodyTag[0].length;
121
+ return body.slice(0, end) + element + body.slice(end);
122
+ }
93
123
  function normalizeSubject(subject) {
94
124
  return subject.replace(/\s+/g, ' ').trim();
95
125
  }
@@ -1 +1 @@
1
- {"version":3,"file":"liquid-inline-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-inline-email-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,2BAA2B,EAC3B,KAAK,6BAA6B,EACnC,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;GASG;AACH,qBAAa,iCAAiC,CAC5C,MAAM,SAAS,0BAA0B,CACzC,SAAQ,2BAA2B,CAAC,MAAM,CAAC;IAC3C,mBAA4B,YAAY,EAAE,MAAM,CAAuC;gBAGrF,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAM;CAIjE;AAED,qBAAa,wCAAwC,CAAC,MAAM,SAAS,0BAA0B;IAC7F,MAAM,CACJ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAM,GAC7D,iCAAiC,CAAC,MAAM,CAAC;CAG7C"}
1
+ {"version":3,"file":"liquid-inline-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-inline-email-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,2BAA2B,EAC3B,KAAK,6BAA6B,EACnC,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;GASG;AACH,qBAAa,iCAAiC,CAC5C,MAAM,SAAS,0BAA0B,CACzC,SAAQ,2BAA2B,CAAC,MAAM,CAAC;IAC3C,mBAA4B,YAAY,EAAE,MAAM,CAAuC;IAEvF,YACE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAM,EAG/D;CACF;AAED,qBAAa,wCAAwC,CAAC,MAAM,SAAS,0BAA0B;IAC7F,MAAM,CACJ,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,OAAO,GAAE,IAAI,CAAC,6BAA6B,EAAE,WAAW,CAAM,GAC7D,iCAAiC,CAAC,MAAM,CAAC,CAE3C;CACF"}
@@ -18,6 +18,7 @@ export declare class LiquidSmsTemplateRenderer<Config extends BaseNotificationTy
18
18
  protected readonly rendererName: string;
19
19
  protected readonly templateKind: string;
20
20
  protected readonly subjectRequired: boolean;
21
+ protected readonly preheaderSupported: boolean;
21
22
  constructor(options?: LiquidTemplateRendererOptions);
22
23
  protected normalizeBody(body: string): string;
23
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"liquid-sms-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-sms-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,2BAA2B,EAC3B,KAAK,6BAA6B,EACnC,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAAyB,CACpC,MAAM,SAAS,0BAA0B,CACzC,SAAQ,2BAA2B,CAAC,MAAM,CAAC;IAC3C,mBAA4B,YAAY,EAAE,MAAM,CAA+B;IAC/E,mBAA4B,YAAY,EAAE,MAAM,CAAS;IACzD,mBAA4B,eAAe,EAAE,OAAO,CAAS;gBAEjD,OAAO,GAAE,6BAAkC;cAIpC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAGvD;AAED,qBAAa,gCAAgC,CAAC,MAAM,SAAS,0BAA0B;IACrF,MAAM,CAAC,OAAO,GAAE,6BAAkC,GAAG,yBAAyB,CAAC,MAAM,CAAC;CAGvF"}
1
+ {"version":3,"file":"liquid-sms-template-renderer.d.ts","sourceRoot":"","sources":["../src/liquid-sms-template-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EACL,2BAA2B,EAC3B,KAAK,6BAA6B,EACnC,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAAyB,CACpC,MAAM,SAAS,0BAA0B,CACzC,SAAQ,2BAA2B,CAAC,MAAM,CAAC;IAC3C,mBAA4B,YAAY,EAAE,MAAM,CAA+B;IAC/E,mBAA4B,YAAY,EAAE,MAAM,CAAS;IACzD,mBAA4B,eAAe,EAAE,OAAO,CAAS;IAC7D,mBAA4B,kBAAkB,EAAE,OAAO,CAAS;IAEhE,YAAY,OAAO,GAAE,6BAAkC,EAEtD;IAED,UAAmB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;CACF;AAED,qBAAa,gCAAgC,CAAC,MAAM,SAAS,0BAA0B;IACrF,MAAM,CAAC,OAAO,GAAE,6BAAkC,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAErF;CACF"}
@@ -19,6 +19,7 @@ export class LiquidSmsTemplateRenderer extends LiquidEmailTemplateRenderer {
19
19
  this.rendererName = 'LiquidSmsTemplateRenderer';
20
20
  this.templateKind = 'SMS';
21
21
  this.subjectRequired = false;
22
+ this.preheaderSupported = false;
22
23
  }
23
24
  normalizeBody(body) {
24
25
  return body.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintasend-liquidjs",
3
- "version": "1.0.0-alpha7",
3
+ "version": "1.0.0-alpha9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/vintasoftware/vintasend-liquidjs.git"
@@ -37,15 +37,15 @@
37
37
  "author": "Hugo Bessa",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "liquidjs": "^10.29.0",
41
- "vintasend": "^1.0.0-alpha7"
40
+ "liquidjs": "^10.30.1",
41
+ "vintasend": "^1.0.0-alpha9"
42
42
  },
43
43
  "devDependencies": {
44
- "@biomejs/biome": "^2.5.10",
45
- "@types/node": "25.3.3",
46
- "@vitest/coverage-v8": "4.0.18",
44
+ "@biomejs/biome": "^2.6.4",
45
+ "@types/node": "26.6.6",
46
+ "@vitest/coverage-v8": "5.0.5",
47
47
  "husky": "^9.1.7",
48
- "typescript": "^5.9.3",
49
- "vitest": "4.0.18"
48
+ "typescript": "^7.0.2",
49
+ "vitest": "5.0.5"
50
50
  }
51
51
  }