@remit/web-client 0.0.302 → 0.0.304
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/package.json +1 -1
- package/src/components/calendar/CalendarEventPane.stories.tsx +51 -0
- package/src/components/calendar/CalendarEventPane.tsx +79 -14
- package/src/components/calendar/OpenCalendarEvent.stories.tsx +128 -10
- package/src/components/calendar/OpenCalendarEvent.tsx +116 -2
- package/src/components/calendar/calendar-story-fixtures.ts +1 -0
- package/src/components/compose/compose.stories.tsx +374 -0
- package/src/components/layout/ComposeFab.tsx +2 -15
- package/src/components/layout/mail-shell.stories.tsx +142 -0
- package/src/components/mail/detail-surface.stories.tsx +171 -0
- package/src/components/mail/mail-pickers.stories.tsx +82 -0
- package/src/components/mail/search.stories.tsx +280 -0
- package/src/components/mail/selection-wizard.stories.tsx +160 -0
- package/src/hooks/calendar/index.ts +4 -0
- package/src/hooks/calendar/instance.test.ts +137 -1
- package/src/hooks/calendar/instance.ts +68 -0
- package/src/hooks/calendar/useCalendarJump.ts +117 -0
- package/src/lib/calendar-report.ts +10 -0
- package/src/lib/calendar-route.ts +2 -0
- package/src/mocks/story-frame/compose-world.ts +191 -0
- package/src/mocks/story-frame/mail-handlers.ts +195 -22
- package/src/mocks/story-frame/mail-world.ts +44 -18
|
@@ -2,19 +2,30 @@ import type {
|
|
|
2
2
|
AccountDetailOperationsUpdateAccountResponse,
|
|
3
3
|
AddressDetailOperationsUpdateAddressResponse,
|
|
4
4
|
AddressOperationsSearchAddressesResponse,
|
|
5
|
+
CalendarEventOperationsListCalendarEventsResponse,
|
|
6
|
+
CalendarOperationsListCalendarsResponse,
|
|
7
|
+
CalendarSuggestionOperationsListCalendarSuggestionsResponse,
|
|
5
8
|
FilterOperationsListFiltersResponse,
|
|
6
9
|
LabelOperationsListLabelsResponse,
|
|
7
10
|
MailboxDetailOperationsRenameMailboxResponse,
|
|
8
11
|
MailboxOperationsListMailboxesResponse,
|
|
9
12
|
MeOperationsListQuarantineResponse,
|
|
10
13
|
MeOperationsListVipSuggestionsResponse,
|
|
14
|
+
MessageBulkOperationsDeleteMessagesData,
|
|
15
|
+
MessageBulkOperationsDeleteMessagesResponse,
|
|
16
|
+
MessageBulkOperationsMoveMessagesData,
|
|
17
|
+
MessageBulkOperationsMoveMessagesResponse,
|
|
11
18
|
MessageBulkOperationsUpdateFlagsResponse,
|
|
12
19
|
MessageCalendarSuggestionOperationsListMessageCalendarSuggestionsResponse,
|
|
20
|
+
OrganizeOperationsPreviewOrganizeResponse,
|
|
13
21
|
OutboxOperationsListOutboxMessagesResponse,
|
|
14
22
|
RemitImapConfigDescriptionResponse,
|
|
23
|
+
RemitImapCreateOutboxMessageInput,
|
|
15
24
|
RemitImapDescribeMessageResponse,
|
|
25
|
+
RemitImapOutboxMessageResponse,
|
|
16
26
|
RemitImapThreadMessageResponse,
|
|
17
27
|
RemitImapThreadSearchResponse,
|
|
28
|
+
RemitImapUpdateOutboxMessageInput,
|
|
18
29
|
SemanticSearchOperationsSemanticSearchResponse,
|
|
19
30
|
SyncOperationsGetSyncStatusResponse,
|
|
20
31
|
SyncOperationsTriggerSyncResponse,
|
|
@@ -32,11 +43,45 @@ export interface MailHandlerOptions {
|
|
|
32
43
|
withholdCounts?: boolean;
|
|
33
44
|
pageSize?: number;
|
|
34
45
|
holdLaterPages?: boolean;
|
|
46
|
+
draftSave?: "saves" | "refused";
|
|
47
|
+
send?: "sends" | "holds";
|
|
48
|
+
search?: "answers" | "holds";
|
|
49
|
+
config?: "answers" | "holds";
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
const contentPath = (row: RemitImapThreadMessageResponse): string =>
|
|
38
53
|
`/content/accounts/${row.accountConfigId}/${row.accountId}/messages/${row.messageId}/parts/1`;
|
|
39
54
|
|
|
55
|
+
const attachmentOnly = (row: RemitImapThreadMessageResponse): boolean =>
|
|
56
|
+
row.hasAttachment && !row.snippet;
|
|
57
|
+
|
|
58
|
+
const bodyPartsOf = (
|
|
59
|
+
row: RemitImapThreadMessageResponse,
|
|
60
|
+
): RemitImapDescribeMessageResponse["bodyParts"] =>
|
|
61
|
+
attachmentOnly(row)
|
|
62
|
+
? [
|
|
63
|
+
{
|
|
64
|
+
bodyPartId: `part-${row.messageId}`,
|
|
65
|
+
mediaType: "APPLICATION",
|
|
66
|
+
mediaSubtype: "pdf",
|
|
67
|
+
sizeOctets: 48_000,
|
|
68
|
+
isMultipart: false,
|
|
69
|
+
disposition: "attachment",
|
|
70
|
+
dispositionFilename: "scan.pdf",
|
|
71
|
+
contentUrl: contentPath(row),
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
: [
|
|
75
|
+
{
|
|
76
|
+
bodyPartId: `part-${row.messageId}`,
|
|
77
|
+
mediaType: "TEXT",
|
|
78
|
+
mediaSubtype: "plain",
|
|
79
|
+
sizeOctets: (row.snippet ?? "").length,
|
|
80
|
+
isMultipart: false,
|
|
81
|
+
contentUrl: contentPath(row),
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
40
85
|
const describe = (
|
|
41
86
|
row: RemitImapThreadMessageResponse,
|
|
42
87
|
): RemitImapDescribeMessageResponse => ({
|
|
@@ -71,16 +116,7 @@ const describe = (
|
|
|
71
116
|
senderTrust: row.senderTrust,
|
|
72
117
|
},
|
|
73
118
|
flags: row.isRead ? ["\\Seen"] : [],
|
|
74
|
-
bodyParts:
|
|
75
|
-
{
|
|
76
|
-
bodyPartId: `part-${row.messageId}`,
|
|
77
|
-
mediaType: "TEXT",
|
|
78
|
-
mediaSubtype: "plain",
|
|
79
|
-
sizeOctets: (row.snippet ?? "").length,
|
|
80
|
-
isMultipart: false,
|
|
81
|
-
contentUrl: contentPath(row),
|
|
82
|
-
},
|
|
83
|
-
],
|
|
119
|
+
bodyParts: bodyPartsOf(row),
|
|
84
120
|
references: [],
|
|
85
121
|
});
|
|
86
122
|
|
|
@@ -167,11 +203,23 @@ export const mailHandlers = (
|
|
|
167
203
|
),
|
|
168
204
|
);
|
|
169
205
|
|
|
206
|
+
const outbox = new Map(
|
|
207
|
+
world.outbox.map((message) => [message.outboxMessageId, message]),
|
|
208
|
+
);
|
|
209
|
+
let draftsCreated = 0;
|
|
210
|
+
const refuseSave = () =>
|
|
211
|
+
HttpResponse.json(
|
|
212
|
+
{ status: 409, message: "The mail server did not accept the draft." },
|
|
213
|
+
{ status: 409 },
|
|
214
|
+
);
|
|
215
|
+
|
|
170
216
|
return [
|
|
171
217
|
http.get(`${API}/config`, () =>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
218
|
+
options.config === "holds"
|
|
219
|
+
? pending()
|
|
220
|
+
: HttpResponse.json<RemitImapConfigDescriptionResponse>(
|
|
221
|
+
makeConfig(world.accounts),
|
|
222
|
+
),
|
|
175
223
|
),
|
|
176
224
|
http.patch(
|
|
177
225
|
`${API}/accounts/:accountId/mailboxes/:mailboxId`,
|
|
@@ -277,11 +325,38 @@ export const mailHandlers = (
|
|
|
277
325
|
failureCount: 0,
|
|
278
326
|
}),
|
|
279
327
|
),
|
|
328
|
+
http.post(`${API}/messages/delete`, async ({ request }) => {
|
|
329
|
+
const { messageIds } =
|
|
330
|
+
(await request.json()) as MessageBulkOperationsDeleteMessagesData["body"];
|
|
331
|
+
return HttpResponse.json<MessageBulkOperationsDeleteMessagesResponse>({
|
|
332
|
+
successCount: messageIds.length,
|
|
333
|
+
failureCount: 0,
|
|
334
|
+
});
|
|
335
|
+
}),
|
|
336
|
+
http.post(`${API}/messages/move`, async ({ request }) => {
|
|
337
|
+
const { messageIds } =
|
|
338
|
+
(await request.json()) as MessageBulkOperationsMoveMessagesData["body"];
|
|
339
|
+
return HttpResponse.json<MessageBulkOperationsMoveMessagesResponse>({
|
|
340
|
+
successCount: messageIds.length,
|
|
341
|
+
failureCount: 0,
|
|
342
|
+
});
|
|
343
|
+
}),
|
|
344
|
+
http.post(`${API}/accounts/:accountId/organize/preview`, ({ params }) => {
|
|
345
|
+
const matched = world.threads.filter(
|
|
346
|
+
(row) => row.accountId === params.accountId,
|
|
347
|
+
);
|
|
348
|
+
return HttpResponse.json<OrganizeOperationsPreviewOrganizeResponse>({
|
|
349
|
+
matchedCount: matched.length,
|
|
350
|
+
messageIds: matched.map((row) => row.messageId),
|
|
351
|
+
});
|
|
352
|
+
}),
|
|
280
353
|
http.get(
|
|
281
354
|
"/content/accounts/:accountConfigId/:accountId/messages/:messageId/parts/*",
|
|
282
355
|
({ params }) =>
|
|
283
356
|
HttpResponse.text(
|
|
284
|
-
|
|
357
|
+
world.bodies[String(params.messageId)] ??
|
|
358
|
+
byMessageId.get(String(params.messageId))?.snippet ??
|
|
359
|
+
"",
|
|
285
360
|
),
|
|
286
361
|
),
|
|
287
362
|
http.get(`${API}/threads/:threadId/messages`, ({ params }) =>
|
|
@@ -300,21 +375,25 @@ export const mailHandlers = (
|
|
|
300
375
|
);
|
|
301
376
|
if (options.holdLaterPages && params.has("continuationToken"))
|
|
302
377
|
return pending();
|
|
378
|
+
if (searching && options.search === "holds") return pending();
|
|
303
379
|
return HttpResponse.json(listing(narrow(scope, params), params, options));
|
|
304
380
|
}),
|
|
305
381
|
http.get(
|
|
306
382
|
`${API}/mailboxes/:mailboxId/threads/search`,
|
|
307
|
-
({ request, params }) =>
|
|
308
|
-
|
|
383
|
+
({ request, params }) => {
|
|
384
|
+
const search = new URL(request.url).searchParams;
|
|
385
|
+
if (search.has("query") && options.search === "holds") return pending();
|
|
386
|
+
return HttpResponse.json(
|
|
309
387
|
listing(
|
|
310
388
|
narrow(
|
|
311
389
|
world.threads.filter((row) => row.mailboxId === params.mailboxId),
|
|
312
|
-
|
|
390
|
+
search,
|
|
313
391
|
),
|
|
314
|
-
|
|
392
|
+
search,
|
|
315
393
|
options,
|
|
316
394
|
),
|
|
317
|
-
)
|
|
395
|
+
);
|
|
396
|
+
},
|
|
318
397
|
),
|
|
319
398
|
http.get(`${API}/mailboxes/:mailboxId/threads`, ({ params }) =>
|
|
320
399
|
HttpResponse.json<ThreadOperationsListThreadsResponse>({
|
|
@@ -325,14 +404,108 @@ export const mailHandlers = (
|
|
|
325
404
|
),
|
|
326
405
|
http.get(`${API}/outbox`, () =>
|
|
327
406
|
HttpResponse.json<OutboxOperationsListOutboxMessagesResponse>({
|
|
328
|
-
items:
|
|
407
|
+
items: [...outbox.values()],
|
|
329
408
|
}),
|
|
330
409
|
),
|
|
331
|
-
http.
|
|
332
|
-
|
|
410
|
+
http.post(`${API}/outbox`, async ({ request }) => {
|
|
411
|
+
if (options.draftSave === "refused") return refuseSave();
|
|
412
|
+
const input = (await request.json()) as RemitImapCreateOutboxMessageInput;
|
|
413
|
+
draftsCreated += 1;
|
|
414
|
+
const account = world.accounts.find(
|
|
415
|
+
(candidate) => candidate.accountId === input.accountId,
|
|
416
|
+
);
|
|
417
|
+
const created: RemitImapOutboxMessageResponse = {
|
|
418
|
+
outboxMessageId: `out-created-${draftsCreated}`,
|
|
419
|
+
accountId: input.accountId,
|
|
420
|
+
fromAddress: account?.email ?? "",
|
|
421
|
+
fromName: account?.displayName,
|
|
422
|
+
toAddresses: input.toAddresses,
|
|
423
|
+
ccAddresses: input.ccAddresses ?? [],
|
|
424
|
+
bccAddresses: input.bccAddresses ?? [],
|
|
425
|
+
subject: input.subject,
|
|
426
|
+
textBody: input.textBody,
|
|
427
|
+
htmlBody: input.htmlBody,
|
|
428
|
+
references: input.references ?? [],
|
|
429
|
+
status: "draft",
|
|
430
|
+
createdAt: Date.UTC(2026, 8, 24, 13, 0),
|
|
431
|
+
updatedAt: Date.UTC(2026, 8, 24, 13, 0),
|
|
432
|
+
attachments: [],
|
|
433
|
+
};
|
|
434
|
+
outbox.set(created.outboxMessageId, created);
|
|
435
|
+
return HttpResponse.json<RemitImapOutboxMessageResponse>(created);
|
|
436
|
+
}),
|
|
437
|
+
http.get(`${API}/outbox/:outboxMessageId`, ({ params }) => {
|
|
438
|
+
const message = outbox.get(String(params.outboxMessageId));
|
|
439
|
+
if (!message)
|
|
440
|
+
return HttpResponse.json(
|
|
441
|
+
{ status: 404, message: "Outbox message not found" },
|
|
442
|
+
{ status: 404 },
|
|
443
|
+
);
|
|
444
|
+
return HttpResponse.json<RemitImapOutboxMessageResponse>(message);
|
|
445
|
+
}),
|
|
446
|
+
http.patch(
|
|
447
|
+
`${API}/outbox/:outboxMessageId`,
|
|
448
|
+
async ({ params, request }) => {
|
|
449
|
+
if (options.draftSave === "refused") return refuseSave();
|
|
450
|
+
const id = String(params.outboxMessageId);
|
|
451
|
+
const current = outbox.get(id);
|
|
452
|
+
if (!current)
|
|
453
|
+
return HttpResponse.json(
|
|
454
|
+
{ status: 404, message: "Outbox message not found" },
|
|
455
|
+
{ status: 404 },
|
|
456
|
+
);
|
|
457
|
+
const input =
|
|
458
|
+
(await request.json()) as RemitImapUpdateOutboxMessageInput;
|
|
459
|
+
const { attachmentIds: _attachmentIds, ...fields } = input;
|
|
460
|
+
const updated: RemitImapOutboxMessageResponse = {
|
|
461
|
+
...current,
|
|
462
|
+
...fields,
|
|
463
|
+
status: "draft",
|
|
464
|
+
};
|
|
465
|
+
outbox.set(id, updated);
|
|
466
|
+
return HttpResponse.json<RemitImapOutboxMessageResponse>(updated);
|
|
467
|
+
},
|
|
468
|
+
),
|
|
469
|
+
http.post(`${API}/outbox/:outboxMessageId/send`, ({ params }) => {
|
|
470
|
+
if (options.send === "holds") return pending();
|
|
471
|
+
const id = String(params.outboxMessageId);
|
|
472
|
+
const current = outbox.get(id);
|
|
473
|
+
if (!current)
|
|
474
|
+
return HttpResponse.json(
|
|
475
|
+
{ status: 404, message: "Outbox message not found" },
|
|
476
|
+
{ status: 404 },
|
|
477
|
+
);
|
|
478
|
+
const queued: RemitImapOutboxMessageResponse = {
|
|
479
|
+
...current,
|
|
480
|
+
status: "queued",
|
|
481
|
+
};
|
|
482
|
+
outbox.set(id, queued);
|
|
483
|
+
return HttpResponse.json<RemitImapOutboxMessageResponse>(queued);
|
|
484
|
+
}),
|
|
485
|
+
http.delete(`${API}/outbox/:outboxMessageId`, ({ params }) => {
|
|
486
|
+
outbox.delete(String(params.outboxMessageId));
|
|
487
|
+
return new HttpResponse(null, { status: 204 });
|
|
488
|
+
}),
|
|
489
|
+
http.get(`${API}/calendars`, () =>
|
|
490
|
+
HttpResponse.json<CalendarOperationsListCalendarsResponse>({ items: [] }),
|
|
491
|
+
),
|
|
492
|
+
http.get(`${API}/calendar-events`, () =>
|
|
493
|
+
HttpResponse.json<CalendarEventOperationsListCalendarEventsResponse>({
|
|
333
494
|
items: [],
|
|
334
495
|
}),
|
|
335
496
|
),
|
|
497
|
+
http.get(`${API}/calendar-suggestions`, () =>
|
|
498
|
+
HttpResponse.json<CalendarSuggestionOperationsListCalendarSuggestionsResponse>(
|
|
499
|
+
{ items: [] },
|
|
500
|
+
),
|
|
501
|
+
),
|
|
502
|
+
http.get(`${API}/search/semantic`, () =>
|
|
503
|
+
options.search === "holds"
|
|
504
|
+
? pending()
|
|
505
|
+
: HttpResponse.json<SemanticSearchOperationsSemanticSearchResponse>({
|
|
506
|
+
items: [],
|
|
507
|
+
}),
|
|
508
|
+
),
|
|
336
509
|
http.get(`${API}/me/quarantine`, () =>
|
|
337
510
|
HttpResponse.json<MeOperationsListQuarantineResponse>({
|
|
338
511
|
entries: world.quarantine,
|
|
@@ -26,11 +26,12 @@ export interface MailWorld {
|
|
|
26
26
|
vipSuggestions: RemitImapVipSuggestionEntry[];
|
|
27
27
|
addresses: RemitImapAddressResponse[];
|
|
28
28
|
systemUpdate: RemitImapSystemUpdateResponse;
|
|
29
|
+
bodies: Record<string, string>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const MINUTE = 60_000;
|
|
32
|
-
const HOUR = 60 * MINUTE;
|
|
33
|
-
const DAY = 24 * HOUR;
|
|
32
|
+
export const MINUTE = 60_000;
|
|
33
|
+
export const HOUR = 60 * MINUTE;
|
|
34
|
+
export const DAY = 24 * HOUR;
|
|
34
35
|
export const NOW = Date.UTC(2026, 8, 24, 13, 0);
|
|
35
36
|
|
|
36
37
|
const ROLES = [
|
|
@@ -93,8 +94,9 @@ const accounts = [
|
|
|
93
94
|
accountWithFolders(WORK, "alice.tan@acme.example", "Work"),
|
|
94
95
|
];
|
|
95
96
|
|
|
96
|
-
interface ThreadSeed {
|
|
97
|
+
export interface ThreadSeed {
|
|
97
98
|
id: string;
|
|
99
|
+
inThread?: string;
|
|
98
100
|
accountId: string;
|
|
99
101
|
role?: RemitImapCanonicalMailboxRole;
|
|
100
102
|
fromName: string;
|
|
@@ -108,10 +110,10 @@ interface ThreadSeed {
|
|
|
108
110
|
hasAttachment?: boolean;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
const
|
|
113
|
+
export const threadRow = (seed: ThreadSeed): RemitImapThreadMessageResponse =>
|
|
112
114
|
makeThreadMessage({
|
|
113
115
|
messageId: `msg-${seed.id}`,
|
|
114
|
-
threadId: `thread-${seed.id}`,
|
|
116
|
+
threadId: `thread-${seed.inThread ?? seed.id}`,
|
|
115
117
|
threadMessageId: `tm-${seed.id}`,
|
|
116
118
|
accountId: seed.accountId,
|
|
117
119
|
mailboxId: mailboxIdFor(seed.accountId, seed.role ?? "Inbox"),
|
|
@@ -129,7 +131,7 @@ const thread = (seed: ThreadSeed): RemitImapThreadMessageResponse =>
|
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
132
|
-
|
|
134
|
+
threadRow({
|
|
133
135
|
id: "q3-planning",
|
|
134
136
|
accountId: WORK,
|
|
135
137
|
fromName: "Ada Lovelace",
|
|
@@ -140,7 +142,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
140
142
|
ago: 20 * MINUTE,
|
|
141
143
|
starred: true,
|
|
142
144
|
}),
|
|
143
|
-
|
|
145
|
+
threadRow({
|
|
144
146
|
id: "lease",
|
|
145
147
|
accountId: PERSONAL,
|
|
146
148
|
fromName: "Amara Okafor",
|
|
@@ -152,7 +154,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
152
154
|
hasAttachment: true,
|
|
153
155
|
starred: true,
|
|
154
156
|
}),
|
|
155
|
-
|
|
157
|
+
threadRow({
|
|
156
158
|
id: "dinner",
|
|
157
159
|
accountId: PERSONAL,
|
|
158
160
|
fromName: "Grace Hopper",
|
|
@@ -163,7 +165,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
163
165
|
ago: 5 * HOUR,
|
|
164
166
|
isRead: true,
|
|
165
167
|
}),
|
|
166
|
-
|
|
168
|
+
threadRow({
|
|
167
169
|
id: "invoice",
|
|
168
170
|
accountId: WORK,
|
|
169
171
|
fromName: "Billing",
|
|
@@ -175,7 +177,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
175
177
|
isRead: true,
|
|
176
178
|
starred: true,
|
|
177
179
|
}),
|
|
178
|
-
|
|
180
|
+
threadRow({
|
|
179
181
|
id: "shipping",
|
|
180
182
|
accountId: PERSONAL,
|
|
181
183
|
fromName: "Parcel Service",
|
|
@@ -185,7 +187,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
185
187
|
category: "transactional",
|
|
186
188
|
ago: 9 * HOUR,
|
|
187
189
|
}),
|
|
188
|
-
|
|
190
|
+
threadRow({
|
|
189
191
|
id: "weekly-digest",
|
|
190
192
|
accountId: PERSONAL,
|
|
191
193
|
fromName: "The Weekly",
|
|
@@ -195,7 +197,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
195
197
|
category: "newsletter",
|
|
196
198
|
ago: DAY,
|
|
197
199
|
}),
|
|
198
|
-
|
|
200
|
+
threadRow({
|
|
199
201
|
id: "sale",
|
|
200
202
|
accountId: PERSONAL,
|
|
201
203
|
fromName: "Outdoor Store",
|
|
@@ -205,7 +207,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
205
207
|
category: "marketing",
|
|
206
208
|
ago: DAY + 3 * HOUR,
|
|
207
209
|
}),
|
|
208
|
-
|
|
210
|
+
threadRow({
|
|
209
211
|
id: "mention",
|
|
210
212
|
accountId: WORK,
|
|
211
213
|
fromName: "Chat",
|
|
@@ -215,7 +217,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
215
217
|
category: "social",
|
|
216
218
|
ago: DAY + 5 * HOUR,
|
|
217
219
|
}),
|
|
218
|
-
|
|
220
|
+
threadRow({
|
|
219
221
|
id: "build",
|
|
220
222
|
accountId: WORK,
|
|
221
223
|
fromName: "CI",
|
|
@@ -226,7 +228,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
226
228
|
ago: 2 * DAY,
|
|
227
229
|
isRead: true,
|
|
228
230
|
}),
|
|
229
|
-
|
|
231
|
+
threadRow({
|
|
230
232
|
id: "talk-outline",
|
|
231
233
|
accountId: PERSONAL,
|
|
232
234
|
role: "Archive",
|
|
@@ -242,7 +244,7 @@ const inboxThreads: RemitImapThreadMessageResponse[] = [
|
|
|
242
244
|
];
|
|
243
245
|
|
|
244
246
|
const serverDrafts: RemitImapThreadMessageResponse[] = [
|
|
245
|
-
|
|
247
|
+
threadRow({
|
|
246
248
|
id: "draft-talk",
|
|
247
249
|
accountId: PERSONAL,
|
|
248
250
|
role: "Drafts",
|
|
@@ -376,7 +378,7 @@ export const newsletterBacklog = (
|
|
|
376
378
|
count: number,
|
|
377
379
|
): RemitImapThreadMessageResponse[] =>
|
|
378
380
|
Array.from({ length: count }, (_, index) =>
|
|
379
|
-
|
|
381
|
+
threadRow({
|
|
380
382
|
id: `digest-${index}`,
|
|
381
383
|
accountId: PERSONAL,
|
|
382
384
|
fromName: "The Weekly",
|
|
@@ -388,6 +390,29 @@ export const newsletterBacklog = (
|
|
|
388
390
|
}),
|
|
389
391
|
);
|
|
390
392
|
|
|
393
|
+
export const senderAddress = (
|
|
394
|
+
row: RemitImapThreadMessageResponse,
|
|
395
|
+
): RemitImapAddressResponse => {
|
|
396
|
+
const email = (row.fromEmail ?? "").toLowerCase();
|
|
397
|
+
const [localPart = "", domain = ""] = email.split("@");
|
|
398
|
+
return {
|
|
399
|
+
addressId: `addr-${email}`,
|
|
400
|
+
accountConfigId: row.accountConfigId,
|
|
401
|
+
displayName: row.fromName,
|
|
402
|
+
localPart,
|
|
403
|
+
domain,
|
|
404
|
+
normalizedEmail: email,
|
|
405
|
+
flags: {},
|
|
406
|
+
inboundCount: 1,
|
|
407
|
+
outboundCount: 0,
|
|
408
|
+
replyCount: 0,
|
|
409
|
+
lastInboundAt: row.sentDate,
|
|
410
|
+
lastReplyAt: 0,
|
|
411
|
+
createdAt: row.sentDate,
|
|
412
|
+
updatedAt: row.sentDate,
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
|
|
391
416
|
export const mailWorld = (overrides: Partial<MailWorld> = {}): MailWorld => ({
|
|
392
417
|
accounts,
|
|
393
418
|
mailboxes: (overrides.accounts ?? accounts).flatMap((account) =>
|
|
@@ -403,5 +428,6 @@ export const mailWorld = (overrides: Partial<MailWorld> = {}): MailWorld => ({
|
|
|
403
428
|
check: { status: "disabled" },
|
|
404
429
|
run: null,
|
|
405
430
|
},
|
|
431
|
+
bodies: {},
|
|
406
432
|
...overrides,
|
|
407
433
|
});
|