@handled-ai/design-system 0.20.1 → 0.20.3
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 +1 -1
- package/dist/components/conversation-panel.js +282 -15
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/owner-chips.d.ts +3 -4
- package/dist/components/owner-chips.js +77 -41
- package/dist/components/owner-chips.js.map +1 -1
- package/dist/components/timeline-activity.d.ts +4 -2
- package/dist/components/timeline-activity.js +366 -154
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.js +10 -8
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +276 -0
- package/src/components/__tests__/owner-chips.test.tsx +137 -17
- package/src/components/__tests__/timeline-activity.test.tsx +92 -1
- package/src/components/conversation-panel.tsx +358 -21
- package/src/components/owner-chips.tsx +98 -63
- package/src/components/timeline-activity.tsx +452 -160
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +6 -8
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +16 -2
- package/src/prototype/prototype-inbox-view.tsx +14 -15
|
@@ -84,7 +84,7 @@ interface ConversationPanelProps {
|
|
|
84
84
|
/** Deployment brand, used in the paused-playbook copy. */
|
|
85
85
|
tenantName?: string;
|
|
86
86
|
onSendReply?: (payload: ConversationReplyPayload) => void | Promise<void>;
|
|
87
|
-
onCreateGmailDraft?: (payload: ConversationReplyPayload) => void
|
|
87
|
+
onCreateGmailDraft?: (payload: ConversationReplyPayload) => void | Promise<void>;
|
|
88
88
|
onOpenInGmail?: (threadId: string) => void;
|
|
89
89
|
/** Inline-open this thread initially (defaults to the first responded one). */
|
|
90
90
|
defaultOpenThreadId?: string;
|
|
@@ -69,6 +69,248 @@ function escapeHtml(s) {
|
|
|
69
69
|
function textToHtml(text) {
|
|
70
70
|
return text.split(/\n{2,}/).map((p) => p.trim()).filter(Boolean).map((p) => `<p>${escapeHtml(p).replace(/\n/g, "<br>")}</p>`).join("");
|
|
71
71
|
}
|
|
72
|
+
const SPLITTABLE_BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div"]);
|
|
73
|
+
const WHOLE_BLOCK_TAGS = /* @__PURE__ */ new Set(["blockquote", "table", "ul", "ol", "hr"]);
|
|
74
|
+
const BLOCK_TAGS = /* @__PURE__ */ new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS]);
|
|
75
|
+
const BR_TAG_RE = /<br\s*\/?>/gi;
|
|
76
|
+
const HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\b[^>]*>/i;
|
|
77
|
+
const SIGNATURE_DELIMITER_RE = /^--\s*$/;
|
|
78
|
+
const SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i;
|
|
79
|
+
const 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;
|
|
80
|
+
const 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;
|
|
81
|
+
function decodeHtmlText(value) {
|
|
82
|
+
const withoutTags = value.replace(BR_TAG_RE, "\n").replace(/<\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, "\n").replace(/<[^>]*>/g, "");
|
|
83
|
+
if (typeof document !== "undefined") {
|
|
84
|
+
const textarea = document.createElement("textarea");
|
|
85
|
+
textarea.innerHTML = withoutTags;
|
|
86
|
+
return textarea.value;
|
|
87
|
+
}
|
|
88
|
+
return withoutTags.replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'|'/gi, "'");
|
|
89
|
+
}
|
|
90
|
+
function htmlToVisibleText(html) {
|
|
91
|
+
return decodeHtmlText(html).replace(/\u00a0/g, " ").replace(/[ \t]+/g, " ").trim();
|
|
92
|
+
}
|
|
93
|
+
function serializeNode(node) {
|
|
94
|
+
const host = document.createElement("div");
|
|
95
|
+
host.appendChild(node.cloneNode(true));
|
|
96
|
+
return host.innerHTML;
|
|
97
|
+
}
|
|
98
|
+
function wrapHtmlLike(element, innerHtml) {
|
|
99
|
+
const clone = element.cloneNode(false);
|
|
100
|
+
clone.innerHTML = innerHtml;
|
|
101
|
+
return clone.outerHTML;
|
|
102
|
+
}
|
|
103
|
+
function hasDirectBr(nodes) {
|
|
104
|
+
return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === "br");
|
|
105
|
+
}
|
|
106
|
+
function hasDirectBlockChild(element) {
|
|
107
|
+
return Array.from(element.children).some((child) => {
|
|
108
|
+
const tagName = child.tagName.toLowerCase();
|
|
109
|
+
return tagName !== "br" && BLOCK_TAGS.has(tagName);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function makeHtmlSegment(html) {
|
|
113
|
+
const visibleText = htmlToVisibleText(html);
|
|
114
|
+
if (!html.trim() || !visibleText && !/<(?:img|hr)\b/i.test(html)) return null;
|
|
115
|
+
return { html, visibleText };
|
|
116
|
+
}
|
|
117
|
+
function splitInlineNodes(nodes, wrapper) {
|
|
118
|
+
const containsBr = hasDirectBr(nodes);
|
|
119
|
+
const chunks = [[]];
|
|
120
|
+
nodes.forEach((node) => {
|
|
121
|
+
var _a;
|
|
122
|
+
if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === "br") {
|
|
123
|
+
chunks.push([]);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
(_a = chunks[chunks.length - 1]) == null ? void 0 : _a.push(serializeNode(node));
|
|
127
|
+
});
|
|
128
|
+
return chunks.map((chunk) => chunk.join("")).map((innerHtml) => {
|
|
129
|
+
if (wrapper) return wrapHtmlLike(wrapper, innerHtml);
|
|
130
|
+
return containsBr ? `<div>${innerHtml}</div>` : innerHtml;
|
|
131
|
+
}).map(makeHtmlSegment).filter((segment) => Boolean(segment));
|
|
132
|
+
}
|
|
133
|
+
function splitElementSegment(element) {
|
|
134
|
+
const tagName = element.tagName.toLowerCase();
|
|
135
|
+
if (tagName === "div" && hasDirectBlockChild(element)) {
|
|
136
|
+
const childSegments = splitHtmlNodes(Array.from(element.childNodes));
|
|
137
|
+
return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {
|
|
140
|
+
return splitInlineNodes(Array.from(element.childNodes), element);
|
|
141
|
+
}
|
|
142
|
+
const segment = makeHtmlSegment(element.outerHTML);
|
|
143
|
+
return segment ? [segment] : [];
|
|
144
|
+
}
|
|
145
|
+
function splitHtmlNodes(nodes) {
|
|
146
|
+
const segments = [];
|
|
147
|
+
let inlineNodes = [];
|
|
148
|
+
const flushInline = () => {
|
|
149
|
+
if (!inlineNodes.length) return;
|
|
150
|
+
segments.push(...splitInlineNodes(inlineNodes));
|
|
151
|
+
inlineNodes = [];
|
|
152
|
+
};
|
|
153
|
+
nodes.forEach((node) => {
|
|
154
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
155
|
+
const element = node;
|
|
156
|
+
const tagName = element.tagName.toLowerCase();
|
|
157
|
+
if (BLOCK_TAGS.has(tagName)) {
|
|
158
|
+
flushInline();
|
|
159
|
+
segments.push(...splitElementSegment(element));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
inlineNodes.push(node);
|
|
164
|
+
});
|
|
165
|
+
flushInline();
|
|
166
|
+
return segments;
|
|
167
|
+
}
|
|
168
|
+
function findMatchingCloseTag(html, tagName, openTagEnd) {
|
|
169
|
+
if (tagName === "hr") return openTagEnd + 1;
|
|
170
|
+
const tagPattern = new RegExp(`</?${tagName}\\b[^>]*>`, "gi");
|
|
171
|
+
tagPattern.lastIndex = openTagEnd + 1;
|
|
172
|
+
let depth = 1;
|
|
173
|
+
let match;
|
|
174
|
+
while ((match = tagPattern.exec(html)) !== null) {
|
|
175
|
+
const rawTag = match[0];
|
|
176
|
+
if (/^<\//.test(rawTag)) depth -= 1;
|
|
177
|
+
else if (!/\/\s*>$/.test(rawTag)) depth += 1;
|
|
178
|
+
if (depth === 0) return tagPattern.lastIndex;
|
|
179
|
+
}
|
|
180
|
+
return html.length;
|
|
181
|
+
}
|
|
182
|
+
function splitHtmlSegmentsFallback(html) {
|
|
183
|
+
const segments = [];
|
|
184
|
+
let cursor = 0;
|
|
185
|
+
const pushInline = (inlineHtml) => {
|
|
186
|
+
const chunks = inlineHtml.split(BR_TAG_RE);
|
|
187
|
+
const hadBr = chunks.length > 1;
|
|
188
|
+
chunks.forEach((chunk) => {
|
|
189
|
+
const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk);
|
|
190
|
+
if (segment) segments.push(segment);
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
while (cursor < html.length) {
|
|
194
|
+
const rest = html.slice(cursor);
|
|
195
|
+
const match = HTML_BLOCK_START_RE.exec(rest);
|
|
196
|
+
if (!match || match.index === void 0) {
|
|
197
|
+
pushInline(rest);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
if (match.index > 0) pushInline(rest.slice(0, match.index));
|
|
201
|
+
const tagStart = cursor + match.index;
|
|
202
|
+
const rawOpen = match[0];
|
|
203
|
+
const tagName = match[1].toLowerCase();
|
|
204
|
+
const openTagEnd = tagStart + rawOpen.length - 1;
|
|
205
|
+
const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd);
|
|
206
|
+
const blockHtml = html.slice(tagStart, segmentEnd);
|
|
207
|
+
if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {
|
|
208
|
+
const openTag = rawOpen;
|
|
209
|
+
const closeTag = `</${tagName}>`;
|
|
210
|
+
const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "i"), "").replace(new RegExp(`${closeTag}$`, "i"), "");
|
|
211
|
+
inner.split(BR_TAG_RE).forEach((chunk) => {
|
|
212
|
+
const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`);
|
|
213
|
+
if (segment) segments.push(segment);
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
const segment = makeHtmlSegment(blockHtml);
|
|
217
|
+
if (segment) segments.push(segment);
|
|
218
|
+
}
|
|
219
|
+
cursor = segmentEnd;
|
|
220
|
+
}
|
|
221
|
+
return segments;
|
|
222
|
+
}
|
|
223
|
+
function segmentHtmlMessage(html) {
|
|
224
|
+
if (typeof document === "undefined" || typeof Node === "undefined") {
|
|
225
|
+
return splitHtmlSegmentsFallback(html);
|
|
226
|
+
}
|
|
227
|
+
const template = document.createElement("template");
|
|
228
|
+
template.innerHTML = html;
|
|
229
|
+
return splitHtmlNodes(Array.from(template.content.childNodes));
|
|
230
|
+
}
|
|
231
|
+
function segmentTextMessage(text) {
|
|
232
|
+
var _a;
|
|
233
|
+
const lines = (_a = text.match(/[^\n]*(?:\n|$)/g)) != null ? _a : [];
|
|
234
|
+
return lines.filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === "")).map((line) => ({ text: line, visibleText: line.replace(/\u00a0/g, " ").trim() }));
|
|
235
|
+
}
|
|
236
|
+
function firstVisibleLine(text) {
|
|
237
|
+
var _a, _b;
|
|
238
|
+
return (_b = (_a = text.replace(/\u00a0/g, " ").trimStart().split(/\r?\n/).find((line) => line.trim())) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
239
|
+
}
|
|
240
|
+
function isLikelySenderNameLine(line) {
|
|
241
|
+
if (!line || line.length > 60) return false;
|
|
242
|
+
if (/[,@:;!?]|https?:\/\/|www\.|\d/.test(line)) return false;
|
|
243
|
+
const words = line.split(/\s+/).filter(Boolean);
|
|
244
|
+
if (words.length < 1 || words.length > 4) return false;
|
|
245
|
+
return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word));
|
|
246
|
+
}
|
|
247
|
+
function nextVisibleSegmentText(segments, fromIndex) {
|
|
248
|
+
var _a, _b;
|
|
249
|
+
for (let index = fromIndex + 1; index < segments.length; index += 1) {
|
|
250
|
+
const text = firstVisibleLine((_b = (_a = segments[index]) == null ? void 0 : _a.visibleText) != null ? _b : "");
|
|
251
|
+
if (text) return text;
|
|
252
|
+
}
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
function isFooterBoundary(segments, index) {
|
|
256
|
+
var _a, _b;
|
|
257
|
+
const line = firstVisibleLine((_b = (_a = segments[index]) == null ? void 0 : _a.visibleText) != null ? _b : "");
|
|
258
|
+
if (!line) return false;
|
|
259
|
+
if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line)) return true;
|
|
260
|
+
const nextText = nextVisibleSegmentText(segments, index);
|
|
261
|
+
if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)));
|
|
262
|
+
return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText);
|
|
263
|
+
}
|
|
264
|
+
function splitFooterSegments(segments) {
|
|
265
|
+
const visibleIndexes = segments.map((segment, index) => segment.visibleText ? index : -1).filter((index) => index >= 0);
|
|
266
|
+
const visibleCount = visibleIndexes.length;
|
|
267
|
+
if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] };
|
|
268
|
+
const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8);
|
|
269
|
+
const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount);
|
|
270
|
+
for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {
|
|
271
|
+
const index = visibleIndexes[ordinal];
|
|
272
|
+
if (ordinal > 0 && isFooterBoundary(segments, index)) {
|
|
273
|
+
return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { bodySegments: segments, detailsSegments: [] };
|
|
277
|
+
}
|
|
278
|
+
function splitMessageHtml(rawHtml) {
|
|
279
|
+
const sanitizedHtml = sanitizeHtml(rawHtml);
|
|
280
|
+
const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml));
|
|
281
|
+
if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: "" };
|
|
282
|
+
return {
|
|
283
|
+
bodyHtml: bodySegments.map((segment) => {
|
|
284
|
+
var _a;
|
|
285
|
+
return (_a = segment.html) != null ? _a : "";
|
|
286
|
+
}).join(""),
|
|
287
|
+
detailsHtml: detailsSegments.map((segment) => {
|
|
288
|
+
var _a;
|
|
289
|
+
return (_a = segment.html) != null ? _a : "";
|
|
290
|
+
}).join("")
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function splitMessageText(text) {
|
|
294
|
+
const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(text));
|
|
295
|
+
if (!detailsSegments.length) return { bodyText: text, detailsText: "" };
|
|
296
|
+
return {
|
|
297
|
+
bodyText: bodySegments.map((segment) => {
|
|
298
|
+
var _a;
|
|
299
|
+
return (_a = segment.text) != null ? _a : "";
|
|
300
|
+
}).join(""),
|
|
301
|
+
detailsText: detailsSegments.map((segment) => {
|
|
302
|
+
var _a;
|
|
303
|
+
return (_a = segment.text) != null ? _a : "";
|
|
304
|
+
}).join("")
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function messageBodySnippet(message, maxLength = 140) {
|
|
308
|
+
var _a, _b, _c;
|
|
309
|
+
if (message.bodyHtml) {
|
|
310
|
+
return htmlToTextSnippet(splitMessageHtml(message.bodyHtml).bodyHtml, maxLength);
|
|
311
|
+
}
|
|
312
|
+
return (_c = (_b = splitMessageText((_a = message.body) != null ? _a : "").bodyText.split("\n").find((line) => line.trim())) == null ? void 0 : _b.trim()) != null ? _c : "";
|
|
313
|
+
}
|
|
72
314
|
function GmailMark({ size = 14 }) {
|
|
73
315
|
return (
|
|
74
316
|
// eslint-disable-next-line @next/next/no-img-element
|
|
@@ -114,10 +356,11 @@ function MessageView({
|
|
|
114
356
|
onToggle,
|
|
115
357
|
me
|
|
116
358
|
}) {
|
|
117
|
-
var _a, _b
|
|
359
|
+
var _a, _b;
|
|
118
360
|
const [quoteOpen, setQuoteOpen] = React.useState(false);
|
|
119
|
-
const
|
|
361
|
+
const [detailsOpen, setDetailsOpen] = React.useState(false);
|
|
120
362
|
if (!expanded) {
|
|
363
|
+
const snippet = messageBodySnippet(message, 140);
|
|
121
364
|
return /* @__PURE__ */ jsxs(
|
|
122
365
|
"button",
|
|
123
366
|
{
|
|
@@ -132,13 +375,16 @@ function MessageView({
|
|
|
132
375
|
" \xB7 ",
|
|
133
376
|
snippet
|
|
134
377
|
] }),
|
|
135
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: (
|
|
378
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 shrink-0 text-xs", children: (_a = message.ago) != null ? _a : message.date }),
|
|
136
379
|
/* @__PURE__ */ jsx(ChevronDown, { size: 13, className: "text-muted-foreground shrink-0" })
|
|
137
380
|
]
|
|
138
381
|
}
|
|
139
382
|
);
|
|
140
383
|
}
|
|
141
384
|
const toLabel = me && message.to.email === me.email ? "me" : firstName(message.to.name);
|
|
385
|
+
const htmlParts = message.bodyHtml ? splitMessageHtml(message.bodyHtml) : null;
|
|
386
|
+
const textParts = message.bodyHtml ? null : splitMessageText((_b = message.body) != null ? _b : "");
|
|
387
|
+
const hasDetails = Boolean((htmlParts == null ? void 0 : htmlParts.detailsHtml) || (textParts == null ? void 0 : textParts.detailsText));
|
|
142
388
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-message", className: "rounded-md border border-border bg-background", children: [
|
|
143
389
|
/* @__PURE__ */ jsxs(
|
|
144
390
|
"button",
|
|
@@ -173,8 +419,21 @@ function MessageView({
|
|
|
173
419
|
]
|
|
174
420
|
}
|
|
175
421
|
),
|
|
176
|
-
/* @__PURE__ */ jsxs("div", { className: "px-3 pb-
|
|
177
|
-
|
|
422
|
+
/* @__PURE__ */ jsxs("div", { className: "px-3 pb-2.5", children: [
|
|
423
|
+
htmlParts ? /* @__PURE__ */ jsx("div", { "data-slot": "conv-message-body", className: PROSE, dangerouslySetInnerHTML: { __html: htmlParts.bodyHtml } }) : /* @__PURE__ */ jsx("div", { "data-slot": "conv-message-body", className: cn(PROSE, "whitespace-pre-line"), children: textParts == null ? void 0 : textParts.bodyText }),
|
|
424
|
+
hasDetails ? /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
|
|
425
|
+
/* @__PURE__ */ jsx(
|
|
426
|
+
"button",
|
|
427
|
+
{
|
|
428
|
+
type: "button",
|
|
429
|
+
onClick: () => setDetailsOpen((v) => !v),
|
|
430
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1.5 text-xs leading-5",
|
|
431
|
+
"aria-expanded": detailsOpen,
|
|
432
|
+
children: detailsOpen ? "Hide signature/details" : "Show signature/details"
|
|
433
|
+
}
|
|
434
|
+
),
|
|
435
|
+
detailsOpen ? /* @__PURE__ */ jsx("div", { className: "border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]", children: htmlParts ? /* @__PURE__ */ jsx("div", { "data-slot": "conv-message-details", className: PROSE, dangerouslySetInnerHTML: { __html: htmlParts.detailsHtml } }) : /* @__PURE__ */ jsx("div", { "data-slot": "conv-message-details", className: cn(PROSE, "whitespace-pre-line"), children: textParts == null ? void 0 : textParts.detailsText }) }) : null
|
|
436
|
+
] }) : null,
|
|
178
437
|
message.quoted ? /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
|
|
179
438
|
/* @__PURE__ */ jsx(
|
|
180
439
|
"button",
|
|
@@ -224,6 +483,18 @@ function ReplyComposer({
|
|
|
224
483
|
setSending(false);
|
|
225
484
|
}
|
|
226
485
|
};
|
|
486
|
+
const handleDraft = async () => {
|
|
487
|
+
setSending(true);
|
|
488
|
+
setSendError(null);
|
|
489
|
+
try {
|
|
490
|
+
await onDraft(body, sig);
|
|
491
|
+
setPreview(false);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
setSendError(error instanceof Error ? error.message : "Could not create the Gmail draft. Please try again.");
|
|
494
|
+
} finally {
|
|
495
|
+
setSending(false);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
227
498
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-reply", className: "border-border bg-muted/20 rounded-md border p-3", children: [
|
|
228
499
|
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
229
500
|
me ? /* @__PURE__ */ jsx(PersonAvatar, { person: me }) : null,
|
|
@@ -340,10 +611,7 @@ function ReplyComposer({
|
|
|
340
611
|
{
|
|
341
612
|
type: "button",
|
|
342
613
|
disabled: sending,
|
|
343
|
-
onClick:
|
|
344
|
-
setPreview(false);
|
|
345
|
-
onDraft(body, sig);
|
|
346
|
-
},
|
|
614
|
+
onClick: handleDraft,
|
|
347
615
|
className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50",
|
|
348
616
|
children: [
|
|
349
617
|
/* @__PURE__ */ jsx(GmailMark, { size: 14 }),
|
|
@@ -393,7 +661,7 @@ function ThreadBody({
|
|
|
393
661
|
return o;
|
|
394
662
|
});
|
|
395
663
|
const toggle = (id) => setExpanded((e) => __spreadProps(__spreadValues({}, e), { [id]: !e[id] }));
|
|
396
|
-
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread-body", className: "space-y-
|
|
664
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread-body", className: "space-y-1.5", children: [
|
|
397
665
|
canReply && thread.paused ? /* @__PURE__ */ jsxs("div", { className: "border-status-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
|
|
398
666
|
/* @__PURE__ */ jsx(Pause, { size: 13, className: "mt-0.5 shrink-0" }),
|
|
399
667
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -405,7 +673,7 @@ function ThreadBody({
|
|
|
405
673
|
" or Gmail."
|
|
406
674
|
] })
|
|
407
675
|
] }) : null,
|
|
408
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-1
|
|
676
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1", children: thread.messages.map((m) => /* @__PURE__ */ jsx(MessageView, { message: m, expanded: !!expanded[m.id], onToggle: () => toggle(m.id), me }, m.id)) }),
|
|
409
677
|
!canReply ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
|
|
410
678
|
/* @__PURE__ */ jsx(Eye, { size: 14, className: "mt-0.5 shrink-0" }),
|
|
411
679
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -449,8 +717,8 @@ function ThreadBody({
|
|
|
449
717
|
await (onSendReply == null ? void 0 : onSendReply({ threadId: thread.threadId, body, includeSignature, replyAll }));
|
|
450
718
|
setMode("sent");
|
|
451
719
|
},
|
|
452
|
-
onDraft: (body, includeSignature) => {
|
|
453
|
-
onCreateGmailDraft == null ? void 0 : onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll });
|
|
720
|
+
onDraft: async (body, includeSignature) => {
|
|
721
|
+
await (onCreateGmailDraft == null ? void 0 : onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll }));
|
|
454
722
|
setMode("draft");
|
|
455
723
|
}
|
|
456
724
|
}
|
|
@@ -497,11 +765,10 @@ function ThreadRow({
|
|
|
497
765
|
onCreateGmailDraft,
|
|
498
766
|
onOpenInGmail
|
|
499
767
|
}) {
|
|
500
|
-
var _a, _b;
|
|
501
768
|
const status = effectiveStatus(thread);
|
|
502
769
|
const last = thread.messages[thread.messages.length - 1];
|
|
503
770
|
const who = (last == null ? void 0 : last.direction) === "inbound" ? firstName(last.from.name) : "You";
|
|
504
|
-
const lastSnippet =
|
|
771
|
+
const lastSnippet = last ? messageBodySnippet(last, 120) : "";
|
|
505
772
|
const pill = STATUS_PILL[status];
|
|
506
773
|
return /* @__PURE__ */ jsxs("div", { "data-slot": "conv-thread", "data-open": open ? "true" : void 0, className: "border-border border-b last:border-b-0", children: [
|
|
507
774
|
/* @__PURE__ */ jsxs(
|