@rapidmx/web-client 0.6.0 → 0.7.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/apps/shared/components/admin/settings/BrandingForm.tsx +398 -385
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -112
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +159 -152
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1288 -1235
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +9 -1
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -290
- package/apps/shared/components/admin/setup/SetupWizard.tsx +443 -416
- package/apps/shared/components/layout/AppShell.tsx +288 -288
- package/apps/shared/components/mail/ConversationList.tsx +225 -33
- package/apps/shared/components/mail/ConversationThreadPane.tsx +434 -216
- package/apps/shared/components/mail/MailListToolbar.tsx +265 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +222 -0
- package/apps/shared/components/mail/MenuButton.tsx +384 -0
- package/apps/shared/components/mail/MessageDetailPane.tsx +280 -215
- package/apps/shared/components/mail/MoveToFolderDialog.tsx +263 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +29 -2
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1700 -1652
- package/apps/shared/components/mail/compose/RecipientInput.tsx +356 -0
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +38 -3
- package/apps/shared/components/mail/compose/quotedBody.ts +100 -0
- package/apps/shared/components/mail/compose/recipients.ts +104 -0
- package/apps/shared/components/mail/labelMenu.tsx +278 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +445 -420
- package/apps/shared/components/mail/listPreferences.ts +228 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +0 -1
- package/apps/shared/styles/app.css +28 -5
- package/apps/www/index.tsx +1854 -1314
- package/apps/www/messages/[uid].tsx +5 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +7 -3
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +6 -5
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +4 -1
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +2 -2
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +4 -1
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +2 -2
- package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +8 -1
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +33 -14
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +4 -1
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +2 -2
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +1 -1
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +14 -10
- package/dist/apps/shared/components/mail/ConversationList.d.ts +42 -6
- package/dist/apps/shared/components/mail/ConversationList.js +84 -10
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +52 -13
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +217 -60
- package/dist/apps/shared/components/mail/MailListToolbar.d.ts +55 -0
- package/dist/apps/shared/components/mail/MailListToolbar.js +149 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +59 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.js +48 -0
- package/dist/apps/shared/components/mail/MenuButton.d.ts +71 -0
- package/dist/apps/shared/components/mail/MenuButton.js +211 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +27 -19
- package/dist/apps/shared/components/mail/MessageDetailPane.js +126 -129
- package/dist/apps/shared/components/mail/MoveToFolderDialog.d.ts +57 -0
- package/dist/apps/shared/components/mail/MoveToFolderDialog.js +138 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +16 -1
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +3 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +52 -20
- package/dist/apps/shared/components/mail/compose/RecipientInput.d.ts +36 -0
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +203 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +10 -1
- package/dist/apps/shared/components/mail/compose/RichTextEditor.js +21 -3
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +37 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +85 -0
- package/dist/apps/shared/components/mail/compose/recipients.d.ts +24 -0
- package/dist/apps/shared/components/mail/compose/recipients.js +87 -0
- package/dist/apps/shared/components/mail/labelMenu.d.ts +97 -0
- package/dist/apps/shared/components/mail/labelMenu.js +134 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +8 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +15 -3
- package/dist/apps/shared/components/mail/listPreferences.d.ts +101 -0
- package/dist/apps/shared/components/mail/listPreferences.js +186 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +0 -1
- package/dist/apps/shared/styles/app.css +28 -5
- package/dist/apps/www/index.js +506 -112
- package/dist/apps/www/messages/[uid].js +2 -3
- package/package.json +2 -2
- package/apps/www/settings/focused-inbox/index.tsx +0 -150
- package/dist/apps/www/settings/focused-inbox/index.d.ts +0 -4
- package/dist/apps/www/settings/focused-inbox/index.js +0 -71
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { FormEvent, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { HiOutlineFolder, HiOutlineFolderPlus } from "react-icons/hi2";
|
|
7
|
+
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
+
import { Folder, createFolder } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
10
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
11
|
+
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The folder types a message can be moved *into* - Outbox is a transient send queue the server owns, and
|
|
15
|
+
* the non-mail folders (`calendar`/`contacts`/`tasks`/`notes`) aren't message folders at all, so neither is
|
|
16
|
+
* offered. Shared by the reading pane's Move to and select mode's bulk Move to, which is the whole point of
|
|
17
|
+
* this component: one destination list, one set of rules.
|
|
18
|
+
*/
|
|
19
|
+
export const MOVE_TARGET_TYPES = new Set(["inbox", "drafts", "sent_items", "junk", "archive", "deleted_items", "user"]);
|
|
20
|
+
|
|
21
|
+
/** Above this many destinations the dialog offers a filter box - below it, one is just another thing to
|
|
22
|
+
* skip past on the way to a list that already fits on screen. */
|
|
23
|
+
export const FOLDER_FILTER_THRESHOLD = 8;
|
|
24
|
+
|
|
25
|
+
/** What `@rapidmx/restapi` accepts as a folder name, so a name it would refuse is refused here, next to the
|
|
26
|
+
* field, rather than as a 400 after a round trip. */
|
|
27
|
+
export const MAX_FOLDER_NAME_LENGTH = 255;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Why a name can't be used, or `null` when it can. Folders are a real tree (`Folder.parentFolderUid`), not
|
|
31
|
+
* paths, so a separator in a name would be a character in the name rather than a nesting instruction -
|
|
32
|
+
* which is exactly why it is refused: it would read as a hierarchy this app never created.
|
|
33
|
+
*/
|
|
34
|
+
export function folderNameError(name: string, existing: Folder[]): string | null {
|
|
35
|
+
const trimmed = name.trim();
|
|
36
|
+
if (!trimmed) {
|
|
37
|
+
return "Enter a name for the new folder.";
|
|
38
|
+
}
|
|
39
|
+
if (trimmed.length > MAX_FOLDER_NAME_LENGTH) {
|
|
40
|
+
return `A folder name can be at most ${MAX_FOLDER_NAME_LENGTH} characters.`;
|
|
41
|
+
}
|
|
42
|
+
if (/[/\\]/.test(trimmed)) {
|
|
43
|
+
return "A folder name can't contain / or \\.";
|
|
44
|
+
}
|
|
45
|
+
const clash = existing.find((folder) => folder.name.toLowerCase() === trimmed.toLowerCase());
|
|
46
|
+
if (clash) {
|
|
47
|
+
return `This mailbox already has a folder called "${clash.name}". Pick it from the list instead.`;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MoveToFolderDialogProps {
|
|
53
|
+
open: boolean;
|
|
54
|
+
onClose: () => void;
|
|
55
|
+
/** The mailbox whose folders are offered, and the one a new folder is created in. */
|
|
56
|
+
mailboxUid: string;
|
|
57
|
+
/** Every folder of that mailbox - filtered to the movable types here, so callers hand over the list
|
|
58
|
+
* they already hold rather than pre-filtering it each time. */
|
|
59
|
+
folders: Folder[];
|
|
60
|
+
/** Where the message(s) are now: offered, but disabled, so the list doesn't change shape per folder. */
|
|
61
|
+
currentFolderUid?: string;
|
|
62
|
+
/** What is being moved, for the dialog's own title ("Move 3 messages to"). Defaults to one message. */
|
|
63
|
+
count?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Performs the move, resolving once it has settled. A rejection is shown in this dialog, beside the
|
|
66
|
+
* destination that would retry it; the dialog closes on success. The bulk caller resolves either way
|
|
67
|
+
* and reports its own outcome in the selection bar, which is where a partly-applied bulk update has to
|
|
68
|
+
* be explained (see `runBulkAction()`).
|
|
69
|
+
*/
|
|
70
|
+
onMove: (folderUid: string) => Promise<void>;
|
|
71
|
+
/** A folder this dialog created, so the caller's own folder list - and the sidebar - pick it up without
|
|
72
|
+
* a page load. Called before the move, so a failed move still leaves the folder that was created. */
|
|
73
|
+
onFolderCreated?: (folder: Folder) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Outlook's "Move to" prompt: pick a destination folder for the open message or the selection, or create a
|
|
78
|
+
* folder and move into it in one step.
|
|
79
|
+
*
|
|
80
|
+
* A `Modal` rather than a `MenuButton` because creating a folder needs a text field, and a `role="menu"`
|
|
81
|
+
* has nowhere to put one (the same reason `NewLabelDialog` exists) - and because a destination list can be
|
|
82
|
+
* as long as the mailbox has folders, which wants a filter box rather than a scrolling popup.
|
|
83
|
+
*
|
|
84
|
+
* A new folder is created at the **top level of the mailbox**, typed `user`, never as a child of the folder
|
|
85
|
+
* being moved out of: this app's sidebar lists folders flat (`MailShell`'s own `FOLDER_ORDER`), so a
|
|
86
|
+
* subfolder would be created into a hierarchy nothing here renders.
|
|
87
|
+
*/
|
|
88
|
+
export default function MoveToFolderDialog({
|
|
89
|
+
open,
|
|
90
|
+
onClose,
|
|
91
|
+
mailboxUid,
|
|
92
|
+
folders,
|
|
93
|
+
currentFolderUid,
|
|
94
|
+
count = 1,
|
|
95
|
+
onMove,
|
|
96
|
+
onFolderCreated,
|
|
97
|
+
}: MoveToFolderDialogProps) {
|
|
98
|
+
const [filter, setFilter] = useState("");
|
|
99
|
+
const [creating, setCreating] = useState(false);
|
|
100
|
+
const [newName, setNewName] = useState("");
|
|
101
|
+
const [busy, setBusy] = useState(false);
|
|
102
|
+
const [error, setError] = useState<string | null>(null);
|
|
103
|
+
const nameRef = useRef<HTMLInputElement | null>(null);
|
|
104
|
+
|
|
105
|
+
// Every opening starts from the same place, so a filter or a half-typed folder name from last time is
|
|
106
|
+
// never what the next move is made against.
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (open) {
|
|
109
|
+
setFilter("");
|
|
110
|
+
setCreating(false);
|
|
111
|
+
setNewName("");
|
|
112
|
+
setBusy(false);
|
|
113
|
+
setError(null);
|
|
114
|
+
}
|
|
115
|
+
}, [open]);
|
|
116
|
+
|
|
117
|
+
// The field is only mounted once "New folder" is chosen, so focus moves to it when it appears.
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (creating) {
|
|
120
|
+
nameRef.current?.focus();
|
|
121
|
+
}
|
|
122
|
+
}, [creating]);
|
|
123
|
+
|
|
124
|
+
const targets = folders.filter((folder) => MOVE_TARGET_TYPES.has(folder.type));
|
|
125
|
+
const shown = filter.trim()
|
|
126
|
+
? targets.filter((folder) => folder.name.toLowerCase().includes(filter.trim().toLowerCase()))
|
|
127
|
+
: targets;
|
|
128
|
+
const offerFilter = targets.length > FOLDER_FILTER_THRESHOLD;
|
|
129
|
+
const noun = count === 1 ? "message" : `${count} messages`;
|
|
130
|
+
|
|
131
|
+
async function moveTo(folderUid: string) {
|
|
132
|
+
setBusy(true);
|
|
133
|
+
setError(null);
|
|
134
|
+
try {
|
|
135
|
+
await onMove(folderUid);
|
|
136
|
+
onClose();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
setError(err instanceof ApiRequestError ? err.message : "Could not move to that folder.");
|
|
139
|
+
} finally {
|
|
140
|
+
setBusy(false);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function handleCreate(e: FormEvent) {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
const invalid = folderNameError(newName, folders);
|
|
147
|
+
if (invalid) {
|
|
148
|
+
setError(invalid);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
setBusy(true);
|
|
152
|
+
setError(null);
|
|
153
|
+
let created: Folder;
|
|
154
|
+
try {
|
|
155
|
+
created = await createFolder({ mailboxUid, name: newName.trim(), type: "user" });
|
|
156
|
+
} catch (err) {
|
|
157
|
+
setError(err instanceof ApiRequestError ? err.message : "Could not create that folder.");
|
|
158
|
+
setBusy(false);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Handed over before the move: the folder exists from here on whether or not the move lands, and
|
|
162
|
+
// the reader would otherwise have to create it again to retry.
|
|
163
|
+
onFolderCreated?.(created);
|
|
164
|
+
setBusy(false);
|
|
165
|
+
await moveTo(created.uid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<Modal open={open} onClose={() => !busy && onClose()} title={`Move ${noun} to`}>
|
|
170
|
+
{offerFilter && !creating && (
|
|
171
|
+
<input
|
|
172
|
+
type="search"
|
|
173
|
+
value={filter}
|
|
174
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
175
|
+
aria-label="Filter folders"
|
|
176
|
+
placeholder="Filter folders…"
|
|
177
|
+
className="w-full text-sm px-3 py-1.5 mb-2 rounded-md border border-border bg-surface"
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
{!creating && (
|
|
181
|
+
<ul className="max-h-72 overflow-y-auto border border-border rounded-md divide-y divide-border">
|
|
182
|
+
{shown.map((folder) => {
|
|
183
|
+
const here = folder.uid === currentFolderUid;
|
|
184
|
+
return (
|
|
185
|
+
<li key={folder.uid}>
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={() => void moveTo(folder.uid)}
|
|
189
|
+
disabled={busy || here}
|
|
190
|
+
// Disabled rather than hidden, so the list doesn't change shape as the
|
|
191
|
+
// reader moves between folders.
|
|
192
|
+
title={here ? `Already in ${folder.name}` : undefined}
|
|
193
|
+
className="w-full text-left flex items-center gap-2 px-3 py-2 text-sm hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent"
|
|
194
|
+
>
|
|
195
|
+
<HiOutlineFolder size={16} aria-hidden="true" className="shrink-0 text-text-muted" />
|
|
196
|
+
<span className="truncate">{folder.name}</span>
|
|
197
|
+
{here && <span className="ml-auto text-xs text-text-muted shrink-0">Already here</span>}
|
|
198
|
+
</button>
|
|
199
|
+
</li>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
{shown.length === 0 && (
|
|
203
|
+
<li className="px-3 py-2 text-sm text-text-muted">
|
|
204
|
+
{targets.length === 0 ? "This mailbox has no folders to move to yet." : "No folder matches that."}
|
|
205
|
+
</li>
|
|
206
|
+
)}
|
|
207
|
+
</ul>
|
|
208
|
+
)}
|
|
209
|
+
{creating ? (
|
|
210
|
+
<form onSubmit={handleCreate}>
|
|
211
|
+
<label className="block text-sm font-medium mb-1" htmlFor="move-to-new-folder">
|
|
212
|
+
New folder name
|
|
213
|
+
</label>
|
|
214
|
+
<input
|
|
215
|
+
id="move-to-new-folder"
|
|
216
|
+
ref={nameRef}
|
|
217
|
+
value={newName}
|
|
218
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
219
|
+
maxLength={MAX_FOLDER_NAME_LENGTH}
|
|
220
|
+
className="w-full text-sm px-3 py-1.5 rounded-md border border-border bg-surface"
|
|
221
|
+
/>
|
|
222
|
+
<p className="text-xs text-text-muted mt-1">
|
|
223
|
+
Created at the top level of this mailbox, and the {count === 1 ? "message is" : "messages are"} moved
|
|
224
|
+
into it.
|
|
225
|
+
</p>
|
|
226
|
+
{error && (
|
|
227
|
+
<div className="mt-3">
|
|
228
|
+
<Alert>{error}</Alert>
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
<div className="flex justify-end gap-2 mt-4">
|
|
232
|
+
<Button type="button" variant="secondary" onClick={() => setCreating(false)} disabled={busy}>
|
|
233
|
+
Back
|
|
234
|
+
</Button>
|
|
235
|
+
<Button type="submit" disabled={busy}>
|
|
236
|
+
{busy ? "Creating…" : "Create and move"}
|
|
237
|
+
</Button>
|
|
238
|
+
</div>
|
|
239
|
+
</form>
|
|
240
|
+
) : (
|
|
241
|
+
<>
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={() => {
|
|
245
|
+
setCreating(true);
|
|
246
|
+
setError(null);
|
|
247
|
+
}}
|
|
248
|
+
disabled={busy}
|
|
249
|
+
className="mt-3 inline-flex items-center gap-1.5 text-sm font-medium text-primary-dark hover:underline disabled:opacity-50 disabled:no-underline"
|
|
250
|
+
>
|
|
251
|
+
<HiOutlineFolderPlus size={16} aria-hidden="true" />
|
|
252
|
+
New folder…
|
|
253
|
+
</button>
|
|
254
|
+
{error && (
|
|
255
|
+
<div className="mt-3">
|
|
256
|
+
<Alert>{error}</Alert>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</>
|
|
260
|
+
)}
|
|
261
|
+
</Modal>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import React, { PropsWithChildren, createContext, useContext, useMemo, useState } from "react";
|
|
6
|
+
import { DraftThreading } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
6
7
|
import useIsMobile from "@rapidmx/react-shared/util/useIsMobile.js";
|
|
7
8
|
import ComposeWindow from "./ComposeWindow.js";
|
|
8
9
|
|
|
@@ -14,8 +15,13 @@ export interface ComposeSession {
|
|
|
14
15
|
initialCc?: string;
|
|
15
16
|
initialSubject?: string;
|
|
16
17
|
/** Pre-built HTML (already includes its own quote-attribution wrapper — see `composeQuoting.ts`)
|
|
17
|
-
* inserted below the resolved default signature. Absent for a fresh, non-reply/forward compose.
|
|
18
|
+
* inserted below the resolved default signature. Absent for a fresh, non-reply/forward compose. Its presence
|
|
19
|
+
* is what makes the window open with the caret at the top of the body instead of in To. */
|
|
18
20
|
initialQuotedHtml?: string;
|
|
21
|
+
/** See `OpenComposeInput.encrypt`'s own doc comment. */
|
|
22
|
+
initialEncrypt?: boolean;
|
|
23
|
+
/** See `OpenComposeInput.threading`'s own doc comment - handed to `createDraft()` by the window. */
|
|
24
|
+
threading?: DraftThreading;
|
|
19
25
|
/** Which of a signature's two "default" flags to resolve against — `"new"` (the default) uses
|
|
20
26
|
* `isDefaultForNewMessages`, `"reply_forward"` uses `isDefaultForReplyForward`. */
|
|
21
27
|
signatureContext: "new" | "reply_forward";
|
|
@@ -46,6 +52,15 @@ export interface OpenComposeInput {
|
|
|
46
52
|
* "Mailing lists" note), so the new compose window defaults its Sign toggle off rather than on.
|
|
47
53
|
* The user can still turn it back on manually; this only changes the *default*. */
|
|
48
54
|
suppressSigning?: boolean;
|
|
55
|
+
/** `true` for a reply to or forward of an encrypted message, whose quote may carry its decrypted content: the
|
|
56
|
+
* compose window starts with "Encrypt this message" requested, so it is never autosaved as a plaintext draft and
|
|
57
|
+
* can't be sent unencrypted without the user explicitly choosing to. */
|
|
58
|
+
encrypt?: boolean;
|
|
59
|
+
/** The thread this compose continues (`buildReplyThreading()` over the message being replied to or
|
|
60
|
+
* forwarded), recorded on the draft by `createDraft()`. Without it the message is relayed with no
|
|
61
|
+
* `In-Reply-To`/`References` at all and every mail system - the sender's own Sent Items included - files it
|
|
62
|
+
* as a new conversation rather than part of the thread. Absent for a fresh compose, which starts one. */
|
|
63
|
+
threading?: DraftThreading;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
export interface ComposeContextValue {
|
|
@@ -71,7 +86,17 @@ export default function ComposeProvider({ children, userUid, trusted }: PropsWit
|
|
|
71
86
|
const [sessions, setSessions] = useState<ComposeSession[]>([]);
|
|
72
87
|
const isMobile = useIsMobile();
|
|
73
88
|
|
|
74
|
-
function openCompose({
|
|
89
|
+
function openCompose({
|
|
90
|
+
mailboxUid,
|
|
91
|
+
to,
|
|
92
|
+
cc,
|
|
93
|
+
subject,
|
|
94
|
+
quotedHtml,
|
|
95
|
+
signatureContext = "new",
|
|
96
|
+
suppressSigning,
|
|
97
|
+
encrypt,
|
|
98
|
+
threading,
|
|
99
|
+
}: OpenComposeInput) {
|
|
75
100
|
setSessions((prev) => [
|
|
76
101
|
...prev,
|
|
77
102
|
{
|
|
@@ -81,6 +106,8 @@ export default function ComposeProvider({ children, userUid, trusted }: PropsWit
|
|
|
81
106
|
initialCc: cc,
|
|
82
107
|
initialSubject: subject,
|
|
83
108
|
initialQuotedHtml: quotedHtml,
|
|
109
|
+
initialEncrypt: encrypt,
|
|
110
|
+
threading,
|
|
84
111
|
signatureContext,
|
|
85
112
|
suppressSigning,
|
|
86
113
|
minimized: false,
|