@wtfalch/email 0.4.1 → 0.6.0
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/mailbox/download.d.ts +43 -0
- package/dist/mailbox/download.js +68 -0
- package/dist/mailbox/fake/mailbox.js +61 -1
- package/dist/mailbox/fake/sample.d.ts +0 -9
- package/dist/mailbox/fake/sample.js +224 -4
- package/dist/mailbox/index.d.ts +4 -0
- package/dist/mailbox/index.js +2 -0
- package/dist/mailbox/mail.css +697 -47
- package/dist/mailbox/mutate.d.ts +51 -0
- package/dist/mailbox/mutate.js +132 -0
- package/dist/mailbox/react/Composer.d.ts +5 -0
- package/dist/mailbox/react/Composer.js +11 -2
- package/dist/mailbox/react/Mail.js +343 -32
- package/dist/mailbox/react/MailboxTree.d.ts +3 -1
- package/dist/mailbox/react/MailboxTree.js +43 -13
- package/dist/mailbox/react/ThreadList.d.ts +46 -14
- package/dist/mailbox/react/ThreadList.js +109 -8
- package/dist/mailbox/react/ThreadView.d.ts +33 -4
- package/dist/mailbox/react/ThreadView.js +89 -19
- package/dist/mailbox/react/index.d.ts +6 -3
- package/dist/mailbox/react/index.js +4 -2
- package/dist/mailbox/react/layout.d.ts +31 -0
- package/dist/mailbox/react/layout.js +42 -0
- package/dist/mailbox/react/save.d.ts +25 -0
- package/dist/mailbox/react/save.js +42 -0
- package/package.json +3 -3
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MailClient } from './client.ts';
|
|
2
|
+
import type { Attachment } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Fetching an attachment's bytes.
|
|
5
|
+
*
|
|
6
|
+
* **This module exists because a download URL is not a link.** JMAP's
|
|
7
|
+
* `downloadUrl` carries no credential of its own — RFC 8620 §6.2 expects the
|
|
8
|
+
* same authentication the API uses — so `<a href={downloadUrl} download>` and
|
|
9
|
+
* `<img src={cid}>` both fetch it without an `Authorization` header and get a
|
|
10
|
+
* 401. The bytes have to be fetched by code that holds the token, and the
|
|
11
|
+
* result handed to the browser as a blob it already has.
|
|
12
|
+
*
|
|
13
|
+
* That is the whole reason attachments were unreachable: `ThreadView` has
|
|
14
|
+
* taken an `onDownload` since it was written, and nothing could supply one
|
|
15
|
+
* without this.
|
|
16
|
+
*
|
|
17
|
+
* **`credentials: 'omit'`, for the reason `client.ts` gives at length.** A
|
|
18
|
+
* refused request makes Stalwart answer with `WWW-Authenticate: Basic` as
|
|
19
|
+
* well as `Bearer`, and a browser allowed to use credentials honours the
|
|
20
|
+
* second by opening its native password prompt — a modal that hides whatever
|
|
21
|
+
* the application was about to say. Declining that here keeps a 401 an
|
|
22
|
+
* ordinary response.
|
|
23
|
+
*/
|
|
24
|
+
/** How much of a message this will pull into memory before refusing. A mail
|
|
25
|
+
* server will happily serve a 2GB blob and a browser tab will not survive
|
|
26
|
+
* holding one; Stalwart's own default message size limit is well under this.
|
|
27
|
+
* A caller that genuinely wants more passes its own `maxBytes`. */
|
|
28
|
+
export declare const DEFAULT_MAX_DOWNLOAD_BYTES: number;
|
|
29
|
+
export type DownloadOptions = {
|
|
30
|
+
/** Refuse a blob larger than this, rather than exhausting the tab. */
|
|
31
|
+
maxBytes?: number;
|
|
32
|
+
/** Abort the fetch — a person navigating away from a slow attachment. */
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* An attachment's bytes, as a `Blob`.
|
|
37
|
+
*
|
|
38
|
+
* The type comes from the message rather than from the response: a server
|
|
39
|
+
* that sends `application/octet-stream` for everything would otherwise turn
|
|
40
|
+
* every PDF into a file the operating system cannot open, and the part's
|
|
41
|
+
* declared type is the one the sender chose.
|
|
42
|
+
*/
|
|
43
|
+
export declare function downloadAttachment(client: MailClient, attachment: Pick<Attachment, 'blobId' | 'name' | 'type' | 'size' | 'downloadUrl'>, options?: DownloadOptions): Promise<Blob>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MailError, guard } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fetching an attachment's bytes.
|
|
4
|
+
*
|
|
5
|
+
* **This module exists because a download URL is not a link.** JMAP's
|
|
6
|
+
* `downloadUrl` carries no credential of its own — RFC 8620 §6.2 expects the
|
|
7
|
+
* same authentication the API uses — so `<a href={downloadUrl} download>` and
|
|
8
|
+
* `<img src={cid}>` both fetch it without an `Authorization` header and get a
|
|
9
|
+
* 401. The bytes have to be fetched by code that holds the token, and the
|
|
10
|
+
* result handed to the browser as a blob it already has.
|
|
11
|
+
*
|
|
12
|
+
* That is the whole reason attachments were unreachable: `ThreadView` has
|
|
13
|
+
* taken an `onDownload` since it was written, and nothing could supply one
|
|
14
|
+
* without this.
|
|
15
|
+
*
|
|
16
|
+
* **`credentials: 'omit'`, for the reason `client.ts` gives at length.** A
|
|
17
|
+
* refused request makes Stalwart answer with `WWW-Authenticate: Basic` as
|
|
18
|
+
* well as `Bearer`, and a browser allowed to use credentials honours the
|
|
19
|
+
* second by opening its native password prompt — a modal that hides whatever
|
|
20
|
+
* the application was about to say. Declining that here keeps a 401 an
|
|
21
|
+
* ordinary response.
|
|
22
|
+
*/
|
|
23
|
+
/** How much of a message this will pull into memory before refusing. A mail
|
|
24
|
+
* server will happily serve a 2GB blob and a browser tab will not survive
|
|
25
|
+
* holding one; Stalwart's own default message size limit is well under this.
|
|
26
|
+
* A caller that genuinely wants more passes its own `maxBytes`. */
|
|
27
|
+
export const DEFAULT_MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
28
|
+
/**
|
|
29
|
+
* An attachment's bytes, as a `Blob`.
|
|
30
|
+
*
|
|
31
|
+
* The type comes from the message rather than from the response: a server
|
|
32
|
+
* that sends `application/octet-stream` for everything would otherwise turn
|
|
33
|
+
* every PDF into a file the operating system cannot open, and the part's
|
|
34
|
+
* declared type is the one the sender chose.
|
|
35
|
+
*/
|
|
36
|
+
export async function downloadAttachment(client, attachment, options = {}) {
|
|
37
|
+
const max = options.maxBytes ?? DEFAULT_MAX_DOWNLOAD_BYTES;
|
|
38
|
+
/* Checked before the request as well as after. The message says how big
|
|
39
|
+
the part is, and refusing on that alone saves pulling a gigabyte over
|
|
40
|
+
the wire to then refuse it. */
|
|
41
|
+
if (attachment.size > max) {
|
|
42
|
+
throw new MailError(`${attachment.name} is ${attachment.size} bytes, over the ${max}-byte limit for a download`, { type: 'tooLarge', operation: 'download' });
|
|
43
|
+
}
|
|
44
|
+
/* The URL on the attachment was expanded when the message was read. A
|
|
45
|
+
caller that built an `Attachment` by hand may not have one, so it is
|
|
46
|
+
recomputed rather than assumed. */
|
|
47
|
+
const url = attachment.downloadUrl || (await client.downloadUrl(attachment));
|
|
48
|
+
return guard('download', async () => {
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
headers: { Authorization: client.authorization },
|
|
51
|
+
credentials: 'omit',
|
|
52
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new MailError(`the server refused to send ${attachment.name}`, {
|
|
56
|
+
status: response.status,
|
|
57
|
+
operation: 'download',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const bytes = await response.arrayBuffer();
|
|
61
|
+
/* A server is not obliged to send `Content-Length`, and a chunked
|
|
62
|
+
response can be any size whatever the message claimed. */
|
|
63
|
+
if (bytes.byteLength > max) {
|
|
64
|
+
throw new MailError(`${attachment.name} arrived at ${bytes.byteLength} bytes, over the ${max}-byte limit`, { type: 'tooLarge', operation: 'download' });
|
|
65
|
+
}
|
|
66
|
+
return new Blob([bytes], { type: attachment.type || 'application/octet-stream' });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -262,7 +262,62 @@ export class FakeMailbox {
|
|
|
262
262
|
this.#threads.set(threadId, [id]);
|
|
263
263
|
created[key] = { id, blobId: `blob-${id}`, threadId, size: 512 };
|
|
264
264
|
}
|
|
265
|
-
|
|
265
|
+
/* The patch language, enough of it for what `mutate.ts` sends:
|
|
266
|
+
`keywords/$seen`, a whole `mailboxIds` replacement, and
|
|
267
|
+
`mailboxIds/<id>` removal. A `null` value deletes rather than sets
|
|
268
|
+
false, which is RFC 8620 §5.3's rule and the one a fake gets wrong
|
|
269
|
+
by treating the patch as a merge -- `keywords: { $seen: false }`
|
|
270
|
+
reads as unread everywhere in this package, so a fake that stores
|
|
271
|
+
the false would pass a test the real server fails. */
|
|
272
|
+
const update = (args.update ?? {});
|
|
273
|
+
const updated = {};
|
|
274
|
+
const notUpdated = {};
|
|
275
|
+
for (const [id, patch] of Object.entries(update)) {
|
|
276
|
+
const email = this.#emails.find((one) => one.id === id);
|
|
277
|
+
if (!email) {
|
|
278
|
+
notUpdated[id] = { type: 'notFound' };
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
for (const [path, value] of Object.entries(patch)) {
|
|
282
|
+
if (path === 'mailboxIds') {
|
|
283
|
+
email.mailboxIds = { ...value };
|
|
284
|
+
}
|
|
285
|
+
else if (path.startsWith('mailboxIds/')) {
|
|
286
|
+
const boxId = unpointer(path.slice('mailboxIds/'.length));
|
|
287
|
+
const next = { ...email.mailboxIds };
|
|
288
|
+
if (value === null)
|
|
289
|
+
delete next[boxId];
|
|
290
|
+
else
|
|
291
|
+
next[boxId] = true;
|
|
292
|
+
email.mailboxIds = next;
|
|
293
|
+
}
|
|
294
|
+
else if (path.startsWith('keywords/')) {
|
|
295
|
+
const keyword = unpointer(path.slice('keywords/'.length));
|
|
296
|
+
const next = { ...email.keywords };
|
|
297
|
+
if (value === null)
|
|
298
|
+
delete next[keyword];
|
|
299
|
+
else
|
|
300
|
+
next[keyword] = true;
|
|
301
|
+
email.keywords = next;
|
|
302
|
+
}
|
|
303
|
+
else if (path === 'keywords') {
|
|
304
|
+
email.keywords = { ...value };
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
notUpdated[id] = { type: 'invalidPatch', description: `cannot patch ${path}` };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
updated[id] = null;
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
accountId: ACCOUNT,
|
|
314
|
+
oldState: '1',
|
|
315
|
+
newState: '2',
|
|
316
|
+
created,
|
|
317
|
+
notCreated: null,
|
|
318
|
+
updated,
|
|
319
|
+
notUpdated: Object.keys(notUpdated).length > 0 ? notUpdated : null,
|
|
320
|
+
};
|
|
266
321
|
}
|
|
267
322
|
case 'EmailSubmission/set': {
|
|
268
323
|
const create = (args.create ?? {});
|
|
@@ -376,6 +431,11 @@ export class FakeMailbox {
|
|
|
376
431
|
return out;
|
|
377
432
|
}
|
|
378
433
|
}
|
|
434
|
+
/** RFC 6901 §3 in reverse: one patch-key segment, unescaped. A mailbox id is
|
|
435
|
+
* the server's to choose, so a key built from one may carry `~0` or `~1`. */
|
|
436
|
+
function unpointer(segment) {
|
|
437
|
+
return segment.replaceAll('~1', '/').replaceAll('~0', '~');
|
|
438
|
+
}
|
|
379
439
|
/** RFC 8620 §3.7's pointer, with the `*` that maps over a list. */
|
|
380
440
|
function pointer(value, path) {
|
|
381
441
|
let current = value;
|
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
import type { SeedThread } from './mailbox.ts';
|
|
2
|
-
/**
|
|
3
|
-
* A plausible inbox.
|
|
4
|
-
*
|
|
5
|
-
* Chosen to put every state a list view has to draw on the screen at once:
|
|
6
|
-
* unread and read, a thread with several messages, one with an attachment,
|
|
7
|
-
* one flagged, one from a machine with no display name, and one subject long
|
|
8
|
-
* enough to need truncating. A sample that is five tidy one-line messages
|
|
9
|
-
* demonstrates a component that has never met mail.
|
|
10
|
-
*/
|
|
11
2
|
export declare const SAMPLE: SeedThread[];
|
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
* A plausible inbox.
|
|
3
3
|
*
|
|
4
4
|
* Chosen to put every state a list view has to draw on the screen at once:
|
|
5
|
-
* unread and read, a thread with several messages, one with
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* unread and read, a thread with several messages, one with several *unread*
|
|
6
|
+
* messages, one with an attachment, several with attachments of different
|
|
7
|
+
* kinds, one flagged, one from a machine with no display name, one with no
|
|
8
|
+
* subject at all, one whose sender has no display name, one subject long
|
|
9
|
+
* enough to need truncating and one preview that is a single word. A sample
|
|
10
|
+
* that is five tidy one-line messages demonstrates a component that has never
|
|
11
|
+
* met mail.
|
|
12
|
+
*
|
|
13
|
+
* **It is long on purpose.** A list of four rows cannot show whether a row is
|
|
14
|
+
* the right height, whether the date groups earn their space, whether the
|
|
15
|
+
* scroll region behaves or whether the pager is reachable. Sixteen
|
|
16
|
+
* conversations spread over a fortnight can, and that is the shortest sample
|
|
17
|
+
* that can: relative time collapses at an hour, a day, a week and a year, and
|
|
18
|
+
* a fixture with nothing older than yesterday exercises one of the four.
|
|
19
|
+
*
|
|
20
|
+
* Times are minutes before the fake's fixed "now", so every screenshot of
|
|
21
|
+
* this is the same screenshot.
|
|
9
22
|
*/
|
|
23
|
+
const HOUR = 60;
|
|
24
|
+
const DAY = 24 * HOUR;
|
|
10
25
|
export const SAMPLE = [
|
|
11
26
|
{
|
|
12
27
|
mailbox: 'mb-inbox',
|
|
@@ -69,6 +84,187 @@ export const SAMPLE = [
|
|
|
69
84
|
},
|
|
70
85
|
],
|
|
71
86
|
},
|
|
87
|
+
/* Two people going back and forth, none of it read: the case where the row
|
|
88
|
+
has to say "4" and the mailbox count has to say one, not four. */
|
|
89
|
+
{
|
|
90
|
+
mailbox: 'mb-inbox',
|
|
91
|
+
messages: [
|
|
92
|
+
{
|
|
93
|
+
from: { name: 'Mary Somerville', email: 'mary@example.org' },
|
|
94
|
+
subject: 'The Difference Engine chapter',
|
|
95
|
+
text: 'I have marked up the proofs. Three passages need a decision from you before Friday.',
|
|
96
|
+
agoMinutes: 5 * HOUR,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
from: { name: 'Mary Somerville', email: 'mary@example.org' },
|
|
100
|
+
subject: 'Re: The Difference Engine chapter',
|
|
101
|
+
text: 'Ignore the second passage — I have resolved it myself.',
|
|
102
|
+
agoMinutes: 4 * HOUR,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
from: { name: 'Charles Babbage', email: 'charles@example.com' },
|
|
106
|
+
subject: 'Re: The Difference Engine chapter',
|
|
107
|
+
text: 'The third passage is the one that matters. I would rather we said nothing than said it vaguely.',
|
|
108
|
+
agoMinutes: 3 * HOUR,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
from: { name: 'Mary Somerville', email: 'mary@example.org' },
|
|
112
|
+
subject: 'Re: The Difference Engine chapter',
|
|
113
|
+
text: 'Agreed. Ada, you have the casting vote.',
|
|
114
|
+
agoMinutes: 2 * HOUR,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
/* A message with no subject, which is a real thing that arrives and a real
|
|
119
|
+
way to make a list view draw an empty line. */
|
|
120
|
+
{
|
|
121
|
+
mailbox: 'mb-inbox',
|
|
122
|
+
messages: [
|
|
123
|
+
{
|
|
124
|
+
from: { name: 'Sophie Germain', email: 'sophie@example.fr' },
|
|
125
|
+
subject: '',
|
|
126
|
+
text: 'Yes.',
|
|
127
|
+
agoMinutes: 7 * HOUR,
|
|
128
|
+
seen: true,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
mailbox: 'mb-inbox',
|
|
134
|
+
messages: [
|
|
135
|
+
{
|
|
136
|
+
from: { name: 'The Royal Society', email: 'events@royalsociety.example.org' },
|
|
137
|
+
subject: 'Invitation: demonstration of the analytical engine, 14 October',
|
|
138
|
+
text: 'You are invited to a demonstration. Papers are attached. Please reply with numbers by the end of the month.',
|
|
139
|
+
agoMinutes: 9 * HOUR,
|
|
140
|
+
attachments: [
|
|
141
|
+
{ name: 'programme.pdf', type: 'application/pdf', size: 214_882 },
|
|
142
|
+
{ name: 'directions.png', type: 'image/png', size: 48_120 },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
mailbox: 'mb-inbox',
|
|
149
|
+
messages: [
|
|
150
|
+
{
|
|
151
|
+
from: { name: 'Augustus De Morgan', email: 'augustus@example.ac.uk' },
|
|
152
|
+
subject: 'A correction, and an apology for the correction',
|
|
153
|
+
text: 'The recurrence in your note 7 is right and my objection was not. I have written to the editor withdrawing it.',
|
|
154
|
+
agoMinutes: 26 * HOUR,
|
|
155
|
+
seen: true,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
from: { name: 'Ada Lovelace', email: 'ada@example.com' },
|
|
159
|
+
subject: 'Re: A correction, and an apology for the correction',
|
|
160
|
+
text: 'No apology needed. The objection made the note better.',
|
|
161
|
+
agoMinutes: 25 * HOUR,
|
|
162
|
+
seen: true,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
/* Only an HTML body, which the reading pane refuses to render and says so.
|
|
167
|
+
Its own row so the state is reachable from the list rather than only
|
|
168
|
+
from a unit test. */
|
|
169
|
+
{
|
|
170
|
+
mailbox: 'mb-inbox',
|
|
171
|
+
messages: [
|
|
172
|
+
{
|
|
173
|
+
from: { email: 'campaigns@marketing.example.com' },
|
|
174
|
+
subject: 'Your weekly digest, beautifully formatted',
|
|
175
|
+
text: '',
|
|
176
|
+
html: '<html><body><h1>This week</h1><p>Rendered nowhere, on purpose.</p></body></html>',
|
|
177
|
+
agoMinutes: 30 * HOUR,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
mailbox: 'mb-inbox',
|
|
183
|
+
messages: [
|
|
184
|
+
{
|
|
185
|
+
from: { name: 'William King', email: 'william@example.com' },
|
|
186
|
+
subject: 'Ockham Park, the weekend',
|
|
187
|
+
text: 'The house is free from Friday. Bring the tables if you must, but not the mill.',
|
|
188
|
+
agoMinutes: 2 * DAY,
|
|
189
|
+
seen: true,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
mailbox: 'mb-inbox',
|
|
195
|
+
messages: [
|
|
196
|
+
{
|
|
197
|
+
from: { name: 'Michael Faraday', email: 'michael@example.org' },
|
|
198
|
+
subject: 'Re: your question about the coil',
|
|
199
|
+
text: 'It is the rate of change, not the field. I have drawn it out, which is quicker than the sentence.',
|
|
200
|
+
agoMinutes: 3 * DAY,
|
|
201
|
+
seen: true,
|
|
202
|
+
attachments: [{ name: 'coil.svg', type: 'image/svg+xml', size: 4_902 }],
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
mailbox: 'mb-inbox',
|
|
208
|
+
messages: [
|
|
209
|
+
{
|
|
210
|
+
from: { email: 'security-noreply@accounts.example.net' },
|
|
211
|
+
subject: 'New sign-in from an unrecognised device',
|
|
212
|
+
text: 'A new sign-in was recorded. If this was you, no action is needed.',
|
|
213
|
+
agoMinutes: 4 * DAY,
|
|
214
|
+
seen: true,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
mailbox: 'mb-inbox',
|
|
220
|
+
messages: [
|
|
221
|
+
{
|
|
222
|
+
from: { name: 'Anne Isabella Byron', email: 'annabella@example.com' },
|
|
223
|
+
subject: 'Sunday',
|
|
224
|
+
text: 'Come at four. There is nothing to discuss and I would like to discuss it at length.',
|
|
225
|
+
agoMinutes: 6 * DAY,
|
|
226
|
+
seen: true,
|
|
227
|
+
flagged: true,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
mailbox: 'mb-inbox',
|
|
233
|
+
messages: [
|
|
234
|
+
{
|
|
235
|
+
from: { name: 'Joseph-Marie Jacquard', email: 'joseph@example.fr' },
|
|
236
|
+
subject: 'On the punched card mechanism, its failure modes in humid weather, and what I propose to do about the third of them before the demonstration',
|
|
237
|
+
text: 'The cards swell. Everything else in the list follows from the cards swelling, so I have started there.',
|
|
238
|
+
agoMinutes: 9 * DAY,
|
|
239
|
+
seen: true,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
mailbox: 'mb-inbox',
|
|
245
|
+
messages: [
|
|
246
|
+
{
|
|
247
|
+
from: { name: 'Charles Wheatstone', email: 'charles.w@example.org' },
|
|
248
|
+
subject: 'Telegraph trial — results',
|
|
249
|
+
text: 'Twelve miles, no repeater, legible throughout. The full log is attached.',
|
|
250
|
+
agoMinutes: 14 * DAY,
|
|
251
|
+
seen: true,
|
|
252
|
+
attachments: [{ name: 'trial-log.csv', type: 'text/csv', size: 11_204 }],
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
mailbox: 'mb-inbox',
|
|
258
|
+
messages: [
|
|
259
|
+
{
|
|
260
|
+
from: { name: 'Ada Lovelace', email: 'ada@example.com' },
|
|
261
|
+
subject: 'Notes to myself: the Bernoulli table',
|
|
262
|
+
text: 'Start from the recurrence, not the closed form. The closed form is where the last three attempts went wrong.',
|
|
263
|
+
agoMinutes: 400 * DAY,
|
|
264
|
+
seen: true,
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
},
|
|
72
268
|
{
|
|
73
269
|
mailbox: 'mb-sent',
|
|
74
270
|
messages: [
|
|
@@ -82,4 +278,28 @@ export const SAMPLE = [
|
|
|
82
278
|
},
|
|
83
279
|
],
|
|
84
280
|
},
|
|
281
|
+
{
|
|
282
|
+
mailbox: 'mb-sent',
|
|
283
|
+
messages: [
|
|
284
|
+
{
|
|
285
|
+
from: { name: 'Ada Lovelace', email: 'ada@example.com' },
|
|
286
|
+
to: [{ name: 'Mary Somerville', email: 'mary@example.org' }],
|
|
287
|
+
subject: 'Re: The Difference Engine chapter',
|
|
288
|
+
text: 'Give me until tomorrow on the third passage.',
|
|
289
|
+
agoMinutes: 90,
|
|
290
|
+
seen: true,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
mailbox: 'mb-junk',
|
|
296
|
+
messages: [
|
|
297
|
+
{
|
|
298
|
+
from: { email: 'winner@lottery.example' },
|
|
299
|
+
subject: 'YOU HAVE BEEN SELECTED',
|
|
300
|
+
text: 'Reply with your bank details to claim your prize.',
|
|
301
|
+
agoMinutes: 8 * HOUR,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
85
305
|
];
|
package/dist/mailbox/index.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export { attribution, formatAddress, forwardDraft, forwardIntroduction, forwardS
|
|
|
21
21
|
export type { ForwardOptions, ReplyOptions } from './drafts.ts';
|
|
22
22
|
export { draftToEmail, send, sentPatch } from './submit.ts';
|
|
23
23
|
export type { SendOptions } from './submit.ts';
|
|
24
|
+
export { moveThread, moveToRole, removeFromMailbox, setFlagged, setRead, } from './mutate.ts';
|
|
25
|
+
export type { MoveOptions } from './mutate.ts';
|
|
26
|
+
export { DEFAULT_MAX_DOWNLOAD_BYTES, downloadAttachment } from './download.ts';
|
|
27
|
+
export type { DownloadOptions } from './download.ts';
|
|
24
28
|
export { push } from './push.ts';
|
|
25
29
|
export type { PushHandle, PushOptions } from './push.ts';
|
|
26
30
|
export { expandTemplate } from './uri.ts';
|
package/dist/mailbox/index.js
CHANGED
|
@@ -13,5 +13,7 @@ export { search } from "./search.js";
|
|
|
13
13
|
export { identities } from "./identities.js";
|
|
14
14
|
export { attribution, formatAddress, forwardDraft, forwardIntroduction, forwardSubject, quoteText, replyDraft, replySubject, } from "./drafts.js";
|
|
15
15
|
export { draftToEmail, send, sentPatch } from "./submit.js";
|
|
16
|
+
export { moveThread, moveToRole, removeFromMailbox, setFlagged, setRead, } from "./mutate.js";
|
|
17
|
+
export { DEFAULT_MAX_DOWNLOAD_BYTES, downloadAttachment } from "./download.js";
|
|
16
18
|
export { push } from "./push.js";
|
|
17
19
|
export { expandTemplate } from "./uri.js";
|