@nocobase/portal-template-default 1.0.15 → 1.0.17
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/AGENTS.md +8 -0
- package/README.MD +4 -0
- package/dist/assets/{LineDraw-iG-ADes-.js → LineDraw-BBB5hwuJ.js} +1 -1
- package/dist/assets/{SymbolDraw-DXoSw1tM.js → SymbolDraw-CKS3uUb_.js} +1 -1
- package/dist/assets/{VisualMapping-C_ee4Eht.js → VisualMapping-BCwaW3TD.js} +1 -1
- package/dist/assets/auth-method-demo-BdCCvEEz.js +1 -0
- package/dist/assets/{auto-login-provider-5Uxza9Ms.js → auto-login-provider-DO5Il6Dp.js} +2 -2
- package/dist/assets/auto-login-provider-TQ08B6-i.js +1 -0
- package/dist/assets/auto-redirect-provider-ButXp0A3.js +1 -0
- package/dist/assets/auto-redirect-provider-DnGGP2Vn.js +1 -0
- package/dist/assets/auto-redirect-provider-ja-vFPaF.js +1 -0
- package/dist/assets/calendar-C7xnRgOT.js +1 -0
- package/dist/assets/{cas-sign-in-button-CyqG1QME.js → cas-sign-in-button-BZCa4lR6.js} +1 -1
- package/dist/assets/{components-D-ptYjX_.js → components-_g-GjQeV.js} +3 -3
- package/dist/assets/{configuration-gate-C08mkkiX.js → configuration-gate-B4XVz7tS.js} +1 -1
- package/dist/assets/{createClipPathFromCoordSys-wj34-b1B.js → createClipPathFromCoordSys-z9Kmh-tu.js} +1 -1
- package/dist/assets/createSeriesDataSimply-CPqysF0K.js +1 -0
- package/dist/assets/{customGraphicKeyframeAnimation-DWrhrHL_.js → customGraphicKeyframeAnimation-DMU5crAu.js} +1 -1
- package/dist/assets/demo-BPlae2bp.js +1 -0
- package/dist/assets/demo-Bj_Y0RrW.js +1 -0
- package/dist/assets/demo-CKPAQOib.js +1 -0
- package/dist/assets/demo-CXRTPvIx.js +1 -0
- package/dist/assets/demo-DJuVDu1n.js +1 -0
- package/dist/assets/demo-DUmWU7ik.js +1 -0
- package/dist/assets/demo-NhIzFRE1.js +1 -0
- package/dist/assets/{dingtalk-sign-in-button-DSWsSruv.js → dingtalk-sign-in-button-DMPgE5kT.js} +1 -1
- package/dist/assets/{echarts-preview-DxHNfq1C.js → echarts-preview-R96Pbqmz.js} +1 -1
- package/dist/assets/{echarts-runtime-D8kXn6ME.js → echarts-runtime-Ce_z2eX_.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-QY8v95qK.js → echarts-runtime-advanced-Dr80KHJR.js} +1 -1
- package/dist/assets/{echarts-runtime-common-D-yqAK-r.js → echarts-runtime-common-DB95Yfc1.js} +1 -1
- package/dist/assets/{echarts-runtime-components-P4-i8jPU.js → echarts-runtime-components-DCJEaTJk.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-ByQZ-DbP.js → echarts-runtime-hierarchy-CezgZs9O.js} +1 -1
- package/dist/assets/{floating-BFVEyPEp.js → floating-O9WmiDXk.js} +2 -2
- package/dist/assets/index-BBKV6O5d.css +1 -0
- package/dist/assets/{index-Btk35Nx-.js → index-CPzehPGe.js} +3 -3
- package/dist/assets/index-Cc6awvdg.js +1064 -0
- package/dist/assets/index-ChA0z9ke.js +38 -0
- package/dist/assets/{index-um5hdKdZ.js → index-DgB7uTMs.js} +2 -2
- package/dist/assets/{index-CwNxd66J.js → index-DroUAK80.js} +1 -1
- package/dist/assets/index-WKvygTBp.js +59 -0
- package/dist/assets/{labelHelper-DQqRy3a_.js → labelHelper-Dk5jkiD-.js} +1 -1
- package/dist/assets/ldap-sign-in-form-CKN4l7qM.js +1 -0
- package/dist/assets/{oidc-sign-in-button-CQdRcR_p.js → oidc-sign-in-button-BIRonrpC.js} +1 -1
- package/dist/assets/{page-context-CnQ4GYM7.js → page-context-DAiW_dId.js} +3 -3
- package/dist/assets/prompt-card-rGZqpqkF.js +1 -0
- package/dist/assets/prompt-output-B4h3uLWf.js +1 -0
- package/dist/assets/saml-sign-in-button-B4ugaLIb.js +1 -0
- package/dist/assets/{sectorHelper-DN_Vv6OL.js → sectorHelper-BuxrrJrA.js} +1 -1
- package/dist/assets/shortcut-BhKXxJLs.js +47 -0
- package/dist/assets/{sms-sign-in-form-DTX9GV2I.js → sms-sign-in-form-DKpxRDdZ.js} +1 -1
- package/dist/assets/{static.browser-jI1psK0d.js → static.browser-kzzpH0Xt.js} +8 -8
- package/dist/assets/switch-BdVxPIVU.js +1 -0
- package/dist/assets/{tool-cards-DFttLAfo.js → tool-cards-CpczIke-.js} +3 -3
- package/dist/assets/{use-authenticator-sign-in-CRGjzXju.js → use-authenticator-sign-in-CIj210ry.js} +1 -1
- package/dist/assets/use-dingtalk-sign-in-BW4kkXD3.js +1 -0
- package/dist/assets/{use-wecom-sign-in-C9j8wfCD.js → use-wecom-sign-in-dIqMtcN9.js} +1 -1
- package/dist/assets/wecom-sign-in-button-Dn_2zDab.js +1 -0
- package/dist/index.html +2 -2
- package/dist/index.raw.html +2 -2
- package/package.json +9 -1
- package/src/App.tsx +5 -2
- package/src/app/extension.ts +2 -0
- package/src/app/extensions.tsx +18 -6
- package/src/app/route-access-guard.tsx +22 -0
- package/src/app/route-runtime.ts +173 -0
- package/src/app/routes.tsx +2 -2
- package/src/components/app-shell/header.tsx +56 -1
- package/src/components/app-shell/sidebar.tsx +14 -2
- package/src/components/auth/sign-up-form.tsx +287 -59
- package/src/components/auth/types.ts +23 -1
- package/src/extensions/nocobase-acl/README.md +28 -14
- package/src/extensions/nocobase-acl/demo/index.tsx +87 -15
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +1 -0
- package/src/extensions/nocobase-mail/README.md +74 -0
- package/src/extensions/nocobase-mail/components/index.ts +79 -0
- package/src/extensions/nocobase-mail/components/mail-api.ts +423 -0
- package/src/extensions/nocobase-mail/components/mail-attachment-list.tsx +67 -0
- package/src/extensions/nocobase-mail/components/mail-compose-attachments.tsx +166 -0
- package/src/extensions/nocobase-mail/components/mail-compose.tsx +1071 -0
- package/src/extensions/nocobase-mail/components/mail-detail.tsx +653 -0
- package/src/extensions/nocobase-mail/components/mail-draft-autosave.ts +26 -0
- package/src/extensions/nocobase-mail/components/mail-empty.tsx +48 -0
- package/src/extensions/nocobase-mail/components/mail-filters.tsx +277 -0
- package/src/extensions/nocobase-mail/components/mail-inbox.tsx +544 -0
- package/src/extensions/nocobase-mail/components/mail-inline-images.ts +69 -0
- package/src/extensions/nocobase-mail/components/mail-label-badge.tsx +25 -0
- package/src/extensions/nocobase-mail/components/mail-label-manager.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-labels-editor.tsx +267 -0
- package/src/extensions/nocobase-mail/components/mail-mass-tracking.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-note-editor.tsx +99 -0
- package/src/extensions/nocobase-mail/components/mail-recipient-input.tsx +260 -0
- package/src/extensions/nocobase-mail/components/mail-reply-quote.ts +28 -0
- package/src/extensions/nocobase-mail/components/mail-rich-editor.tsx +287 -0
- package/src/extensions/nocobase-mail/components/mail-send-actions.tsx +238 -0
- package/src/extensions/nocobase-mail/components/mail-senders.ts +61 -0
- package/src/extensions/nocobase-mail/components/mail-settings-drawer.tsx +708 -0
- package/src/extensions/nocobase-mail/components/mail-showcase-layout.tsx +64 -0
- package/src/extensions/nocobase-mail/components/mail-signature-manager.tsx +258 -0
- package/src/extensions/nocobase-mail/components/mail-signatures.ts +25 -0
- package/src/extensions/nocobase-mail/components/mail-table.tsx +476 -0
- package/src/extensions/nocobase-mail/components/mail-template-manager.tsx +204 -0
- package/src/extensions/nocobase-mail/components/mail-toolbar.tsx +155 -0
- package/src/extensions/nocobase-mail/components/mail-unread.tsx +111 -0
- package/src/extensions/nocobase-mail/components/types.ts +297 -0
- package/src/extensions/nocobase-mail/components/use-mail-compose.tsx +262 -0
- package/src/extensions/nocobase-mail/components/use-mail-mass-messages.ts +54 -0
- package/src/extensions/nocobase-mail/components/use-mail-messages.ts +113 -0
- package/src/extensions/nocobase-mail/components/use-mail-signatures.ts +122 -0
- package/src/extensions/nocobase-mail/components/use-mail-templates.ts +54 -0
- package/src/extensions/nocobase-mail/extension.tsx +105 -0
- package/src/extensions/nocobase-mail/index.ts +1 -0
- package/src/extensions/nocobase-mail/mail-demo-pages.tsx +503 -0
- package/src/extensions/nocobase-mail/mail-pages.tsx +201 -0
- package/src/extensions/nocobase-mail/tests/mail-quote-html-regression.mjs +51 -0
- package/src/extensions/nocobase-mail/tests/mail-regression.mjs +649 -0
- package/src/extensions/nocobase-users-example/app-routes.tsx +121 -0
- package/src/extensions/nocobase-users-example/extension.tsx +2 -100
- package/src/extensions/nocobase-users-example/index.ts +1 -0
- package/src/lib/nocobase/acl/menu.ts +17 -3
- package/src/lib/nocobase/acl/types.ts +4 -0
- package/src/locales/en-US.ts +2 -1
- package/src/locales/zh-CN.ts +2 -1
- package/src/providers/runtime-config.ts +18 -0
- package/src/routes.tsx +10 -0
- package/src/vite-env.d.ts +3 -0
- package/vite.config.ts +8 -0
- package/dist/assets/auth-method-demo-DaOCuvsq.js +0 -1
- package/dist/assets/auto-login-provider-DyaPGK2D.js +0 -1
- package/dist/assets/auto-redirect-provider-CfwRvYOI.js +0 -1
- package/dist/assets/auto-redirect-provider-Cr3jmYts.js +0 -1
- package/dist/assets/auto-redirect-provider-QkJQRBbG.js +0 -1
- package/dist/assets/calendar-DjDRfD5t.js +0 -1
- package/dist/assets/card-DuQBOS6A.js +0 -1
- package/dist/assets/createSeriesDataSimply-D0A6SAVQ.js +0 -1
- package/dist/assets/demo-BYWHeKUe.js +0 -1
- package/dist/assets/demo-CPWoNtx8.js +0 -1
- package/dist/assets/demo-CyvJk5zV.js +0 -1
- package/dist/assets/demo-D4OiKplc.js +0 -1
- package/dist/assets/demo-DBueJg0m.js +0 -1
- package/dist/assets/demo-DkIhyiRK.js +0 -1
- package/dist/assets/demo-tpTjO0sC.js +0 -1
- package/dist/assets/index-BgCbGDMc.js +0 -43
- package/dist/assets/index-MB-jfQld.js +0 -686
- package/dist/assets/index-NH3aqqQz.css +0 -1
- package/dist/assets/index-gFtCXH3o.js +0 -58
- package/dist/assets/ldap-sign-in-form--6Gt3yGq.js +0 -1
- package/dist/assets/prompt-card-B2RS4nt2.js +0 -1
- package/dist/assets/prompt-output-DZHRM2ft.js +0 -1
- package/dist/assets/saml-sign-in-button-BHL5a0JV.js +0 -1
- package/dist/assets/shortcut-C5x5LQWU.js +0 -47
- package/dist/assets/switch-CRL8PmRI.js +0 -1
- package/dist/assets/use-dingtalk-sign-in-8WYzJ2jf.js +0 -1
- package/dist/assets/wecom-sign-in-button-mIS1zGSU.js +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# NocoBase Mail
|
|
2
|
+
|
|
3
|
+
Reusable mail inbox, compose, detail, and label components backed by the NocoBase mail API.
|
|
4
|
+
|
|
5
|
+
After installation, import the components from `@/extensions/nocobase-mail`.
|
|
6
|
+
|
|
7
|
+
- `MailInbox` full inbox with toolbar, table, detail panel, and compose.
|
|
8
|
+
- `MailCompose` / `MailComposeForm` standalone compose dialog or embedded form,
|
|
9
|
+
with immediate and scheduled sending plus opt-in bulk mode, account-aware sender aliases,
|
|
10
|
+
server templates/signatures, recipient suggestions, reply/forward references, automatic draft
|
|
11
|
+
saving and recovery, and real attachment uploads.
|
|
12
|
+
- `MailComposeAttachments` uploads files through `mail:messageAttachmentUpload`
|
|
13
|
+
with retry, removal, and the backend's 25 MB limit.
|
|
14
|
+
- `MailFilters` folder, read-state, label, and todo-message filters.
|
|
15
|
+
- `MailUnreadProvider` / `MailUnreadIndicator` / `MailUnreadIcon` reusable unread-count
|
|
16
|
+
display with interval, window-focus, and visibility refresh.
|
|
17
|
+
- `MailDetail` threaded message detail with per-message Reply, conditional Reply all,
|
|
18
|
+
and Forward actions.
|
|
19
|
+
- `MailSettingsDrawer` personal mailbox settings with Mailbox, Labels, Templates,
|
|
20
|
+
and Signature management tabs.
|
|
21
|
+
- `MailMassTracking` bulk delivery tracking with recipient status, cancel, and resend.
|
|
22
|
+
- `useMailMessages` hook for paginated message listing with search and filters.
|
|
23
|
+
- `useMailCompose` hook to open compose from anywhere.
|
|
24
|
+
- `MailLabelBadge` / `MailLabelsEditor` label display, assignment, and full CRUD.
|
|
25
|
+
- `MailNoteEditor` per-message private notes.
|
|
26
|
+
- `MailAttachmentList` attachment preview and download.
|
|
27
|
+
- Message detail resolves authenticated `cid:` inline images and keeps those images out of the
|
|
28
|
+
regular attachment list.
|
|
29
|
+
|
|
30
|
+
The mail API client (`mailApi`) uses the Starter's built-in `API_URL`, `getNocoBaseHeaders`, and `NOCOBASE_TOKEN_KEY` from `@/providers/constants`. A compatible NocoBase backend with the mail plugin enabled is required.
|
|
31
|
+
|
|
32
|
+
## Send modes
|
|
33
|
+
|
|
34
|
+
The standard compose form enables scheduled sending by default. Bulk sending is
|
|
35
|
+
kept on the dedicated `/dev/mail/bulk` showcase page, where the bulk form and
|
|
36
|
+
delivery jobs are visible together. Reusable compose forms can still opt into bulk mode:
|
|
37
|
+
|
|
38
|
+
- **Schedule send** calls `mail:messageSend` with an ISO `scheduleSendAt` value.
|
|
39
|
+
- **Bulk send** calls `mailMassMessages:send`, creates one separate message per
|
|
40
|
+
To recipient, and defaults to a 2-second interval between messages.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<MailComposeForm
|
|
44
|
+
allowScheduleSend
|
|
45
|
+
allowBulkSend
|
|
46
|
+
defaultBulkIntervalMs={2000}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Bulk and scheduled sending are separate modes, matching the NocoBase email
|
|
51
|
+
manager backend. Bulk sending requires at least two recipients and does not
|
|
52
|
+
support a scheduled start time.
|
|
53
|
+
|
|
54
|
+
Scheduled messages can be canceled back into drafts and edited or rescheduled.
|
|
55
|
+
Opening a local draft enters compose mode with its uploaded attachments; local
|
|
56
|
+
drafts can be updated or permanently deleted. Provider-backed drafts remain
|
|
57
|
+
read-only and must be changed in the original mailbox so provider state and
|
|
58
|
+
attachments are preserved. Trash actions are provider-backed: messages
|
|
59
|
+
can be moved to trash, put back, or permanently deleted after confirmation.
|
|
60
|
+
|
|
61
|
+
Templates are stored in `mailTemplates`; the backend model stores template
|
|
62
|
+
content but no subject. Signatures are stored per mail account in
|
|
63
|
+
`mailAccounts.config.signatures`, so changing the selected sender also changes
|
|
64
|
+
the available/default signature. Use `recipientOptions` on the compose
|
|
65
|
+
components to add application-specific record recipients alongside NocoBase
|
|
66
|
+
users.
|
|
67
|
+
|
|
68
|
+
Gmail aliases can be viewed and refreshed from Mail settings. Synced aliases are
|
|
69
|
+
immediately available in the compose sender selector. Outlook does not expose
|
|
70
|
+
sender aliases through this integration.
|
|
71
|
+
|
|
72
|
+
Mailbox account creation, OAuth authorization, and provider settings are not
|
|
73
|
+
part of this component package; they remain owned by the separate mail settings
|
|
74
|
+
center.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export { mailApi } from "./mail-api";
|
|
3
|
+
export { MailInbox } from "./mail-inbox";
|
|
4
|
+
export type { MailInboxProps } from "./mail-inbox";
|
|
5
|
+
export { MailToolbar } from "./mail-toolbar";
|
|
6
|
+
export { MailTable } from "./mail-table";
|
|
7
|
+
export { MailDetail } from "./mail-detail";
|
|
8
|
+
export { MailCompose, MailComposeForm } from "./mail-compose";
|
|
9
|
+
export type {
|
|
10
|
+
ComposeInitialValues,
|
|
11
|
+
ComposeReference,
|
|
12
|
+
ComposeMode,
|
|
13
|
+
ComposeVariant,
|
|
14
|
+
MailComposeFormProps,
|
|
15
|
+
MailComposeProps,
|
|
16
|
+
} from "./mail-compose";
|
|
17
|
+
export { MailSendActions } from "./mail-send-actions";
|
|
18
|
+
export type { MailSendActionsProps } from "./mail-send-actions";
|
|
19
|
+
export { MailRecipientInput } from "./mail-recipient-input";
|
|
20
|
+
export type { MailRecipientInputProps } from "./mail-recipient-input";
|
|
21
|
+
export { MailComposeAttachments } from "./mail-compose-attachments";
|
|
22
|
+
export type { MailComposeAttachmentsProps } from "./mail-compose-attachments";
|
|
23
|
+
export { getMailSenderCandidates, resolveMailSender } from "./mail-senders";
|
|
24
|
+
export type { MailSenderCandidate } from "./mail-senders";
|
|
25
|
+
export { MailRichEditor } from "./mail-rich-editor";
|
|
26
|
+
export type { MailRichEditorProps } from "./mail-rich-editor";
|
|
27
|
+
export { useMailSignatures } from "./use-mail-signatures";
|
|
28
|
+
export type { MailSignatureValues } from "./use-mail-signatures";
|
|
29
|
+
export { MailSignatureManager } from "./mail-signature-manager";
|
|
30
|
+
export type { MailSignatureManagerProps } from "./mail-signature-manager";
|
|
31
|
+
export { applySignature } from "./mail-signatures";
|
|
32
|
+
export { useMailTemplates } from "./use-mail-templates";
|
|
33
|
+
export type { MailTemplateValues } from "./use-mail-templates";
|
|
34
|
+
export { MailTemplateManager } from "./mail-template-manager";
|
|
35
|
+
export type { MailTemplateManagerProps } from "./mail-template-manager";
|
|
36
|
+
export { useMailMessages } from "./use-mail-messages";
|
|
37
|
+
export type {
|
|
38
|
+
UseMailMessagesOptions,
|
|
39
|
+
UseMailMessagesResult,
|
|
40
|
+
} from "./use-mail-messages";
|
|
41
|
+
export {
|
|
42
|
+
useMailCompose,
|
|
43
|
+
buildComposeInitial,
|
|
44
|
+
canReplyAll,
|
|
45
|
+
} from "./use-mail-compose";
|
|
46
|
+
export type { UseMailComposeOptions } from "./use-mail-compose";
|
|
47
|
+
export { MailLabelBadge } from "./mail-label-badge";
|
|
48
|
+
export { MailLabelsEditor } from "./mail-labels-editor";
|
|
49
|
+
export { MailLabelManager } from "./mail-label-manager";
|
|
50
|
+
export type { MailLabelManagerProps } from "./mail-label-manager";
|
|
51
|
+
export { MailNoteEditor } from "./mail-note-editor";
|
|
52
|
+
export { MailAttachmentList } from "./mail-attachment-list";
|
|
53
|
+
export { createDebouncedDraftSaver } from "./mail-draft-autosave";
|
|
54
|
+
export type { DebouncedDraftSaver } from "./mail-draft-autosave";
|
|
55
|
+
export { serializeReplyQuote, splitReplyQuote } from "./mail-reply-quote";
|
|
56
|
+
export type { MailReplyContent } from "./mail-reply-quote";
|
|
57
|
+
export {
|
|
58
|
+
collectInlineContentIds,
|
|
59
|
+
filterInlineAttachments,
|
|
60
|
+
normalizeContentId,
|
|
61
|
+
replaceInlineImageSources,
|
|
62
|
+
} from "./mail-inline-images";
|
|
63
|
+
export { MailEmpty } from "./mail-empty";
|
|
64
|
+
export {
|
|
65
|
+
MailUnreadIcon,
|
|
66
|
+
MailUnreadIndicator,
|
|
67
|
+
MailUnreadProvider,
|
|
68
|
+
useMailUnread,
|
|
69
|
+
} from "./mail-unread";
|
|
70
|
+
export type {
|
|
71
|
+
MailUnreadIndicatorProps,
|
|
72
|
+
MailUnreadProviderProps,
|
|
73
|
+
} from "./mail-unread";
|
|
74
|
+
export { MailSettingsDrawer } from "./mail-settings-drawer";
|
|
75
|
+
export type { MailSettingsDrawerProps } from "./mail-settings-drawer";
|
|
76
|
+
export { MailMassTracking } from "./mail-mass-tracking";
|
|
77
|
+
export { useMailMassMessages } from "./use-mail-mass-messages";
|
|
78
|
+
export { MailFilters } from "./mail-filters";
|
|
79
|
+
export type { MailFilterValue, MailFiltersProps } from "./mail-filters";
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { nocobaseClient } from "@/lib/nocobase/client";
|
|
2
|
+
import type {
|
|
3
|
+
MailAccount,
|
|
4
|
+
MailIdentity,
|
|
5
|
+
MailLabel,
|
|
6
|
+
MailListParams,
|
|
7
|
+
MailListResponse,
|
|
8
|
+
MailMassSendSettings,
|
|
9
|
+
MailMassListResponse,
|
|
10
|
+
MailMassMessage,
|
|
11
|
+
MailMessage,
|
|
12
|
+
MailNote,
|
|
13
|
+
MailSendPayload,
|
|
14
|
+
MailTemplate,
|
|
15
|
+
MailUploadedAttachment,
|
|
16
|
+
MailUserRecord,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
function parseListResponse(payload: unknown): MailListResponse {
|
|
20
|
+
if (!payload || typeof payload !== "object") return { rows: [], count: 0 };
|
|
21
|
+
const obj = payload as {
|
|
22
|
+
data?: unknown;
|
|
23
|
+
meta?: { count?: number };
|
|
24
|
+
rows?: unknown;
|
|
25
|
+
count?: number;
|
|
26
|
+
};
|
|
27
|
+
if (Array.isArray(obj.data)) {
|
|
28
|
+
return { rows: obj.data, count: obj.meta?.count ?? obj.data.length };
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(obj.rows)) {
|
|
31
|
+
return { rows: obj.rows, count: obj.count ?? obj.rows.length };
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(payload)) {
|
|
34
|
+
return { rows: payload, count: payload.length };
|
|
35
|
+
}
|
|
36
|
+
return { rows: [], count: 0 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const mailApi = {
|
|
40
|
+
listMessages(params: MailListParams = {}): Promise<MailListResponse> {
|
|
41
|
+
const filter: Record<string, unknown> = {};
|
|
42
|
+
if (params.boxType) filter.boxType = { $in: [params.boxType] };
|
|
43
|
+
if (params.isRead !== undefined) filter.isRead = params.isRead;
|
|
44
|
+
if (params.labelId) filter["labels.id"] = { $in: [params.labelId] };
|
|
45
|
+
if (params.userId !== undefined) filter.userId = params.userId;
|
|
46
|
+
if (params.search) {
|
|
47
|
+
filter.$or = [
|
|
48
|
+
{ subject: { $includes: params.search } },
|
|
49
|
+
{ from: { $includes: params.search } },
|
|
50
|
+
{ to: { $includes: params.search } },
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (params.filter) Object.assign(filter, params.filter);
|
|
54
|
+
|
|
55
|
+
const action = params.scope === "personal" ? "listPerson" : "list";
|
|
56
|
+
|
|
57
|
+
return nocobaseClient
|
|
58
|
+
.action<unknown>("mailMessages", action, {
|
|
59
|
+
query: {
|
|
60
|
+
page: params.page ?? 1,
|
|
61
|
+
pageSize: params.pageSize ?? 20,
|
|
62
|
+
subjectMerge: true,
|
|
63
|
+
appends: "user",
|
|
64
|
+
...(Object.keys(filter).length
|
|
65
|
+
? { filter: JSON.stringify(filter) }
|
|
66
|
+
: {}),
|
|
67
|
+
sort: params.sort ?? "-relatedMessageLatestDate",
|
|
68
|
+
},
|
|
69
|
+
unwrap: "none",
|
|
70
|
+
})
|
|
71
|
+
.then(parseListResponse);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
getMessage(id: number | string): Promise<MailMessage> {
|
|
75
|
+
return nocobaseClient.action<MailMessage>("mailMessages", "get", {
|
|
76
|
+
query: { filterByTk: id, appends: "children,labels,note" },
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
trashMessages(
|
|
81
|
+
ids: (number | string)[],
|
|
82
|
+
moveToTrash = true
|
|
83
|
+
): Promise<unknown> {
|
|
84
|
+
return nocobaseClient.action("mailMessages", "trash", {
|
|
85
|
+
body: { filterByTk: ids, moveToTrash },
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
destroyMessages(ids: (number | string)[]): Promise<unknown> {
|
|
90
|
+
return nocobaseClient.action("mailMessages", "destroy", {
|
|
91
|
+
query: { filterByTk: ids },
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
cancelScheduled(id: number | string): Promise<unknown> {
|
|
96
|
+
return nocobaseClient.action("mailMessages", "cancelTimelySend", {
|
|
97
|
+
query: { id },
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
setRead(mailIds: string[], isRead: boolean): Promise<unknown> {
|
|
102
|
+
return nocobaseClient.action("mail", "messageSetReaded", {
|
|
103
|
+
body: { mailIds, isRead },
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
send(payload: MailSendPayload): Promise<unknown> {
|
|
108
|
+
return nocobaseClient.action("mail", "messageSend", { body: payload });
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
massSend(
|
|
112
|
+
payload: MailSendPayload,
|
|
113
|
+
settings: MailMassSendSettings = {}
|
|
114
|
+
): Promise<unknown> {
|
|
115
|
+
return nocobaseClient.action("mailMassMessages", "send", {
|
|
116
|
+
body: {
|
|
117
|
+
data: payload,
|
|
118
|
+
mailSendSetting: settings,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async listMassMessages(
|
|
124
|
+
parentId: number | null = null
|
|
125
|
+
): Promise<MailMassListResponse> {
|
|
126
|
+
const pageSize = 100;
|
|
127
|
+
const rows: MailMassMessage[] = [];
|
|
128
|
+
let page = 1;
|
|
129
|
+
let count = 0;
|
|
130
|
+
|
|
131
|
+
do {
|
|
132
|
+
const payload = await nocobaseClient.action<unknown>(
|
|
133
|
+
"mailMassMessages",
|
|
134
|
+
"list",
|
|
135
|
+
{
|
|
136
|
+
query: {
|
|
137
|
+
page,
|
|
138
|
+
pageSize,
|
|
139
|
+
filter: JSON.stringify({ parentId }),
|
|
140
|
+
sort: "-createdAt",
|
|
141
|
+
},
|
|
142
|
+
unwrap: "none",
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
const parsed = parseListResponse(payload);
|
|
146
|
+
const nextRows = parsed.rows as unknown as MailMassMessage[];
|
|
147
|
+
rows.push(...nextRows);
|
|
148
|
+
count = parsed.count;
|
|
149
|
+
if (!nextRows.length) break;
|
|
150
|
+
page += 1;
|
|
151
|
+
} while (rows.length < count);
|
|
152
|
+
|
|
153
|
+
return { rows, count };
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
cancelMassMessage(id: number | string): Promise<unknown> {
|
|
157
|
+
return nocobaseClient.action("mailMassMessages", "cancel", {
|
|
158
|
+
body: { id },
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
resendMassMessage(id: number | string): Promise<unknown> {
|
|
163
|
+
return nocobaseClient.action("mailMassMessages", "resend", {
|
|
164
|
+
body: { id },
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
uploadAttachment(file: File): Promise<MailUploadedAttachment> {
|
|
169
|
+
const formData = new FormData();
|
|
170
|
+
formData.append("file", file);
|
|
171
|
+
return nocobaseClient
|
|
172
|
+
.action<MailUploadedAttachment>("mail", "messageAttachmentUpload", {
|
|
173
|
+
body: formData,
|
|
174
|
+
})
|
|
175
|
+
.then((attachment) => ({
|
|
176
|
+
...attachment,
|
|
177
|
+
// Google reads originalname/mimetype; Microsoft reads filename/mimeType.
|
|
178
|
+
// Keep both pairs aligned so recipients see the real file name and type.
|
|
179
|
+
filename: attachment.originalname,
|
|
180
|
+
mimeType: attachment.mimetype,
|
|
181
|
+
}));
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
saveDraft(payload: MailSendPayload): Promise<{ id: number }> {
|
|
185
|
+
return nocobaseClient.action<{ id: number }>("mail", "messageSavingDraft", {
|
|
186
|
+
body: payload,
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
sync(emails: string[]): Promise<unknown> {
|
|
191
|
+
return nocobaseClient.action("mail", "messagesSync", { body: { emails } });
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
unreadCount(): Promise<number> {
|
|
195
|
+
return nocobaseClient
|
|
196
|
+
.action<unknown>("mail", "messageUnreadCount")
|
|
197
|
+
.then((value) => {
|
|
198
|
+
if (typeof value === "number") return value;
|
|
199
|
+
if (value && typeof value === "object" && "count" in value) {
|
|
200
|
+
const count = Number((value as { count?: unknown }).count);
|
|
201
|
+
return Number.isFinite(count) ? count : 0;
|
|
202
|
+
}
|
|
203
|
+
return 0;
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
getAccounts(): Promise<MailAccount[]> {
|
|
208
|
+
return nocobaseClient.action<MailAccount[]>("mail", "getMailAccounts");
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
findDraft(values: {
|
|
212
|
+
accountEmail: string;
|
|
213
|
+
identityEmail: string;
|
|
214
|
+
to: string[];
|
|
215
|
+
}): Promise<MailMessage | undefined> {
|
|
216
|
+
return nocobaseClient
|
|
217
|
+
.action<MailMessage | undefined>("mailMessages", "get", {
|
|
218
|
+
query: {
|
|
219
|
+
filter: JSON.stringify({
|
|
220
|
+
isDraft: true,
|
|
221
|
+
from: values.identityEmail,
|
|
222
|
+
email: values.accountEmail,
|
|
223
|
+
to: values.to,
|
|
224
|
+
}),
|
|
225
|
+
appends: "labels,note",
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
.then((draft) => draft || undefined);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
getOAuthUrl(
|
|
232
|
+
type: "google" | "microsoft",
|
|
233
|
+
options: { email?: string; reauthorize?: boolean } = {}
|
|
234
|
+
): Promise<string> {
|
|
235
|
+
return nocobaseClient
|
|
236
|
+
.action<{ url?: string }>("mail", "oauth2url", {
|
|
237
|
+
query: { type, ...options },
|
|
238
|
+
})
|
|
239
|
+
.then((response) => response.url || "");
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
deleteAccount(email: string): Promise<unknown> {
|
|
243
|
+
return nocobaseClient.action("mail", "deleteMailAccount", {
|
|
244
|
+
body: { email },
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
resyncAccount(email: string): Promise<unknown> {
|
|
249
|
+
return nocobaseClient.action("mail", "messagesResync", {
|
|
250
|
+
body: { email },
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
syncAliases(email: string): Promise<MailIdentity[]> {
|
|
255
|
+
return nocobaseClient
|
|
256
|
+
.action<MailIdentity[]>("mail", "accountIdentitiesSync", {
|
|
257
|
+
body: { email },
|
|
258
|
+
})
|
|
259
|
+
.then((identities) => (Array.isArray(identities) ? identities : []));
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
getUsers(): Promise<MailUserRecord[]> {
|
|
263
|
+
return nocobaseClient
|
|
264
|
+
.action<MailUserRecord[]>("users", "list", {
|
|
265
|
+
query: { pageSize: 100 },
|
|
266
|
+
})
|
|
267
|
+
.then((res) => (Array.isArray(res) ? res : []));
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
getLabels(userId?: number | string): Promise<MailLabel[]> {
|
|
271
|
+
const filter =
|
|
272
|
+
userId !== undefined ? { createdBy: { id: userId } } : undefined;
|
|
273
|
+
return nocobaseClient
|
|
274
|
+
.action<MailLabel[]>("mailMessageLabels", "list", {
|
|
275
|
+
query: {
|
|
276
|
+
paginate: false,
|
|
277
|
+
...(filter ? { filter: JSON.stringify(filter) } : {}),
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
.then((res) => (Array.isArray(res) ? res : []));
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
createLabel(values: {
|
|
284
|
+
label: string;
|
|
285
|
+
color: string;
|
|
286
|
+
description?: string;
|
|
287
|
+
}): Promise<MailLabel> {
|
|
288
|
+
return nocobaseClient.action<MailLabel>("mailMessageLabels", "create", {
|
|
289
|
+
body: values,
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
updateLabel(
|
|
294
|
+
id: number | string,
|
|
295
|
+
values: { label: string; color: string; description?: string }
|
|
296
|
+
): Promise<MailLabel> {
|
|
297
|
+
return nocobaseClient.action<MailLabel>("mailMessageLabels", "update", {
|
|
298
|
+
query: { filterByTk: id },
|
|
299
|
+
body: values,
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
deleteLabel(id: number | string): Promise<unknown> {
|
|
304
|
+
return nocobaseClient.action("mailMessageLabels", "destroy", {
|
|
305
|
+
query: { filterByTk: id },
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
getTemplates(): Promise<MailTemplate[]> {
|
|
310
|
+
return nocobaseClient
|
|
311
|
+
.action<MailTemplate[]>("mailTemplates", "list", {
|
|
312
|
+
query: { paginate: false, sort: "createdAt" },
|
|
313
|
+
})
|
|
314
|
+
.then((res) => (Array.isArray(res) ? res : []));
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
createTemplate(values: {
|
|
318
|
+
name: string;
|
|
319
|
+
content: string;
|
|
320
|
+
}): Promise<MailTemplate> {
|
|
321
|
+
return nocobaseClient.action<MailTemplate>("mailTemplates", "create", {
|
|
322
|
+
body: values,
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
updateTemplate(
|
|
327
|
+
id: number | string,
|
|
328
|
+
values: { name: string; content: string }
|
|
329
|
+
): Promise<MailTemplate> {
|
|
330
|
+
return nocobaseClient.action<MailTemplate>("mailTemplates", "update", {
|
|
331
|
+
query: { filterByTk: id },
|
|
332
|
+
body: values,
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
deleteTemplate(id: number | string): Promise<unknown> {
|
|
337
|
+
return nocobaseClient.action("mailTemplates", "destroy", {
|
|
338
|
+
query: { filterByTk: id },
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
updateAccount(
|
|
343
|
+
id: number | string,
|
|
344
|
+
values: Partial<MailAccount>
|
|
345
|
+
): Promise<MailAccount> {
|
|
346
|
+
return nocobaseClient.action<MailAccount>("mailAccounts", "update", {
|
|
347
|
+
query: { filterByTk: id },
|
|
348
|
+
body: values,
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
setMessageLabels(
|
|
353
|
+
messageId: number | string,
|
|
354
|
+
labelIds: number[]
|
|
355
|
+
): Promise<unknown> {
|
|
356
|
+
return nocobaseClient.action("mailMessages", "update", {
|
|
357
|
+
query: { filterByTk: messageId },
|
|
358
|
+
body: { labels: labelIds },
|
|
359
|
+
});
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
setTodo(messageId: number | string, isTodo: boolean): Promise<unknown> {
|
|
363
|
+
return nocobaseClient.action("mailMessages", "update", {
|
|
364
|
+
query: { filterByTk: messageId },
|
|
365
|
+
body: { isTodo },
|
|
366
|
+
});
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
setBoxType(messageId: number | string, boxType: string): Promise<unknown> {
|
|
370
|
+
return nocobaseClient.action("mailMessages", "update", {
|
|
371
|
+
query: { filterByTk: messageId },
|
|
372
|
+
body: { boxType },
|
|
373
|
+
});
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
createNote(messageId: number | string, note: string): Promise<MailNote> {
|
|
377
|
+
return nocobaseClient.action<MailNote>("mailMessageNotes", "create", {
|
|
378
|
+
body: { mailMessageId: messageId, note },
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
updateNote(
|
|
383
|
+
noteId: number | string,
|
|
384
|
+
messageId: number | string,
|
|
385
|
+
note: string
|
|
386
|
+
): Promise<MailNote> {
|
|
387
|
+
return nocobaseClient.action<MailNote>("mailMessageNotes", "update", {
|
|
388
|
+
query: { filterByTk: noteId },
|
|
389
|
+
body: { mailMessageId: messageId, note },
|
|
390
|
+
});
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
attachmentUrl(messageId: number | string, attachmentId: string): string {
|
|
394
|
+
return nocobaseClient
|
|
395
|
+
.buildUrl("mail:messageAttachmentGet", {
|
|
396
|
+
id: messageId,
|
|
397
|
+
attachmentId,
|
|
398
|
+
})
|
|
399
|
+
.toString();
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
inlineImageUrl(messageId: number | string, contentId: string): string {
|
|
403
|
+
return nocobaseClient
|
|
404
|
+
.buildUrl("mail:messageContentPreview", { messageId, contentId })
|
|
405
|
+
.toString();
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
async fetchInlineImage(
|
|
409
|
+
messageId: number | string,
|
|
410
|
+
contentId: string
|
|
411
|
+
): Promise<Blob> {
|
|
412
|
+
const response = await fetch(this.inlineImageUrl(messageId, contentId), {
|
|
413
|
+
headers: nocobaseClient.getHeaders({
|
|
414
|
+
withAclMeta: false,
|
|
415
|
+
headers: { Accept: "image/*" },
|
|
416
|
+
}),
|
|
417
|
+
});
|
|
418
|
+
if (!response.ok) {
|
|
419
|
+
throw new Error(`Inline image request failed (${response.status})`);
|
|
420
|
+
}
|
|
421
|
+
return response.blob();
|
|
422
|
+
},
|
|
423
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Download, FileText, Image as ImageIcon, Paperclip } from "lucide-react";
|
|
2
|
+
import type { MailAttachment } from "./types";
|
|
3
|
+
import { mailApi } from "./mail-api";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
function formatSize(size?: number) {
|
|
7
|
+
if (!size) return "";
|
|
8
|
+
if (size < 1024) return `${size} B`;
|
|
9
|
+
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
|
10
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isImage(mimeType: string) {
|
|
14
|
+
return mimeType.startsWith("image/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function MailAttachmentList({
|
|
18
|
+
messageId,
|
|
19
|
+
attachments,
|
|
20
|
+
className,
|
|
21
|
+
}: {
|
|
22
|
+
messageId: number | string;
|
|
23
|
+
attachments: MailAttachment[];
|
|
24
|
+
className?: string;
|
|
25
|
+
}) {
|
|
26
|
+
if (!attachments?.length) return null;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={cn("flex flex-col gap-2", className)}>
|
|
30
|
+
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
|
31
|
+
<Paperclip className="size-3.5" />
|
|
32
|
+
{attachments.length} attachment{attachments.length > 1 ? "s" : ""}
|
|
33
|
+
</div>
|
|
34
|
+
<div className="flex flex-wrap gap-2">
|
|
35
|
+
{attachments.map((att) => (
|
|
36
|
+
<a
|
|
37
|
+
key={att.attachmentId}
|
|
38
|
+
href={mailApi.attachmentUrl(messageId, att.attachmentId)}
|
|
39
|
+
target="_blank"
|
|
40
|
+
rel="noreferrer"
|
|
41
|
+
className={cn(
|
|
42
|
+
"group flex items-center gap-2.5 rounded-lg border border-border/80 bg-card px-3 py-2",
|
|
43
|
+
"shadow-[0_1px_2px_rgba(0,0,0,0.04)] transition-all hover:border-primary/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
{isImage(att.mimeType) ? (
|
|
47
|
+
<ImageIcon className="size-4 shrink-0 text-sky-500" />
|
|
48
|
+
) : (
|
|
49
|
+
<FileText className="size-4 shrink-0 text-muted-foreground" />
|
|
50
|
+
)}
|
|
51
|
+
<div className="min-w-0">
|
|
52
|
+
<div className="max-w-[180px] truncate text-xs font-medium text-foreground">
|
|
53
|
+
{att.filename}
|
|
54
|
+
</div>
|
|
55
|
+
{att.size ? (
|
|
56
|
+
<div className="text-[10px] text-muted-foreground">
|
|
57
|
+
{formatSize(att.size)}
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
</div>
|
|
61
|
+
<Download className="size-3.5 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" />
|
|
62
|
+
</a>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|