@handled-ai/design-system 0.20.22 → 0.20.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/conversation-panel.d.ts +9 -0
- package/dist/components/conversation-panel.js +2 -1
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/email-display-helpers.d.ts +3 -1
- package/dist/components/email-display-helpers.js +63 -14
- package/dist/components/email-display-helpers.js.map +1 -1
- package/dist/components/timeline-activity.js +18 -6
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/internal/safe-html.js +32 -0
- package/dist/internal/safe-html.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +40 -1
- package/src/components/__tests__/email-display-helpers.test.ts +45 -1
- package/src/components/__tests__/timeline-activity.test.tsx +7 -1
- package/src/components/conversation-panel.tsx +10 -0
- package/src/components/email-display-helpers.ts +63 -15
- package/src/components/timeline-activity.tsx +28 -6
- package/src/internal/__tests__/safe-html.test.ts +24 -5
- package/src/internal/safe-html.ts +39 -0
|
@@ -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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")\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":"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","names":["segment"]}
|
|
@@ -199,10 +199,20 @@ function cleanGongText(value) {
|
|
|
199
199
|
if (!value) return "";
|
|
200
200
|
return decodeEmailDisplayText(value).trim();
|
|
201
201
|
}
|
|
202
|
-
function
|
|
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
|
|
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
|
|
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",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink, Phone } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n /**\n * Sender signature HTML, split out of the main body by the consuming app's\n * email pipeline. When present, it is hidden behind a subtle \"Show\n * signature\" toggle (collapsed by default) so signatures don't add noise to\n * the timeline. Sanitized before rendering. Optional — when absent the body\n * renders exactly as before. Mirrors `signatureHtml` on EmailPreviewCard.\n */\n signatureHtml?: string | null\n /**\n * Quoted / trailing thread content (the \"On <date> X wrote:\" history) split\n * out of the main body. When present, it is hidden behind a subtle \"Show\n * quoted text\" toggle (collapsed by default). Sanitized before rendering.\n * Optional — when absent the body renders exactly as before.\n */\n quotedHtml?: string | null\n }\n /**\n * Gong call payload. When present, the card renders a dedicated, first-class\n * Gong call card (mirroring the `email` treatment) instead of generic\n * content: a collapsed duration + brief-snippet preview, and an expanded\n * card with the call title, start time, direction/outcome chips, the AI\n * brief, key points, next steps, and a \"View in Gong\" link. Opt-in: when\n * absent, existing consumers are unaffected. Every field except `title` is\n * optional/nullable and renders nothing when absent (no empty sections,\n * no \"null\"/\"undefined\", no \"Invalid Date\").\n */\n gongCall?: {\n /** Gong call title. */\n title: string\n /** ISO datetime of the call start. */\n startTime?: string | null\n /** Call length in seconds. Formatted to a human duration when present. */\n durationSeconds?: number | null\n /** Call direction, e.g. \"Outbound\" / \"Inbound\". */\n direction?: string | null\n /** Call outcome, e.g. \"Connected\". */\n outcome?: string | null\n /** Gong AI call brief (plain text, may be multi-paragraph). */\n brief?: string | null\n /** Gong AI key points (plain text, often newline-delimited bullets). */\n keyPoints?: string | null\n /** Gong AI highlights / next steps (plain text). */\n nextSteps?: string | null\n /** Deep link to the call in Gong. */\n url?: string | null\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-start justify-between border-b border-border/60 bg-muted/30 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const hasGongCall = !!event.gongCall\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div className={classes.outerRowGap}>\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail || hasGongCall) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasGongCall ? (\n <GongCallCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n ) : hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineGongCall = NonNullable<TimelineEvent[\"gongCall\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\n/**\n * Formats a call length in seconds to a compact human duration:\n * \"38 min\", \">= 60 min\" → \"1 h 12 min\". Returns \"\" for absent/invalid\n * input so callers can omit the chip entirely (never render \"0 min\").\n */\nfunction formatCallDuration(durationSeconds?: number | null): string {\n if (durationSeconds == null || !Number.isFinite(durationSeconds) || durationSeconds <= 0) return \"\"\n const totalMinutes = Math.round(durationSeconds / 60)\n if (totalMinutes < 1) return \"< 1 min\"\n if (totalMinutes < 60) return `${totalMinutes} min`\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`\n}\n\nfunction cleanGongText(value?: string | null): string {\n if (!value) return \"\"\n return decodeEmailDisplayText(value).trim()\n}\n\nfunction getTimelineGongCallDisplay(gongCall: TimelineGongCall) {\n const title = cleanGongText(gongCall.title)\n const startTime = formatEmailTimestamp(gongCall.startTime) ?? \"\"\n const duration = formatCallDuration(gongCall.durationSeconds)\n const direction = cleanGongText(gongCall.direction)\n const outcome = cleanGongText(gongCall.outcome)\n const brief = cleanGongText(gongCall.brief)\n const keyPoints = cleanGongText(gongCall.keyPoints)\n const nextSteps = cleanGongText(gongCall.nextSteps)\n const url = gongCall.url?.trim() || \"\"\n const snippet = brief.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n\n return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet }\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n const hasRecipients = Boolean(to || cc || bcc)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet, hasRecipients }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const display = getTimelineEmailDisplay(email)\n const hasExpandableRecipients = Boolean(display.cc || display.bcc)\n\n return (\n <>\n {/* Row 1: sender name + email, with the date right-aligned. The `gap-4`\n guarantees whitespace between the sender block and the date so they\n never visually jam together. */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n {/* Recipient lines live on their own full-width, stacked rows below the\n sender/date row. Each line truncates independently. The To-segment is\n suppressed entirely when there is no recipient data (history emails)\n rather than rendering a meaningless \"no recipient yet\" placeholder. */}\n {display.hasRecipients ? (\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <span className=\"min-w-0 flex-1 truncate\">\n {display.to ? (\n <>To {display.to}</>\n ) : (\n <>\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc || display.bcc}\n </>\n )}\n {display.to && !showAllRecipients && hasExpandableRecipients ? (\n <>, …</>\n ) : null}\n </span>\n {hasExpandableRecipients ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n aria-label={showAllRecipients ? \"Hide cc and bcc\" : \"Show cc and bcc\"}\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n {showAllRecipients && display.to && display.cc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc}\n </div>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction SuppressedHtmlSection({\n html,\n showLabel,\n hideLabel,\n slot,\n}: {\n html: string\n showLabel: string\n hideLabel: string\n slot: string\n}) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <div className=\"mt-2\" data-slot={slot}>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setOpen((value) => !value)\n }}\n aria-expanded={open}\n className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground\"\n >\n {open ? hideLabel : showLabel}\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open ? (\n <div\n data-slot={`${slot}-content`}\n className=\"mt-2 border-l-2 border-border pl-3 text-sm leading-relaxed text-muted-foreground\"\n >\n <SharedEmailBody html={html} variant=\"history\" collapseDetails={false} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n const hasSignature = Boolean(email.signatureHtml && email.signatureHtml.trim())\n const hasQuoted = Boolean(email.quotedHtml && email.quotedHtml.trim())\n\n // Fast path: no signature/quoted slots → identical output to before.\n if (!hasSignature && !hasQuoted) {\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n }\n\n return (\n <div data-slot={email.bodyHtml ? \"timeline-email-html\" : undefined}>\n {body}\n {hasSignature ? (\n <SuppressedHtmlSection\n html={email.signatureHtml as string}\n showLabel=\"Show signature\"\n hideLabel=\"Hide signature\"\n slot=\"timeline-email-signature\"\n />\n ) : null}\n {hasQuoted ? (\n <SuppressedHtmlSection\n html={email.quotedHtml as string}\n showLabel=\"Show quoted text\"\n hideLabel=\"Hide quoted text\"\n slot=\"timeline-email-quoted\"\n />\n ) : null}\n </div>\n )\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\n/**\n * Top-of-card collapse affordance. Long expanded emails are tedious to collapse\n * from the bottom alone, so the header row also carries a \"Show less\" control.\n */\nfunction CollapseTopButton({\n onClick,\n className,\n}: {\n onClick: React.MouseEventHandler<HTMLButtonElement>\n className?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Show less\"\n data-slot=\"timeline-collapse-top\"\n className={cn(\n \"shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-foreground\",\n className,\n )}\n >\n <span className=\"sr-only sm:not-sr-only\">Show less</span>\n <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction GongLinkAction({\n url,\n className,\n}: {\n url: string\n className: string\n}) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={className}\n data-slot=\"timeline-gong-link\"\n >\n View in Gong\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\n/**\n * The expanded Gong call card body — call title + start time header, optional\n * direction/outcome chips, the AI brief as primary body text, and labeled\n * Key points / Next steps subsections. Every section renders only when its\n * content is present.\n */\nfunction TimelineGongCallBody({\n display,\n labelClassName,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n labelClassName: string\n}) {\n return (\n <div className=\"space-y-3\" data-slot=\"timeline-gong-body\">\n {display.brief ? (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.brief}\n </div>\n ) : null}\n {display.keyPoints ? (\n <div data-slot=\"timeline-gong-key-points\">\n <div className={labelClassName}>Key points</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.keyPoints}\n </div>\n </div>\n ) : null}\n {display.nextSteps ? (\n <div data-slot=\"timeline-gong-next-steps\">\n <div className={labelClassName}>Next steps</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.nextSteps}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction GongCallHeader({\n display,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n}) {\n return (\n <>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/60\" />\n <span className=\"min-w-0 truncate font-semibold text-foreground text-[13px]\">{display.title}</span>\n </div>\n {display.startTime ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.startTime}</span>\n ) : null}\n </div>\n {display.duration || display.direction || display.outcome ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {display.duration ? <span data-slot=\"timeline-gong-duration\">{display.duration}</span> : null}\n {display.direction ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.direction}</span>\n ) : null}\n {display.outcome ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.outcome}</span>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction CollapsedGongCallPreview({\n display,\n className,\n actionClassName,\n onClick,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px] text-muted-foreground\">\n {display.duration ? (\n <>\n <span className=\"inline-flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {display.duration}\n </span>\n {display.snippet ? <span className=\"mx-1.5 text-muted-foreground/40\">·</span> : null}\n </>\n ) : null}\n {display.snippet ? <span>{display.snippet}</span> : null}\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction GongCallCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n const gongCall = event.gongCall as TimelineGongCall\n const display = getTimelineGongCallDisplay(gongCall)\n\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\",\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n\n <div className=\"mt-2 flex items-center gap-3\">\n {display.url ? (\n <GongLinkAction\n url={display.url}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n {expanded ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, display.url ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {display.url ? <GongLinkAction url={display.url} className={classes.actionLink} /> : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink, Phone } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n /**\n * Sender signature HTML, split out of the main body by the consuming app's\n * email pipeline. When present, it is hidden behind a subtle \"Show\n * signature\" toggle (collapsed by default) so signatures don't add noise to\n * the timeline. Sanitized before rendering. Optional — when absent the body\n * renders exactly as before. Mirrors `signatureHtml` on EmailPreviewCard.\n */\n signatureHtml?: string | null\n /**\n * Quoted / trailing thread content (the \"On <date> X wrote:\" history) split\n * out of the main body. When present, it is hidden behind a subtle \"Show\n * quoted text\" toggle (collapsed by default). Sanitized before rendering.\n * Optional — when absent the body renders exactly as before.\n */\n quotedHtml?: string | null\n }\n /**\n * Gong call payload. When present, the card renders a dedicated, first-class\n * Gong call card (mirroring the `email` treatment) instead of generic\n * content: a collapsed duration + brief-snippet preview, and an expanded\n * card with the call title, start time, direction/outcome chips, the AI\n * brief, key points, next steps, and a \"View in Gong\" link. Opt-in: when\n * absent, existing consumers are unaffected. Every field except `title` is\n * optional/nullable and renders nothing when absent (no empty sections,\n * no \"null\"/\"undefined\", no \"Invalid Date\").\n */\n gongCall?: {\n /** Gong call title. */\n title: string\n /** ISO datetime of the call start. */\n startTime?: string | null\n /** Call length in seconds. Formatted to a human duration when present. */\n durationSeconds?: number | null\n /** Call direction, e.g. \"Outbound\" / \"Inbound\". */\n direction?: string | null\n /** Call outcome, e.g. \"Connected\". */\n outcome?: string | null\n /** Gong AI call brief (plain text, may be multi-paragraph). */\n brief?: string | null\n /** Gong AI key points (plain text, often newline-delimited bullets). */\n keyPoints?: string | null\n /** Gong AI highlights / next steps (plain text). */\n nextSteps?: string | null\n /** Deep link to the call in Gong. */\n url?: string | null\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-start justify-between border-b border-border/60 bg-muted/30 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const hasGongCall = !!event.gongCall\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div className={classes.outerRowGap}>\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail || hasGongCall) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasGongCall ? (\n <GongCallCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n ) : hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineGongCall = NonNullable<TimelineEvent[\"gongCall\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\n/**\n * Formats a call length in seconds to a compact human duration:\n * \"38 min\", \">= 60 min\" → \"1 h 12 min\". Returns \"\" for absent/invalid\n * input so callers can omit the chip entirely (never render \"0 min\").\n */\nfunction formatCallDuration(durationSeconds?: number | null): string {\n if (durationSeconds == null || !Number.isFinite(durationSeconds) || durationSeconds <= 0) return \"\"\n const totalMinutes = Math.round(durationSeconds / 60)\n if (totalMinutes < 1) return \"< 1 min\"\n if (totalMinutes < 60) return `${totalMinutes} min`\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`\n}\n\nfunction cleanGongText(value?: string | null): string {\n if (!value) return \"\"\n return decodeEmailDisplayText(value).trim()\n}\n\n/**\n * True once the component has mounted on the client. Timestamps render in\n * UTC for SSR + the hydration pass (server and client HTML must match — the\n * server has no idea what timezone the viewer is in), then flip to the\n * viewer's local timezone immediately after mount.\n */\nfunction useHydrated(): boolean {\n const [hydrated, setHydrated] = React.useState(false)\n React.useEffect(() => {\n setHydrated(true)\n }, [])\n return hydrated\n}\n\ntype TimestampDisplayOptions = { utcTimestamps?: boolean }\n\nfunction timestampTimeZone(opts?: TimestampDisplayOptions): { timeZone?: string } {\n return opts?.utcTimestamps ? { timeZone: \"UTC\" } : {}\n}\n\nfunction getTimelineGongCallDisplay(gongCall: TimelineGongCall, opts?: TimestampDisplayOptions) {\n const title = cleanGongText(gongCall.title)\n const startTime = formatEmailTimestamp(gongCall.startTime, timestampTimeZone(opts)) ?? \"\"\n const duration = formatCallDuration(gongCall.durationSeconds)\n const direction = cleanGongText(gongCall.direction)\n const outcome = cleanGongText(gongCall.outcome)\n const brief = cleanGongText(gongCall.brief)\n const keyPoints = cleanGongText(gongCall.keyPoints)\n const nextSteps = cleanGongText(gongCall.nextSteps)\n const url = gongCall.url?.trim() || \"\"\n const snippet = brief.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n\n return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet }\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail, opts?: TimestampDisplayOptions) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date, timestampTimeZone(opts)) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n const hasRecipients = Boolean(to || cc || bcc)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet, hasRecipients }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const hydrated = useHydrated()\n const display = getTimelineEmailDisplay(email, { utcTimestamps: !hydrated })\n const hasExpandableRecipients = Boolean(display.cc || display.bcc)\n\n return (\n <>\n {/* Row 1: sender name + email, with the date right-aligned. The `gap-4`\n guarantees whitespace between the sender block and the date so they\n never visually jam together. */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n {/* Recipient lines live on their own full-width, stacked rows below the\n sender/date row. Each line truncates independently. The To-segment is\n suppressed entirely when there is no recipient data (history emails)\n rather than rendering a meaningless \"no recipient yet\" placeholder. */}\n {display.hasRecipients ? (\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <span className=\"min-w-0 flex-1 truncate\">\n {display.to ? (\n <>To {display.to}</>\n ) : (\n <>\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc || display.bcc}\n </>\n )}\n {display.to && !showAllRecipients && hasExpandableRecipients ? (\n <>, …</>\n ) : null}\n </span>\n {hasExpandableRecipients ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n aria-label={showAllRecipients ? \"Hide cc and bcc\" : \"Show cc and bcc\"}\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n {showAllRecipients && display.to && display.cc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc}\n </div>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction SuppressedHtmlSection({\n html,\n showLabel,\n hideLabel,\n slot,\n}: {\n html: string\n showLabel: string\n hideLabel: string\n slot: string\n}) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <div className=\"mt-2\" data-slot={slot}>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setOpen((value) => !value)\n }}\n aria-expanded={open}\n className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground\"\n >\n {open ? hideLabel : showLabel}\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open ? (\n <div\n data-slot={`${slot}-content`}\n className=\"mt-2 border-l-2 border-border pl-3 text-sm leading-relaxed text-muted-foreground\"\n >\n <SharedEmailBody html={html} variant=\"history\" collapseDetails={false} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n const hasSignature = Boolean(email.signatureHtml && email.signatureHtml.trim())\n const hasQuoted = Boolean(email.quotedHtml && email.quotedHtml.trim())\n\n // Fast path: no signature/quoted slots → identical output to before.\n if (!hasSignature && !hasQuoted) {\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n }\n\n return (\n <div data-slot={email.bodyHtml ? \"timeline-email-html\" : undefined}>\n {body}\n {hasSignature ? (\n <SuppressedHtmlSection\n html={email.signatureHtml as string}\n showLabel=\"Show signature\"\n hideLabel=\"Hide signature\"\n slot=\"timeline-email-signature\"\n />\n ) : null}\n {hasQuoted ? (\n <SuppressedHtmlSection\n html={email.quotedHtml as string}\n showLabel=\"Show quoted text\"\n hideLabel=\"Hide quoted text\"\n slot=\"timeline-email-quoted\"\n />\n ) : null}\n </div>\n )\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">·</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\n/**\n * Top-of-card collapse affordance. Long expanded emails are tedious to collapse\n * from the bottom alone, so the header row also carries a \"Show less\" control.\n */\nfunction CollapseTopButton({\n onClick,\n className,\n}: {\n onClick: React.MouseEventHandler<HTMLButtonElement>\n className?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Show less\"\n data-slot=\"timeline-collapse-top\"\n className={cn(\n \"shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-foreground\",\n className,\n )}\n >\n <span className=\"sr-only sm:not-sr-only\">Show less</span>\n <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction GongLinkAction({\n url,\n className,\n}: {\n url: string\n className: string\n}) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={className}\n data-slot=\"timeline-gong-link\"\n >\n View in Gong\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\n/**\n * The expanded Gong call card body — call title + start time header, optional\n * direction/outcome chips, the AI brief as primary body text, and labeled\n * Key points / Next steps subsections. Every section renders only when its\n * content is present.\n */\nfunction TimelineGongCallBody({\n display,\n labelClassName,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n labelClassName: string\n}) {\n return (\n <div className=\"space-y-3\" data-slot=\"timeline-gong-body\">\n {display.brief ? (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.brief}\n </div>\n ) : null}\n {display.keyPoints ? (\n <div data-slot=\"timeline-gong-key-points\">\n <div className={labelClassName}>Key points</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.keyPoints}\n </div>\n </div>\n ) : null}\n {display.nextSteps ? (\n <div data-slot=\"timeline-gong-next-steps\">\n <div className={labelClassName}>Next steps</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.nextSteps}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction GongCallHeader({\n display,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n}) {\n return (\n <>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/60\" />\n <span className=\"min-w-0 truncate font-semibold text-foreground text-[13px]\">{display.title}</span>\n </div>\n {display.startTime ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.startTime}</span>\n ) : null}\n </div>\n {display.duration || display.direction || display.outcome ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {display.duration ? <span data-slot=\"timeline-gong-duration\">{display.duration}</span> : null}\n {display.direction ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.direction}</span>\n ) : null}\n {display.outcome ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.outcome}</span>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction CollapsedGongCallPreview({\n display,\n className,\n actionClassName,\n onClick,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px] text-muted-foreground\">\n {display.duration ? (\n <>\n <span className=\"inline-flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {display.duration}\n </span>\n {display.snippet ? <span className=\"mx-1.5 text-muted-foreground/40\">·</span> : null}\n </>\n ) : null}\n {display.snippet ? <span>{display.snippet}</span> : null}\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction GongCallCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n const gongCall = event.gongCall as TimelineGongCall\n const hydrated = useHydrated()\n const display = getTimelineGongCallDisplay(gongCall, { utcTimestamps: !hydrated })\n\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\",\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n\n <div className=\"mt-2 flex items-center gap-3\">\n {display.url ? (\n <GongLinkAction\n url={display.url}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n {expanded ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, display.url ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {display.url ? <GongLinkAction url={display.url} className={classes.actionLink} /> : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -122,6 +122,33 @@ function sanitizeFontSize(value) {
|
|
|
122
122
|
if (!Number.isFinite(amount) || amount <= 0 || amount > maxByUnit[unit]) return null;
|
|
123
123
|
return `${amount}${unit}`;
|
|
124
124
|
}
|
|
125
|
+
const COLOR_VALUE = /^(#[0-9a-f]{3,8}|rgba?\([\d\s,./%]+\)|[a-z]+)$/;
|
|
126
|
+
const LENGTH_BOX_VALUE = /^(-?[\d.]+(px|em|rem|%)?|auto|0)(\s+(-?[\d.]+(px|em|rem|%)?|auto|0)){0,3}$/;
|
|
127
|
+
const SAFE_TEXT_STYLE_PROPS = {
|
|
128
|
+
color: COLOR_VALUE,
|
|
129
|
+
"background-color": COLOR_VALUE,
|
|
130
|
+
"font-family": /^[a-z0-9\s,'"-]+$/,
|
|
131
|
+
"font-style": /^(italic|normal|oblique)$/,
|
|
132
|
+
"font-weight": /^(bold|bolder|lighter|normal|[1-9]00)$/,
|
|
133
|
+
"font-variant": /^[a-z\s-]+$/,
|
|
134
|
+
"text-decoration": /^[a-z\s-]+$/,
|
|
135
|
+
"text-decoration-line": /^[a-z\s-]+$/,
|
|
136
|
+
"text-transform": /^(none|capitalize|uppercase|lowercase)$/,
|
|
137
|
+
"text-align": /^(left|right|center|justify)$/,
|
|
138
|
+
"letter-spacing": /^(normal|-?[\d.]+(px|em|rem)?)$/,
|
|
139
|
+
"line-height": /^(normal|[\d.]+(px|em|rem|%)?)$/,
|
|
140
|
+
"white-space": /^[a-z-]+$/,
|
|
141
|
+
margin: LENGTH_BOX_VALUE,
|
|
142
|
+
"margin-top": LENGTH_BOX_VALUE,
|
|
143
|
+
"margin-right": LENGTH_BOX_VALUE,
|
|
144
|
+
"margin-bottom": LENGTH_BOX_VALUE,
|
|
145
|
+
"margin-left": LENGTH_BOX_VALUE,
|
|
146
|
+
padding: LENGTH_BOX_VALUE,
|
|
147
|
+
"padding-top": LENGTH_BOX_VALUE,
|
|
148
|
+
"padding-right": LENGTH_BOX_VALUE,
|
|
149
|
+
"padding-bottom": LENGTH_BOX_VALUE,
|
|
150
|
+
"padding-left": LENGTH_BOX_VALUE
|
|
151
|
+
};
|
|
125
152
|
function sanitizeStyle(value) {
|
|
126
153
|
const declarations = [];
|
|
127
154
|
for (const rawDeclaration of value.split(";")) {
|
|
@@ -137,6 +164,11 @@ function sanitizeStyle(value) {
|
|
|
137
164
|
if (property === "font-size") {
|
|
138
165
|
const fontSize = sanitizeFontSize(rawValue);
|
|
139
166
|
if (fontSize) declarations.push(`font-size: ${fontSize}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const allowedValue = SAFE_TEXT_STYLE_PROPS[property];
|
|
170
|
+
if (allowedValue == null ? void 0 : allowedValue.test(rawValue)) {
|
|
171
|
+
declarations.push(`${property}: ${rawValue}`);
|
|
140
172
|
}
|
|
141
173
|
}
|
|
142
174
|
return declarations.length ? declarations.join("; ") : null;
|