@things-factory/notification 10.1.247 → 10.1.248

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.
@@ -75,53 +75,77 @@ function renderNoticeMarkdown(markdown, base) {
75
75
  function noticeEmailForm({ lang, words, link, format = 'text', base, brand = app_brand_js_1.DEFAULT_BRAND }) {
76
76
  const color = escapeHtml(brand.color);
77
77
  const onColor = (0, app_brand_js_1.textColorOn)(brand.color);
78
+ /*
79
+ * Laid out as tables, each colour on its cell as `bgcolor` as well as in its style: mail programs drop a box's
80
+ * margins and backgrounds, and one in dark mode repaints what it was not told is light - so the mail says it is light.
81
+ */
78
82
  const action = link
79
- ? `<a
80
- href="${escapeHtml(link)}"
81
- style="display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:${color};padding:7px 15px;color:${onColor};font-size:18px;font-weight:bold;text-decoration:none;"
82
- >${escapeHtml(words.action)}</a
83
- >`
84
- : `<span style="display:block;color:#555">${escapeHtml(words.note)}</span>`;
83
+ ? `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate">
84
+ <tr>
85
+ <td bgcolor="${color}" style="background-color:${color};border-radius:7px;padding:8px 16px">
86
+ <a href="${escapeHtml(link)}" style="color:${onColor};font-size:18px;font-weight:bold;text-decoration:none"
87
+ ><font color="${onColor}">${escapeHtml(words.action)}</font></a
88
+ >
89
+ </td>
90
+ </tr>
91
+ </table>`
92
+ : `<span style="color:#555555">${escapeHtml(words.note)}</span>`;
85
93
  return `
86
94
  <html lang="${escapeHtml(lang)}">
87
95
  <head>
88
96
  <meta charset="utf-8" />
97
+ <meta name="color-scheme" content="light" />
98
+ <meta name="supported-color-schemes" content="light" />
89
99
  <title>${escapeHtml(words.subject)}</title>
90
100
  <meta name="author" content="hatiolab" />
91
101
  <meta name="google" content="notranslate"/>
102
+ <style>:root { color-scheme: light; supported-color-schemes: light; }</style>
92
103
  </head>
93
104
 
94
- <body>
95
- <div style="background-color:#f6f6f6">
105
+ <body style="margin:0;padding:0" bgcolor="#f6f6f6">
106
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f6f6f6" style="background-color:#f6f6f6;border-collapse:collapse">
96
107
  <!--header begin-->
97
- <div style="background-color:#fff;padding:10px 20px;border-top:2px solid ${color};">
98
- <span style="font-size:20px;font-weight:bold;color:#1f2933">${escapeHtml(brand.name)}</span>${brand.tagline ? `<span style="margin-left:10px;font-size:13px;color:#6b7280">${escapeHtml(brand.tagline)}</span>` : ''}
99
- </div>
108
+ <tr>
109
+ <td bgcolor="#ffffff" style="background-color:#ffffff;border-top:2px solid ${color};padding:10px 20px">
110
+ <table role="presentation" cellpadding="0" cellspacing="0" border="0">
111
+ <tr>
112
+ <td style="font-size:20px;font-weight:bold;color:#1f2933;white-space:nowrap">${escapeHtml(brand.name)}</td>${brand.tagline
113
+ ? `
114
+ <td style="padding-left:12px;font-size:13px;color:#6b7280">&nbsp;${escapeHtml(brand.tagline)}</td>`
115
+ : ''}
116
+ </tr>
117
+ </table>
118
+ </td>
119
+ </tr>
100
120
  <!--header end-->
101
121
 
102
122
  <!--title begin-->
103
- <div style="background-color:${color};padding:12px 10px 10px 10px;min-height:50px;">
104
- <span style="display:block;color:${onColor};font-size:24px;font-weight:bold;margin-left:40px"
105
- >${escapeHtml(words.subject)}</span
123
+ <tr>
124
+ <td bgcolor="${color}" style="background-color:${color};padding:14px 20px 12px 60px;color:${onColor};font-size:24px;font-weight:bold"
125
+ ><font color="${onColor}">${escapeHtml(words.subject)}</font></td
106
126
  >
107
- </div>
127
+ </tr>
108
128
  <!--title end-->
109
129
 
110
130
  <!--body begin-->
111
- ${format === 'markdown'
112
- ? `<div style="padding:10px 20px;line-height:1.5;font-size:16px">${renderNoticeMarkdown(words.body, base)}</div>`
113
- : `<p style="padding:10px 20px;line-height:1.5;font-size:16px;white-space:pre-line">${escapeHtml(words.body)}</p>`}
114
- <p style="padding:0 20px 10px 20px;font-size:16px">
115
- ${action}
116
- </p>
131
+ <tr>
132
+ <td style="padding:10px 20px;line-height:1.5;font-size:16px;color:#1f2933">${format === 'markdown' ? renderNoticeMarkdown(words.body, base) : `<div style="white-space:pre-line">${escapeHtml(words.body)}</div>`}</td>
133
+ </tr>
134
+ <tr>
135
+ <td style="padding:6px 20px 20px 20px;font-size:16px">
136
+ ${action}
137
+ </td>
138
+ </tr>
117
139
  <!--body end-->
118
140
 
119
141
  <!--footer begin-->
120
- <div style="background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef">
121
- © Hatio, Lab. Inc. All rights reserved.
122
- </div>
142
+ <tr>
143
+ <td bgcolor="#3d5874" style="background-color:#3d5874;padding:8px 20px 6px 20px;font-size:12px;color:#efefef"
144
+ ><font color="#efefef">© Hatio, Lab. Inc. All rights reserved.</font></td
145
+ >
146
+ </tr>
123
147
  <!--footer end-->
124
- </div>
148
+ </table>
125
149
  </body>
126
150
  </html>
127
151
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"notice-email.js","sourceRoot":"","sources":["../../../server/controllers/notice/notice-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { Marked } from 'marked'\n\nimport { DEFAULT_BRAND, textColorOn, type AppBrand } from './app-brand.js'\nimport type { NoticeWords } from './notice-channels.js'\n\n/** Every value goes into the HTML through this - a subject like `<b>` or `A&B` stays text. */\nexport function escapeHtml(value: unknown): string {\n return String(value ?? '')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/** The schemes a link in a notice may take; anything else is shown as its text. */\nconst SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:'])\nconst NO_BASE = 'https://no-base.invalid/'\n\n/**\n * Where a link in a notice may go: http(s) and mailto as written, and an address of the app (no scheme) under `base`,\n * the domain's own address. With no base an app address has nowhere to go and is dropped, as is every other scheme -\n * `javascript:` in any spelling the URL parser reads as one.\n */\nexport function safeNoticeHref(href: string | null | undefined, base?: string): string | undefined {\n const raw = String(href ?? '').trim()\n if (!raw) return undefined\n\n let url: URL\n try {\n url = new URL(raw, base || NO_BASE)\n } catch {\n return undefined\n }\n\n if (!SAFE_PROTOCOLS.has(url.protocol)) return undefined\n if (url.origin === new URL(NO_BASE).origin) return undefined\n return url.href\n}\n\n/**\n * Markdown as mail HTML, rendered here on the server. Raw HTML in the text is shown as text, never as markup; a link or\n * an image becomes a link only to an address `safeNoticeHref` allows, and an image is not loaded - it is shown as a\n * link with its alt text, so a mail does not fetch from wherever its writer pointed.\n */\nexport function renderNoticeMarkdown(markdown: string, base?: string): string {\n const marked = new Marked({\n gfm: true,\n breaks: true,\n async: false,\n renderer: {\n html({ text }) {\n return escapeHtml(text)\n },\n link({ href, title, tokens }) {\n const label = this.parser.parseInline(tokens)\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\"${title ? ` title=\"${escapeHtml(title)}\"` : ''}>${label}</a>` : label\n },\n image({ href, text }) {\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\">${escapeHtml(text || safe)}</a>` : escapeHtml(text)\n }\n }\n })\n\n return marked.parse(String(markdown ?? '')) as string\n}\n\n/**\n * The notice mail (ADR-0164) - the head, band, button and foot of the auth mails, so a person knows the sender. The head\n * names the application that sends it with its tagline, and the band and button are in its brand colour (`appBrand`).\n * Without a link (a notice sent with no request to take the address from) the button gives way to the note.\n */\nexport function noticeEmailForm({\n lang,\n words,\n link,\n format = 'text',\n base,\n brand = DEFAULT_BRAND\n}: {\n lang: string\n words: NoticeWords\n link?: string\n /** How the body is written - an announcement's is Markdown. */\n format?: 'text' | 'markdown'\n /** The domain's address, under which an app address in a Markdown body is resolved. */\n base?: string\n /** The application sending it. */\n brand?: AppBrand\n}) {\n const color = escapeHtml(brand.color)\n const onColor = textColorOn(brand.color)\n const action = link\n ? `<a\n href=\"${escapeHtml(link)}\"\n style=\"display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:${color};padding:7px 15px;color:${onColor};font-size:18px;font-weight:bold;text-decoration:none;\"\n >${escapeHtml(words.action)}</a\n >`\n : `<span style=\"display:block;color:#555\">${escapeHtml(words.note)}</span>`\n\n return `\n <html lang=\"${escapeHtml(lang)}\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(words.subject)}</title>\n <meta name=\"author\" content=\"hatiolab\" />\n <meta name=\"google\" content=\"notranslate\"/>\n </head>\n\n <body>\n <div style=\"background-color:#f6f6f6\">\n <!--header begin-->\n <div style=\"background-color:#fff;padding:10px 20px;border-top:2px solid ${color};\">\n <span style=\"font-size:20px;font-weight:bold;color:#1f2933\">${escapeHtml(brand.name)}</span>${\n brand.tagline ? `<span style=\"margin-left:10px;font-size:13px;color:#6b7280\">${escapeHtml(brand.tagline)}</span>` : ''\n }\n </div>\n <!--header end-->\n\n <!--title begin-->\n <div style=\"background-color:${color};padding:12px 10px 10px 10px;min-height:50px;\">\n <span style=\"display:block;color:${onColor};font-size:24px;font-weight:bold;margin-left:40px\"\n >${escapeHtml(words.subject)}</span\n >\n </div>\n <!--title end-->\n\n <!--body begin-->\n ${\n format === 'markdown'\n ? `<div style=\"padding:10px 20px;line-height:1.5;font-size:16px\">${renderNoticeMarkdown(words.body, base)}</div>`\n : `<p style=\"padding:10px 20px;line-height:1.5;font-size:16px;white-space:pre-line\">${escapeHtml(words.body)}</p>`\n }\n <p style=\"padding:0 20px 10px 20px;font-size:16px\">\n ${action}\n </p>\n <!--body end-->\n\n <!--footer begin-->\n <div style=\"background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef\">\n © Hatio, Lab. Inc. All rights reserved.\n </div>\n <!--footer end-->\n </div>\n </body>\n </html>\n `\n}\n"]}
1
+ {"version":3,"file":"notice-email.js","sourceRoot":"","sources":["../../../server/controllers/notice/notice-email.ts"],"names":[],"mappings":";;AAMA,gCAOC;AAWD,wCAcC;AAOD,oDAsBC;AAOD,0CAmGC;AA7KD,mCAA+B;AAE/B,iDAA0E;AAG1E,8FAA8F;AAC9F,SAAgB,UAAU,CAAC,KAAc;IACvC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED,mFAAmF;AACnF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAC9D,MAAM,OAAO,GAAG,0BAA0B,CAAA;AAE1C;;;;GAIG;AACH,SAAgB,cAAc,CAAC,IAA+B,EAAE,IAAa;IAC3E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAE1B,IAAI,GAAQ,CAAA;IACZ,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI,OAAO,CAAC,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAA;IACvD,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC5D,OAAO,GAAG,CAAC,IAAI,CAAA;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB,CAAC,QAAgB,EAAE,IAAa;IAClE,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC;QACxB,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE;YACR,IAAI,CAAC,EAAE,IAAI,EAAE;gBACX,OAAO,UAAU,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC;YACD,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;gBAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;gBAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;gBACvC,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,WAAW,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAA;YACnH,CAAC;YACD,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;gBAClB,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;gBACvC,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAClG,CAAC;SACF;KACF,CAAC,CAAA;IAEF,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAW,CAAA;AACvD,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,MAAM,GAAG,MAAM,EACf,IAAI,EACJ,KAAK,GAAG,4BAAa,EAWtB;IACC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrC,MAAM,OAAO,GAAG,IAAA,0BAAW,EAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACxC;;;OAGG;IACH,MAAM,MAAM,GAAG,IAAI;QACjB,CAAC,CAAC;;+BAEyB,KAAK,6BAA6B,KAAK;6BACzC,UAAU,CAAC,IAAI,CAAC,kBAAkB,OAAO;oCAClC,OAAO,KAAK,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;;;;qBAInD;QACjB,CAAC,CAAC,+BAA+B,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAA;IAElE,OAAO;gBACO,UAAU,CAAC,IAAI,CAAC;;;;;eAKjB,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;;;;;;;;;;uFAU+C,KAAK;;;+FAGG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,QACnG,KAAK,CAAC,OAAO;QACX,CAAC,CAAC;mFAC6D,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO;QAC/F,CAAC,CAAC,EACN;;;;;;;;;yBASS,KAAK,6BAA6B,KAAK,sCAAsC,OAAO;4BACjF,OAAO,KAAK,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;;;;;;;uFAQrD,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,qCAAqC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,QAC9H;;;;cAII,MAAM;;;;;;;;;;;;;;;GAejB,CAAA;AACH,CAAC","sourcesContent":["import { Marked } from 'marked'\n\nimport { DEFAULT_BRAND, textColorOn, type AppBrand } from './app-brand.js'\nimport type { NoticeWords } from './notice-channels.js'\n\n/** Every value goes into the HTML through this - a subject like `<b>` or `A&B` stays text. */\nexport function escapeHtml(value: unknown): string {\n return String(value ?? '')\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\n/** The schemes a link in a notice may take; anything else is shown as its text. */\nconst SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:'])\nconst NO_BASE = 'https://no-base.invalid/'\n\n/**\n * Where a link in a notice may go: http(s) and mailto as written, and an address of the app (no scheme) under `base`,\n * the domain's own address. With no base an app address has nowhere to go and is dropped, as is every other scheme -\n * `javascript:` in any spelling the URL parser reads as one.\n */\nexport function safeNoticeHref(href: string | null | undefined, base?: string): string | undefined {\n const raw = String(href ?? '').trim()\n if (!raw) return undefined\n\n let url: URL\n try {\n url = new URL(raw, base || NO_BASE)\n } catch {\n return undefined\n }\n\n if (!SAFE_PROTOCOLS.has(url.protocol)) return undefined\n if (url.origin === new URL(NO_BASE).origin) return undefined\n return url.href\n}\n\n/**\n * Markdown as mail HTML, rendered here on the server. Raw HTML in the text is shown as text, never as markup; a link or\n * an image becomes a link only to an address `safeNoticeHref` allows, and an image is not loaded - it is shown as a\n * link with its alt text, so a mail does not fetch from wherever its writer pointed.\n */\nexport function renderNoticeMarkdown(markdown: string, base?: string): string {\n const marked = new Marked({\n gfm: true,\n breaks: true,\n async: false,\n renderer: {\n html({ text }) {\n return escapeHtml(text)\n },\n link({ href, title, tokens }) {\n const label = this.parser.parseInline(tokens)\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\"${title ? ` title=\"${escapeHtml(title)}\"` : ''}>${label}</a>` : label\n },\n image({ href, text }) {\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\">${escapeHtml(text || safe)}</a>` : escapeHtml(text)\n }\n }\n })\n\n return marked.parse(String(markdown ?? '')) as string\n}\n\n/**\n * The notice mail (ADR-0164) - the head, band, button and foot of the auth mails, so a person knows the sender. The head\n * names the application that sends it with its tagline, and the band and button are in its brand colour (`appBrand`).\n * Without a link (a notice sent with no request to take the address from) the button gives way to the note.\n */\nexport function noticeEmailForm({\n lang,\n words,\n link,\n format = 'text',\n base,\n brand = DEFAULT_BRAND\n}: {\n lang: string\n words: NoticeWords\n link?: string\n /** How the body is written - an announcement's is Markdown. */\n format?: 'text' | 'markdown'\n /** The domain's address, under which an app address in a Markdown body is resolved. */\n base?: string\n /** The application sending it. */\n brand?: AppBrand\n}) {\n const color = escapeHtml(brand.color)\n const onColor = textColorOn(brand.color)\n /*\n * Laid out as tables, each colour on its cell as `bgcolor` as well as in its style: mail programs drop a box's\n * margins and backgrounds, and one in dark mode repaints what it was not told is light - so the mail says it is light.\n */\n const action = link\n ? `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:separate\">\n <tr>\n <td bgcolor=\"${color}\" style=\"background-color:${color};border-radius:7px;padding:8px 16px\">\n <a href=\"${escapeHtml(link)}\" style=\"color:${onColor};font-size:18px;font-weight:bold;text-decoration:none\"\n ><font color=\"${onColor}\">${escapeHtml(words.action)}</font></a\n >\n </td>\n </tr>\n </table>`\n : `<span style=\"color:#555555\">${escapeHtml(words.note)}</span>`\n\n return `\n <html lang=\"${escapeHtml(lang)}\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"color-scheme\" content=\"light\" />\n <meta name=\"supported-color-schemes\" content=\"light\" />\n <title>${escapeHtml(words.subject)}</title>\n <meta name=\"author\" content=\"hatiolab\" />\n <meta name=\"google\" content=\"notranslate\"/>\n <style>:root { color-scheme: light; supported-color-schemes: light; }</style>\n </head>\n\n <body style=\"margin:0;padding:0\" bgcolor=\"#f6f6f6\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" bgcolor=\"#f6f6f6\" style=\"background-color:#f6f6f6;border-collapse:collapse\">\n <!--header begin-->\n <tr>\n <td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;border-top:2px solid ${color};padding:10px 20px\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n <tr>\n <td style=\"font-size:20px;font-weight:bold;color:#1f2933;white-space:nowrap\">${escapeHtml(brand.name)}</td>${\n brand.tagline\n ? `\n <td style=\"padding-left:12px;font-size:13px;color:#6b7280\">&nbsp;${escapeHtml(brand.tagline)}</td>`\n : ''\n }\n </tr>\n </table>\n </td>\n </tr>\n <!--header end-->\n\n <!--title begin-->\n <tr>\n <td bgcolor=\"${color}\" style=\"background-color:${color};padding:14px 20px 12px 60px;color:${onColor};font-size:24px;font-weight:bold\"\n ><font color=\"${onColor}\">${escapeHtml(words.subject)}</font></td\n >\n </tr>\n <!--title end-->\n\n <!--body begin-->\n <tr>\n <td style=\"padding:10px 20px;line-height:1.5;font-size:16px;color:#1f2933\">${\n format === 'markdown' ? renderNoticeMarkdown(words.body, base) : `<div style=\"white-space:pre-line\">${escapeHtml(words.body)}</div>`\n }</td>\n </tr>\n <tr>\n <td style=\"padding:6px 20px 20px 20px;font-size:16px\">\n ${action}\n </td>\n </tr>\n <!--body end-->\n\n <!--footer begin-->\n <tr>\n <td bgcolor=\"#3d5874\" style=\"background-color:#3d5874;padding:8px 20px 6px 20px;font-size:12px;color:#efefef\"\n ><font color=\"#efefef\">© Hatio, Lab. Inc. All rights reserved.</font></td\n >\n </tr>\n <!--footer end-->\n </table>\n </body>\n </html>\n `\n}\n"]}