@arbidocs/blocks 0.4.84 → 0.4.85

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 (114) hide show
  1. package/dist/MailBodyEditor-5PIQLYVY.js +217 -0
  2. package/dist/MailBodyEditor-5PIQLYVY.js.map +1 -0
  3. package/dist/MailBodyEditor-D7MGJMLH.cjs +223 -0
  4. package/dist/MailBodyEditor-D7MGJMLH.cjs.map +1 -0
  5. package/dist/agents/index.cjs +281 -280
  6. package/dist/agents/index.js +6 -5
  7. package/dist/agents/workflows/StepBarFrame.d.ts +3 -1
  8. package/dist/agents/workflows/WorkflowRunner.d.ts +10 -1
  9. package/dist/agents/workflows/run.d.ts +24 -3
  10. package/dist/agents/workflows/stepWindow.d.ts +4 -2
  11. package/dist/agents/workflows/types.d.ts +5 -0
  12. package/dist/agents/workflows/useWorkflowView.d.ts +16 -1
  13. package/dist/chat/index.cjs +1 -1
  14. package/dist/chat/index.js +1 -1
  15. package/dist/chunk-3R6XBYL7.cjs +576 -0
  16. package/dist/chunk-3R6XBYL7.cjs.map +1 -0
  17. package/dist/{chunk-CKB64Z4R.js → chunk-4XJZ6AAY.js} +6 -245
  18. package/dist/chunk-4XJZ6AAY.js.map +1 -0
  19. package/dist/{chunk-SB5ZKOYN.js → chunk-7C35WXYB.js} +4 -63
  20. package/dist/chunk-7C35WXYB.js.map +1 -0
  21. package/dist/chunk-7RJTEWYM.js +362 -0
  22. package/dist/chunk-7RJTEWYM.js.map +1 -0
  23. package/dist/chunk-AFSOUS5X.cjs +253 -0
  24. package/dist/chunk-AFSOUS5X.cjs.map +1 -0
  25. package/dist/{chunk-F4RUCUXF.cjs → chunk-C2HRZSAT.cjs} +2 -63
  26. package/dist/chunk-C2HRZSAT.cjs.map +1 -0
  27. package/dist/{chunk-RX35SMPR.js → chunk-FHOZMT5P.js} +7 -6
  28. package/dist/chunk-FHOZMT5P.js.map +1 -0
  29. package/dist/{chunk-CONIA2ZA.js → chunk-KHY4PYU7.js} +246 -146
  30. package/dist/chunk-KHY4PYU7.js.map +1 -0
  31. package/dist/chunk-LGX6UFYG.js +557 -0
  32. package/dist/chunk-LGX6UFYG.js.map +1 -0
  33. package/dist/chunk-O4YPVYPO.cjs +373 -0
  34. package/dist/chunk-O4YPVYPO.cjs.map +1 -0
  35. package/dist/chunk-R26OHONI.js +250 -0
  36. package/dist/chunk-R26OHONI.js.map +1 -0
  37. package/dist/{chunk-RYYYHPFU.cjs → chunk-RZUMQM7W.cjs} +246 -146
  38. package/dist/chunk-RZUMQM7W.cjs.map +1 -0
  39. package/dist/{chunk-YJQVLVBJ.cjs → chunk-S5HZBXZA.cjs} +2 -243
  40. package/dist/chunk-S5HZBXZA.cjs.map +1 -0
  41. package/dist/{chunk-ZQYE7J3J.cjs → chunk-WRSBM6QN.cjs} +332 -94
  42. package/dist/chunk-WRSBM6QN.cjs.map +1 -0
  43. package/dist/{chunk-RSKSXEPW.js → chunk-X23U6QEJ.js} +324 -88
  44. package/dist/chunk-X23U6QEJ.js.map +1 -0
  45. package/dist/{chunk-XA5S4776.cjs → chunk-Z7YUCYI6.cjs} +51 -50
  46. package/dist/chunk-Z7YUCYI6.cjs.map +1 -0
  47. package/dist/collaboration/index.cjs +100 -49
  48. package/dist/collaboration/index.js +4 -1
  49. package/dist/collaboration/mail/MailBodyEditor.d.ts +7 -0
  50. package/dist/collaboration/mail/MailDeliveryList.d.ts +8 -0
  51. package/dist/collaboration/mail/MailDocumentPicker.d.ts +15 -0
  52. package/dist/collaboration/mail/MailGrid.d.ts +17 -0
  53. package/dist/collaboration/mail/MailMessageView.d.ts +27 -0
  54. package/dist/collaboration/mail/PlainMailBodyEditor.d.ts +12 -0
  55. package/dist/collaboration/mail/index.d.ts +6 -0
  56. package/dist/collaboration/mail/mailCompose.d.ts +24 -0
  57. package/dist/connect/index.cjs +1647 -65
  58. package/dist/connect/index.cjs.map +1 -1
  59. package/dist/connect/index.d.ts +2 -1
  60. package/dist/connect/index.js +1631 -55
  61. package/dist/connect/index.js.map +1 -1
  62. package/dist/connection/ConnectedTagTable.d.ts +32 -0
  63. package/dist/connection/mail/ConnectedMailAccounts.d.ts +21 -0
  64. package/dist/connection/mail/ConnectedMailComposer.d.ts +40 -0
  65. package/dist/connection/mail/ConnectedMailSetup.d.ts +16 -0
  66. package/dist/connection/mail/ConnectedMailbox.d.ts +65 -0
  67. package/dist/connection/mail/MailComposerForm.d.ts +25 -0
  68. package/dist/connection/mail/attachments.d.ts +13 -0
  69. package/dist/connection/mail/index.d.ts +6 -0
  70. package/dist/connection/mail/mailbox.d.ts +6 -0
  71. package/dist/connection/mail/types.d.ts +92 -0
  72. package/dist/connection/useConnectedWorkNotifications.d.ts +6 -0
  73. package/dist/connection/useWorkflowThread.d.ts +7 -2
  74. package/dist/facts/index.cjs +2 -2
  75. package/dist/facts/index.js +2 -2
  76. package/dist/files/index.cjs +47 -367
  77. package/dist/files/index.cjs.map +1 -1
  78. package/dist/files/index.js +10 -360
  79. package/dist/files/index.js.map +1 -1
  80. package/dist/index.cjs +450 -391
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.js +21 -18
  83. package/dist/index.js.map +1 -1
  84. package/dist/settings/index.cjs +10 -9
  85. package/dist/settings/index.cjs.map +1 -1
  86. package/dist/settings/index.js +2 -1
  87. package/dist/settings/index.js.map +1 -1
  88. package/dist/shell/index.cjs +1 -1
  89. package/dist/shell/index.js +1 -1
  90. package/dist/tags/TagColumnsEditor.d.ts +34 -0
  91. package/dist/tags/TagDetail.d.ts +13 -11
  92. package/dist/tags/TagTable.d.ts +99 -0
  93. package/dist/tags/TagsNav.d.ts +2 -2
  94. package/dist/tags/index.d.ts +7 -5
  95. package/dist/tags/{tagMatrixDocx.d.ts → tagTableDocx.d.ts} +12 -12
  96. package/dist/tags/tagTableModel.d.ts +47 -0
  97. package/dist/workflow-canvas/index.cjs +49 -48
  98. package/dist/workflow-canvas/index.cjs.map +1 -1
  99. package/dist/workflow-canvas/index.js +6 -5
  100. package/dist/workflow-canvas/index.js.map +1 -1
  101. package/package.json +29 -4
  102. package/dist/chunk-CKB64Z4R.js.map +0 -1
  103. package/dist/chunk-CONIA2ZA.js.map +0 -1
  104. package/dist/chunk-F4RUCUXF.cjs.map +0 -1
  105. package/dist/chunk-RSKSXEPW.js.map +0 -1
  106. package/dist/chunk-RX35SMPR.js.map +0 -1
  107. package/dist/chunk-RYYYHPFU.cjs.map +0 -1
  108. package/dist/chunk-SB5ZKOYN.js.map +0 -1
  109. package/dist/chunk-XA5S4776.cjs.map +0 -1
  110. package/dist/chunk-YJQVLVBJ.cjs.map +0 -1
  111. package/dist/chunk-ZQYE7J3J.cjs.map +0 -1
  112. package/dist/connection/ConnectedTagMatrix.d.ts +0 -23
  113. package/dist/tags/TagMatrix.d.ts +0 -87
  114. package/dist/tags/tagMatrixModel.d.ts +0 -43
@@ -7,30 +7,38 @@ import { ConnectionProvider, useConnection } from '../chunk-OYMWV45N.js';
7
7
  export { ConnectionProvider, SESSION_EXPIRED_MESSAGE, isAuthenticated, useConnection } from '../chunk-OYMWV45N.js';
8
8
  import { SignDocumentDialog } from '../chunk-2YOVK524.js';
9
9
  import '../chunk-OPA2Y6YZ.js';
10
- import { TagMatrix, tagMatrixDocx, DOCX_MIME_TYPE } from '../chunk-RSKSXEPW.js';
11
- import { splitDocRef, WorkflowStepBar, stepLevels, WorkflowRunner, initialRunInputs, workflowSteps, fromPlanSteps, stepStatesFromActivity, toPlanSteps, runThreadTitle, useWorkflowView, stepFields } from '../chunk-CONIA2ZA.js';
10
+ import { FilesFilterBar, FolderFilterButton, FolderPickerRow } from '../chunk-7RJTEWYM.js';
11
+ import { TagTable, tagTableDocx, DOCX_MIME_TYPE } from '../chunk-X23U6QEJ.js';
12
+ import { splitDocRef, WorkflowStepBar, stepLevels, WorkflowRunner, initialRunInputs, workflowSteps, fromPlanSteps, stepStatesFromActivity, toPlanSteps, runThreadTitle, useWorkflowView, stepFields } from '../chunk-KHY4PYU7.js';
13
+ import { TriStateFilterMenu } from '../chunk-R26OHONI.js';
14
+ import { TabHeader } from '../chunk-4WWM3GQ5.js';
12
15
  import '../chunk-EVUEIBNU.js';
16
+ import { FilterInput } from '../chunk-YU2KUMGE.js';
13
17
  import '../chunk-XD2M6LIG.js';
14
- import { StreamedArtifactFrame } from '../chunk-LOJVUGBJ.js';
15
18
  import '../chunk-VILAPTCB.js';
19
+ import { StreamedArtifactFrame } from '../chunk-LOJVUGBJ.js';
16
20
  import { ChatPage } from '../chunk-EWSEQEPL.js';
17
21
  import '../chunk-KZJAG7T3.js';
18
22
  import '../chunk-HOXKR26S.js';
19
23
  import '../chunk-AA3PT6KR.js';
20
24
  import '../chunk-NPLYHR2E.js';
21
25
  import { initials } from '../chunk-OXF2IFXG.js';
26
+ import { PlainMailBodyEditor, draftMailCompose, emptyMailCompose, MailGrid, MailMessageView, replyMailCompose, MailDocumentPicker, parseMailRecipients } from '../chunk-LGX6UFYG.js';
22
27
  import { ModalScaffold } from '../chunk-TX2ZICS7.js';
28
+ import '../chunk-ZPLG7QYF.js';
29
+ import '../chunk-SLSBJRWW.js';
23
30
  import '../chunk-7F6CBP3Z.js';
24
31
  import '../chunk-VUNV25KB.js';
25
32
  import { lazy, useState, Suspense, useEffect, useRef, useCallback, useMemo } from 'react';
26
33
  import { QueryClient, QueryClientProvider, useQueryClient, useQuery } from '@tanstack/react-query';
27
- import { ArbiProvider, useConversations, useRunConversation, useUploadSkillFiles, ArtifactCanvas, useArbiChat, useArbi, useDocuments, useTagMatrix, useUpdateTagFields, useInvalidateTagData, useWorkspaceUsers, useWorkspaces, useShellWorkspaces, useProfile, useUpdateProfile, useRequestEmailChange, useConfirmEmailChange, useNameEditor, useAvatarCrop, useEmailChange, ImageCropModal, useIdentityVerification, usePasswordChange, usePasswordChangeForm, useNotificationPreferences, useSessions, useCurrentSessionId, useTerminateSession, useLogoutAllSessions, useDeleteAccount, useDeleteAccountFlow, useArbiWebSocket, notify, useWorkNotifications, useAgentSession, useStartAgentSession, useCloseAgentSession, ArbiWebSocketProvider, useCreateWork, useActOnStep, useConversationActivity, useConversationThreads, useTags, AiAnswer } from '@arbidocs/react';
34
+ import { ArbiProvider, useConversations, useRunConversation, useUploadSkillFiles, ArtifactCanvas, useArbiChat, useArbi, useDocuments, useTagKindSchema, useTags, useSetTagValues, useUpdateTagKindSchema, useInvalidateTagData, useWorkspaceUsers, useWorkspaces, useShellWorkspaces, useProfile, useUpdateProfile, useRequestEmailChange, useConfirmEmailChange, useNameEditor, useAvatarCrop, useEmailChange, ImageCropModal, useIdentityVerification, usePasswordChange, usePasswordChangeForm, useNotificationPreferences, useSessions, useCurrentSessionId, useTerminateSession, useLogoutAllSessions, useDeleteAccount, useDeleteAccountFlow, useArbiWebSocket, notify, useWorkNotifications, mailDraftIdFromContent, useMailClient, defaultMailAlias, useClaimNativeAlias, useMailOnline, useConnectMailAccount, useDisconnectMailAccount, useMailPolicy, useMailAccounts, useMailUnlock, useMailFolders, useMailMessages, useMailDraftCompose, useMailCache, useMailMessage, MailOfflineUnavailableError, accountPath, useAgentSession, useStartAgentSession, useCloseAgentSession, isAliasTakenError, nextAliasSuggestion, aliasTakenMessage, mailJson, ArbiWebSocketProvider, useCreateWork, useActOnStep, useConversationActivity, useConversationThreads, fileAttachment, useSaveMailDraft, useSendMail, AiAnswer } from '@arbidocs/react';
28
35
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
29
- import { Button, Label, Input, cn } from '@arbidocs/react/ui';
36
+ import { Button, Label, Input, Dialog, DialogContent, DialogTitle, DialogDescription, Tooltip, TooltipTrigger, TooltipContent, cn } from '@arbidocs/react/ui';
30
37
  import { useStreamedArtifact } from '@arbidocs/react/artifacts';
31
- import { ExternalLink, UserCircle, KeyRound, Radio, Loader2, MessageSquare } from 'lucide-react';
38
+ import { ExternalLink, UserCircle, KeyRound, Radio, Mail, Settings, Plus, ArrowLeft, Loader2, X, Paperclip, Send, MessageSquare } from 'lucide-react';
32
39
  import { useOptionalDocumentTools, fetchDocumentPdf, DocumentToolsProvider } from '@arbidocs/react/document-tools';
33
40
  import { useNotify } from '@arbidocs/react/shell';
41
+ import { bytesToBase64 } from '@arbidocs/client';
34
42
 
35
43
  function ConnectionBoundWebSocket({ children }) {
36
44
  const { status } = useConnection();
@@ -292,6 +300,43 @@ function useWorkflowThread(workflow, options = {}) {
292
300
  },
293
301
  [actOnStep, refresh]
294
302
  );
303
+ const createThread = useCallback(
304
+ async (values, done) => {
305
+ await ensureWorkspaceOpen();
306
+ const created = await createWork.mutateAsync({
307
+ title: runThreadTitle(workflow, values).slice(0, 60),
308
+ description: workflow.description || null,
309
+ prompt: null,
310
+ skill_ref: null,
311
+ skill_inputs: values,
312
+ agent_ext_id: options.agentId ?? null,
313
+ auth_mode: "run_now_temp",
314
+ plan: {
315
+ goal: "",
316
+ criteria: [],
317
+ max_passes: 3,
318
+ documents: [],
319
+ steps: toPlanSteps(workflow, {
320
+ inputs: values,
321
+ resolveWorkflow: options.resolveWorkflow,
322
+ compose: options.compose
323
+ })
324
+ }
325
+ });
326
+ const id = created.external_id;
327
+ setThreadId(id);
328
+ if (done) setLocalStates((s) => ({ ...s, [done]: { status: "done" } }));
329
+ options.onStarted?.(id);
330
+ for (const [stepId, st] of Object.entries(localStates))
331
+ if (st.status === "skipped")
332
+ await actOnStep.mutateAsync({
333
+ conversationId: id,
334
+ body: { step: stepId, action: "skip" }
335
+ });
336
+ return id;
337
+ },
338
+ [ensureWorkspaceOpen, createWork, workflow, options, localStates, actOnStep]
339
+ );
295
340
  const complete = useCallback(
296
341
  async (step) => {
297
342
  let values = inputs;
@@ -301,34 +346,7 @@ function useWorkflowThread(workflow, options = {}) {
301
346
  setLocalInputs(values);
302
347
  setEdits({});
303
348
  }
304
- let id = threadId;
305
- if (!id) {
306
- await ensureWorkspaceOpen();
307
- const created = await createWork.mutateAsync({
308
- title: runThreadTitle(workflow, values).slice(0, 60),
309
- description: workflow.description || null,
310
- prompt: null,
311
- skill_ref: null,
312
- skill_inputs: values,
313
- agent_ext_id: options.agentId ?? null,
314
- auth_mode: "run_now_temp",
315
- plan: {
316
- goal: "",
317
- criteria: [],
318
- max_passes: 3,
319
- documents: [],
320
- steps: toPlanSteps(workflow, {
321
- inputs: values,
322
- resolveWorkflow: options.resolveWorkflow,
323
- compose: options.compose
324
- })
325
- }
326
- });
327
- id = created.external_id;
328
- setThreadId(id);
329
- setLocalStates((s) => ({ ...s, [step.id]: { status: "done" } }));
330
- options.onStarted?.(id);
331
- }
349
+ const id = threadId ?? await createThread(values, step.id);
332
350
  const documents = step.kind === "upload" ? options.documentsFor?.(step.id) : void 0;
333
351
  await act(id, {
334
352
  step: step.id,
@@ -337,8 +355,17 @@ function useWorkflowThread(workflow, options = {}) {
337
355
  ...documents?.length ? { documents } : {}
338
356
  });
339
357
  },
340
- [inputs, options, localStates, threadId, ensureWorkspaceOpen, createWork, workflow, act]
358
+ [inputs, options, localStates, threadId, createThread, act]
341
359
  );
360
+ const start = useCallback(async () => {
361
+ if (!threadId) {
362
+ await createThread(inputs);
363
+ await refresh();
364
+ return;
365
+ }
366
+ await arbi.conversations.runWithAuth(threadId);
367
+ await refresh();
368
+ }, [threadId, createThread, inputs, arbi, refresh]);
342
369
  const skip = useCallback(
343
370
  async (stepId) => {
344
371
  if (!threadId) {
@@ -372,6 +399,10 @@ function useWorkflowThread(workflow, options = {}) {
372
399
  [threadId, act]
373
400
  );
374
401
  const loaded = !options.conversationId || activity.isSuccess || activity.isError;
402
+ const idle = !!conversation && !conversation.in_progress;
403
+ const anyRunning = Object.values(states).some((s) => s.status === "running");
404
+ const needsStart = !threadId || idle && !anyRunning;
405
+ const runError = idle ? conversation?.last_error ?? null : null;
375
406
  const view = useWorkflowView({
376
407
  steps,
377
408
  states,
@@ -380,6 +411,9 @@ function useWorkflowThread(workflow, options = {}) {
380
411
  complete,
381
412
  skip,
382
413
  retry,
414
+ start,
415
+ needsStart,
416
+ runError,
383
417
  sendBack,
384
418
  decline,
385
419
  nameOf,
@@ -988,7 +1022,17 @@ function ReviewNotify({
988
1022
  ] })
989
1023
  ] });
990
1024
  }
991
- function ConnectedTagMatrix({
1025
+ var FLAT_KINDS = ["issue", "person", "organisation"];
1026
+ var KIND_TITLES = {
1027
+ label: "Labels",
1028
+ annotation: "Annotations",
1029
+ search: "Searches",
1030
+ issue: "Issues",
1031
+ person: "People",
1032
+ organisation: "Organisations"
1033
+ };
1034
+ function ConnectedTagTable({
1035
+ kind,
992
1036
  parentId,
993
1037
  linksLimit = 3,
994
1038
  exportTitle,
@@ -999,8 +1043,16 @@ function ConnectedTagMatrix({
999
1043
  }) {
1000
1044
  const arbi = useArbi();
1001
1045
  const { workspaceId, status, ensureWorkspaceOpen } = useConnection();
1002
- const matrix = useTagMatrix({ parentId }, { linksLimit });
1003
- const update = useUpdateTagFields();
1046
+ const schema = useTagKindSchema(kind);
1047
+ const rows = useTags(workspaceId ?? void 0, {
1048
+ kind,
1049
+ ...parentId ? { parentId } : {},
1050
+ include: ["links"],
1051
+ linksLimit
1052
+ });
1053
+ const canAddRows = !readOnly && kind !== "annotation" && kind !== "search";
1054
+ const setValues = useSetTagValues();
1055
+ const updateSchema = useUpdateTagKindSchema();
1004
1056
  const invalidate = useInvalidateTagData();
1005
1057
  const members = useWorkspaceUsers(workspaceId ?? void 0, {
1006
1058
  enabled: status === "authenticated" && !!workspaceId,
@@ -1011,10 +1063,14 @@ function ConnectedTagMatrix({
1011
1063
  value: m.user.external_id,
1012
1064
  label: [m.user.given_name, m.user.family_name].filter(Boolean).join(" ") || m.user.email
1013
1065
  }));
1066
+ const table = useMemo(
1067
+ () => schema.data && rows.data ? { columns: schema.data.fields, rows: rows.data } : void 0,
1068
+ [schema.data, rows.data]
1069
+ );
1014
1070
  const fail = (e) => setError(e instanceof Error ? e.message : String(e));
1015
1071
  const snapshot = (data) => {
1016
- const title = exportTitle ?? data.parent?.name ?? "Matrix";
1017
- const bytes = tagMatrixDocx(data, {
1072
+ const title = exportTitle ?? KIND_TITLES[kind];
1073
+ const bytes = tagTableDocx(data, {
1018
1074
  title,
1019
1075
  nameOf: (id) => people.find((p) => p.value === id)?.label
1020
1076
  });
@@ -1024,32 +1080,42 @@ function ConnectedTagMatrix({
1024
1080
  };
1025
1081
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1026
1082
  /* @__PURE__ */ jsx(
1027
- TagMatrix,
1083
+ TagTable,
1028
1084
  {
1029
1085
  ...props,
1030
- matrix: matrix.data,
1031
- isLoading: matrix.isLoading,
1086
+ table,
1087
+ isLoading: schema.isLoading || rows.isLoading,
1032
1088
  people,
1033
1089
  onChangeValue: readOnly ? void 0 : async (tagId, fieldId, value) => {
1034
1090
  setError(null);
1035
- await update.mutateAsync({ tagId, values: { [fieldId]: value } }).catch(fail);
1091
+ await setValues.mutateAsync({ tagId, values: { [fieldId]: value } }).catch(fail);
1036
1092
  },
1037
- onAddRow: readOnly ? void 0 : async (name) => {
1093
+ onChangeColumns: readOnly ? void 0 : async (fields) => {
1094
+ setError(null);
1095
+ await ensureWorkspaceOpen();
1096
+ await updateSchema.mutateAsync({ kind, fields }).catch((e) => {
1097
+ fail(e);
1098
+ throw e;
1099
+ });
1100
+ },
1101
+ onAddRow: !canAddRows ? void 0 : async (name) => {
1038
1102
  setError(null);
1039
1103
  try {
1040
1104
  await ensureWorkspaceOpen();
1041
- await arbi.tags.create({ name, parentId });
1105
+ await arbi.tags.create({
1106
+ name,
1107
+ kind,
1108
+ ...parentId && !FLAT_KINDS.includes(kind) ? { parentId } : {}
1109
+ });
1042
1110
  await invalidate();
1043
1111
  } catch (e) {
1044
1112
  fail(e);
1045
1113
  }
1046
1114
  },
1047
- onExport: onExported && matrix.data ? async () => {
1115
+ onExport: onExported && table ? async () => {
1048
1116
  setError(null);
1049
1117
  try {
1050
- const data = matrix.data;
1051
- if (!data) return;
1052
- const { blob, name } = snapshot(data);
1118
+ const { blob, name } = snapshot(table);
1053
1119
  await ensureWorkspaceOpen();
1054
1120
  const uploaded = await arbi.documents.uploadFile(blob, name);
1055
1121
  const id = uploaded?.doc_ext_ids?.[0];
@@ -1058,12 +1124,10 @@ function ConnectedTagMatrix({
1058
1124
  fail(e);
1059
1125
  }
1060
1126
  } : void 0,
1061
- onDownload: downloadable && matrix.data ? () => {
1127
+ onDownload: downloadable && table ? () => {
1062
1128
  setError(null);
1063
1129
  try {
1064
- const data = matrix.data;
1065
- if (!data) return;
1066
- const { blob, name } = snapshot(data);
1130
+ const { blob, name } = snapshot(table);
1067
1131
  const url = URL.createObjectURL(blob);
1068
1132
  const link = document.createElement("a");
1069
1133
  link.href = url;
@@ -1597,6 +1661,14 @@ function mapWorkNotification(note) {
1597
1661
  return null;
1598
1662
  }
1599
1663
  }
1664
+ if (note.type === "mail_draft_ready") {
1665
+ const draftId = mailDraftIdFromContent(note.content);
1666
+ if (!draftId) return null;
1667
+ return {
1668
+ title: `${sender} left a draft email for you to review`,
1669
+ target: { kind: "mail_draft", draftId, mailbox: "native" }
1670
+ };
1671
+ }
1600
1672
  const titles = {
1601
1673
  mention: `${sender} mentioned you in a conversation`,
1602
1674
  workspace_access_request: `${sender} needs workspace access to run a task`,
@@ -1616,6 +1688,1510 @@ function mapWorkNotification(note) {
1616
1688
  }
1617
1689
  };
1618
1690
  }
1691
+ function attachmentPath(accountId, messageId, attachmentId) {
1692
+ return `${accountPath(accountId)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`;
1693
+ }
1694
+ function saveFile(blob, name) {
1695
+ const url = URL.createObjectURL(blob);
1696
+ const link = document.createElement("a");
1697
+ link.href = url;
1698
+ link.download = name;
1699
+ link.click();
1700
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1701
+ }
1702
+ function hydrateMailAttachments(client, account, messageId, attachments, { isOnline, offline }) {
1703
+ return Promise.all(
1704
+ attachments.map(async (a) => {
1705
+ if (a.content_base64 != null) return a;
1706
+ if (!isOnline) {
1707
+ const file = await offline?.loadAttachment(account.id, messageId, a);
1708
+ return file ? { ...a, content_base64: bytesToBase64(new Uint8Array(await file.arrayBuffer())) } : { ...a, content_base64: null };
1709
+ }
1710
+ const response = await client.fetch(attachmentPath(account.id, messageId, a.id));
1711
+ return fileAttachment(new File([await response.blob()], a.name, { type: a.content_type }));
1712
+ })
1713
+ );
1714
+ }
1715
+ function ConnectedMailAccounts({
1716
+ policy,
1717
+ accounts,
1718
+ onClose,
1719
+ embedded = false,
1720
+ user,
1721
+ oauthReturnPath,
1722
+ settings
1723
+ }) {
1724
+ const { client } = useMailClient();
1725
+ const [profileId, setProfileId] = useState(policy.server_profiles[0]?.id ?? "");
1726
+ const [email, setEmail] = useState("");
1727
+ const [username, setUsername] = useState("");
1728
+ const [password, setPassword] = useState("");
1729
+ const [error, setError] = useState("");
1730
+ const [alias, setAlias] = useState(() => defaultMailAlias(user));
1731
+ const native = useClaimNativeAlias(onClose);
1732
+ const isOnline = useMailOnline();
1733
+ const offline = !isOnline;
1734
+ const onNativeError = (err) => {
1735
+ if (!isAliasTakenError(err)) return setError(err.message);
1736
+ const suggestion = nextAliasSuggestion(alias);
1737
+ setAlias(suggestion);
1738
+ setError(aliasTakenMessage(alias, suggestion, policy.native_domain));
1739
+ };
1740
+ const connect = useConnectMailAccount();
1741
+ const disconnect = useDisconnectMailAccount();
1742
+ const authorizeOAuth = client.authorizeOAuth;
1743
+ const oauth = async (vendor) => {
1744
+ if (!authorizeOAuth) return;
1745
+ try {
1746
+ window.location.assign(
1747
+ await authorizeOAuth(
1748
+ vendor,
1749
+ oauthReturnPath ?? `${window.location.pathname}${window.location.search}`
1750
+ )
1751
+ );
1752
+ } catch (err) {
1753
+ setError(err instanceof Error ? err.message : "Could not connect account");
1754
+ }
1755
+ };
1756
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1757
+ offline && /* @__PURE__ */ jsx(
1758
+ "p",
1759
+ {
1760
+ role: "status",
1761
+ className: "mb-4 rounded bg-muted/40 px-3 py-2 text-sm text-muted-foreground",
1762
+ children: "You're offline. Creating, connecting or removing accounts needs a connection."
1763
+ }
1764
+ ),
1765
+ policy.native_enabled && !accounts.some((a) => a.provider === "native") && /* @__PURE__ */ jsxs(
1766
+ "form",
1767
+ {
1768
+ className: "mb-5 grid gap-3",
1769
+ onSubmit: (e) => {
1770
+ e.preventDefault();
1771
+ native.mutate(alias, { onError: onNativeError });
1772
+ },
1773
+ children: [
1774
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-sm", children: [
1775
+ "Choose your email address",
1776
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1777
+ /* @__PURE__ */ jsx(
1778
+ "input",
1779
+ {
1780
+ "aria-label": "Email alias",
1781
+ required: true,
1782
+ pattern: "[a-z0-9][a-z0-9._\\-]{0,62}",
1783
+ className: "min-w-0 flex-1 rounded border bg-background p-2",
1784
+ value: alias,
1785
+ onChange: (e) => setAlias(e.target.value.toLowerCase())
1786
+ }
1787
+ ),
1788
+ /* @__PURE__ */ jsxs("span", { children: [
1789
+ "@",
1790
+ policy.native_domain
1791
+ ] })
1792
+ ] })
1793
+ ] }),
1794
+ /* @__PURE__ */ jsx(Button, { disabled: native.isPending || offline, type: "submit", children: "Create inbox" })
1795
+ ]
1796
+ }
1797
+ ),
1798
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: "Connect an account using the providers enabled by your administrator." }),
1799
+ authorizeOAuth && /* @__PURE__ */ jsx("div", { className: "mb-4 flex flex-wrap gap-2", children: policy.oauth_providers.map((vendor) => /* @__PURE__ */ jsxs(
1800
+ Button,
1801
+ {
1802
+ variant: "outline",
1803
+ disabled: offline,
1804
+ onClick: () => void oauth(vendor),
1805
+ children: [
1806
+ "Connect ",
1807
+ vendor === "microsoft" ? "Outlook" : "Gmail"
1808
+ ]
1809
+ },
1810
+ vendor
1811
+ )) }),
1812
+ accounts.map((account) => /* @__PURE__ */ jsxs(
1813
+ "div",
1814
+ {
1815
+ className: "mb-2 flex items-center justify-between gap-2 rounded border p-3 text-sm",
1816
+ children: [
1817
+ /* @__PURE__ */ jsxs("div", { children: [
1818
+ /* @__PURE__ */ jsx("div", { children: account.email }),
1819
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: account.display_name })
1820
+ ] }),
1821
+ account.profile_id ? /* @__PURE__ */ jsx(
1822
+ Button,
1823
+ {
1824
+ variant: "ghost",
1825
+ disabled: disconnect.isPending || offline,
1826
+ onClick: () => disconnect.mutate(account.id, { onError: (err) => setError(err.message) }),
1827
+ children: "Disconnect"
1828
+ }
1829
+ ) : account.provider !== "native" ? authorizeOAuth && /* @__PURE__ */ jsx(
1830
+ Button,
1831
+ {
1832
+ variant: "ghost",
1833
+ disabled: offline,
1834
+ onClick: () => void oauth(account.provider),
1835
+ children: "Reconnect"
1836
+ }
1837
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Native inbox" })
1838
+ ]
1839
+ },
1840
+ account.id
1841
+ )),
1842
+ policy.server_profiles.length > 0 && /* @__PURE__ */ jsxs(
1843
+ "form",
1844
+ {
1845
+ className: "mt-5 grid gap-3",
1846
+ onSubmit: (event) => {
1847
+ event.preventDefault();
1848
+ setError("");
1849
+ connect.mutate(
1850
+ { profileId, email, username, password },
1851
+ {
1852
+ onSuccess: () => {
1853
+ setPassword("");
1854
+ onClose();
1855
+ },
1856
+ onError: (err) => setError(err.message)
1857
+ }
1858
+ );
1859
+ },
1860
+ children: [
1861
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-sm", children: [
1862
+ "Mail server",
1863
+ /* @__PURE__ */ jsx(
1864
+ "select",
1865
+ {
1866
+ "aria-label": "Mail server",
1867
+ className: "rounded border bg-background p-2",
1868
+ value: profileId,
1869
+ onChange: (e) => setProfileId(e.target.value),
1870
+ children: policy.server_profiles.map((profile) => /* @__PURE__ */ jsx("option", { value: profile.id, children: profile.name }, profile.id))
1871
+ }
1872
+ )
1873
+ ] }),
1874
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-sm", children: [
1875
+ "Email address",
1876
+ /* @__PURE__ */ jsx(
1877
+ "input",
1878
+ {
1879
+ required: true,
1880
+ type: "email",
1881
+ autoComplete: "email",
1882
+ className: "rounded border bg-background p-2",
1883
+ value: email,
1884
+ onChange: (e) => setEmail(e.target.value)
1885
+ }
1886
+ )
1887
+ ] }),
1888
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-sm", children: [
1889
+ "Username",
1890
+ /* @__PURE__ */ jsx(
1891
+ "input",
1892
+ {
1893
+ placeholder: "Same as email address",
1894
+ autoComplete: "username",
1895
+ className: "rounded border bg-background p-2",
1896
+ value: username,
1897
+ onChange: (e) => setUsername(e.target.value)
1898
+ }
1899
+ )
1900
+ ] }),
1901
+ /* @__PURE__ */ jsxs("label", { className: "grid gap-1 text-sm", children: [
1902
+ "Password or app password",
1903
+ /* @__PURE__ */ jsx(
1904
+ "input",
1905
+ {
1906
+ required: true,
1907
+ type: "password",
1908
+ autoComplete: "current-password",
1909
+ className: "rounded border bg-background p-2",
1910
+ value: password,
1911
+ onChange: (e) => setPassword(e.target.value)
1912
+ }
1913
+ )
1914
+ ] }),
1915
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: connect.isPending || offline, children: connect.isPending ? "Connecting\u2026" : "Connect mailbox" })
1916
+ ]
1917
+ }
1918
+ ),
1919
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: error }),
1920
+ accounts.length > 0 && settings
1921
+ ] });
1922
+ if (embedded) return /* @__PURE__ */ jsx("div", { className: "max-w-2xl", children: content });
1923
+ return /* @__PURE__ */ jsx(
1924
+ Dialog,
1925
+ {
1926
+ open: true,
1927
+ onOpenChange: (open) => {
1928
+ if (!open) onClose();
1929
+ },
1930
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90dvh] max-w-lg overflow-auto", children: [
1931
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Mail accounts" }),
1932
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Choose your email address or connect an external mailbox." }),
1933
+ content
1934
+ ] })
1935
+ }
1936
+ );
1937
+ }
1938
+ var MailBodyEditor = lazy(
1939
+ () => import('../MailBodyEditor-5PIQLYVY.js').catch(() => ({
1940
+ default: PlainMailBodyEditor
1941
+ }))
1942
+ );
1943
+ function MailComposerForm({
1944
+ account,
1945
+ initial,
1946
+ maxBytes,
1947
+ ready,
1948
+ drafts,
1949
+ onClose,
1950
+ onOpenDraft,
1951
+ onChange,
1952
+ embedded = false,
1953
+ closeRequestRef,
1954
+ localDraftId: initialLocalDraftId,
1955
+ offline,
1956
+ documents,
1957
+ notify: notify3
1958
+ }) {
1959
+ const { client } = useMailClient();
1960
+ const [to, setTo] = useState(initial.to.map((r) => r.email).join("; "));
1961
+ const [cc, setCc] = useState(initial.cc.map((r) => r.email).join("; "));
1962
+ const [showCc, setShowCc] = useState(initial.cc.length > 0);
1963
+ const [showBcc, setShowBcc] = useState(initial.bcc.length > 0);
1964
+ const [bcc, setBcc] = useState(initial.bcc.map((r) => r.email).join("; "));
1965
+ const [subject, setSubject] = useState(initial.subject);
1966
+ const [text, setText] = useState(initial.text);
1967
+ const [html, setHtml] = useState(initial.html);
1968
+ const [attachments, setAttachments] = useState(initial.attachments);
1969
+ const [draftId, setDraftId] = useState(initial.draft_id);
1970
+ const [error, setError] = useState("");
1971
+ const [dirty, setDirty] = useState(false);
1972
+ const [attaching, setAttaching] = useState(false);
1973
+ const [saved, setSaved] = useState(false);
1974
+ const isOnline = useMailOnline();
1975
+ const [localDraftId, setLocalDraftId] = useState(initialLocalDraftId);
1976
+ const [savedLocally, setSavedLocally] = useState(false);
1977
+ const [secure, setSecure] = useState(initial.secure_delivery ?? false);
1978
+ const securePayload = useRef(null);
1979
+ const requestId = useRef(crypto.randomUUID());
1980
+ const attemptedPayload = useRef(null);
1981
+ const fileInput = useRef(null);
1982
+ const sameSubjectDraft = useMemo(() => {
1983
+ const key = subject.trim().toLowerCase();
1984
+ return key ? drafts.find((d) => d.id !== draftId && d.subject.trim().toLowerCase() === key) : void 0;
1985
+ }, [drafts, draftId, subject]);
1986
+ const body = () => ({
1987
+ ...initial,
1988
+ to: parseMailRecipients(to),
1989
+ cc: parseMailRecipients(cc),
1990
+ bcc: parseMailRecipients(bcc),
1991
+ subject,
1992
+ text,
1993
+ html,
1994
+ attachments,
1995
+ draft_id: draftId,
1996
+ secure_delivery: secure
1997
+ });
1998
+ const saveOnDevice = async (compose) => {
1999
+ const draft = await offline.saveDraft(account.id, compose, localDraftId);
2000
+ setLocalDraftId(draft.id);
2001
+ };
2002
+ const save = useSaveMailDraft(
2003
+ offline && { keep: saveOnDevice, isNetworkFailure: offline.isNetworkFailure }
2004
+ );
2005
+ const queueInOutbox = async (compose) => {
2006
+ await offline.queueSend({
2007
+ account,
2008
+ compose,
2009
+ requestId: requestId.current,
2010
+ secure,
2011
+ securePayload: securePayload.current ?? void 0
2012
+ });
2013
+ if (localDraftId) await offline.deleteDraft(localDraftId);
2014
+ };
2015
+ const send = useSendMail(
2016
+ offline && { keep: queueInOutbox, isNetworkFailure: offline.isNetworkFailure }
2017
+ );
2018
+ const saveDraft = () => save.mutate(
2019
+ { accountId: account.id, compose: body() },
2020
+ {
2021
+ onSuccess: (result) => {
2022
+ setSaved(true);
2023
+ setDirty(false);
2024
+ if ("onDevice" in result) {
2025
+ setSavedLocally(true);
2026
+ return;
2027
+ }
2028
+ setSavedLocally(false);
2029
+ setDraftId(result.id);
2030
+ if (localDraftId) {
2031
+ void offline?.deleteDraft(localDraftId);
2032
+ setLocalDraftId(void 0);
2033
+ }
2034
+ },
2035
+ onError: (err) => setError(err.message)
2036
+ }
2037
+ );
2038
+ const sendMessage = () => {
2039
+ let value = attemptedPayload.current;
2040
+ if (value === null) {
2041
+ value = body();
2042
+ const recipients = [...value.to, ...value.cc, ...value.bcc];
2043
+ const problem = !recipients.length || recipients.length > 100 ? "Enter between 1 and 100 recipients" : recipients.some((r) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.email)) ? "Check the recipient email addresses" : value.attachments.reduce((total, a) => total + a.size, 0) > maxBytes ? "Attachments exceed the email size limit" : null;
2044
+ if (problem) return setError(problem);
2045
+ if (!(offline && !isOnline)) attemptedPayload.current = value;
2046
+ }
2047
+ const sending = account;
2048
+ send.mutate(
2049
+ {
2050
+ accountId: account.id,
2051
+ compose: value,
2052
+ requestId: requestId.current,
2053
+ // Secure delivery prepares its encrypted payload once.
2054
+ secure: secure ? async () => {
2055
+ if (sending?.provider !== "native" || !client.prepareSecureMail)
2056
+ throw new Error("Choose your native inbox to send securely");
2057
+ securePayload.current ??= await client.prepareSecureMail(
2058
+ value,
2059
+ sending,
2060
+ requestId.current
2061
+ );
2062
+ return securePayload.current;
2063
+ } : void 0
2064
+ },
2065
+ {
2066
+ onSuccess: (result) => {
2067
+ if (result && typeof result === "object" && "onDevice" in result) {
2068
+ notify3("Saved to your Outbox", "info", {
2069
+ description: "It will be sent automatically when you're back online."
2070
+ });
2071
+ onClose();
2072
+ return;
2073
+ }
2074
+ notify3("Email queued for delivery", "success");
2075
+ if (localDraftId) void offline?.deleteDraft(localDraftId);
2076
+ onClose();
2077
+ },
2078
+ onError: (err) => setError(err.message)
2079
+ }
2080
+ );
2081
+ };
2082
+ const busy = attaching || save.isPending || send.isPending;
2083
+ const locked = busy || attemptedPayload.current !== null;
2084
+ const addFiles = async (files) => {
2085
+ if (!files) return;
2086
+ const selected = Array.from(files);
2087
+ if (attachments.reduce((n, a) => n + a.size, 0) + selected.reduce((n, f) => n + f.size, 0) > maxBytes) {
2088
+ setError(`Attachments must total less than ${Math.round(maxBytes / 1024 / 1024)} MB`);
2089
+ return;
2090
+ }
2091
+ setAttaching(true);
2092
+ try {
2093
+ const added = await Promise.all(selected.map(fileAttachment));
2094
+ setAttachments((previous) => [...previous, ...added]);
2095
+ setSaved(false);
2096
+ setDirty(true);
2097
+ } catch (err) {
2098
+ setError(err instanceof Error ? err.message : "Could not attach files");
2099
+ } finally {
2100
+ setAttaching(false);
2101
+ }
2102
+ };
2103
+ const addDocument = async (documentId) => {
2104
+ if (!documents) return;
2105
+ const used = attachments.reduce((n, a) => n + a.size, 0);
2106
+ setError("");
2107
+ setAttaching(true);
2108
+ try {
2109
+ const added = await documents.toAttachment(documentId, maxBytes - used);
2110
+ setAttachments((previous) => [...previous, added]);
2111
+ setSaved(false);
2112
+ setDirty(true);
2113
+ } catch (err) {
2114
+ setError(err instanceof Error ? err.message : "Could not attach the document");
2115
+ } finally {
2116
+ setAttaching(false);
2117
+ }
2118
+ };
2119
+ const close = () => {
2120
+ if (busy) return;
2121
+ if (!saved && (dirty || !draftId) && (text || subject || to || cc || bcc || attachments.length) && !window.confirm("Close this message without saving your latest changes?"))
2122
+ return;
2123
+ onClose();
2124
+ };
2125
+ useEffect(() => {
2126
+ if (closeRequestRef) closeRequestRef.current = close;
2127
+ });
2128
+ useEffect(() => {
2129
+ onChange?.(body());
2130
+ }, [to, cc, bcc, subject, text, html, attachments, draftId, secure]);
2131
+ const form = /* @__PURE__ */ jsxs(
2132
+ "form",
2133
+ {
2134
+ onKeyDown: (event) => {
2135
+ if (event.key !== "Enter") return;
2136
+ if (event.ctrlKey || event.metaKey) {
2137
+ event.preventDefault();
2138
+ if (ready && !busy) event.currentTarget.requestSubmit();
2139
+ } else if (event.target instanceof HTMLInputElement) {
2140
+ event.preventDefault();
2141
+ }
2142
+ },
2143
+ onSubmit: (e) => {
2144
+ e.preventDefault();
2145
+ setError("");
2146
+ sendMessage();
2147
+ },
2148
+ className: embedded ? "flex h-full min-h-0 w-full flex-col bg-background" : "flex max-h-[95dvh] w-full max-w-3xl flex-col rounded-xl border bg-background shadow-2xl",
2149
+ children: [
2150
+ !embedded && /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b px-5 py-3", children: [
2151
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold", children: "New message" }),
2152
+ /* @__PURE__ */ jsx(
2153
+ Button,
2154
+ {
2155
+ type: "button",
2156
+ variant: "ghost",
2157
+ "aria-label": "Close message",
2158
+ disabled: busy,
2159
+ onClick: close,
2160
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
2161
+ }
2162
+ )
2163
+ ] }),
2164
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-auto px-5 py-3", children: [
2165
+ sameSubjectDraft && /* @__PURE__ */ jsxs(
2166
+ "div",
2167
+ {
2168
+ role: "status",
2169
+ className: "mb-3 flex items-center justify-between gap-3 rounded-md bg-muted/40 px-3 py-2 text-xs text-muted-foreground",
2170
+ children: [
2171
+ "A draft with this subject is already in Drafts.",
2172
+ onOpenDraft && /* @__PURE__ */ jsx(
2173
+ Button,
2174
+ {
2175
+ type: "button",
2176
+ variant: "ghost",
2177
+ className: "h-7 shrink-0 text-xs",
2178
+ onClick: () => onOpenDraft(sameSubjectDraft.id),
2179
+ children: "Open draft"
2180
+ }
2181
+ )
2182
+ ]
2183
+ }
2184
+ ),
2185
+ /* @__PURE__ */ jsxs("p", { className: "mb-2 flex items-center gap-3 border-b border-border/60 pb-3 text-xs text-muted-foreground", children: [
2186
+ /* @__PURE__ */ jsx("span", { className: "w-14 shrink-0", children: "From" }),
2187
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-muted/50 px-2 py-1 text-foreground", children: account?.email ?? "Connect a mailbox to send" })
2188
+ ] }),
2189
+ [
2190
+ ["To", to, setTo],
2191
+ ["Cc", cc, setCc],
2192
+ ["Bcc", bcc, setBcc]
2193
+ ].filter(
2194
+ ([label]) => label === "To" || label === "Cc" && showCc || label === "Bcc" && showBcc
2195
+ ).map(([label, value, setter]) => /* @__PURE__ */ jsxs(
2196
+ "label",
2197
+ {
2198
+ className: "flex items-center gap-3 border-b border-border/60 py-2.5 text-sm focus-within:border-primary",
2199
+ children: [
2200
+ /* @__PURE__ */ jsx("span", { className: "w-14 shrink-0 text-xs text-muted-foreground", children: label }),
2201
+ /* @__PURE__ */ jsx(
2202
+ "input",
2203
+ {
2204
+ "aria-label": label,
2205
+ required: label === "To" && !cc && !bcc,
2206
+ disabled: locked,
2207
+ className: "min-w-0 flex-1 bg-transparent outline-none",
2208
+ value,
2209
+ placeholder: label === "To" ? "Add recipients" : "",
2210
+ onChange: (e) => {
2211
+ setter(e.target.value);
2212
+ setSaved(false);
2213
+ setDirty(true);
2214
+ }
2215
+ }
2216
+ ),
2217
+ label === "To" && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 gap-2", children: [
2218
+ !showCc && /* @__PURE__ */ jsx(
2219
+ "button",
2220
+ {
2221
+ type: "button",
2222
+ disabled: locked,
2223
+ className: "text-xs text-muted-foreground hover:text-primary",
2224
+ onClick: () => setShowCc(true),
2225
+ children: "Cc"
2226
+ }
2227
+ ),
2228
+ !showBcc && /* @__PURE__ */ jsx(
2229
+ "button",
2230
+ {
2231
+ type: "button",
2232
+ disabled: locked,
2233
+ className: "text-xs text-muted-foreground hover:text-primary",
2234
+ onClick: () => setShowBcc(true),
2235
+ children: "Bcc"
2236
+ }
2237
+ )
2238
+ ] })
2239
+ ]
2240
+ },
2241
+ label
2242
+ )),
2243
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-3 border-b border-border/60 py-2.5 text-sm focus-within:border-primary", children: [
2244
+ /* @__PURE__ */ jsx("span", { className: "w-14 shrink-0 text-xs text-muted-foreground", children: "Subject" }),
2245
+ /* @__PURE__ */ jsx(
2246
+ "input",
2247
+ {
2248
+ "aria-label": "Subject",
2249
+ placeholder: "Add a subject",
2250
+ disabled: locked,
2251
+ className: "min-w-0 flex-1 bg-transparent outline-none",
2252
+ value: subject,
2253
+ onChange: (e) => {
2254
+ setSubject(e.target.value);
2255
+ setSaved(false);
2256
+ setDirty(true);
2257
+ }
2258
+ }
2259
+ )
2260
+ ] }),
2261
+ /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("p", { className: "py-4 text-sm text-muted-foreground", children: "Loading editor\u2026" }), children: /* @__PURE__ */ jsx(
2262
+ MailBodyEditor,
2263
+ {
2264
+ text,
2265
+ html,
2266
+ disabled: locked,
2267
+ onChange: (nextText, nextHtml) => {
2268
+ setText(nextText);
2269
+ setHtml(nextHtml);
2270
+ setSaved(false);
2271
+ }
2272
+ }
2273
+ ) }),
2274
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: attachments.map((attachment, index) => /* @__PURE__ */ jsxs(
2275
+ "span",
2276
+ {
2277
+ className: "flex items-center gap-2 rounded-md border border-border/70 bg-muted/30 px-3 py-2 text-xs",
2278
+ children: [
2279
+ /* @__PURE__ */ jsx(Paperclip, { className: "size-3" }),
2280
+ attachment.name,
2281
+ /* @__PURE__ */ jsx(
2282
+ "button",
2283
+ {
2284
+ type: "button",
2285
+ disabled: locked,
2286
+ "aria-label": `Remove ${attachment.name}`,
2287
+ onClick: () => {
2288
+ setAttachments((a) => a.filter((_, i) => i !== index));
2289
+ setSaved(false);
2290
+ setDirty(true);
2291
+ },
2292
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
2293
+ }
2294
+ )
2295
+ ]
2296
+ },
2297
+ `${attachment.name}-${index}`
2298
+ )) }),
2299
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-3 text-sm text-destructive", children: error })
2300
+ ] }),
2301
+ /* @__PURE__ */ jsxs("footer", { className: "flex flex-wrap items-center gap-1 border-t border-border/60 bg-muted/20 px-4 py-2 [&_button]:h-8 [&_button]:text-xs", children: [
2302
+ /* @__PURE__ */ jsx(
2303
+ Button,
2304
+ {
2305
+ type: "button",
2306
+ variant: "outline",
2307
+ disabled: !ready || locked,
2308
+ onClick: () => {
2309
+ setError("");
2310
+ saveDraft();
2311
+ },
2312
+ children: save.isPending ? "Saving\u2026" : "Save draft"
2313
+ }
2314
+ ),
2315
+ /* @__PURE__ */ jsxs(
2316
+ Button,
2317
+ {
2318
+ type: "button",
2319
+ variant: "ghost",
2320
+ disabled: locked,
2321
+ onClick: () => fileInput.current?.click(),
2322
+ children: [
2323
+ /* @__PURE__ */ jsx(Paperclip, { className: "mr-2 size-4" }),
2324
+ "Attach"
2325
+ ]
2326
+ }
2327
+ ),
2328
+ documents && /* @__PURE__ */ jsx(
2329
+ MailDocumentPicker,
2330
+ {
2331
+ documents: documents.documents,
2332
+ disabled: locked,
2333
+ onAttach: (doc) => void addDocument(doc.id)
2334
+ }
2335
+ ),
2336
+ /* @__PURE__ */ jsx(
2337
+ "input",
2338
+ {
2339
+ ref: fileInput,
2340
+ type: "file",
2341
+ multiple: true,
2342
+ hidden: true,
2343
+ onChange: (e) => {
2344
+ void addFiles(e.target.files);
2345
+ e.target.value = "";
2346
+ }
2347
+ }
2348
+ ),
2349
+ saved && /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs text-muted-foreground", children: savedLocally ? "Saved on this device \xB7 syncs to Drafts when online" : "Saved to Drafts" }),
2350
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
2351
+ account?.provider === "native" && client.prepareSecureMail && /* @__PURE__ */ jsxs(Tooltip, { children: [
2352
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, showWithText: true, children: /* @__PURE__ */ jsxs("label", { className: "flex h-8 shrink-0 cursor-pointer items-center gap-1.5 rounded border border-border/70 px-2 text-xs text-primary", children: [
2353
+ /* @__PURE__ */ jsx(
2354
+ "input",
2355
+ {
2356
+ type: "checkbox",
2357
+ className: "size-3.5 accent-primary",
2358
+ checked: secure,
2359
+ disabled: locked,
2360
+ onChange: (e) => setSecure(e.target.checked)
2361
+ }
2362
+ ),
2363
+ "Secure"
2364
+ ] }) }),
2365
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: "The recipient will receive a secure link to access the message through the platform." })
2366
+ ] }),
2367
+ /* @__PURE__ */ jsxs(
2368
+ Button,
2369
+ {
2370
+ type: "submit",
2371
+ className: "h-8 rounded bg-primary px-4 text-primary-foreground shadow-none hover:bg-primary/90",
2372
+ disabled: !ready || busy,
2373
+ children: [
2374
+ /* @__PURE__ */ jsx(Send, { className: "mr-2 size-4" }),
2375
+ send.isPending ? "Sending\u2026" : attemptedPayload.current ? "Check send result" : "Send"
2376
+ ]
2377
+ }
2378
+ )
2379
+ ] }),
2380
+ offline && !isOnline && /* @__PURE__ */ jsx(
2381
+ "p",
2382
+ {
2383
+ className: "w-full text-[11px] text-muted-foreground",
2384
+ "data-testid": "compose-offline-hint",
2385
+ children: "You're offline. Send puts this message in your Outbox; it goes out when you reconnect."
2386
+ }
2387
+ )
2388
+ ] })
2389
+ ]
2390
+ }
2391
+ );
2392
+ if (embedded)
2393
+ return /* @__PURE__ */ jsx("div", { role: "dialog", "aria-label": "Compose email", className: "h-full min-h-0", children: form });
2394
+ return /* @__PURE__ */ jsx(
2395
+ Dialog,
2396
+ {
2397
+ open: true,
2398
+ onOpenChange: (open) => {
2399
+ if (!open && !busy) close();
2400
+ },
2401
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[95dvh] max-w-3xl overflow-auto p-0", children: [
2402
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: "Compose email" }),
2403
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: "Write a message and attach files." }),
2404
+ form
2405
+ ] })
2406
+ }
2407
+ );
2408
+ }
2409
+ var defaultMailNotify = (message, variant, options) => {
2410
+ notify[variant](message, options);
2411
+ };
2412
+ function resolveMailbox(accounts, mailbox) {
2413
+ if (!accounts) return void 0;
2414
+ if (mailbox === "native") return accounts.find((a) => a.provider === "native");
2415
+ return mailbox ? accounts.find((a) => a.id === mailbox) : void 0;
2416
+ }
2417
+ var DEFAULT_MAX_BYTES = 15 * 1024 * 1024;
2418
+ function ConnectedMailComposer({
2419
+ draftId,
2420
+ initial,
2421
+ account: accountProp,
2422
+ mailbox = "native",
2423
+ maxBytes: maxBytesProp,
2424
+ ready: readyProp,
2425
+ drafts: draftsProp,
2426
+ notify: notify3 = defaultMailNotify,
2427
+ offline,
2428
+ ...rest
2429
+ }) {
2430
+ const { client } = useMailClient();
2431
+ const isOnline = useMailOnline();
2432
+ const policy = useMailPolicy();
2433
+ const accounts = useMailAccounts({ enabled: !accountProp && !!policy.data?.enabled });
2434
+ const account = accountProp ?? resolveMailbox(accounts.data, mailbox);
2435
+ const unlock = useMailUnlock(readyProp === void 0 ? account : void 0);
2436
+ const ready = readyProp ?? (!!account && (!!unlock.data || !isOnline));
2437
+ const folders = useMailFolders(account, { enabled: !draftsProp && ready });
2438
+ const draftsFolderId = folders.data?.find((f) => f.role === "drafts")?.id;
2439
+ const draftsList = useMailMessages(account, draftsFolderId, "", null, {
2440
+ enabled: !draftsProp && ready
2441
+ });
2442
+ const draft = useMailDraftCompose(
2443
+ account,
2444
+ draftId,
2445
+ async (from, id) => {
2446
+ const message = await client.getMessage(from, id);
2447
+ return draftMailCompose(
2448
+ message,
2449
+ await hydrateMailAttachments(client, from, message.id, message.attachments, {
2450
+ isOnline,
2451
+ offline
2452
+ })
2453
+ );
2454
+ },
2455
+ { enabled: !initial && ready }
2456
+ );
2457
+ const start = useMemo(
2458
+ () => initial ?? (draftId ? draft.data : emptyMailCompose()),
2459
+ [initial, draftId, draft.data]
2460
+ );
2461
+ if (!start)
2462
+ return draft.error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "p-4 text-sm text-destructive", children: draft.error.message }) : /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Loading draft\u2026" });
2463
+ return /* @__PURE__ */ jsx(
2464
+ MailComposerForm,
2465
+ {
2466
+ account,
2467
+ initial: start,
2468
+ maxBytes: maxBytesProp ?? policy.data?.max_attachment_bytes ?? DEFAULT_MAX_BYTES,
2469
+ ready,
2470
+ drafts: draftsProp ?? draftsList.data?.messages ?? [],
2471
+ notify: notify3,
2472
+ offline,
2473
+ ...rest
2474
+ },
2475
+ `${account?.id}-${draftId ?? ""}`
2476
+ );
2477
+ }
2478
+ function ConnectedMailSetup({
2479
+ policy,
2480
+ user,
2481
+ productName,
2482
+ onClaimed,
2483
+ onConnectExisting
2484
+ }) {
2485
+ const isOnline = useMailOnline();
2486
+ const [alias, setAlias] = useState(() => defaultMailAlias(user));
2487
+ const [error, setError] = useState("");
2488
+ const claim = useClaimNativeAlias(onClaimed);
2489
+ const hasExternal = policy.oauth_providers.length > 0 || policy.server_profiles.length > 0;
2490
+ const onClaimError = (err) => {
2491
+ if (!isAliasTakenError(err)) return setError(err.message);
2492
+ const suggestion = nextAliasSuggestion(alias);
2493
+ setAlias(suggestion);
2494
+ setError(aliasTakenMessage(alias, suggestion, policy.native_domain));
2495
+ };
2496
+ return /* @__PURE__ */ jsxs(
2497
+ "div",
2498
+ {
2499
+ "data-testid": "mail-setup",
2500
+ className: "mx-auto flex max-w-xl flex-col items-center gap-6 px-6 py-16 text-center",
2501
+ children: [
2502
+ /* @__PURE__ */ jsx(Mail, { className: "size-10 text-muted-foreground", "aria-hidden": true }),
2503
+ policy.native_enabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
2504
+ /* @__PURE__ */ jsxs("div", { children: [
2505
+ /* @__PURE__ */ jsxs("h2", { className: "text-xl font-semibold text-foreground", children: [
2506
+ "Set up your ",
2507
+ productName ? `${productName} ` : "",
2508
+ "email"
2509
+ ] }),
2510
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Confirm your address and you are ready to go: you can send and receive email as" })
2511
+ ] }),
2512
+ /* @__PURE__ */ jsxs(
2513
+ "form",
2514
+ {
2515
+ className: "flex w-full flex-col items-center gap-4",
2516
+ onSubmit: (e) => {
2517
+ e.preventDefault();
2518
+ setError("");
2519
+ claim.mutate(alias, { onError: onClaimError });
2520
+ },
2521
+ children: [
2522
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-center gap-2 text-base", children: [
2523
+ /* @__PURE__ */ jsx(
2524
+ Input,
2525
+ {
2526
+ "aria-label": "Email alias",
2527
+ required: true,
2528
+ pattern: "[a-z0-9][a-z0-9._\\-]{0,62}",
2529
+ autoFocus: true,
2530
+ className: "max-w-60 text-right",
2531
+ value: alias,
2532
+ onChange: (e) => setAlias(e.target.value.toLowerCase())
2533
+ }
2534
+ ),
2535
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground", children: [
2536
+ "@",
2537
+ policy.native_domain
2538
+ ] })
2539
+ ] }),
2540
+ /* @__PURE__ */ jsx(
2541
+ Button,
2542
+ {
2543
+ type: "submit",
2544
+ disabled: claim.isPending || !alias || !isOnline,
2545
+ title: isOnline ? void 0 : "Available when you are back online",
2546
+ children: claim.isPending ? "Setting up\u2026" : "Confirm"
2547
+ }
2548
+ )
2549
+ ]
2550
+ }
2551
+ )
2552
+ ] }) : /* @__PURE__ */ jsxs("div", { children: [
2553
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground", children: "Connect your email" }),
2554
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm text-muted-foreground", children: [
2555
+ "Connect an existing account to read and send email",
2556
+ " ",
2557
+ productName ? `from ${productName}` : "here",
2558
+ "."
2559
+ ] })
2560
+ ] }),
2561
+ hasExternal && /* @__PURE__ */ jsx(
2562
+ Button,
2563
+ {
2564
+ variant: policy.native_enabled ? "ghost" : "default",
2565
+ onClick: onConnectExisting,
2566
+ className: policy.native_enabled ? "text-muted-foreground" : void 0,
2567
+ children: policy.native_enabled ? "Connect an existing email account instead" : "Connect an email account"
2568
+ }
2569
+ ),
2570
+ !policy.native_enabled && !hasExternal && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Mail is not set up for this deployment yet. Ask your administrator." }),
2571
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error })
2572
+ ]
2573
+ }
2574
+ );
2575
+ }
2576
+ var NO_DRAFTS = [];
2577
+ var NO_OUTBOX = [];
2578
+ var LOCAL_DRAFT_PREFIX = "local-draft:";
2579
+ var OFFLINE_FOLDERS = [
2580
+ { id: "device:drafts", name: "Drafts", role: "drafts", unread: 0 }
2581
+ ];
2582
+ function localDraftRow(draft) {
2583
+ return {
2584
+ id: LOCAL_DRAFT_PREFIX + draft.id,
2585
+ subject: draft.compose.subject,
2586
+ sender: [],
2587
+ to: draft.compose.to,
2588
+ cc: draft.compose.cc,
2589
+ bcc: draft.compose.bcc,
2590
+ received_at: new Date(draft.updatedAt).toISOString(),
2591
+ preview: draft.error ? `Saved on this device only: ${draft.error}` : "Saved on this device \xB7 syncs to Drafts when online",
2592
+ unread: false,
2593
+ text: "",
2594
+ references: [],
2595
+ attachments: draft.compose.attachments.map((a) => ({ ...a, content_base64: null }))
2596
+ };
2597
+ }
2598
+ function applyQuickFilters(messages, filters) {
2599
+ const included = [...filters].filter(([, s]) => s === "include").map(([k]) => k);
2600
+ const excluded = [...filters].filter(([, s]) => s === "exclude").map(([k]) => k);
2601
+ if (!included.length && !excluded.length) return messages;
2602
+ return messages.filter((m) => {
2603
+ const has = {
2604
+ unread: m.unread,
2605
+ attachments: m.attachments.length > 0,
2606
+ secure: !!m.secure_delivery
2607
+ };
2608
+ if (excluded.some((k) => has[k])) return false;
2609
+ return !included.length || included.some((k) => has[k]);
2610
+ });
2611
+ }
2612
+ function SidePanel({ isOpen, title, onClose, children }) {
2613
+ if (!isOpen) return null;
2614
+ return /* @__PURE__ */ jsxs(
2615
+ "aside",
2616
+ {
2617
+ "aria-label": title,
2618
+ className: "flex min-h-0 w-full max-w-2xl shrink-0 flex-col border-l border-border bg-background",
2619
+ children: [
2620
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-2 border-b px-3 py-2", children: [
2621
+ /* @__PURE__ */ jsx("h2", { className: "truncate text-sm font-semibold", children: title }),
2622
+ /* @__PURE__ */ jsx(
2623
+ Button,
2624
+ {
2625
+ variant: "ghost",
2626
+ size: "sm",
2627
+ "aria-label": "Close",
2628
+ "data-testid": "close-mail-viewer",
2629
+ onClick: onClose,
2630
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
2631
+ }
2632
+ )
2633
+ ] }),
2634
+ /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1", children })
2635
+ ]
2636
+ }
2637
+ );
2638
+ }
2639
+ function ConnectedMailbox({
2640
+ mailbox,
2641
+ draftId,
2642
+ onDraftOpened,
2643
+ folder: requestedFolder,
2644
+ onFolderOpened,
2645
+ composeAttachments,
2646
+ onComposeOpened,
2647
+ onOpenDocument,
2648
+ user,
2649
+ productName,
2650
+ oauthReturnPath,
2651
+ personLabel,
2652
+ offline,
2653
+ documents,
2654
+ saveToWorkspace,
2655
+ renderInvite,
2656
+ renderPanel = SidePanel,
2657
+ onViewerOpenChange,
2658
+ gridState,
2659
+ onGridStateChange,
2660
+ initialMessageId = null,
2661
+ notify: notify3 = defaultMailNotify
2662
+ }) {
2663
+ const cache = useMailCache();
2664
+ const { client } = useMailClient();
2665
+ const [savingToWorkspace, setSavingToWorkspace] = useState(false);
2666
+ const [savePending, setSavePending] = useState(false);
2667
+ const [saveWorkspaceId, setSaveWorkspaceId] = useState("");
2668
+ const { data: policy, error: policyError } = useMailPolicy();
2669
+ const isOnline = useMailOnline();
2670
+ const accounts = useMailAccounts({ enabled: !!policy?.enabled });
2671
+ const [accountId, setAccountId] = useState("");
2672
+ const account = accounts.data?.find((a) => a.id === accountId) ?? resolveMailbox(accounts.data, mailbox) ?? accounts.data?.[0];
2673
+ const unlock = useMailUnlock(account);
2674
+ const [folder, setFolder] = useState("inbox");
2675
+ const [folderMenuOpen, setFolderMenuOpen] = useState(false);
2676
+ const [filterMenuOpen, setFilterMenuOpen] = useState(false);
2677
+ const [query, setQuery] = useState("");
2678
+ const [mailFilters, setMailFilters] = useState(/* @__PURE__ */ new Map());
2679
+ const [cursor, setCursor] = useState(null);
2680
+ const [selected, setSelected] = useState(initialMessageId);
2681
+ const [compose, setCompose] = useState(null);
2682
+ const [composeLocalDraftId, setComposeLocalDraftId] = useState();
2683
+ const [composeSession, setComposeSession] = useState(0);
2684
+ const openCompose = (value, localDraftId) => {
2685
+ setComposeSession((n) => n + 1);
2686
+ setComposeLocalDraftId(localDraftId);
2687
+ setCompose(value);
2688
+ };
2689
+ const composeClose = useRef(null);
2690
+ useEffect(() => {
2691
+ onViewerOpenChange?.(!!selected || !!compose);
2692
+ }, [selected, compose, onViewerOpenChange]);
2693
+ useEffect(() => () => onViewerOpenChange?.(false), [onViewerOpenChange]);
2694
+ const [settings, setSettings] = useState(false);
2695
+ const [justCreated, setJustCreated] = useState(false);
2696
+ const [actionError, setActionError] = useState("");
2697
+ const ready = !!account && (!!unlock.data || !isOnline);
2698
+ useEffect(() => {
2699
+ if (unlock.data) void cache.refreshFolders();
2700
+ }, [unlock.data, cache]);
2701
+ const folders = useMailFolders(account, { enabled: ready });
2702
+ const outbox = offline?.useOutbox() ?? NO_OUTBOX;
2703
+ const outboxRole = offline?.outboxRole;
2704
+ const waitingInOutbox = outbox.filter(
2705
+ (item) => item.account.id === account?.id && item.status !== "sent"
2706
+ ).length;
2707
+ const folderList = useMemo(() => {
2708
+ const base = folders.data ?? (offline && !isOnline && folders.error ? OFFLINE_FOLDERS : void 0);
2709
+ if (!base || !outboxRole) return base;
2710
+ const withCount = (f) => f.role === outboxRole ? { ...f, unread: f.unread + waitingInOutbox } : f;
2711
+ return base.some((f) => f.role === outboxRole) ? base.map(withCount) : [...base, withCount({ id: "device:outbox", name: "Outbox", role: outboxRole, unread: 0 })];
2712
+ }, [folders.data, folders.error, isOnline, waitingInOutbox, offline, outboxRole]);
2713
+ const folderId = folderList?.find((f) => f.role === folder || f.id === folder)?.id ?? folderList?.[0]?.id;
2714
+ const folderRole = folderList?.find((f) => f.id === folderId)?.role ?? folder;
2715
+ const deviceFolder = !!folderId?.startsWith("device:");
2716
+ const list = useMailMessages(account, folderId, query, cursor, {
2717
+ enabled: ready && !deviceFolder,
2718
+ refetchInterval: 6e4
2719
+ });
2720
+ const localDrafts = offline?.useLocalDrafts(account?.id) ?? NO_DRAFTS;
2721
+ const draftsFolderId = folders.data?.find((f) => f.role === "drafts")?.id;
2722
+ const drafts = useMailMessages(account, draftsFolderId, "", null, {
2723
+ enabled: ready && !!compose
2724
+ });
2725
+ const loadedMessages = useMemo(() => {
2726
+ if (folderRole !== "drafts") return list.data?.messages;
2727
+ const words = query.toLowerCase();
2728
+ const mine = localDrafts.map(localDraftRow).filter(
2729
+ (m) => !words || `${m.subject} ${m.to.map((r) => r.email).join(" ")}`.toLowerCase().includes(words)
2730
+ );
2731
+ return [...mine, ...list.data?.messages ?? []];
2732
+ }, [folderRole, list.data, localDrafts, query]);
2733
+ const filteredMessages = useMemo(
2734
+ () => applyQuickFilters(loadedMessages ?? [], mailFilters),
2735
+ [loadedMessages, mailFilters]
2736
+ );
2737
+ const mailFilterOptions = useMemo(
2738
+ () => [
2739
+ {
2740
+ id: "unread",
2741
+ label: "Unread",
2742
+ count: (loadedMessages ?? []).filter((m) => m.unread).length
2743
+ },
2744
+ {
2745
+ id: "attachments",
2746
+ label: "Has attachments",
2747
+ count: (loadedMessages ?? []).filter((m) => m.attachments.length > 0).length
2748
+ },
2749
+ {
2750
+ id: "secure",
2751
+ label: "Secure delivery",
2752
+ count: (loadedMessages ?? []).filter((m) => m.secure_delivery).length
2753
+ }
2754
+ ],
2755
+ [loadedMessages]
2756
+ );
2757
+ const hasActiveFilters = !!query || [...mailFilters.values()].some(Boolean);
2758
+ const detail = useMailMessage(account, selected, { enabled: ready && !compose });
2759
+ const message = detail.data;
2760
+ useEffect(() => {
2761
+ if (!composeAttachments?.length) return;
2762
+ openCompose({ ...emptyMailCompose(), attachments: composeAttachments });
2763
+ onComposeOpened?.();
2764
+ }, [composeAttachments]);
2765
+ useEffect(() => {
2766
+ if (!requestedFolder) return;
2767
+ setFolder(requestedFolder);
2768
+ setCursor(null);
2769
+ setSelected(null);
2770
+ onFolderOpened?.();
2771
+ }, [requestedFolder]);
2772
+ const [pendingDraft, setPendingDraft] = useState(null);
2773
+ useEffect(() => {
2774
+ if (!draftId) return;
2775
+ composeClose.current?.();
2776
+ setFolder("drafts");
2777
+ setCursor(null);
2778
+ setSelected(null);
2779
+ setPendingDraft(draftId);
2780
+ onDraftOpened?.();
2781
+ }, [draftId]);
2782
+ useEffect(() => {
2783
+ if (!pendingDraft || !accounts.data) return;
2784
+ const native = accounts.data.find((a) => a.provider === "native");
2785
+ if (native) setAccountId(native.id);
2786
+ setSelected(pendingDraft);
2787
+ setPendingDraft(null);
2788
+ }, [pendingDraft, accounts.data]);
2789
+ useEffect(() => {
2790
+ const chosen = resolveMailbox(accounts.data, mailbox);
2791
+ if (!chosen) return;
2792
+ setAccountId(chosen.id);
2793
+ }, [mailbox, !!accounts.data]);
2794
+ const patch = (messageId, body) => mailJson(
2795
+ client.fetch,
2796
+ `${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}`,
2797
+ {
2798
+ method: "PATCH",
2799
+ body: JSON.stringify(body)
2800
+ }
2801
+ );
2802
+ const update = async (body) => {
2803
+ if (!selected || !account || !isOnline) return;
2804
+ try {
2805
+ await patch(selected, body);
2806
+ await cache.refreshAll();
2807
+ if (body.folder_id || body.action) setSelected(null);
2808
+ } catch (e) {
2809
+ setActionError(e instanceof Error ? e.message : "Could not update message");
2810
+ }
2811
+ };
2812
+ const download = async (path, name) => {
2813
+ try {
2814
+ const response = await client.fetch(path);
2815
+ saveFile(await response.blob(), name);
2816
+ } catch (e) {
2817
+ setActionError(e instanceof Error ? e.message : "Download failed");
2818
+ }
2819
+ };
2820
+ const downloadAttachment = async (a) => {
2821
+ if (!message || !account) return;
2822
+ if (isOnline) {
2823
+ await download(attachmentPath(account.id, message.id, a.id), a.name);
2824
+ return;
2825
+ }
2826
+ const file = await offline?.loadAttachment(account.id, message.id, a);
2827
+ if (!file) {
2828
+ setActionError(`${a.name} isn't saved on this device. Open it when you're back online.`);
2829
+ return;
2830
+ }
2831
+ saveFile(file, a.name);
2832
+ };
2833
+ const saveEmail = async () => {
2834
+ if (!message || !account || !saveWorkspaceId || !saveToWorkspace) return;
2835
+ setSavePending(true);
2836
+ try {
2837
+ const response = await client.fetch(
2838
+ `${accountPath(account.id)}/messages/${message.id}/original`
2839
+ );
2840
+ const fileName = `${message.subject.replace(/[^a-zA-Z0-9 ._-]/g, "").slice(0, 80) || "email"}.eml`;
2841
+ const workspaceId = saveWorkspaceId;
2842
+ const documentId = await saveToWorkspace.save(
2843
+ workspaceId,
2844
+ new File([await response.blob()], fileName, { type: "message/rfc822" })
2845
+ );
2846
+ setSavingToWorkspace(false);
2847
+ notify3(
2848
+ "Email saved to workspace",
2849
+ "success",
2850
+ documentId && onOpenDocument ? { action: { label: "Open", onClick: () => onOpenDocument(documentId, workspaceId) } } : void 0
2851
+ );
2852
+ } catch (e) {
2853
+ setActionError(e instanceof Error ? e.message : "Could not save email to workspace");
2854
+ } finally {
2855
+ setSavePending(false);
2856
+ }
2857
+ };
2858
+ const hydrate = (attachments) => message && account ? hydrateMailAttachments(client, account, message.id, attachments, { isOnline, offline }) : Promise.resolve([]);
2859
+ const editDraft = async () => {
2860
+ if (!message) return;
2861
+ try {
2862
+ openCompose(draftMailCompose(message, await hydrate(message.attachments)));
2863
+ } catch (e) {
2864
+ setActionError(e instanceof Error ? e.message : "Could not load draft attachments");
2865
+ }
2866
+ };
2867
+ const reply = async (forward = false, all = false) => {
2868
+ if (!message || !account) return;
2869
+ try {
2870
+ const attachments = forward ? await hydrate(message.attachments) : [];
2871
+ openCompose(replyMailCompose(message, { account, folderRole, forward, all, attachments }));
2872
+ } catch (e) {
2873
+ setActionError(e instanceof Error ? e.message : "Could not prepare forwarded attachments");
2874
+ }
2875
+ };
2876
+ const failures = [policyError, accounts.error, unlock.error, folders.error, list.error];
2877
+ const offlineNotice = failures.find((e) => e instanceof MailOfflineUnavailableError)?.message;
2878
+ const error = actionError || failures.find((e) => e && !(e instanceof MailOfflineUnavailableError))?.message || (detail.error instanceof MailOfflineUnavailableError ? void 0 : detail.error?.message);
2879
+ return /* @__PURE__ */ jsxs("section", { className: "flex h-full min-h-0 flex-col bg-background", "aria-label": "Mail", children: [
2880
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 bg-background", children: [
2881
+ /* @__PURE__ */ jsx(
2882
+ TabHeader,
2883
+ {
2884
+ testId: "mail-tab-header",
2885
+ tabs: [
2886
+ {
2887
+ id: "mail",
2888
+ // Icon + count only, like the Files, Facts and Threads headers.
2889
+ label: "",
2890
+ count: filteredMessages.length,
2891
+ countIcon: /* @__PURE__ */ jsx(Mail, { className: "size-3.5" }),
2892
+ countPlacement: "leading",
2893
+ presentation: "count",
2894
+ hasActiveFilters,
2895
+ testId: "mail-tab"
2896
+ }
2897
+ ],
2898
+ activeTabId: "mail",
2899
+ onTabChange: () => {
2900
+ },
2901
+ compactBreakpoint: 360,
2902
+ firstTabContent: account ? /* @__PURE__ */ jsxs(FilesFilterBar, { children: [
2903
+ /* @__PURE__ */ jsx(
2904
+ FilterInput,
2905
+ {
2906
+ value: query,
2907
+ onChange: (q) => {
2908
+ setQuery(q);
2909
+ setCursor(null);
2910
+ },
2911
+ hideFieldPicker: true,
2912
+ fields: ["text"],
2913
+ onFieldsChange: () => {
2914
+ },
2915
+ ariaLabel: "Filter mail",
2916
+ labels: { placeholder: "Filter..." },
2917
+ testIds: { root: "mail-search", input: "mail-filter-input" },
2918
+ className: "min-w-28 flex-1 sm:max-w-72",
2919
+ filter: /* @__PURE__ */ jsx(
2920
+ TriStateFilterMenu,
2921
+ {
2922
+ testId: "mail-bar-filter-button",
2923
+ open: filterMenuOpen,
2924
+ onOpenChange: setFilterMenuOpen,
2925
+ filterStates: mailFilters,
2926
+ onFiltersChange: setMailFilters,
2927
+ options: mailFilterOptions,
2928
+ groups: [{ label: "Messages", options: ["unread", "attachments", "secure"] }],
2929
+ renderChevron: true,
2930
+ disableHoverOpen: true,
2931
+ triggerClassName: "inline-flex items-center gap-0.5 h-6 rounded px-1 text-muted-foreground hover:bg-foreground/10 cursor-pointer transition-colors pointer-coarse:h-11! pointer-coarse:min-w-11 pointer-coarse:justify-center"
2932
+ }
2933
+ )
2934
+ }
2935
+ ),
2936
+ folderList && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: /* @__PURE__ */ jsx(
2937
+ FolderFilterButton,
2938
+ {
2939
+ label: folderList.find((f) => f.id === folderId)?.name ?? "Folders",
2940
+ isActive: false,
2941
+ open: folderMenuOpen,
2942
+ onOpenChange: setFolderMenuOpen,
2943
+ testId: "mail-folder-button",
2944
+ children: folderList.map((item) => /* @__PURE__ */ jsx(
2945
+ FolderPickerRow,
2946
+ {
2947
+ label: item.name,
2948
+ count: item.unread > 0 ? item.unread : void 0,
2949
+ depth: 0,
2950
+ isActive: item.id === folderId,
2951
+ onClick: () => {
2952
+ setFolder(item.id);
2953
+ setCursor(null);
2954
+ setSelected(null);
2955
+ setFolderMenuOpen(false);
2956
+ }
2957
+ },
2958
+ item.id
2959
+ ))
2960
+ }
2961
+ ) })
2962
+ ] }) : void 0,
2963
+ rightContent: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
2964
+ account && /* @__PURE__ */ jsx(
2965
+ Button,
2966
+ {
2967
+ size: "sm",
2968
+ variant: "ghost",
2969
+ "aria-label": "Mail settings",
2970
+ "data-testid": "mail-settings-button",
2971
+ onClick: () => setSettings(true),
2972
+ children: /* @__PURE__ */ jsx(Settings, { className: "size-4" })
2973
+ }
2974
+ ),
2975
+ /* @__PURE__ */ jsxs(
2976
+ Button,
2977
+ {
2978
+ size: "sm",
2979
+ disabled: !ready || !!compose,
2980
+ onClick: () => openCompose(emptyMailCompose()),
2981
+ children: [
2982
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 size-4" }),
2983
+ "Email"
2984
+ ]
2985
+ }
2986
+ )
2987
+ ] })
2988
+ }
2989
+ ),
2990
+ (accounts.data?.length ?? 0) > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-1", children: /* @__PURE__ */ jsx(
2991
+ "select",
2992
+ {
2993
+ "aria-label": "Mail account",
2994
+ disabled: !!compose,
2995
+ className: "min-w-0 max-w-64 rounded border-0 bg-transparent px-2 py-1.5 text-xs text-muted-foreground",
2996
+ value: account?.id ?? "",
2997
+ onChange: (e) => {
2998
+ setAccountId(e.target.value);
2999
+ setFolder("inbox");
3000
+ setCursor(null);
3001
+ setSelected(null);
3002
+ },
3003
+ children: accounts.data?.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.email }, a.id))
3004
+ }
3005
+ ) })
3006
+ ] }),
3007
+ account && offline?.renderStatus?.(),
3008
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "border-b p-3 text-sm text-destructive", children: error }),
3009
+ !error && offlineNotice && /* @__PURE__ */ jsx("p", { role: "status", className: "border-b p-3 text-sm text-muted-foreground", children: offlineNotice }),
3010
+ policy && !policy.enabled && /* @__PURE__ */ jsx("p", { className: "p-6 text-muted-foreground", children: "Mail is not enabled on this deployment." }),
3011
+ policy?.enabled && accounts.isSuccess && !account && /* @__PURE__ */ jsx(
3012
+ ConnectedMailSetup,
3013
+ {
3014
+ policy,
3015
+ user,
3016
+ productName,
3017
+ onClaimed: () => setJustCreated(true),
3018
+ onConnectExisting: () => setSettings(true)
3019
+ }
3020
+ ),
3021
+ account && justCreated && /* @__PURE__ */ jsxs(
3022
+ "div",
3023
+ {
3024
+ role: "status",
3025
+ "data-testid": "mail-ready-banner",
3026
+ className: "flex items-start justify-between gap-3 border-b bg-accent/40 px-4 py-3 text-sm",
3027
+ children: [
3028
+ /* @__PURE__ */ jsxs("p", { children: [
3029
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold", children: [
3030
+ "Your ",
3031
+ productName ? `${productName} ` : "",
3032
+ "email is ready to go."
3033
+ ] }),
3034
+ " ",
3035
+ "You can send and receive email as ",
3036
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: account.email }),
3037
+ "."
3038
+ ] }),
3039
+ /* @__PURE__ */ jsx(
3040
+ "button",
3041
+ {
3042
+ type: "button",
3043
+ "aria-label": "Dismiss",
3044
+ className: "text-muted-foreground hover:text-foreground",
3045
+ onClick: () => setJustCreated(false),
3046
+ children: "\xD7"
3047
+ }
3048
+ )
3049
+ ]
3050
+ }
3051
+ ),
3052
+ account && /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1", children: [
3053
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 w-full flex-col", children: [
3054
+ outboxRole && folderRole === outboxRole && offline?.renderOutbox({
3055
+ accountId: account.id,
3056
+ onEdit: (value) => openCompose({ ...value })
3057
+ }),
3058
+ /* @__PURE__ */ jsx("div", { className: deviceFolder ? "hidden" : "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
3059
+ MailGrid,
3060
+ {
3061
+ messages: filteredMessages,
3062
+ loading: list.isPending && list.fetchStatus !== "idle",
3063
+ selected,
3064
+ outgoing: folderRole === "sent" || folderRole === "drafts" || folderRole === outboxRole,
3065
+ personLabel,
3066
+ gridState,
3067
+ onGridStateChange,
3068
+ onSelect: (id) => {
3069
+ if (id.startsWith(LOCAL_DRAFT_PREFIX)) {
3070
+ const draft = localDrafts.find((d) => LOCAL_DRAFT_PREFIX + d.id === id);
3071
+ if (draft) openCompose({ ...draft.compose }, draft.id);
3072
+ return;
3073
+ }
3074
+ setSelected(id);
3075
+ if (isOnline && list.data?.messages.find((m) => m.id === id)?.unread) {
3076
+ void patch(id, { unread: false }).then(() => cache.settleMessage(account.id, id)).catch((error2) => setActionError(error2.message));
3077
+ }
3078
+ }
3079
+ }
3080
+ ) }),
3081
+ (cursor || list.data?.next_cursor) && /* @__PURE__ */ jsxs("footer", { className: "flex gap-2 p-2", children: [
3082
+ cursor && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setCursor(null), children: "First page" }),
3083
+ list.data?.next_cursor && /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setCursor(list.data.next_cursor), children: "Next page" })
3084
+ ] })
3085
+ ] }),
3086
+ renderPanel({
3087
+ isOpen: !!selected || !!compose,
3088
+ title: compose ? "New message" : message?.subject || "Mail",
3089
+ onClose: () => {
3090
+ if (compose) composeClose.current?.();
3091
+ else setSelected(null);
3092
+ },
3093
+ children: compose ? /* @__PURE__ */ jsx(
3094
+ ConnectedMailComposer,
3095
+ {
3096
+ account,
3097
+ initial: compose,
3098
+ maxBytes: policy?.max_attachment_bytes ?? 15 * 1024 * 1024,
3099
+ ready,
3100
+ drafts: drafts.data?.messages ?? [],
3101
+ embedded: true,
3102
+ localDraftId: composeLocalDraftId,
3103
+ offline,
3104
+ documents,
3105
+ notify: notify3,
3106
+ onChange: setCompose,
3107
+ closeRequestRef: composeClose,
3108
+ onClose: () => {
3109
+ setCompose(null);
3110
+ if (compose.draft_id) setSelected(null);
3111
+ },
3112
+ onOpenDraft: (id) => {
3113
+ composeClose.current?.();
3114
+ setFolder("drafts");
3115
+ setCursor(null);
3116
+ setSelected(id);
3117
+ }
3118
+ },
3119
+ `${account?.id}-${composeSession}`
3120
+ ) : message && selected ? /* @__PURE__ */ jsx(
3121
+ MailMessageView,
3122
+ {
3123
+ account,
3124
+ message,
3125
+ folderRole,
3126
+ folders: folders.data,
3127
+ isOnline,
3128
+ loading: detail.isFetching,
3129
+ uploadPending: savePending,
3130
+ personLabel,
3131
+ renderInvite: renderInvite && (() => renderInvite({
3132
+ accountId: account.id,
3133
+ messageId: message.id,
3134
+ attachments: message.attachments,
3135
+ isOnline
3136
+ })),
3137
+ onBack: () => setSelected(null),
3138
+ onEditDraft: () => void editDraft(),
3139
+ onReply: (forward, all) => void reply(forward, all),
3140
+ onUpdate: (body) => void update(body),
3141
+ onDownloadAttachment: (a) => void downloadAttachment(a),
3142
+ onDownloadOriginal: () => void download(
3143
+ `${accountPath(account.id)}/messages/${message.id}/original`,
3144
+ "original.eml"
3145
+ ),
3146
+ onSaveToWorkspace: saveToWorkspace && (() => {
3147
+ setSaveWorkspaceId(
3148
+ saveToWorkspace.defaultWorkspaceId ?? saveToWorkspace.workspaces[0]?.id ?? ""
3149
+ );
3150
+ setSavingToWorkspace(true);
3151
+ })
3152
+ }
3153
+ ) : /* @__PURE__ */ jsxs("article", { className: "min-w-0 flex-1 overflow-auto p-4 sm:p-5", children: [
3154
+ selected && /* @__PURE__ */ jsxs(Button, { className: "mb-3", variant: "ghost", onClick: () => setSelected(null), children: [
3155
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2 size-4" }),
3156
+ "Back"
3157
+ ] }),
3158
+ detail.isFetching && selected && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Loading message\u2026" }),
3159
+ detail.error instanceof MailOfflineUnavailableError && /* @__PURE__ */ jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: detail.error.message })
3160
+ ] })
3161
+ })
3162
+ ] }),
3163
+ saveToWorkspace && /* @__PURE__ */ jsx(Dialog, { open: savingToWorkspace, onOpenChange: setSavingToWorkspace, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
3164
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Save email to workspace" }),
3165
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Save a copy as an .eml file. Members with access to the file can read it." }),
3166
+ /* @__PURE__ */ jsxs(
3167
+ "select",
3168
+ {
3169
+ "aria-label": "Destination workspace",
3170
+ value: saveWorkspaceId,
3171
+ onChange: (e) => setSaveWorkspaceId(e.target.value),
3172
+ className: "rounded border bg-background p-2",
3173
+ children: [
3174
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Choose a workspace" }),
3175
+ saveToWorkspace.workspaces.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.name }, w.id))
3176
+ ]
3177
+ }
3178
+ ),
3179
+ /* @__PURE__ */ jsx(Button, { disabled: !saveWorkspaceId || savePending, onClick: () => void saveEmail(), children: savePending ? "Saving\u2026" : "Save email" }),
3180
+ actionError && /* @__PURE__ */ jsx("p", { role: "alert", children: actionError })
3181
+ ] }) }),
3182
+ settings && policy && /* @__PURE__ */ jsx(
3183
+ ConnectedMailAccounts,
3184
+ {
3185
+ policy,
3186
+ accounts: accounts.data ?? [],
3187
+ user,
3188
+ oauthReturnPath,
3189
+ settings: offline?.renderSettings?.(),
3190
+ onClose: () => setSettings(false)
3191
+ }
3192
+ )
3193
+ ] });
3194
+ }
1619
3195
  function ActiveChat({ agentId, workspaceId }) {
1620
3196
  const chat = useArbiChat({ agentId, workspaceId });
1621
3197
  return /* @__PURE__ */ jsx(
@@ -1679,6 +3255,6 @@ function ConnectedAgentChatPanel({
1679
3255
  ] });
1680
3256
  }
1681
3257
 
1682
- export { ArbiAppProvider, ConnectedAgentChatPanel, ConnectedDangerZone, ConnectedIdentityVerification, ConnectedLogoutDialog, ConnectedNotificationPreferences, ConnectedProfileCard, ConnectedRequireVerified, ConnectedSecuritySection, ConnectedSessionsRow, ConnectedSkillUpload, ConnectedTagMatrix, ConnectedTaskAccessRequest, ConnectedWorkflowRunner, DEFAULT_CONNECTED_WORKFLOW_RUNNER_LABELS, StreamedArtifact, WorkflowRunThread, defaultUnmetPasswordRequirement, useConnectedDocxNotifications, useConnectedShellWorkspaces, useConnectedWorkNotifications };
3258
+ export { ArbiAppProvider, ConnectedAgentChatPanel, ConnectedDangerZone, ConnectedIdentityVerification, ConnectedLogoutDialog, ConnectedMailAccounts, ConnectedMailComposer, ConnectedMailSetup, ConnectedMailbox, ConnectedNotificationPreferences, ConnectedProfileCard, ConnectedRequireVerified, ConnectedSecuritySection, ConnectedSessionsRow, ConnectedSkillUpload, ConnectedTagTable, ConnectedTaskAccessRequest, ConnectedWorkflowRunner, DEFAULT_CONNECTED_WORKFLOW_RUNNER_LABELS, StreamedArtifact, WorkflowRunThread, defaultMailNotify, defaultUnmetPasswordRequirement, resolveMailbox, useConnectedDocxNotifications, useConnectedShellWorkspaces, useConnectedWorkNotifications };
1683
3259
  //# sourceMappingURL=index.js.map
1684
3260
  //# sourceMappingURL=index.js.map