@handled-ai/design-system 0.20.5 → 0.20.6

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.
Files changed (34) hide show
  1. package/dist/components/conversation-panel.d.ts +19 -0
  2. package/dist/components/conversation-panel.js +116 -292
  3. package/dist/components/conversation-panel.js.map +1 -1
  4. package/dist/components/email-body.d.ts +15 -0
  5. package/dist/components/email-body.js +101 -0
  6. package/dist/components/email-body.js.map +1 -0
  7. package/dist/components/email-display-helpers.d.ts +34 -0
  8. package/dist/components/email-display-helpers.js +436 -0
  9. package/dist/components/email-display-helpers.js.map +1 -0
  10. package/dist/components/email-preview-card.d.ts +7 -4
  11. package/dist/components/email-preview-card.js +48 -25
  12. package/dist/components/email-preview-card.js.map +1 -1
  13. package/dist/components/timeline-activity.js +66 -42
  14. package/dist/components/timeline-activity.js.map +1 -1
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/internal/safe-html.d.ts +1 -1
  19. package/dist/internal/safe-html.js +64 -3
  20. package/dist/internal/safe-html.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/__tests__/conversation-panel.test.tsx +182 -22
  23. package/src/components/__tests__/email-body.test.tsx +83 -0
  24. package/src/components/__tests__/email-display-helpers.test.ts +91 -0
  25. package/src/components/__tests__/email-preview-card.test.tsx +36 -2
  26. package/src/components/__tests__/timeline-activity.test.tsx +53 -1
  27. package/src/components/conversation-panel.tsx +136 -350
  28. package/src/components/email-body.tsx +126 -0
  29. package/src/components/email-display-helpers.ts +557 -0
  30. package/src/components/email-preview-card.tsx +54 -29
  31. package/src/components/timeline-activity.tsx +73 -53
  32. package/src/index.ts +2 -0
  33. package/src/internal/__tests__/safe-html.test.ts +34 -2
  34. package/src/internal/safe-html.ts +79 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/email-preview-card.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Eye } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\nexport interface EmailPreviewCardProps {\n from: { name: string; email: string }\n to?: string\n subject?: string\n htmlBody?: string\n textBody?: string\n signatureHtml?: string | null\n className?: string\n}\n\nfunction getInitials(name: string): string {\n return name\n .split(\" \")\n .map((part) => part[0])\n .filter(Boolean)\n .slice(0, 2)\n .join(\"\")\n .toUpperCase()\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\")\n}\n\nexport function EmailPreviewCard({\n from,\n to,\n subject,\n htmlBody,\n textBody,\n signatureHtml,\n className,\n}: EmailPreviewCardProps) {\n const recipientLabel = to ? `${to}'s` : \"the recipient's\"\n const bodyHtml = htmlBody ?? (textBody ? escapeHtml(textBody) : \"\")\n\n return (\n <div className={cn(\"p-4 bg-muted/30 min-h-full\", className)}>\n <div className=\"flex items-start gap-2 mb-3 py-2.5 px-3 border rounded-lg bg-background text-[11.5px] text-muted-foreground\">\n <Eye className=\"size-4 shrink-0 mt-0.5\" />\n <span>\n This is how your email lands in {recipientLabel} inbox. Nothing has\n been sent yet.\n </span>\n </div>\n\n <div className=\"bg-background border rounded-xl shadow-sm overflow-hidden\">\n <div className=\"px-[18px] pt-4 pb-3 text-base font-semibold border-b border-border/50\">\n {subject || \"(no subject)\"}\n </div>\n\n <div className=\"flex items-center gap-3 px-[18px] pt-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground text-background text-xs font-semibold\">\n {getInitials(from.name)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm\">\n <span className=\"font-semibold text-foreground\">{from.name}</span>{\" \"}\n <span className=\"text-muted-foreground\">&lt;{from.email}&gt;</span>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {to ? `to ${to}` : \"to no recipient yet\"}\n </div>\n </div>\n <div className=\"text-xs text-muted-foreground shrink-0\">just now</div>\n </div>\n\n <div\n className=\"px-[18px] py-2 ml-[47px] text-[13.5px] leading-relaxed whitespace-pre-wrap\"\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n\n {signatureHtml ? (\n <div\n className=\"ml-[47px] px-[18px] pt-3 mt-3 border-t border-border/50 pb-4 text-xs leading-relaxed text-muted-foreground\"\n dangerouslySetInnerHTML={{ __html: signatureHtml }}\n />\n ) : null}\n </div>\n </div>\n )\n}\n"],"mappings":";AAmDQ,cACA,YADA;AAhDR,SAAS,WAAW;AAEpB,SAAS,UAAU;AAYnB,SAAS,YAAY,MAAsB;AACzC,SAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,EACrB,OAAO,OAAO,EACd,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,EACP,YAAY;AACjB;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAEO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AACxB,QAAM,iBAAiB,KAAK,GAAG,EAAE,OAAO;AACxC,QAAM,WAAW,8BAAa,WAAW,WAAW,QAAQ,IAAI;AAEhE,SACE,qBAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACxD;AAAA,yBAAC,SAAI,WAAU,+GACb;AAAA,0BAAC,OAAI,WAAU,0BAAyB;AAAA,MACxC,qBAAC,UAAK;AAAA;AAAA,QAC6B;AAAA,QAAe;AAAA,SAElD;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,6DACb;AAAA,0BAAC,SAAI,WAAU,yEACZ,qBAAW,gBACd;AAAA,MAEA,qBAAC,SAAI,WAAU,0CACb;AAAA,4BAAC,SAAI,WAAU,qHACZ,sBAAY,KAAK,IAAI,GACxB;AAAA,QACA,qBAAC,SAAI,WAAU,kBACb;AAAA,+BAAC,SAAI,WAAU,WACb;AAAA,gCAAC,UAAK,WAAU,iCAAiC,eAAK,MAAK;AAAA,YAAQ;AAAA,YACnE,qBAAC,UAAK,WAAU,yBAAwB;AAAA;AAAA,cAAK,KAAK;AAAA,cAAM;AAAA,eAAI;AAAA,aAC9D;AAAA,UACA,oBAAC,SAAI,WAAU,iCACZ,eAAK,MAAM,EAAE,KAAK,uBACrB;AAAA,WACF;AAAA,QACA,oBAAC,SAAI,WAAU,0CAAyC,sBAAQ;AAAA,SAClE;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,yBAAyB,EAAE,QAAQ,SAAS;AAAA;AAAA,MAC9C;AAAA,MAEC,gBACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,yBAAyB,EAAE,QAAQ,cAAc;AAAA;AAAA,MACnD,IACE;AAAA,OACN;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/email-preview-card.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Eye } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { EmailBody } from \"./email-body\"\nimport { decodeEmailDisplayText, formatAddressList, normalizeEmailSender } from \"./email-display-helpers\"\n\nexport interface EmailPreviewCardProps {\n from: { name?: string | null; email?: string | null }\n to?: string | string[] | null\n cc?: string | string[] | null\n bcc?: string | string[] | null\n subject?: string\n htmlBody?: string\n textBody?: string\n signatureHtml?: string | null\n signatureText?: string | null\n className?: string\n}\n\nfunction getInitials(name: string): string {\n return name\n .split(\" \")\n .map((part) => part[0])\n .filter(Boolean)\n .slice(0, 2)\n .join(\"\")\n .toUpperCase()\n}\n\nfunction formatRecipientLabel(to?: string | string[] | null): string {\n const formatted = formatAddressList(to)\n if (!formatted) return \"the recipient's\"\n\n if (Array.isArray(to) && to.length > 1) return \"the recipients'\"\n return `${formatted}'s`\n}\n\nfunction RecipientRow({ label, value }: { label: string; value: string }) {\n if (!value) return null\n\n return (\n <div className=\"flex items-start gap-2 text-xs text-muted-foreground\">\n <span className=\"w-8 shrink-0 font-medium text-muted-foreground/70\">{label}</span>\n <span className=\"min-w-0 flex-1 break-words\">{value}</span>\n </div>\n )\n}\n\nexport function EmailPreviewCard({\n from,\n to,\n cc,\n bcc,\n subject,\n htmlBody,\n textBody,\n signatureHtml,\n signatureText,\n className,\n}: EmailPreviewCardProps) {\n const sender = normalizeEmailSender({ name: from.name, email: from.email, fallbackName: from.email ?? \"Unknown sender\" })\n const toLabel = formatAddressList(to)\n const ccLabel = formatAddressList(cc)\n const bccLabel = formatAddressList(bcc)\n const recipientLabel = formatRecipientLabel(to)\n const subjectLabel = subject ? decodeEmailDisplayText(subject) : \"(no subject)\"\n\n return (\n <div className={cn(\"p-4 bg-muted/30 min-h-full\", className)}>\n <div className=\"flex items-start gap-2 mb-3 py-2.5 px-3 border rounded-lg bg-background text-[11.5px] text-muted-foreground\">\n <Eye className=\"size-4 shrink-0 mt-0.5\" />\n <span>\n This is how your email lands in {recipientLabel} inbox. Nothing has\n been sent yet.\n </span>\n </div>\n\n <div className=\"bg-background border rounded-xl shadow-sm overflow-hidden\">\n <div className=\"px-[18px] pt-4 pb-3 text-base font-semibold border-b border-border/50\">\n {subjectLabel}\n </div>\n\n <div className=\"flex items-start gap-3 px-[18px] pt-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground text-background text-xs font-semibold\">\n {getInitials(sender.name || sender.email || \"?\") || \"?\"}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-sm\">\n <span className=\"font-semibold text-foreground\">{sender.name}</span>{\" \"}\n {sender.email ? <span className=\"text-muted-foreground\">&lt;{sender.email}&gt;</span> : null}\n </div>\n <div className=\"mt-1 space-y-0.5\">\n <RecipientRow label=\"To\" value={toLabel || \"no recipient yet\"} />\n <RecipientRow label=\"Cc\" value={ccLabel} />\n <RecipientRow label=\"Bcc\" value={bccLabel} />\n </div>\n </div>\n <div className=\"text-xs text-muted-foreground shrink-0 pt-0.5\">just now</div>\n </div>\n\n <div className=\"px-[18px] py-3 ml-[47px]\">\n <EmailBody\n html={htmlBody}\n text={textBody}\n detailsHtml={signatureHtml}\n detailsText={signatureText}\n variant=\"preview\"\n collapseDetails={false}\n defaultDetailsOpen\n className=\"text-[13.5px]\"\n />\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":";AA4CI,SACE,KADF;AAzCJ,SAAS,WAAW;AAEpB,SAAS,UAAU;AACnB,SAAS,iBAAiB;AAC1B,SAAS,wBAAwB,mBAAmB,4BAA4B;AAehF,SAAS,YAAY,MAAsB;AACzC,SAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,EACrB,OAAO,OAAO,EACd,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,EACP,YAAY;AACjB;AAEA,SAAS,qBAAqB,IAAuC;AACnE,QAAM,YAAY,kBAAkB,EAAE;AACtC,MAAI,CAAC,UAAW,QAAO;AAEvB,MAAI,MAAM,QAAQ,EAAE,KAAK,GAAG,SAAS,EAAG,QAAO;AAC/C,SAAO,GAAG,SAAS;AACrB;AAEA,SAAS,aAAa,EAAE,OAAO,MAAM,GAAqC;AACxE,MAAI,CAAC,MAAO,QAAO;AAEnB,SACE,qBAAC,SAAI,WAAU,wDACb;AAAA,wBAAC,UAAK,WAAU,qDAAqD,iBAAM;AAAA,IAC3E,oBAAC,UAAK,WAAU,8BAA8B,iBAAM;AAAA,KACtD;AAEJ;AAEO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0B;AA9D1B;AA+DE,QAAM,SAAS,qBAAqB,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,eAAc,UAAK,UAAL,YAAc,iBAAiB,CAAC;AACxH,QAAM,UAAU,kBAAkB,EAAE;AACpC,QAAM,UAAU,kBAAkB,EAAE;AACpC,QAAM,WAAW,kBAAkB,GAAG;AACtC,QAAM,iBAAiB,qBAAqB,EAAE;AAC9C,QAAM,eAAe,UAAU,uBAAuB,OAAO,IAAI;AAEjE,SACE,qBAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACxD;AAAA,yBAAC,SAAI,WAAU,+GACb;AAAA,0BAAC,OAAI,WAAU,0BAAyB;AAAA,MACxC,qBAAC,UAAK;AAAA;AAAA,QAC6B;AAAA,QAAe;AAAA,SAElD;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,6DACb;AAAA,0BAAC,SAAI,WAAU,yEACZ,wBACH;AAAA,MAEA,qBAAC,SAAI,WAAU,yCACb;AAAA,4BAAC,SAAI,WAAU,qHACZ,sBAAY,OAAO,QAAQ,OAAO,SAAS,GAAG,KAAK,KACtD;AAAA,QACA,qBAAC,SAAI,WAAU,kBACb;AAAA,+BAAC,SAAI,WAAU,WACb;AAAA,gCAAC,UAAK,WAAU,iCAAiC,iBAAO,MAAK;AAAA,YAAQ;AAAA,YACpE,OAAO,QAAQ,qBAAC,UAAK,WAAU,yBAAwB;AAAA;AAAA,cAAK,OAAO;AAAA,cAAM;AAAA,eAAI,IAAU;AAAA,aAC1F;AAAA,UACA,qBAAC,SAAI,WAAU,oBACb;AAAA,gCAAC,gBAAa,OAAM,MAAK,OAAO,WAAW,oBAAoB;AAAA,YAC/D,oBAAC,gBAAa,OAAM,MAAK,OAAO,SAAS;AAAA,YACzC,oBAAC,gBAAa,OAAM,OAAM,OAAO,UAAU;AAAA,aAC7C;AAAA,WACF;AAAA,QACA,oBAAC,SAAI,WAAU,iDAAgD,sBAAQ;AAAA,SACzE;AAAA,MAEA,oBAAC,SAAI,WAAU,4BACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,MAAM;AAAA,UACN,aAAa;AAAA,UACb,aAAa;AAAA,UACb,SAAQ;AAAA,UACR,iBAAiB;AAAA,UACjB,oBAAkB;AAAA,UAClB,WAAU;AAAA;AAAA,MACZ,GACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -4,17 +4,15 @@
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import * as React from "react";
6
6
  import { cn } from "../lib/utils.js";
7
- import { sanitizeHtml } from "../internal/safe-html.js";
8
7
  import { ChevronDown, ChevronUp, ExternalLink } from "lucide-react";
9
- const EMAIL_HTML_CLASS = cn(
10
- "text-sm leading-[1.62] text-foreground/90 break-words",
11
- "[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0",
12
- "[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline",
13
- "[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5",
14
- "[&_img]:max-w-full [&_img]:h-auto",
15
- "[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:text-[13px]",
16
- "[&_.gmail_quote]:border-l-2 [&_.gmail_quote]:border-border [&_.gmail_quote]:pl-3 [&_.gmail_quote]:text-muted-foreground [&_.gmail_quote]:text-[13px]"
17
- );
8
+ import { EmailBody as SharedEmailBody } from "./email-body.js";
9
+ import {
10
+ decodeEmailDisplayText,
11
+ emailBodySnippet,
12
+ formatAddressList,
13
+ formatEmailTimestamp,
14
+ normalizeEmailSender
15
+ } from "./email-display-helpers.js";
18
16
  const TONE_CLASSES = {
19
17
  red: {
20
18
  dot: "bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40",
@@ -174,36 +172,55 @@ function TimelineItem({
174
172
  ] })
175
173
  ] });
176
174
  }
175
+ function reactNodeToDisplayText(value) {
176
+ if (typeof value === "string" || typeof value === "number") return decodeEmailDisplayText(String(value));
177
+ return "";
178
+ }
179
+ function getTimelineEmailDisplay(email) {
180
+ var _a, _b, _c, _d, _e;
181
+ const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail });
182
+ const to = formatAddressList(email.to) || decodeEmailDisplayText((_a = email.to) != null ? _a : "");
183
+ const cc = formatAddressList(email.cc) || decodeEmailDisplayText((_b = email.cc) != null ? _b : "");
184
+ const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText((_c = email.bcc) != null ? _c : "");
185
+ const date = (_e = formatEmailTimestamp(email.date)) != null ? _e : decodeEmailDisplayText((_d = email.date) != null ? _d : "");
186
+ const subject = email.subject ? decodeEmailDisplayText(email.subject) : "";
187
+ const bodyText = reactNodeToDisplayText(email.body);
188
+ const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140);
189
+ return { sender, to, cc, bcc, date, subject, bodyText, snippet };
190
+ }
177
191
  function EmailMetadata({
178
192
  email,
179
193
  showAllRecipients,
180
194
  setShowAllRecipients
181
195
  }) {
196
+ const display = getTimelineEmailDisplay(email);
182
197
  return /* @__PURE__ */ jsxs(Fragment, { children: [
183
198
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
184
199
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline gap-1.5", children: [
185
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground text-[13px] whitespace-nowrap", children: email.from }),
186
- email.fromEmail && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 text-xs truncate", children: email.fromEmail })
200
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground text-[13px] whitespace-nowrap", children: display.sender.name }),
201
+ display.sender.email ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 text-xs truncate", children: display.sender.email }) : null
187
202
  ] }),
188
- email.date && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap", children: email.date })
203
+ display.date ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap", children: display.date }) : null
189
204
  ] }),
190
205
  /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-1 text-xs text-muted-foreground", children: [
191
206
  /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
192
207
  "To ",
193
- email.to,
194
- !showAllRecipients && (email.cc || email.bcc) ? /* @__PURE__ */ jsx(Fragment, { children: ", ..." }) : null,
195
- showAllRecipients && email.cc ? /* @__PURE__ */ jsxs(Fragment, { children: [
208
+ display.to || "no recipient yet",
209
+ !showAllRecipients && (display.cc || display.bcc) ? /* @__PURE__ */ jsx(Fragment, { children: ", ..." }) : null,
210
+ showAllRecipients && display.cc ? /* @__PURE__ */ jsxs(Fragment, { children: [
196
211
  ", ",
197
- email.cc
212
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "cc" }),
213
+ " ",
214
+ display.cc
198
215
  ] }) : null,
199
- showAllRecipients && email.bcc ? /* @__PURE__ */ jsxs(Fragment, { children: [
216
+ showAllRecipients && display.bcc ? /* @__PURE__ */ jsxs(Fragment, { children: [
200
217
  " ",
201
218
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "bcc" }),
202
219
  " ",
203
- email.bcc
220
+ display.bcc
204
221
  ] }) : null
205
222
  ] }),
206
- (email.cc || email.bcc) && /* @__PURE__ */ jsx(
223
+ display.cc || display.bcc ? /* @__PURE__ */ jsx(
207
224
  "button",
208
225
  {
209
226
  type: "button",
@@ -214,26 +231,31 @@ function EmailMetadata({
214
231
  className: "shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors",
215
232
  children: /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", showAllRecipients && "rotate-180") })
216
233
  }
217
- )
234
+ ) : null
218
235
  ] })
219
236
  ] });
220
237
  }
221
- function EmailBody({ email }) {
222
- if (email.bodyHtml) {
223
- return (
224
- // Gmail reading-pane typography; quoted history
225
- // (blockquote.gmail_quote) is de-emphasized with a left rule.
226
- /* @__PURE__ */ jsx(
227
- "div",
228
- {
229
- "data-slot": "timeline-email-html",
230
- className: EMAIL_HTML_CLASS,
231
- dangerouslySetInnerHTML: { __html: sanitizeHtml(email.bodyHtml) }
232
- }
233
- )
234
- );
238
+ function TimelineEmailBody({ email }) {
239
+ const display = getTimelineEmailDisplay(email);
240
+ const bodyFallback = display.bodyText || (typeof email.body === "string" ? email.body : "");
241
+ if (!email.bodyHtml && !bodyFallback && email.body) {
242
+ return /* @__PURE__ */ jsx("div", { className: "whitespace-pre-line text-sm leading-relaxed text-foreground/90", children: email.body });
235
243
  }
236
- return /* @__PURE__ */ jsx("div", { className: "whitespace-pre-line text-sm leading-relaxed text-foreground/90", children: email.body });
244
+ const body = /* @__PURE__ */ jsx(
245
+ SharedEmailBody,
246
+ {
247
+ html: email.bodyHtml,
248
+ text: bodyFallback,
249
+ variant: "history",
250
+ collapseDetails: true,
251
+ className: "text-sm leading-relaxed"
252
+ }
253
+ );
254
+ return email.bodyHtml ? /* @__PURE__ */ jsx("div", { "data-slot": "timeline-email-html", children: body }) : body;
255
+ }
256
+ function renderDecodedPreview(preview) {
257
+ if (typeof preview === "string" || typeof preview === "number") return decodeEmailDisplayText(String(preview));
258
+ return preview;
237
259
  }
238
260
  function CollapsedEmailPreview({
239
261
  email,
@@ -242,15 +264,17 @@ function CollapsedEmailPreview({
242
264
  actionClassName,
243
265
  onClick
244
266
  }) {
267
+ const display = email ? getTimelineEmailDisplay(email) : null;
268
+ const previewContent = (display == null ? void 0 : display.snippet) || renderDecodedPreview(preview);
245
269
  return /* @__PURE__ */ jsxs("div", { className, onClick, children: [
246
270
  /* @__PURE__ */ jsxs("span", { className: "line-clamp-1 pr-3 text-[13px]", children: [
247
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: email == null ? void 0 : email.from }),
271
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: display == null ? void 0 : display.sender.name }),
248
272
  /* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground/40", children: "\xB7" }),
249
- (email == null ? void 0 : email.subject) ? /* @__PURE__ */ jsxs(Fragment, { children: [
250
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: email.subject }),
273
+ (display == null ? void 0 : display.subject) ? /* @__PURE__ */ jsxs(Fragment, { children: [
274
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: display.subject }),
251
275
  /* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground/40", children: "\xB7" })
252
276
  ] }) : null,
253
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: preview })
277
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: previewContent })
254
278
  ] }),
255
279
  /* @__PURE__ */ jsxs("button", { type: "button", className: actionClassName, children: [
256
280
  "Expand ",
@@ -333,7 +357,7 @@ function EmailCard({
333
357
  setShowAllRecipients
334
358
  }
335
359
  ) }),
336
- /* @__PURE__ */ jsx(EmailBody, { email: event.email }),
360
+ /* @__PURE__ */ jsx(TimelineEmailBody, { email: event.email }),
337
361
  /* @__PURE__ */ jsx(
338
362
  ShowLessButton,
339
363
  {
@@ -365,7 +389,7 @@ function EmailCard({
365
389
  setShowAllRecipients
366
390
  }
367
391
  ) }),
368
- /* @__PURE__ */ jsx("div", { className: classes.cardBody, "data-slot": "timeline-card-body", children: /* @__PURE__ */ jsx(EmailBody, { email: event.email }) }),
392
+ /* @__PURE__ */ jsx("div", { className: classes.cardBody, "data-slot": "timeline-card-body", children: /* @__PURE__ */ jsx(TimelineEmailBody, { email: event.email }) }),
369
393
  /* @__PURE__ */ jsx("div", { className: cn(classes.cardFooter, classes.actionLinkRow), "data-slot": "timeline-card-footer", children: /* @__PURE__ */ jsx(
370
394
  ShowLessButton,
371
395
  {
@@ -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 { sanitizeHtml } from \"../internal/safe-html\"\nimport { ChevronDown, ChevronUp, ExternalLink } from \"lucide-react\"\n\n/**\n * Gmail-like reading-pane typography for rendered, sanitized email HTML.\n * Self-contained Tailwind child-variant utilities — no global CSS. Links use\n * Gmail blue; quoted history (`blockquote.gmail_quote`) is de-emphasized with a\n * left rule and muted, slightly smaller text.\n */\nconst EMAIL_HTML_CLASS = cn(\n \"text-sm leading-[1.62] text-foreground/90 break-words\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_img]:max-w-full [&_img]:h-auto\",\n \"[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:text-[13px]\",\n \"[&_.gmail_quote]:border-l-2 [&_.gmail_quote]:border-border [&_.gmail_quote]:pl-3 [&_.gmail_quote]:text-muted-foreground [&_.gmail_quote]:text-[13px]\"\n)\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 content?: React.ReactNode\n source?: {\n label: 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-center justify-between border-b border-border/60 bg-muted/30 px-3 py-1.5 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\">&middot;</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\">&middot;</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 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) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\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 TimelineSource = NonNullable<TimelineEvent[\"source\"]>\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 return (\n <>\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\">{email.from}</span>\n {email.fromEmail && (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{email.fromEmail}</span>\n )}\n </div>\n {email.date && (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{email.date}</span>\n )}\n </div>\n <div className=\"mt-0.5 flex items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">\n To {email.to}\n {!showAllRecipients && (email.cc || email.bcc) ? (\n <>, ...</>\n ) : null}\n {showAllRecipients && email.cc ? (\n <>, {email.cc}</>\n ) : null}\n {showAllRecipients && email.bcc ? (\n <> <span className=\"text-muted-foreground/40\">bcc</span> {email.bcc}</>\n ) : null}\n </span>\n {(email.cc || email.bcc) && (\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 >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n )}\n </div>\n </>\n )\n}\n\nfunction EmailBody({ email }: { email: TimelineEmail }) {\n if (email.bodyHtml) {\n return (\n // Gmail reading-pane typography; quoted history\n // (blockquote.gmail_quote) is de-emphasized with a left rule.\n <div\n data-slot=\"timeline-email-html\"\n className={EMAIL_HTML_CLASS}\n dangerouslySetInnerHTML={{ __html: sanitizeHtml(email.bodyHtml) }}\n />\n )\n }\n\n return (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {email.body}\n </div>\n )\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 return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{email?.from}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n {email?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{email.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{preview}</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\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n Open in {source.label}\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 Open in {source.label}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\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>\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <EmailBody email={event.email} />\n\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-2 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 ) : (\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 <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <EmailBody email={event.email} />\n </div>\n\n <div className={cn(classes.cardFooter, classes.actionLinkRow)} data-slot=\"timeline-card-footer\">\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 } 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 content?: React.ReactNode\n source?: {\n label: 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-center justify-between border-b border-border/60 bg-muted/30 px-3 py-1.5 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\">&middot;</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\">&middot;</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 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) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\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 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\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\n return { sender, to, cc, bcc, date, subject, bodyText, snippet }\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\n return (\n <>\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 <div className=\"mt-0.5 flex items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">\n To {display.to || \"no recipient yet\"}\n {!showAllRecipients && (display.cc || display.bcc) ? (\n <>, ...</>\n ) : null}\n {showAllRecipients && display.cc ? (\n <>, <span className=\"text-muted-foreground/40\">cc</span> {display.cc}</>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <> <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}</>\n ) : null}\n </span>\n {(display.cc || display.bcc) ? (\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 >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n </>\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 return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\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 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</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\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n Open in {source.label}\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 Open in {source.label}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\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>\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-2 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 ) : (\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 <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n </div>\n\n <div className={cn(classes.cardFooter, classes.actionLinkRow)} data-slot=\"timeline-card-footer\">\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":[]}
package/dist/index.d.ts CHANGED
@@ -11,7 +11,9 @@ export { Badge, badgeVariants } from './components/badge.js';
11
11
  export { Button, buttonVariants } from './components/button.js';
12
12
  export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './components/card.js';
13
13
  export { CasePanelEmailComposer, CasePanelEmailComposerChip, CasePanelEmailComposerChipProps, CasePanelEmailComposerProps, CasePanelEmailComposerRow, CasePanelEmailComposerRowProps, CasePanelEmailComposerRowState, CasePanelEmailComposerSignatureRow, CasePanelEmailComposerSignatureRowProps, CasePanelEmailComposerSlotHelpers } from './components/case-panel-email-composer.js';
14
+ export { EmailBody, EmailBodyProps } from './components/email-body.js';
14
15
  export { EmailComposerRow, EmailComposerRowProps } from './components/email-composer-row.js';
16
+ export { EmailDisplaySenderInput, NormalizedEmailSender, SplitEmailHtmlResult, SplitEmailTextResult, decodeEmailDisplayText, emailBodySnippet, formatAddressList, formatEmailTimestamp, normalizeEmailSender, splitEmailHtmlForDisplay, splitEmailTextForDisplay } from './components/email-display-helpers.js';
15
17
  export { EmailPreviewCard, EmailPreviewCardProps } from './components/email-preview-card.js';
16
18
  export { EmailRecipientField, EmailRecipientFieldProps, RecipientChip } from './components/email-recipient-field.js';
17
19
  export { EmailSendBar, EmailSendBarAction, EmailSendBarProps } from './components/email-send-bar.js';
package/dist/index.js CHANGED
@@ -11,7 +11,9 @@ export * from "./components/badge.js";
11
11
  export * from "./components/button.js";
12
12
  export * from "./components/card.js";
13
13
  export * from "./components/case-panel-email-composer.js";
14
+ export * from "./components/email-body.js";
14
15
  export * from "./components/email-composer-row.js";
16
+ export * from "./components/email-display-helpers.js";
15
17
  export * from "./components/email-preview-card.js";
16
18
  export * from "./components/email-recipient-field.js";
17
19
  export * from "./components/email-send-bar.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-body\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-display-helpers\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Conservative, deterministic sanitizer for user/email supplied HTML rendered by
3
3
  * design-system components. It keeps common email formatting tags while removing
4
- * executable tags, event handlers, inline styles, and unsafe URLs. This stays
4
+ * executable tags, event handlers, unsafe inline styles, and unsafe URLs. This stays
5
5
  * dependency-free for the shared package and intentionally favors stripping
6
6
  * ambiguous email content over preserving every possible HTML feature.
7
7
  */
@@ -30,6 +30,8 @@ const ALLOWED_TAGS = /* @__PURE__ */ new Set([
30
30
  "s",
31
31
  "span",
32
32
  "strong",
33
+ "sub",
34
+ "sup",
33
35
  "table",
34
36
  "tbody",
35
37
  "td",
@@ -40,7 +42,7 @@ const ALLOWED_TAGS = /* @__PURE__ */ new Set([
40
42
  "ul"
41
43
  ]);
42
44
  const VOID_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img"]);
43
- const SAFE_GLOBAL_ATTRS = /* @__PURE__ */ new Set(["aria-label", "role", "title"]);
45
+ const SAFE_GLOBAL_ATTRS = /* @__PURE__ */ new Set(["aria-label", "dir", "lang", "role", "title"]);
44
46
  const SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
45
47
  function escapeHtml(value) {
46
48
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -96,15 +98,70 @@ function sanitizeClassName(value) {
96
98
  const safeTokens = value.split(/\s+/).map((token) => token.trim()).filter((token) => /^[A-Za-z0-9_-]+$/.test(token));
97
99
  return safeTokens.length ? safeTokens.join(" ") : null;
98
100
  }
101
+ function sanitizeDimension(value) {
102
+ const trimmed = value.trim();
103
+ if (/^\d{1,4}$/.test(trimmed)) return trimmed;
104
+ return null;
105
+ }
106
+ function sanitizeLanguage(value) {
107
+ const trimmed = value.trim();
108
+ if (/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/.test(trimmed)) return trimmed;
109
+ return null;
110
+ }
111
+ function sanitizeDirection(value) {
112
+ const trimmed = value.trim().toLowerCase();
113
+ return trimmed === "ltr" || trimmed === "rtl" || trimmed === "auto" ? trimmed : null;
114
+ }
115
+ function sanitizeFontSize(value) {
116
+ const trimmed = value.trim().toLowerCase();
117
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)(px|em|rem|%)$/);
118
+ if (!match) return null;
119
+ const amount = Number.parseFloat(match[1]);
120
+ const unit = match[2];
121
+ const maxByUnit = { px: 72, em: 4, rem: 4, "%": 400 };
122
+ if (!Number.isFinite(amount) || amount <= 0 || amount > maxByUnit[unit]) return null;
123
+ return `${amount}${unit}`;
124
+ }
125
+ function sanitizeStyle(value) {
126
+ const declarations = [];
127
+ for (const rawDeclaration of value.split(";")) {
128
+ const separatorIndex = rawDeclaration.indexOf(":");
129
+ if (separatorIndex === -1) continue;
130
+ const property = rawDeclaration.slice(0, separatorIndex).trim().toLowerCase();
131
+ const rawValue = decodeHtmlEntities(rawDeclaration.slice(separatorIndex + 1)).trim().toLowerCase();
132
+ if (!property || !rawValue || /url\s*\(|expression\s*\(/i.test(rawValue)) continue;
133
+ if (property === "vertical-align" && (rawValue === "super" || rawValue === "sub")) {
134
+ declarations.push(`vertical-align: ${rawValue}`);
135
+ continue;
136
+ }
137
+ if (property === "font-size") {
138
+ const fontSize = sanitizeFontSize(rawValue);
139
+ if (fontSize) declarations.push(`font-size: ${fontSize}`);
140
+ }
141
+ }
142
+ return declarations.length ? declarations.join("; ") : null;
143
+ }
99
144
  function sanitizeAttribute(tagName, name, value) {
100
145
  const attr = name.toLowerCase();
101
- if (attr.startsWith("on") || attr === "style" || attr === "srcdoc" || attr === "formaction" || attr === "xlink:href" || attr === "xmlns") {
146
+ if (attr.startsWith("on") || attr === "srcdoc" || attr === "formaction" || attr === "xlink:href" || attr === "xmlns") {
102
147
  return null;
103
148
  }
149
+ if (attr === "style") {
150
+ const safeStyle = sanitizeStyle(value);
151
+ return safeStyle ? `style="${escapeAttribute(safeStyle)}"` : null;
152
+ }
104
153
  if (attr === "class") {
105
154
  const safeClassName = sanitizeClassName(value);
106
155
  return safeClassName ? `class="${escapeAttribute(safeClassName)}"` : null;
107
156
  }
157
+ if (attr === "dir") {
158
+ const safeDirection = sanitizeDirection(value);
159
+ return safeDirection ? `dir="${escapeAttribute(safeDirection)}"` : null;
160
+ }
161
+ if (attr === "lang") {
162
+ const safeLanguage = sanitizeLanguage(value);
163
+ return safeLanguage ? `lang="${escapeAttribute(safeLanguage)}"` : null;
164
+ }
108
165
  if (SAFE_GLOBAL_ATTRS.has(attr) || attr.startsWith("aria-")) {
109
166
  return `${attr}="${escapeAttribute(value)}"`;
110
167
  }
@@ -114,9 +171,13 @@ function sanitizeAttribute(tagName, name, value) {
114
171
  if (tagName === "img" && attr === "src" && isSafeUrl(value)) {
115
172
  return `src="${escapeAttribute(value)}"`;
116
173
  }
117
- if (tagName === "img" && (attr === "alt" || attr === "width" || attr === "height")) {
174
+ if (tagName === "img" && attr === "alt") {
118
175
  return `${attr}="${escapeAttribute(value)}"`;
119
176
  }
177
+ if (tagName === "img" && (attr === "width" || attr === "height")) {
178
+ const safeDimension = sanitizeDimension(value);
179
+ return safeDimension ? `${attr}="${escapeAttribute(safeDimension)}"` : null;
180
+ }
120
181
  if ((tagName === "td" || tagName === "th") && (attr === "colspan" || attr === "rowspan")) {
121
182
  return `${attr}="${escapeAttribute(value)}"`;
122
183
  }