@handled-ai/design-system 0.20.23 → 0.20.26

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.
@@ -1,3 +1,19 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
1
17
  import { htmlToTextSnippet, sanitizeHtml } from "../internal/safe-html.js";
2
18
  const HTML_ENTITY_RE = /&(?:#x[0-9a-f]+|#\d+|[a-z][a-z0-9]+);?/i;
3
19
  const EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
@@ -145,18 +161,17 @@ function formatAddressList(input) {
145
161
  const rawItems = Array.isArray(input) ? input : splitAddressList(input);
146
162
  return rawItems.map((item) => formatSingleAddress(item)).filter(Boolean).join(", ");
147
163
  }
148
- function formatEmailTimestamp(value) {
164
+ function formatEmailTimestamp(value, opts) {
149
165
  if (!value) return null;
150
166
  const date = value instanceof Date ? value : new Date(value);
151
167
  if (Number.isNaN(date.getTime())) return null;
152
- return new Intl.DateTimeFormat("en-US", {
168
+ return new Intl.DateTimeFormat("en-US", __spreadValues({
153
169
  month: "short",
154
170
  day: "numeric",
155
171
  year: "numeric",
156
172
  hour: "numeric",
157
- minute: "2-digit",
158
- timeZone: "UTC"
159
- }).format(date);
173
+ minute: "2-digit"
174
+ }, (opts == null ? void 0 : opts.timeZone) ? { timeZone: opts.timeZone } : {})).format(date);
160
175
  }
161
176
  function escapeHtmlText(value) {
162
177
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -212,6 +227,12 @@ function makeHtmlSegment(html) {
212
227
  if (!html.trim() || !visibleText && !/<(?:img|hr)\b/i.test(html)) return null;
213
228
  return { html, visibleText };
214
229
  }
230
+ function makeBlankLineSegment(html) {
231
+ return { html, visibleText: "" };
232
+ }
233
+ function isBlankLineHtml(html) {
234
+ return Boolean(html.trim()) && !htmlToVisibleText(html) && !/<(?:img|hr)\b/i.test(html);
235
+ }
215
236
  function splitInlineNodes(nodes, wrapper) {
216
237
  const containsBr = hasDirectBr(nodes);
217
238
  const chunks = [[]];
@@ -223,10 +244,20 @@ function splitInlineNodes(nodes, wrapper) {
223
244
  }
224
245
  (_a = chunks[chunks.length - 1]) == null ? void 0 : _a.push(serializeNode(node));
225
246
  });
226
- return chunks.map((chunk) => chunk.join("")).map((innerHtml) => {
227
- if (wrapper) return wrapHtmlLike(wrapper, innerHtml);
228
- return containsBr ? `<div>${innerHtml}</div>` : innerHtml;
229
- }).map(makeHtmlSegment).filter((segment) => Boolean(segment));
247
+ const rendered = chunks.map((chunk) => chunk.join(""));
248
+ const segments = [];
249
+ rendered.forEach((innerHtml, index) => {
250
+ const html = wrapper ? wrapHtmlLike(wrapper, innerHtml) : containsBr ? `<div>${innerHtml}</div>` : innerHtml;
251
+ const segment = makeHtmlSegment(html);
252
+ if (segment) {
253
+ segments.push(segment);
254
+ return;
255
+ }
256
+ if (containsBr && index > 0 && index < rendered.length - 1) {
257
+ segments.push(makeBlankLineSegment(wrapper ? wrapHtmlLike(wrapper, "<br>") : "<div><br></div>"));
258
+ }
259
+ });
260
+ return segments;
230
261
  }
231
262
  function splitElementSegment(element) {
232
263
  const tagName = element.tagName.toLowerCase();
@@ -234,6 +265,9 @@ function splitElementSegment(element) {
234
265
  const segment2 = makeHtmlSegment(element.outerHTML);
235
266
  return segment2 ? [segment2] : [];
236
267
  }
268
+ if (isBlankLineHtml(element.outerHTML)) {
269
+ return [makeBlankLineSegment(element.outerHTML)];
270
+ }
237
271
  if (tagName === "div" && hasDirectBlockChild(element)) {
238
272
  const childSegments = splitHtmlNodes(Array.from(element.childNodes));
239
273
  return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean);
@@ -287,9 +321,15 @@ function splitHtmlSegmentsFallback(html) {
287
321
  const pushInline = (inlineHtml) => {
288
322
  const chunks = inlineHtml.split(BR_TAG_RE);
289
323
  const hadBr = chunks.length > 1;
290
- chunks.forEach((chunk) => {
324
+ chunks.forEach((chunk, index) => {
291
325
  const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk);
292
- if (segment) segments.push(segment);
326
+ if (segment) {
327
+ segments.push(segment);
328
+ return;
329
+ }
330
+ if (hadBr && index > 0 && index < chunks.length - 1) {
331
+ segments.push(makeBlankLineSegment("<div><br /></div>"));
332
+ }
293
333
  });
294
334
  };
295
335
  while (cursor < html.length) {
@@ -306,13 +346,22 @@ function splitHtmlSegmentsFallback(html) {
306
346
  const openTagEnd = tagStart + rawOpen.length - 1;
307
347
  const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd);
308
348
  const blockHtml = html.slice(tagStart, segmentEnd);
309
- if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {
349
+ if (isBlankLineHtml(blockHtml)) {
350
+ segments.push(makeBlankLineSegment(blockHtml));
351
+ } else if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {
310
352
  const openTag = rawOpen;
311
353
  const closeTag = `</${tagName}>`;
312
354
  const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "i"), "").replace(new RegExp(`${closeTag}$`, "i"), "");
313
- inner.split(BR_TAG_RE).forEach((chunk) => {
355
+ const innerChunks = inner.split(BR_TAG_RE);
356
+ innerChunks.forEach((chunk, index) => {
314
357
  const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`);
315
- if (segment) segments.push(segment);
358
+ if (segment) {
359
+ segments.push(segment);
360
+ return;
361
+ }
362
+ if (index > 0 && index < innerChunks.length - 1) {
363
+ segments.push(makeBlankLineSegment(`${openTag}<br />${closeTag}`));
364
+ }
316
365
  });
317
366
  } else {
318
367
  const segment = makeHtmlSegment(blockHtml);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/email-display-helpers.ts"],"sourcesContent":["import { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\n\nexport interface NormalizedEmailSender {\n name: string\n email: string | null\n}\n\nexport interface EmailDisplaySenderInput {\n name?: string | null\n email?: string | null\n fallbackName?: string\n}\n\nexport interface SplitEmailHtmlResult {\n bodyHtml: string\n detailsHtml: string\n}\n\nexport interface SplitEmailTextResult {\n bodyText: string\n detailsText: string\n}\n\nconst HTML_ENTITY_RE = /&(?:#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]+);?/i\nconst EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i\nconst ANGLE_ADDRESS_RE = /^\\s*(.*?)\\s*<\\s*([^<>\\s]+@[^<>\\s]+)\\s*>\\s*$/\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst GMAIL_SIGNATURE_RE = /\\b(?:gmail_signature|gmail_signature_prefix|gmail_extra)\\b/i\nconst GMAIL_QUOTE_RE = /<blockquote\\b[^>]*\\bclass=[\"'][^\"']*\\bgmail_quote\\b/i\nconst ON_WROTE_RE = /^On\\s.+wrote:\\s*$/i\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction safeCodePoint(value: number): string {\n return Number.isInteger(value) && value >= 0 && value <= 0x10ffff ? String.fromCodePoint(value) : \"\"\n}\n\nfunction decodeHtmlEntities(value: string): string {\n const namedEntities: Record<string, string> = {\n amp: \"&\",\n apos: \"'\",\n colon: \":\",\n gt: \">\",\n lt: \"<\",\n nbsp: \" \",\n newline: \"\\n\",\n quot: '\"',\n tab: \"\\t\",\n }\n\n let decoded = value\n for (let i = 0; i < 4; i += 1) {\n const next = decoded\n .replace(/&#x([0-9a-f]+);?/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);?/g, (_match, decimal: string) => safeCodePoint(Number.parseInt(decimal, 10)))\n .replace(/&([a-z][a-z0-9]+);?/gi, (match, name: string) => namedEntities[name.toLowerCase()] ?? match)\n\n if (next === decoded) return decoded\n decoded = next\n }\n\n return decoded\n}\n\nfunction decodeJsonEscapes(value: string): string {\n return value\n .replace(/\\\\u\\{([0-9a-f]{1,6})\\}/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/\\\\u([0-9a-f]{4})/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/\\\\r\\\\n|\\\\n|\\\\r/g, \"\\n\")\n .replace(/\\\\t/g, \"\\t\")\n .replace(/\\\\([\"'\\\\/])/g, \"$1\")\n}\n\nfunction maybeParseJsonString(value: string): string {\n const trimmed = value.trim()\n if (!trimmed.startsWith('\"') || !trimmed.endsWith('\"')) return value\n if (!/[\\\\&]/.test(trimmed)) return value\n\n try {\n const parsed = JSON.parse(trimmed) as unknown\n return typeof parsed === \"string\" ? parsed : value\n } catch {\n return value\n }\n}\n\n/**\n * Decodes display-only email text so UI labels, body fallbacks, collapsed rows,\n * and snippets do not show HTML entities or JSON escape artifacts. It does not\n * sanitize HTML; sanitize at the HTML render boundary before using markup.\n */\nexport function decodeEmailDisplayText(value: string): string {\n let decoded = maybeParseJsonString(value).replace(/\\r\\n?/g, \"\\n\")\n\n for (let i = 0; i < 4; i += 1) {\n const next = decodeHtmlEntities(decodeJsonEscapes(decoded))\n if (next === decoded) break\n decoded = next\n }\n\n return decoded.replace(/\\u00a0/g, \" \")\n}\n\nfunction stripWrappingQuotes(value: string): string {\n const trimmed = value.trim()\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n return trimmed.slice(1, -1).trim()\n }\n return trimmed\n}\n\nfunction extractEmail(value: string): string | null {\n const decoded = decodeEmailDisplayText(value)\n const angleMatch = decoded.match(ANGLE_ADDRESS_RE)\n const email = angleMatch?.[2] ?? decoded.match(EMAIL_RE)?.[0]\n return email ? email.trim() : null\n}\n\nfunction extractNameFromAddress(value: string): string {\n const decoded = decodeEmailDisplayText(value)\n const angleMatch = decoded.match(ANGLE_ADDRESS_RE)\n if (angleMatch) return stripWrappingQuotes(angleMatch[1] ?? \"\")\n return stripWrappingQuotes(decoded.replace(EMAIL_RE, \"\").replace(/[<>]/g, \"\").trim())\n}\n\nfunction cleanDisplayName(value: string, email: string | null): string {\n let name = stripWrappingQuotes(decodeEmailDisplayText(value))\n if (email) {\n name = name\n .replace(new RegExp(`<\\\\s*${email.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\s*>`, \"i\"), \"\")\n .replace(new RegExp(`^${email.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}$`, \"i\"), \"\")\n .trim()\n }\n return stripWrappingQuotes(name)\n}\n\nexport function normalizeEmailSender(input: EmailDisplaySenderInput): NormalizedEmailSender {\n const fallbackName = decodeEmailDisplayText(input.fallbackName ?? \"Unknown sender\").trim() || \"Unknown sender\"\n const rawName = input.name ? decodeEmailDisplayText(input.name) : \"\"\n const rawEmail = input.email ? decodeEmailDisplayText(input.email) : \"\"\n const email = extractEmail(rawEmail) ?? extractEmail(rawName)\n\n const nameFromName = rawName ? cleanDisplayName(extractNameFromAddress(rawName) || rawName, email) : \"\"\n const nameFromEmail = rawEmail ? cleanDisplayName(extractNameFromAddress(rawEmail), email) : \"\"\n const name = nameFromName || nameFromEmail || email || fallbackName\n\n return { name, email }\n}\n\nfunction splitAddressList(value: string): string[] {\n const parts: string[] = []\n let current = \"\"\n let quote: '\"' | \"'\" | null = null\n let angleDepth = 0\n\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index]\n\n if (quote) {\n current += char\n if (char === quote && value[index - 1] !== \"\\\\\") quote = null\n continue\n }\n\n if (char === '\"' || char === \"'\") {\n quote = char\n current += char\n continue\n }\n\n if (char === \"<\") angleDepth += 1\n if (char === \">\" && angleDepth > 0) angleDepth -= 1\n\n if (char === \",\" && angleDepth === 0) {\n if (current.trim()) parts.push(current.trim())\n current = \"\"\n continue\n }\n\n current += char\n }\n\n if (current.trim()) parts.push(current.trim())\n return parts\n}\n\nfunction formatSingleAddress(value: string): string {\n const decoded = decodeEmailDisplayText(value).trim()\n if (!decoded) return \"\"\n\n const email = extractEmail(decoded)\n const name = cleanDisplayName(extractNameFromAddress(decoded), email)\n\n if (email && name) return `${name} <${email}>`\n if (email) return email\n return stripWrappingQuotes(decoded)\n}\n\nexport function formatAddressList(input?: string | string[] | null): string {\n if (!input) return \"\"\n\n const rawItems = Array.isArray(input) ? input : splitAddressList(input)\n return rawItems\n .map((item) => formatSingleAddress(item))\n .filter(Boolean)\n .join(\", \")\n}\n\nexport function formatEmailTimestamp(value?: string | Date | null): string | null {\n if (!value) return null\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return null\n\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n timeZone: \"UTC\",\n }).format(date)\n}\n\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nfunction escapeHtmlText(value: string): string {\n return value.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\nfunction decodeHtmlTextNodes(html: string): string {\n if (!HTML_ENTITY_RE.test(html) && !/\\\\[nrt\"'\\\\/]|\\\\u/i.test(html)) return html\n\n if (typeof document !== \"undefined\") {\n const template = document.createElement(\"template\")\n template.innerHTML = html\n const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_TEXT)\n const textNodes: Text[] = []\n let node = walker.nextNode()\n while (node) {\n textNodes.push(node as Text)\n node = walker.nextNode()\n }\n textNodes.forEach((textNode) => {\n textNode.nodeValue = decodeEmailDisplayText(textNode.nodeValue ?? \"\")\n })\n return template.innerHTML\n }\n\n return html\n .split(/(<[^>]+>)/g)\n .map((part) => (part.startsWith(\"<\") && part.endsWith(\">\") ? part : escapeHtmlText(decodeEmailDisplayText(part))))\n .join(\"\")\n}\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n return decodeEmailDisplayText(withoutTags)\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n return chunks\n .map((chunk) => chunk.join(\"\"))\n .map((innerHtml) => {\n if (wrapper) return wrapHtmlLike(wrapper, innerHtml)\n return containsBr ? `<div>${innerHtml}</div>` : innerHtml\n })\n .map(makeHtmlSegment)\n .filter((segment): segment is MessageSegment => Boolean(segment))\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (GMAIL_SIGNATURE_RE.test(element.outerHTML) || GMAIL_QUOTE_RE.test(element.outerHTML)) {\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n }\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : ([makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[])\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) segments.push(segment)\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n inner.split(BR_TAG_RE).forEach((chunk) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) segments.push(segment)\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const normalized = decodeEmailDisplayText(text)\n const lines: string[] = normalized.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isGmailDetailsSegment(segment: MessageSegment): boolean {\n return Boolean(segment.html && (GMAIL_SIGNATURE_RE.test(segment.html) || GMAIL_QUOTE_RE.test(segment.html)))\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const segment = segments[index]\n if (!segment) return false\n if (isGmailDetailsSegment(segment)) return true\n\n const line = firstVisibleLine(segment.visibleText)\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line) || ON_WROTE_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText || isGmailDetailsSegment(segment) ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nexport function splitEmailHtmlForDisplay(html: string): SplitEmailHtmlResult {\n const sanitizedHtml = sanitizeHtml(decodeHtmlTextNodes(html))\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nexport function splitEmailTextForDisplay(text: string): SplitEmailTextResult {\n const decodedText = decodeEmailDisplayText(text)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(decodedText))\n\n if (!detailsSegments.length) return { bodyText: decodedText, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nexport function emailBodySnippet(input: { bodyHtml?: string | null; body?: string | null }, maxLength = 140): string {\n const html = input.bodyHtml?.trim()\n if (html) {\n return decodeEmailDisplayText(htmlToTextSnippet(splitEmailHtmlForDisplay(html).bodyHtml, maxLength)).trim()\n }\n\n const text = input.body ?? \"\"\n const bodyText = splitEmailTextForDisplay(text).bodyText\n const firstLine = bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n return decodeEmailDisplayText(firstLine).replace(/\\s+/g, \" \").trim().slice(0, maxLength)\n}\n"],"mappings":"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","names":["segment"]}
1
+ {"version":3,"sources":["../../src/components/email-display-helpers.ts"],"sourcesContent":["import { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\n\nexport interface NormalizedEmailSender {\n name: string\n email: string | null\n}\n\nexport interface EmailDisplaySenderInput {\n name?: string | null\n email?: string | null\n fallbackName?: string\n}\n\nexport interface SplitEmailHtmlResult {\n bodyHtml: string\n detailsHtml: string\n}\n\nexport interface SplitEmailTextResult {\n bodyText: string\n detailsText: string\n}\n\nconst HTML_ENTITY_RE = /&(?:#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]+);?/i\nconst EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i\nconst ANGLE_ADDRESS_RE = /^\\s*(.*?)\\s*<\\s*([^<>\\s]+@[^<>\\s]+)\\s*>\\s*$/\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst GMAIL_SIGNATURE_RE = /\\b(?:gmail_signature|gmail_signature_prefix|gmail_extra)\\b/i\nconst GMAIL_QUOTE_RE = /<blockquote\\b[^>]*\\bclass=[\"'][^\"']*\\bgmail_quote\\b/i\nconst ON_WROTE_RE = /^On\\s.+wrote:\\s*$/i\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction safeCodePoint(value: number): string {\n return Number.isInteger(value) && value >= 0 && value <= 0x10ffff ? String.fromCodePoint(value) : \"\"\n}\n\nfunction decodeHtmlEntities(value: string): string {\n const namedEntities: Record<string, string> = {\n amp: \"&\",\n apos: \"'\",\n colon: \":\",\n gt: \">\",\n lt: \"<\",\n nbsp: \" \",\n newline: \"\\n\",\n quot: '\"',\n tab: \"\\t\",\n }\n\n let decoded = value\n for (let i = 0; i < 4; i += 1) {\n const next = decoded\n .replace(/&#x([0-9a-f]+);?/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);?/g, (_match, decimal: string) => safeCodePoint(Number.parseInt(decimal, 10)))\n .replace(/&([a-z][a-z0-9]+);?/gi, (match, name: string) => namedEntities[name.toLowerCase()] ?? match)\n\n if (next === decoded) return decoded\n decoded = next\n }\n\n return decoded\n}\n\nfunction decodeJsonEscapes(value: string): string {\n return value\n .replace(/\\\\u\\{([0-9a-f]{1,6})\\}/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/\\\\u([0-9a-f]{4})/gi, (_match, hex: string) => safeCodePoint(Number.parseInt(hex, 16)))\n .replace(/\\\\r\\\\n|\\\\n|\\\\r/g, \"\\n\")\n .replace(/\\\\t/g, \"\\t\")\n .replace(/\\\\([\"'\\\\/])/g, \"$1\")\n}\n\nfunction maybeParseJsonString(value: string): string {\n const trimmed = value.trim()\n if (!trimmed.startsWith('\"') || !trimmed.endsWith('\"')) return value\n if (!/[\\\\&]/.test(trimmed)) return value\n\n try {\n const parsed = JSON.parse(trimmed) as unknown\n return typeof parsed === \"string\" ? parsed : value\n } catch {\n return value\n }\n}\n\n/**\n * Decodes display-only email text so UI labels, body fallbacks, collapsed rows,\n * and snippets do not show HTML entities or JSON escape artifacts. It does not\n * sanitize HTML; sanitize at the HTML render boundary before using markup.\n */\nexport function decodeEmailDisplayText(value: string): string {\n let decoded = maybeParseJsonString(value).replace(/\\r\\n?/g, \"\\n\")\n\n for (let i = 0; i < 4; i += 1) {\n const next = decodeHtmlEntities(decodeJsonEscapes(decoded))\n if (next === decoded) break\n decoded = next\n }\n\n return decoded.replace(/\\u00a0/g, \" \")\n}\n\nfunction stripWrappingQuotes(value: string): string {\n const trimmed = value.trim()\n if ((trimmed.startsWith('\"') && trimmed.endsWith('\"')) || (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))) {\n return trimmed.slice(1, -1).trim()\n }\n return trimmed\n}\n\nfunction extractEmail(value: string): string | null {\n const decoded = decodeEmailDisplayText(value)\n const angleMatch = decoded.match(ANGLE_ADDRESS_RE)\n const email = angleMatch?.[2] ?? decoded.match(EMAIL_RE)?.[0]\n return email ? email.trim() : null\n}\n\nfunction extractNameFromAddress(value: string): string {\n const decoded = decodeEmailDisplayText(value)\n const angleMatch = decoded.match(ANGLE_ADDRESS_RE)\n if (angleMatch) return stripWrappingQuotes(angleMatch[1] ?? \"\")\n return stripWrappingQuotes(decoded.replace(EMAIL_RE, \"\").replace(/[<>]/g, \"\").trim())\n}\n\nfunction cleanDisplayName(value: string, email: string | null): string {\n let name = stripWrappingQuotes(decodeEmailDisplayText(value))\n if (email) {\n name = name\n .replace(new RegExp(`<\\\\s*${email.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\s*>`, \"i\"), \"\")\n .replace(new RegExp(`^${email.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}$`, \"i\"), \"\")\n .trim()\n }\n return stripWrappingQuotes(name)\n}\n\nexport function normalizeEmailSender(input: EmailDisplaySenderInput): NormalizedEmailSender {\n const fallbackName = decodeEmailDisplayText(input.fallbackName ?? \"Unknown sender\").trim() || \"Unknown sender\"\n const rawName = input.name ? decodeEmailDisplayText(input.name) : \"\"\n const rawEmail = input.email ? decodeEmailDisplayText(input.email) : \"\"\n const email = extractEmail(rawEmail) ?? extractEmail(rawName)\n\n const nameFromName = rawName ? cleanDisplayName(extractNameFromAddress(rawName) || rawName, email) : \"\"\n const nameFromEmail = rawEmail ? cleanDisplayName(extractNameFromAddress(rawEmail), email) : \"\"\n const name = nameFromName || nameFromEmail || email || fallbackName\n\n return { name, email }\n}\n\nfunction splitAddressList(value: string): string[] {\n const parts: string[] = []\n let current = \"\"\n let quote: '\"' | \"'\" | null = null\n let angleDepth = 0\n\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index]\n\n if (quote) {\n current += char\n if (char === quote && value[index - 1] !== \"\\\\\") quote = null\n continue\n }\n\n if (char === '\"' || char === \"'\") {\n quote = char\n current += char\n continue\n }\n\n if (char === \"<\") angleDepth += 1\n if (char === \">\" && angleDepth > 0) angleDepth -= 1\n\n if (char === \",\" && angleDepth === 0) {\n if (current.trim()) parts.push(current.trim())\n current = \"\"\n continue\n }\n\n current += char\n }\n\n if (current.trim()) parts.push(current.trim())\n return parts\n}\n\nfunction formatSingleAddress(value: string): string {\n const decoded = decodeEmailDisplayText(value).trim()\n if (!decoded) return \"\"\n\n const email = extractEmail(decoded)\n const name = cleanDisplayName(extractNameFromAddress(decoded), email)\n\n if (email && name) return `${name} <${email}>`\n if (email) return email\n return stripWrappingQuotes(decoded)\n}\n\nexport function formatAddressList(input?: string | string[] | null): string {\n if (!input) return \"\"\n\n const rawItems = Array.isArray(input) ? input : splitAddressList(input)\n return rawItems\n .map((item) => formatSingleAddress(item))\n .filter(Boolean)\n .join(\", \")\n}\n\nexport function formatEmailTimestamp(\n value?: string | Date | null,\n opts?: { timeZone?: string },\n): string | null {\n if (!value) return null\n const date = value instanceof Date ? value : new Date(value)\n if (Number.isNaN(date.getTime())) return null\n\n // Default = the runtime's local timezone (the viewer's, in the browser).\n // Callers that render during SSR/hydration pass timeZone \"UTC\" for the\n // first paint so server and client HTML match, then re-render local after\n // mount (see useHydrated in timeline-activity).\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n ...(opts?.timeZone ? { timeZone: opts.timeZone } : {}),\n }).format(date)\n}\n\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nfunction escapeHtmlText(value: string): string {\n return value.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\nfunction decodeHtmlTextNodes(html: string): string {\n if (!HTML_ENTITY_RE.test(html) && !/\\\\[nrt\"'\\\\/]|\\\\u/i.test(html)) return html\n\n if (typeof document !== \"undefined\") {\n const template = document.createElement(\"template\")\n template.innerHTML = html\n const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_TEXT)\n const textNodes: Text[] = []\n let node = walker.nextNode()\n while (node) {\n textNodes.push(node as Text)\n node = walker.nextNode()\n }\n textNodes.forEach((textNode) => {\n textNode.nodeValue = decodeEmailDisplayText(textNode.nodeValue ?? \"\")\n })\n return template.innerHTML\n }\n\n return html\n .split(/(<[^>]+>)/g)\n .map((part) => (part.startsWith(\"<\") && part.endsWith(\">\") ? part : escapeHtmlText(decodeEmailDisplayText(part))))\n .join(\"\")\n}\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n return decodeEmailDisplayText(withoutTags)\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\n// Blank-line segments carry no visible text, so they never participate in\n// footer-boundary detection — but their html must survive the rebuild, or a\n// split body loses every intentional blank line between paragraphs (the\n// Gmail wire format marks them as <div><br></div>).\nfunction makeBlankLineSegment(html: string): MessageSegment {\n return { html, visibleText: \"\" }\n}\n\nfunction isBlankLineHtml(html: string): boolean {\n return Boolean(html.trim()) && !htmlToVisibleText(html) && !/<(?:img|hr)\\b/i.test(html)\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n const rendered = chunks.map((chunk) => chunk.join(\"\"))\n const segments: MessageSegment[] = []\n rendered.forEach((innerHtml, index) => {\n const html = wrapper ? wrapHtmlLike(wrapper, innerHtml) : containsBr ? `<div>${innerHtml}</div>` : innerHtml\n const segment = makeHtmlSegment(html)\n if (segment) {\n segments.push(segment)\n return\n }\n // An interior empty chunk sits between two <br>s: an intentional blank\n // line. Leading/trailing empties stay dropped, as before.\n if (containsBr && index > 0 && index < rendered.length - 1) {\n segments.push(makeBlankLineSegment(wrapper ? wrapHtmlLike(wrapper, \"<br>\") : \"<div><br></div>\"))\n }\n })\n return segments\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (GMAIL_SIGNATURE_RE.test(element.outerHTML) || GMAIL_QUOTE_RE.test(element.outerHTML)) {\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n }\n\n // A block with no visible content (e.g. <div><br></div>, <p></p>) is a\n // blank-line marker — keep it whole instead of splitting/dropping it.\n if (isBlankLineHtml(element.outerHTML)) {\n return [makeBlankLineSegment(element.outerHTML)]\n }\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : ([makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[])\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk, index) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) {\n segments.push(segment)\n return\n }\n if (hadBr && index > 0 && index < chunks.length - 1) {\n segments.push(makeBlankLineSegment(\"<div><br /></div>\"))\n }\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (isBlankLineHtml(blockHtml)) {\n segments.push(makeBlankLineSegment(blockHtml))\n } else if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n const innerChunks = inner.split(BR_TAG_RE)\n innerChunks.forEach((chunk, index) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) {\n segments.push(segment)\n return\n }\n if (index > 0 && index < innerChunks.length - 1) {\n segments.push(makeBlankLineSegment(`${openTag}<br />${closeTag}`))\n }\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const normalized = decodeEmailDisplayText(text)\n const lines: string[] = normalized.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isGmailDetailsSegment(segment: MessageSegment): boolean {\n return Boolean(segment.html && (GMAIL_SIGNATURE_RE.test(segment.html) || GMAIL_QUOTE_RE.test(segment.html)))\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const segment = segments[index]\n if (!segment) return false\n if (isGmailDetailsSegment(segment)) return true\n\n const line = firstVisibleLine(segment.visibleText)\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line) || ON_WROTE_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText || isGmailDetailsSegment(segment) ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nexport function splitEmailHtmlForDisplay(html: string): SplitEmailHtmlResult {\n const sanitizedHtml = sanitizeHtml(decodeHtmlTextNodes(html))\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nexport function splitEmailTextForDisplay(text: string): SplitEmailTextResult {\n const decodedText = decodeEmailDisplayText(text)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(decodedText))\n\n if (!detailsSegments.length) return { bodyText: decodedText, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nexport function emailBodySnippet(input: { bodyHtml?: string | null; body?: string | null }, maxLength = 140): string {\n const html = input.bodyHtml?.trim()\n if (html) {\n return decodeEmailDisplayText(htmlToTextSnippet(splitEmailHtmlForDisplay(html).bodyHtml, maxLength)).trim()\n }\n\n const text = input.body ?? \"\"\n const bodyText = splitEmailTextForDisplay(text).bodyText\n const firstLine = bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n return decodeEmailDisplayText(firstLine).replace(/\\s+/g, \" \").trim().slice(0, maxLength)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,mBAAmB,oBAAoB;AAuBhD,MAAM,iBAAiB;AACvB,MAAM,WAAW;AACjB,MAAM,mBAAmB;AACzB,MAAM,YAAY;AAElB,MAAM,wBAAwB,oBAAI,IAAI,CAAC,KAAK,KAAK,CAAC;AAClD,MAAM,mBAAmB,oBAAI,IAAI,CAAC,cAAc,SAAS,MAAM,MAAM,IAAI,CAAC;AAC1E,MAAM,aAAa,oBAAI,IAAI,CAAC,GAAG,uBAAuB,GAAG,gBAAgB,CAAC;AAC1E,MAAM,sBAAsB;AAE5B,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAC3B,MAAM,iBAAiB;AACvB,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAE1B,SAAS,cAAc,OAAuB;AAC5C,SAAO,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS,UAAW,OAAO,cAAc,KAAK,IAAI;AACpG;AAEA,SAAS,mBAAmB,OAAuB;AACjD,QAAM,gBAAwC;AAAA,IAC5C,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,KAAK;AAAA,EACP;AAEA,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;AAC7B,UAAM,OAAO,QACV,QAAQ,sBAAsB,CAAC,QAAQ,QAAgB,cAAc,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC,EAC9F,QAAQ,cAAc,CAAC,QAAQ,YAAoB,cAAc,OAAO,SAAS,SAAS,EAAE,CAAC,CAAC,EAC9F,QAAQ,yBAAyB,CAAC,OAAO,SAAc;AA/D9D;AA+DiE,iCAAc,KAAK,YAAY,CAAC,MAAhC,YAAqC;AAAA,KAAK;AAEvG,QAAI,SAAS,QAAS,QAAO;AAC7B,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAuB;AAChD,SAAO,MACJ,QAAQ,4BAA4B,CAAC,QAAQ,QAAgB,cAAc,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC,EACpG,QAAQ,sBAAsB,CAAC,QAAQ,QAAgB,cAAc,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC,EAC9F,QAAQ,mBAAmB,IAAI,EAC/B,QAAQ,QAAQ,GAAI,EACpB,QAAQ,gBAAgB,IAAI;AACjC;AAEA,SAAS,qBAAqB,OAAuB;AACnD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAC/D,MAAI,CAAC,QAAQ,KAAK,OAAO,EAAG,QAAO;AAEnC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,OAAO,WAAW,WAAW,SAAS;AAAA,EAC/C,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,uBAAuB,OAAuB;AAC5D,MAAI,UAAU,qBAAqB,KAAK,EAAE,QAAQ,UAAU,IAAI;AAEhE,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;AAC7B,UAAM,OAAO,mBAAmB,kBAAkB,OAAO,CAAC;AAC1D,QAAI,SAAS,QAAS;AACtB,cAAU;AAAA,EACZ;AAEA,SAAO,QAAQ,QAAQ,WAAW,GAAG;AACvC;AAEA,SAAS,oBAAoB,OAAuB;AAClD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAK,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,KAAO,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,GAAI;AAC5G,WAAO,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,EACnC;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAA8B;AAvHpD;AAwHE,QAAM,UAAU,uBAAuB,KAAK;AAC5C,QAAM,aAAa,QAAQ,MAAM,gBAAgB;AACjD,QAAM,SAAQ,8CAAa,OAAb,aAAmB,aAAQ,MAAM,QAAQ,MAAtB,mBAA0B;AAC3D,SAAO,QAAQ,MAAM,KAAK,IAAI;AAChC;AAEA,SAAS,uBAAuB,OAAuB;AA9HvD;AA+HE,QAAM,UAAU,uBAAuB,KAAK;AAC5C,QAAM,aAAa,QAAQ,MAAM,gBAAgB;AACjD,MAAI,WAAY,QAAO,qBAAoB,gBAAW,CAAC,MAAZ,YAAiB,EAAE;AAC9D,SAAO,oBAAoB,QAAQ,QAAQ,UAAU,EAAE,EAAE,QAAQ,SAAS,EAAE,EAAE,KAAK,CAAC;AACtF;AAEA,SAAS,iBAAiB,OAAe,OAA8B;AACrE,MAAI,OAAO,oBAAoB,uBAAuB,KAAK,CAAC;AAC5D,MAAI,OAAO;AACT,WAAO,KACJ,QAAQ,IAAI,OAAO,QAAQ,MAAM,QAAQ,uBAAuB,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE,EACxF,QAAQ,IAAI,OAAO,IAAI,MAAM,QAAQ,uBAAuB,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,EAChF,KAAK;AAAA,EACV;AACA,SAAO,oBAAoB,IAAI;AACjC;AAEO,SAAS,qBAAqB,OAAuD;AAhJ5F;AAiJE,QAAM,eAAe,wBAAuB,WAAM,iBAAN,YAAsB,gBAAgB,EAAE,KAAK,KAAK;AAC9F,QAAM,UAAU,MAAM,OAAO,uBAAuB,MAAM,IAAI,IAAI;AAClE,QAAM,WAAW,MAAM,QAAQ,uBAAuB,MAAM,KAAK,IAAI;AACrE,QAAM,SAAQ,kBAAa,QAAQ,MAArB,YAA0B,aAAa,OAAO;AAE5D,QAAM,eAAe,UAAU,iBAAiB,uBAAuB,OAAO,KAAK,SAAS,KAAK,IAAI;AACrG,QAAM,gBAAgB,WAAW,iBAAiB,uBAAuB,QAAQ,GAAG,KAAK,IAAI;AAC7F,QAAM,OAAO,gBAAgB,iBAAiB,SAAS;AAEvD,SAAO,EAAE,MAAM,MAAM;AACvB;AAEA,SAAS,iBAAiB,OAAyB;AACjD,QAAM,QAAkB,CAAC;AACzB,MAAI,UAAU;AACd,MAAI,QAA0B;AAC9B,MAAI,aAAa;AAEjB,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpD,UAAM,OAAO,MAAM,KAAK;AAExB,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,SAAS,MAAM,QAAQ,CAAC,MAAM,KAAM,SAAQ;AACzD;AAAA,IACF;AAEA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,SAAS,IAAK,eAAc;AAChC,QAAI,SAAS,OAAO,aAAa,EAAG,eAAc;AAElD,QAAI,SAAS,OAAO,eAAe,GAAG;AACpC,UAAI,QAAQ,KAAK,EAAG,OAAM,KAAK,QAAQ,KAAK,CAAC;AAC7C,gBAAU;AACV;AAAA,IACF;AAEA,eAAW;AAAA,EACb;AAEA,MAAI,QAAQ,KAAK,EAAG,OAAM,KAAK,QAAQ,KAAK,CAAC;AAC7C,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,QAAM,UAAU,uBAAuB,KAAK,EAAE,KAAK;AACnD,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,QAAQ,aAAa,OAAO;AAClC,QAAM,OAAO,iBAAiB,uBAAuB,OAAO,GAAG,KAAK;AAEpE,MAAI,SAAS,KAAM,QAAO,GAAG,IAAI,KAAK,KAAK;AAC3C,MAAI,MAAO,QAAO;AAClB,SAAO,oBAAoB,OAAO;AACpC;AAEO,SAAS,kBAAkB,OAA0C;AAC1E,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,iBAAiB,KAAK;AACtE,SAAO,SACJ,IAAI,CAAC,SAAS,oBAAoB,IAAI,CAAC,EACvC,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAEO,SAAS,qBACd,OACA,MACe;AACf,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AAC3D,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AAMzC,SAAO,IAAI,KAAK,eAAe,SAAS;AAAA,IACtC,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,MACJ,6BAAM,YAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC,EACrD,EAAE,OAAO,IAAI;AAChB;AAIA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MAAM,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AAChF;AAEA,SAAS,oBAAoB,MAAsB;AACjD,MAAI,CAAC,eAAe,KAAK,IAAI,KAAK,CAAC,oBAAoB,KAAK,IAAI,EAAG,QAAO;AAE1E,MAAI,OAAO,aAAa,aAAa;AACnC,UAAM,WAAW,SAAS,cAAc,UAAU;AAClD,aAAS,YAAY;AACrB,UAAM,SAAS,SAAS,iBAAiB,SAAS,SAAS,WAAW,SAAS;AAC/E,UAAM,YAAoB,CAAC;AAC3B,QAAI,OAAO,OAAO,SAAS;AAC3B,WAAO,MAAM;AACX,gBAAU,KAAK,IAAY;AAC3B,aAAO,OAAO,SAAS;AAAA,IACzB;AACA,cAAU,QAAQ,CAAC,aAAa;AAjQpC;AAkQM,eAAS,YAAY,wBAAuB,cAAS,cAAT,YAAsB,EAAE;AAAA,IACtE,CAAC;AACD,WAAO,SAAS;AAAA,EAClB;AAEA,SAAO,KACJ,MAAM,YAAY,EAClB,IAAI,CAAC,SAAU,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,IAAI,OAAO,eAAe,uBAAuB,IAAI,CAAC,CAAE,EAChH,KAAK,EAAE;AACZ;AAEA,SAAS,eAAe,OAAuB;AAC7C,QAAM,cAAc,MACjB,QAAQ,WAAW,IAAI,EACvB,QAAQ,8CAA8C,IAAI,EAC1D,QAAQ,YAAY,EAAE;AAEzB,SAAO,uBAAuB,WAAW;AAC3C;AAEA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,eAAe,IAAI,EAAE,QAAQ,WAAW,GAAG,EAAE,QAAQ,WAAW,GAAG,EAAE,KAAK;AACnF;AAEA,SAAS,cAAc,MAAoB;AACzC,QAAM,OAAO,SAAS,cAAc,KAAK;AACzC,OAAK,YAAY,KAAK,UAAU,IAAI,CAAC;AACrC,SAAO,KAAK;AACd;AAEA,SAAS,aAAa,SAAkB,WAA2B;AACjE,QAAM,QAAQ,QAAQ,UAAU,KAAK;AACrC,QAAM,YAAY;AAClB,SAAO,MAAM;AACf;AAEA,SAAS,YAAY,OAAiC;AACpD,SAAO,MAAM,KAAK,CAAC,SAAS,KAAK,aAAa,KAAK,gBAAiB,KAAiB,QAAQ,YAAY,MAAM,IAAI;AACrH;AAEA,SAAS,oBAAoB,SAA2B;AACtD,SAAO,MAAM,KAAK,QAAQ,QAAQ,EAAE,KAAK,CAAC,UAAU;AAClD,UAAM,UAAU,MAAM,QAAQ,YAAY;AAC1C,WAAO,YAAY,QAAQ,WAAW,IAAI,OAAO;AAAA,EACnD,CAAC;AACH;AAEA,SAAS,gBAAgB,MAAqC;AAC5D,QAAM,cAAc,kBAAkB,IAAI;AAC1C,MAAI,CAAC,KAAK,KAAK,KAAM,CAAC,eAAe,CAAC,iBAAiB,KAAK,IAAI,EAAI,QAAO;AAC3E,SAAO,EAAE,MAAM,YAAY;AAC7B;AAMA,SAAS,qBAAqB,MAA8B;AAC1D,SAAO,EAAE,MAAM,aAAa,GAAG;AACjC;AAEA,SAAS,gBAAgB,MAAuB;AAC9C,SAAO,QAAQ,KAAK,KAAK,CAAC,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI;AACxF;AAEA,SAAS,iBAAiB,OAAwB,SAAqC;AACrF,QAAM,aAAa,YAAY,KAAK;AACpC,QAAM,SAAqB,CAAC,CAAC,CAAC;AAE9B,QAAM,QAAQ,CAAC,SAAS;AAvU1B;AAwUI,QAAI,KAAK,aAAa,KAAK,gBAAiB,KAAiB,QAAQ,YAAY,MAAM,MAAM;AAC3F,aAAO,KAAK,CAAC,CAAC;AACd;AAAA,IACF;AAEA,iBAAO,OAAO,SAAS,CAAC,MAAxB,mBAA2B,KAAK,cAAc,IAAI;AAAA,EACpD,CAAC;AAED,QAAM,WAAW,OAAO,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,CAAC;AACrD,QAAM,WAA6B,CAAC;AACpC,WAAS,QAAQ,CAAC,WAAW,UAAU;AACrC,UAAM,OAAO,UAAU,aAAa,SAAS,SAAS,IAAI,aAAa,QAAQ,SAAS,WAAW;AACnG,UAAM,UAAU,gBAAgB,IAAI;AACpC,QAAI,SAAS;AACX,eAAS,KAAK,OAAO;AACrB;AAAA,IACF;AAGA,QAAI,cAAc,QAAQ,KAAK,QAAQ,SAAS,SAAS,GAAG;AAC1D,eAAS,KAAK,qBAAqB,UAAU,aAAa,SAAS,MAAM,IAAI,iBAAiB,CAAC;AAAA,IACjG;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,SAAS,oBAAoB,SAAoC;AAC/D,QAAM,UAAU,QAAQ,QAAQ,YAAY;AAE5C,MAAI,mBAAmB,KAAK,QAAQ,SAAS,KAAK,eAAe,KAAK,QAAQ,SAAS,GAAG;AACxF,UAAMA,WAAU,gBAAgB,QAAQ,SAAS;AACjD,WAAOA,WAAU,CAACA,QAAO,IAAI,CAAC;AAAA,EAChC;AAIA,MAAI,gBAAgB,QAAQ,SAAS,GAAG;AACtC,WAAO,CAAC,qBAAqB,QAAQ,SAAS,CAAC;AAAA,EACjD;AAEA,MAAI,YAAY,SAAS,oBAAoB,OAAO,GAAG;AACrD,UAAM,gBAAgB,eAAe,MAAM,KAAK,QAAQ,UAAU,CAAC;AACnE,WAAO,cAAc,SAAS,gBAAiB,CAAC,gBAAgB,QAAQ,SAAS,CAAC,EAAE,OAAO,OAAO;AAAA,EACpG;AAEA,MAAI,sBAAsB,IAAI,OAAO,KAAK,YAAY,MAAM,KAAK,QAAQ,UAAU,CAAC,KAAK,CAAC,oBAAoB,OAAO,GAAG;AACtH,WAAO,iBAAiB,MAAM,KAAK,QAAQ,UAAU,GAAG,OAAO;AAAA,EACjE;AAEA,QAAM,UAAU,gBAAgB,QAAQ,SAAS;AACjD,SAAO,UAAU,CAAC,OAAO,IAAI,CAAC;AAChC;AAEA,SAAS,eAAe,OAA0C;AAChE,QAAM,WAA6B,CAAC;AACpC,MAAI,cAAsB,CAAC;AAE3B,QAAM,cAAc,MAAM;AACxB,QAAI,CAAC,YAAY,OAAQ;AACzB,aAAS,KAAK,GAAG,iBAAiB,WAAW,CAAC;AAC9C,kBAAc,CAAC;AAAA,EACjB;AAEA,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,KAAK,aAAa,KAAK,cAAc;AACvC,YAAM,UAAU;AAChB,YAAM,UAAU,QAAQ,QAAQ,YAAY;AAC5C,UAAI,WAAW,IAAI,OAAO,GAAG;AAC3B,oBAAY;AACZ,iBAAS,KAAK,GAAG,oBAAoB,OAAO,CAAC;AAC7C;AAAA,MACF;AAAA,IACF;AAEA,gBAAY,KAAK,IAAI;AAAA,EACvB,CAAC;AAED,cAAY;AACZ,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAc,SAAiB,YAA4B;AACvF,MAAI,YAAY,KAAM,QAAO,aAAa;AAE1C,QAAM,aAAa,IAAI,OAAO,MAAM,OAAO,aAAa,IAAI;AAC5D,aAAW,YAAY,aAAa;AACpC,MAAI,QAAQ;AACZ,MAAI;AAEJ,UAAQ,QAAQ,WAAW,KAAK,IAAI,OAAO,MAAM;AAC/C,UAAM,SAAS,MAAM,CAAC;AACtB,QAAI,OAAO,KAAK,MAAM,EAAG,UAAS;AAAA,aACzB,CAAC,UAAU,KAAK,MAAM,EAAG,UAAS;AAC3C,QAAI,UAAU,EAAG,QAAO,WAAW;AAAA,EACrC;AAEA,SAAO,KAAK;AACd;AAEA,SAAS,0BAA0B,MAAgC;AACjE,QAAM,WAA6B,CAAC;AACpC,MAAI,SAAS;AAEb,QAAM,aAAa,CAAC,eAAuB;AACzC,UAAM,SAAS,WAAW,MAAM,SAAS;AACzC,UAAM,QAAQ,OAAO,SAAS;AAC9B,WAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,YAAM,UAAU,gBAAgB,QAAQ,QAAQ,KAAK,WAAW,KAAK;AACrE,UAAI,SAAS;AACX,iBAAS,KAAK,OAAO;AACrB;AAAA,MACF;AACA,UAAI,SAAS,QAAQ,KAAK,QAAQ,OAAO,SAAS,GAAG;AACnD,iBAAS,KAAK,qBAAqB,mBAAmB,CAAC;AAAA,MACzD;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,SAAS,KAAK,QAAQ;AAC3B,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,UAAM,QAAQ,oBAAoB,KAAK,IAAI;AAC3C,QAAI,CAAC,SAAS,MAAM,UAAU,QAAW;AACvC,iBAAW,IAAI;AACf;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,EAAG,YAAW,KAAK,MAAM,GAAG,MAAM,KAAK,CAAC;AAE1D,UAAM,WAAW,SAAS,MAAM;AAChC,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,MAAM,CAAC,EAAE,YAAY;AACrC,UAAM,aAAa,WAAW,QAAQ,SAAS;AAC/C,UAAM,aAAa,qBAAqB,MAAM,SAAS,UAAU;AACjE,UAAM,YAAY,KAAK,MAAM,UAAU,UAAU;AAEjD,QAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAS,KAAK,qBAAqB,SAAS,CAAC;AAAA,IAC/C,WAAW,sBAAsB,IAAI,OAAO,KAAK,UAAU,KAAK,SAAS,GAAG;AAC1E,YAAM,UAAU;AAChB,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,QAAQ,UAAU,QAAQ,IAAI,OAAO,IAAI,QAAQ,QAAQ,uBAAuB,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,EAAE,QAAQ,IAAI,OAAO,GAAG,QAAQ,KAAK,GAAG,GAAG,EAAE;AACtJ,YAAM,cAAc,MAAM,MAAM,SAAS;AACzC,kBAAY,QAAQ,CAAC,OAAO,UAAU;AACpC,cAAM,UAAU,gBAAgB,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,EAAE;AAC/D,YAAI,SAAS;AACX,mBAAS,KAAK,OAAO;AACrB;AAAA,QACF;AACA,YAAI,QAAQ,KAAK,QAAQ,YAAY,SAAS,GAAG;AAC/C,mBAAS,KAAK,qBAAqB,GAAG,OAAO,SAAS,QAAQ,EAAE,CAAC;AAAA,QACnE;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,YAAM,UAAU,gBAAgB,SAAS;AACzC,UAAI,QAAS,UAAS,KAAK,OAAO;AAAA,IACpC;AAEA,aAAS;AAAA,EACX;AAEA,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAgC;AAC1D,MAAI,OAAO,aAAa,eAAe,OAAO,SAAS,aAAa;AAClE,WAAO,0BAA0B,IAAI;AAAA,EACvC;AAEA,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,YAAY;AACrB,SAAO,eAAe,MAAM,KAAK,SAAS,QAAQ,UAAU,CAAC;AAC/D;AAEA,SAAS,mBAAmB,MAAgC;AArf5D;AAsfE,QAAM,aAAa,uBAAuB,IAAI;AAC9C,QAAM,SAAkB,gBAAW,MAAM,iBAAiB,MAAlC,YAAuC,CAAC;AAChE,SAAO,MACJ,OAAO,CAAC,MAAM,UAAU,KAAK,SAAS,KAAK,EAAE,UAAU,MAAM,SAAS,KAAK,SAAS,GAAG,EACvF,IAAI,CAAC,UAAU,EAAE,MAAM,MAAM,aAAa,KAAK,QAAQ,WAAW,GAAG,EAAE,KAAK,EAAE,EAAE;AACrF;AAEA,SAAS,iBAAiB,MAAsB;AA7fhD;AA8fE,UAAO,gBAAK,QAAQ,WAAW,GAAG,EAAE,UAAU,EAAE,MAAM,OAAO,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,MAAlF,mBAAqF,WAArF,YAA+F;AACxG;AAEA,SAAS,uBAAuB,MAAuB;AACrD,MAAI,CAAC,QAAQ,KAAK,SAAS,GAAI,QAAO;AACtC,MAAI,gCAAgC,KAAK,IAAI,EAAG,QAAO;AAEvD,QAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,OAAO,OAAO;AAC9C,MAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AAEjD,SAAO,MAAM,MAAM,CAAC,SAAS,sBAAsB,KAAK,IAAI,CAAC;AAC/D;AAEA,SAAS,uBAAuB,UAA4B,WAA2B;AA3gBvF;AA4gBE,WAAS,QAAQ,YAAY,GAAG,QAAQ,SAAS,QAAQ,SAAS,GAAG;AACnE,UAAM,OAAO,kBAAiB,oBAAS,KAAK,MAAd,mBAAiB,gBAAjB,YAAgC,EAAE;AAChE,QAAI,KAAM,QAAO;AAAA,EACnB;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,SAAkC;AAC/D,SAAO,QAAQ,QAAQ,SAAS,mBAAmB,KAAK,QAAQ,IAAI,KAAK,eAAe,KAAK,QAAQ,IAAI,EAAE;AAC7G;AAEA,SAAS,iBAAiB,UAA4B,OAAwB;AAC5E,QAAM,UAAU,SAAS,KAAK;AAC9B,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,sBAAsB,OAAO,EAAG,QAAO;AAE3C,QAAM,OAAO,iBAAiB,QAAQ,WAAW;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,uBAAuB,KAAK,IAAI,KAAK,iBAAiB,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI,EAAG,QAAO;AAEvG,QAAM,WAAW,uBAAuB,UAAU,KAAK;AACvD,MAAI,WAAW,KAAK,IAAI,EAAG,QAAO,QAAQ,aAAa,uBAAuB,QAAQ,KAAK,kBAAkB,KAAK,QAAQ,EAAE;AAE5H,SAAO,uBAAuB,IAAI,KAAK,kBAAkB,KAAK,QAAQ;AACxE;AAEA,SAAS,oBAAoB,UAAmG;AAC9H,QAAM,iBAAiB,SACpB,IAAI,CAAC,SAAS,UAAW,QAAQ,eAAe,sBAAsB,OAAO,IAAI,QAAQ,EAAG,EAC5F,OAAO,CAAC,UAAU,SAAS,CAAC;AAC/B,QAAM,eAAe,eAAe;AACpC,MAAI,eAAe,EAAG,QAAO,EAAE,cAAc,UAAU,iBAAiB,CAAC,EAAE;AAE3E,QAAM,gBAAgB,KAAK,IAAI,KAAK,KAAK,eAAe,GAAG,GAAG,CAAC;AAC/D,QAAM,uBAAuB,KAAK,IAAI,GAAG,eAAe,aAAa;AAErE,WAAS,UAAU,sBAAsB,UAAU,cAAc,WAAW,GAAG;AAC7E,UAAM,QAAQ,eAAe,OAAO;AACpC,QAAI,UAAU,KAAK,iBAAiB,UAAU,KAAK,GAAG;AACpD,aAAO,EAAE,cAAc,SAAS,MAAM,GAAG,KAAK,GAAG,iBAAiB,SAAS,MAAM,KAAK,EAAE;AAAA,IAC1F;AAAA,EACF;AAEA,SAAO,EAAE,cAAc,UAAU,iBAAiB,CAAC,EAAE;AACvD;AAEO,SAAS,yBAAyB,MAAoC;AAC3E,QAAM,gBAAgB,aAAa,oBAAoB,IAAI,CAAC;AAC5D,QAAM,EAAE,cAAc,gBAAgB,IAAI,oBAAoB,mBAAmB,aAAa,CAAC;AAE/F,MAAI,CAAC,gBAAgB,OAAQ,QAAO,EAAE,UAAU,eAAe,aAAa,GAAG;AAE/E,SAAO;AAAA,IACL,UAAU,aAAa,IAAI,CAAC,YAAS;AAjkBzC;AAikB4C,2BAAQ,SAAR,YAAgB;AAAA,KAAE,EAAE,KAAK,EAAE;AAAA,IACnE,aAAa,gBAAgB,IAAI,CAAC,YAAS;AAlkB/C;AAkkBkD,2BAAQ,SAAR,YAAgB;AAAA,KAAE,EAAE,KAAK,EAAE;AAAA,EAC3E;AACF;AAEO,SAAS,yBAAyB,MAAoC;AAC3E,QAAM,cAAc,uBAAuB,IAAI;AAC/C,QAAM,EAAE,cAAc,gBAAgB,IAAI,oBAAoB,mBAAmB,WAAW,CAAC;AAE7F,MAAI,CAAC,gBAAgB,OAAQ,QAAO,EAAE,UAAU,aAAa,aAAa,GAAG;AAE7E,SAAO;AAAA,IACL,UAAU,aAAa,IAAI,CAAC,YAAS;AA7kBzC;AA6kB4C,2BAAQ,SAAR,YAAgB;AAAA,KAAE,EAAE,KAAK,EAAE;AAAA,IACnE,aAAa,gBAAgB,IAAI,CAAC,YAAS;AA9kB/C;AA8kBkD,2BAAQ,SAAR,YAAgB;AAAA,KAAE,EAAE,KAAK,EAAE;AAAA,EAC3E;AACF;AAEO,SAAS,iBAAiB,OAA2D,YAAY,KAAa;AAllBrH;AAmlBE,QAAM,QAAO,WAAM,aAAN,mBAAgB;AAC7B,MAAI,MAAM;AACR,WAAO,uBAAuB,kBAAkB,yBAAyB,IAAI,EAAE,UAAU,SAAS,CAAC,EAAE,KAAK;AAAA,EAC5G;AAEA,QAAM,QAAO,WAAM,SAAN,YAAc;AAC3B,QAAM,WAAW,yBAAyB,IAAI,EAAE;AAChD,QAAM,aAAY,oBAAS,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,MAA/C,mBAAkD,WAAlD,YAA4D;AAC9E,SAAO,uBAAuB,SAAS,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;AACzF;","names":["segment"]}
@@ -199,10 +199,20 @@ function cleanGongText(value) {
199
199
  if (!value) return "";
200
200
  return decodeEmailDisplayText(value).trim();
201
201
  }
202
- function getTimelineGongCallDisplay(gongCall) {
202
+ function useHydrated() {
203
+ const [hydrated, setHydrated] = React.useState(false);
204
+ React.useEffect(() => {
205
+ setHydrated(true);
206
+ }, []);
207
+ return hydrated;
208
+ }
209
+ function timestampTimeZone(opts) {
210
+ return (opts == null ? void 0 : opts.utcTimestamps) ? { timeZone: "UTC" } : {};
211
+ }
212
+ function getTimelineGongCallDisplay(gongCall, opts) {
203
213
  var _a, _b, _c, _d;
204
214
  const title = cleanGongText(gongCall.title);
205
- const startTime = (_a = formatEmailTimestamp(gongCall.startTime)) != null ? _a : "";
215
+ const startTime = (_a = formatEmailTimestamp(gongCall.startTime, timestampTimeZone(opts))) != null ? _a : "";
206
216
  const duration = formatCallDuration(gongCall.durationSeconds);
207
217
  const direction = cleanGongText(gongCall.direction);
208
218
  const outcome = cleanGongText(gongCall.outcome);
@@ -213,13 +223,13 @@ function getTimelineGongCallDisplay(gongCall) {
213
223
  const snippet = (_d = (_c = brief.split("\n").find((line) => line.trim())) == null ? void 0 : _c.trim()) != null ? _d : "";
214
224
  return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet };
215
225
  }
216
- function getTimelineEmailDisplay(email) {
226
+ function getTimelineEmailDisplay(email, opts) {
217
227
  var _a, _b, _c, _d, _e;
218
228
  const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail });
219
229
  const to = formatAddressList(email.to) || decodeEmailDisplayText((_a = email.to) != null ? _a : "");
220
230
  const cc = formatAddressList(email.cc) || decodeEmailDisplayText((_b = email.cc) != null ? _b : "");
221
231
  const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText((_c = email.bcc) != null ? _c : "");
222
- const date = (_e = formatEmailTimestamp(email.date)) != null ? _e : decodeEmailDisplayText((_d = email.date) != null ? _d : "");
232
+ const date = (_e = formatEmailTimestamp(email.date, timestampTimeZone(opts))) != null ? _e : decodeEmailDisplayText((_d = email.date) != null ? _d : "");
223
233
  const subject = email.subject ? decodeEmailDisplayText(email.subject) : "";
224
234
  const bodyText = reactNodeToDisplayText(email.body);
225
235
  const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140);
@@ -231,7 +241,8 @@ function EmailMetadata({
231
241
  showAllRecipients,
232
242
  setShowAllRecipients
233
243
  }) {
234
- const display = getTimelineEmailDisplay(email);
244
+ const hydrated = useHydrated();
245
+ const display = getTimelineEmailDisplay(email, { utcTimestamps: !hydrated });
235
246
  const hasExpandableRecipients = Boolean(display.cc || display.bcc);
236
247
  return /* @__PURE__ */ jsxs(Fragment, { children: [
237
248
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
@@ -542,7 +553,8 @@ function GongCallCard({
542
553
  classes
543
554
  }) {
544
555
  const gongCall = event.gongCall;
545
- const display = getTimelineGongCallDisplay(gongCall);
556
+ const hydrated = useHydrated();
557
+ const display = getTimelineGongCallDisplay(gongCall, { utcTimestamps: !hydrated });
546
558
  if (variant === "default") {
547
559
  return /* @__PURE__ */ jsx("div", { className: classes.cardContainer, "data-variant": variant, "data-slot": "timeline-gong-card", children: /* @__PURE__ */ jsx(
548
560
  "div",