@nocobase/portal-template-default 1.0.16 → 1.0.18
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/.env.example +0 -4
- package/AGENTS.md +10 -0
- package/README.MD +4 -0
- package/dist/assets/{LineDraw-vPRTWiQ5.js → LineDraw-B3HsIvjF.js} +1 -1
- package/dist/assets/{SymbolDraw-DOl_Tss3.js → SymbolDraw-C5oA6gYo.js} +1 -1
- package/dist/assets/{VisualMapping-j08W3KBm.js → VisualMapping-CjyVXYl-.js} +1 -1
- package/dist/assets/auth-method-demo-B4sxmO2B.js +1 -0
- package/dist/assets/auto-login-provider-DPtJmWC-.js +1 -0
- package/dist/assets/{auto-login-provider-Bukw7zEe.js → auto-login-provider-pxf3kSYY.js} +2 -2
- package/dist/assets/auto-redirect-provider-B51i6R_U.js +1 -0
- package/dist/assets/auto-redirect-provider-CDA_97es.js +1 -0
- package/dist/assets/auto-redirect-provider-CeJOAYfx.js +1 -0
- package/dist/assets/calendar-B41MfAkB.js +1 -0
- package/dist/assets/{cas-sign-in-button-BQn3Mttx.js → cas-sign-in-button-DmkMYnGb.js} +1 -1
- package/dist/assets/{components-BT5mL9gW.js → components-DLLYm5Qd.js} +3 -3
- package/dist/assets/{configuration-gate-BJoadoA6.js → configuration-gate-CdktspSj.js} +1 -1
- package/dist/assets/{createClipPathFromCoordSys-m8IbnJ1z.js → createClipPathFromCoordSys-BKzf1iWj.js} +1 -1
- package/dist/assets/createSeriesDataSimply-Dcpkokbc.js +1 -0
- package/dist/assets/{customGraphicKeyframeAnimation-CYLPurwK.js → customGraphicKeyframeAnimation-BYcy-PGj.js} +1 -1
- package/dist/assets/demo-BpC2e3zt.js +1 -0
- package/dist/assets/demo-CEA7jJM2.js +1 -0
- package/dist/assets/demo-CX_Zf30k.js +1 -0
- package/dist/assets/demo-CyvGiwq2.js +1 -0
- package/dist/assets/demo-DJgGXOii.js +1 -0
- package/dist/assets/demo-GTMcYVzl.js +1 -0
- package/dist/assets/demo-O2QwJ70o.js +1 -0
- package/dist/assets/{dingtalk-sign-in-button-DSluFaP9.js → dingtalk-sign-in-button-K_2ZYQNC.js} +1 -1
- package/dist/assets/{echarts-preview-mRXoEciY.js → echarts-preview-CTj0x24f.js} +1 -1
- package/dist/assets/{echarts-runtime-CvLr4Slh.js → echarts-runtime-BTfAtt-4.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-BC1tvfQ0.js → echarts-runtime-advanced-Xg4WXsLl.js} +1 -1
- package/dist/assets/{echarts-runtime-common-DhNaV5B8.js → echarts-runtime-common-Dfe5ONoi.js} +1 -1
- package/dist/assets/{echarts-runtime-components-BiioS8t1.js → echarts-runtime-components-BNoMvPY0.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-CAWqYvFh.js → echarts-runtime-hierarchy-bEvX8V8l.js} +1 -1
- package/dist/assets/{floating-RVbHvIiD.js → floating-CVEm6jgD.js} +2 -2
- package/dist/assets/index-0O4dSmNz.js +59 -0
- package/dist/assets/index-BBKV6O5d.css +1 -0
- package/dist/assets/{index-BysibBZu.js → index-Bliz6Sy2.js} +2 -2
- package/dist/assets/index-CMoFG_fD.js +1064 -0
- package/dist/assets/{index-Dw20Lzpp.js → index-N5q0Lo-n.js} +3 -3
- package/dist/assets/{index-C-tdHirk.js → index-dDkRfkPI.js} +1 -1
- package/dist/assets/index-s-Gc7krA.js +38 -0
- package/dist/assets/{labelHelper-DbITmOUW.js → labelHelper-CSRwI3Ku.js} +1 -1
- package/dist/assets/ldap-sign-in-form-BoVMVmwm.js +1 -0
- package/dist/assets/{oidc-sign-in-button-Cgpa8Qzj.js → oidc-sign-in-button-5pd4bZSW.js} +1 -1
- package/dist/assets/{page-context-AU6RU0Qi.js → page-context-o6ISleo3.js} +4 -4
- package/dist/assets/prompt-card-xoH-b2hL.js +1 -0
- package/dist/assets/prompt-output-7ZS2-d1_.js +1 -0
- package/dist/assets/saml-sign-in-button-CN2l8pSN.js +1 -0
- package/dist/assets/{sectorHelper-C6L782Ue.js → sectorHelper-D1jHcZDN.js} +1 -1
- package/dist/assets/shortcut-DUGrhOUX.js +47 -0
- package/dist/assets/{sms-sign-in-form-Bo2WLZuP.js → sms-sign-in-form-ajsTToFF.js} +1 -1
- package/dist/assets/{static.browser-CoPxsICt.js → static.browser-exfE5oYi.js} +1 -1
- package/dist/assets/switch-BVTKHDeA.js +1 -0
- package/dist/assets/{tool-cards-ipgJ_3Ec.js → tool-cards-CQklNnUU.js} +3 -3
- package/dist/assets/{use-authenticator-sign-in-BzFeh7qe.js → use-authenticator-sign-in-B8OPLh-w.js} +1 -1
- package/dist/assets/use-dingtalk-sign-in-DsX00uPU.js +1 -0
- package/dist/assets/{use-wecom-sign-in-PPIMLvGa.js → use-wecom-sign-in-Di3WFrU1.js} +1 -1
- package/dist/assets/{wecom-sign-in-button-rUq9RW8R.js → wecom-sign-in-button-BO8h0tS8.js} +1 -1
- 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 +200 -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/data-table/data-table-filter.tsx +7 -4
- 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 +125 -0
- package/src/extensions/nocobase-users-example/extension.tsx +2 -100
- package/src/extensions/nocobase-users-example/index.ts +1 -1
- 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-B2qcpY8z.js +0 -1
- package/dist/assets/auto-login-provider-BMtzvdxG.js +0 -1
- package/dist/assets/auto-redirect-provider-C6GNPg5r.js +0 -1
- package/dist/assets/auto-redirect-provider-D5c6kN2R.js +0 -1
- package/dist/assets/auto-redirect-provider-iTzGEDpU.js +0 -1
- package/dist/assets/calendar-Dv2RNnsr.js +0 -1
- package/dist/assets/card-C9Hpeq1c.js +0 -1
- package/dist/assets/createSeriesDataSimply-DKUV5b5e.js +0 -1
- package/dist/assets/demo-BWGOwqtJ.js +0 -1
- package/dist/assets/demo-BWbcvkya.js +0 -1
- package/dist/assets/demo-DIASSPgn.js +0 -1
- package/dist/assets/demo-DuZ6asUU.js +0 -1
- package/dist/assets/demo-Dxcy4bDE.js +0 -1
- package/dist/assets/demo-YpEC76uw.js +0 -1
- package/dist/assets/demo-wzkoR8T2.js +0 -1
- package/dist/assets/index-CBZbj45X.js +0 -58
- package/dist/assets/index-CgkdZ6Oc.js +0 -686
- package/dist/assets/index-NH3aqqQz.css +0 -1
- package/dist/assets/index-hSBzW-VA.js +0 -43
- package/dist/assets/ldap-sign-in-form-BOMNDTkK.js +0 -1
- package/dist/assets/prompt-card-FWugRgi-.js +0 -1
- package/dist/assets/prompt-output-DMTuTNab.js +0 -1
- package/dist/assets/saml-sign-in-button-qt8bMeoQ.js +0 -1
- package/dist/assets/shortcut-Bu7jT_-w.js +0 -47
- package/dist/assets/switch-DQkkIE7G.js +0 -1
- package/dist/assets/use-dingtalk-sign-in-mrq6K5Yx.js +0 -1
- package/src/extensions/nocobase-users-example/layout.tsx +0 -16
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export function MailShowcasePage({
|
|
6
|
+
title,
|
|
7
|
+
description,
|
|
8
|
+
badge = "Interactive preview",
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
}: {
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
badge?: string;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={cn("space-y-10 pb-12", className)}>
|
|
20
|
+
<section className="flex flex-wrap items-start justify-between gap-5 border-b pb-8">
|
|
21
|
+
<div>
|
|
22
|
+
<div className="flex items-center gap-2">
|
|
23
|
+
<Badge variant="secondary">Mail Components</Badge>
|
|
24
|
+
<Badge variant="outline">{badge}</Badge>
|
|
25
|
+
</div>
|
|
26
|
+
<h1 className="mt-4 text-3xl font-semibold tracking-[-0.035em]">
|
|
27
|
+
{title}
|
|
28
|
+
</h1>
|
|
29
|
+
<p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
30
|
+
{description}
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
</section>
|
|
34
|
+
{children}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function MailShowcaseSection({
|
|
40
|
+
eyebrow,
|
|
41
|
+
title,
|
|
42
|
+
description,
|
|
43
|
+
children,
|
|
44
|
+
}: {
|
|
45
|
+
eyebrow: string;
|
|
46
|
+
title: string;
|
|
47
|
+
description: string;
|
|
48
|
+
children: ReactNode;
|
|
49
|
+
}) {
|
|
50
|
+
return (
|
|
51
|
+
<section className="space-y-5">
|
|
52
|
+
<div>
|
|
53
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
54
|
+
{eyebrow}
|
|
55
|
+
</p>
|
|
56
|
+
<h2 className="mt-2 text-xl font-semibold tracking-tight">{title}</h2>
|
|
57
|
+
<p className="mt-1.5 max-w-3xl text-sm leading-6 text-muted-foreground">
|
|
58
|
+
{description}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
{children}
|
|
62
|
+
</section>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Plus, Star, Trash2 } from "lucide-react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
import type { MailSignature } from "./types";
|
|
5
|
+
import type { MailSignatureValues } from "./use-mail-signatures";
|
|
6
|
+
import { MailRichEditor } from "./mail-rich-editor";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import { Label } from "@/components/ui/label";
|
|
10
|
+
import {
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogContent,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle,
|
|
15
|
+
} from "@/components/ui/dialog";
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
export interface MailSignatureManagerProps {
|
|
19
|
+
open: boolean;
|
|
20
|
+
onOpenChange: (open: boolean) => void;
|
|
21
|
+
signatures: MailSignature[];
|
|
22
|
+
onCreate: (values: MailSignatureValues) => Promise<MailSignature>;
|
|
23
|
+
onUpdate: (id: string, values: MailSignatureValues) => Promise<unknown>;
|
|
24
|
+
onRemove: (id: string) => Promise<unknown>;
|
|
25
|
+
onSetDefault: (id: string) => Promise<unknown>;
|
|
26
|
+
embedded?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EMPTY: MailSignatureValues = { name: "", content: "", isDefault: false };
|
|
30
|
+
|
|
31
|
+
export function MailSignatureManager({
|
|
32
|
+
open,
|
|
33
|
+
onOpenChange,
|
|
34
|
+
signatures,
|
|
35
|
+
onCreate,
|
|
36
|
+
onUpdate,
|
|
37
|
+
onRemove,
|
|
38
|
+
onSetDefault,
|
|
39
|
+
embedded = false,
|
|
40
|
+
}: MailSignatureManagerProps) {
|
|
41
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
42
|
+
const [form, setForm] = useState<MailSignatureValues>(EMPTY);
|
|
43
|
+
const [busy, setBusy] = useState(false);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!open) return;
|
|
47
|
+
const first = signatures[0];
|
|
48
|
+
if (first) {
|
|
49
|
+
setSelectedId(first.id);
|
|
50
|
+
setForm({
|
|
51
|
+
name: first.name,
|
|
52
|
+
content: first.content,
|
|
53
|
+
isDefault: Boolean(first.isDefault),
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
setSelectedId(null);
|
|
57
|
+
setForm(EMPTY);
|
|
58
|
+
}
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [open]);
|
|
61
|
+
|
|
62
|
+
const selectSignature = (signature: MailSignature) => {
|
|
63
|
+
setSelectedId(signature.id);
|
|
64
|
+
setForm({
|
|
65
|
+
name: signature.name,
|
|
66
|
+
content: signature.content,
|
|
67
|
+
isDefault: Boolean(signature.isDefault),
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const startNew = () => {
|
|
72
|
+
setSelectedId(null);
|
|
73
|
+
setForm(EMPTY);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const canSave = form.name.trim().length > 0;
|
|
77
|
+
const selectedIsDefault = signatures.find(
|
|
78
|
+
(signature) => signature.id === selectedId
|
|
79
|
+
)?.isDefault;
|
|
80
|
+
|
|
81
|
+
const handleSave = async () => {
|
|
82
|
+
if (!canSave) return;
|
|
83
|
+
setBusy(true);
|
|
84
|
+
try {
|
|
85
|
+
if (selectedId) await onUpdate(selectedId, form);
|
|
86
|
+
else setSelectedId((await onCreate(form)).id);
|
|
87
|
+
toast.success("Signature saved");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
toast.error(
|
|
90
|
+
error instanceof Error ? error.message : "Failed to save signature"
|
|
91
|
+
);
|
|
92
|
+
} finally {
|
|
93
|
+
setBusy(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleDelete = async (id: string) => {
|
|
98
|
+
setBusy(true);
|
|
99
|
+
try {
|
|
100
|
+
await onRemove(id);
|
|
101
|
+
if (selectedId === id) {
|
|
102
|
+
setSelectedId(null);
|
|
103
|
+
setForm(EMPTY);
|
|
104
|
+
}
|
|
105
|
+
toast.success("Signature deleted");
|
|
106
|
+
} catch (error) {
|
|
107
|
+
toast.error(
|
|
108
|
+
error instanceof Error ? error.message : "Failed to delete signature"
|
|
109
|
+
);
|
|
110
|
+
} finally {
|
|
111
|
+
setBusy(false);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleSetDefault = async () => {
|
|
116
|
+
if (!selectedId) return;
|
|
117
|
+
setBusy(true);
|
|
118
|
+
try {
|
|
119
|
+
await onSetDefault(selectedId);
|
|
120
|
+
setForm((prev) => ({ ...prev, isDefault: !selectedIsDefault }));
|
|
121
|
+
toast.success(
|
|
122
|
+
selectedIsDefault
|
|
123
|
+
? "Default signature cleared"
|
|
124
|
+
: "Default signature set"
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
toast.error(
|
|
128
|
+
error instanceof Error
|
|
129
|
+
? error.message
|
|
130
|
+
: "Failed to update default signature"
|
|
131
|
+
);
|
|
132
|
+
} finally {
|
|
133
|
+
setBusy(false);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const editor = (
|
|
138
|
+
<div className="grid gap-4 sm:grid-cols-[180px_1fr]">
|
|
139
|
+
<div className="flex flex-col gap-2">
|
|
140
|
+
<div className="flex max-h-80 flex-col gap-0.5 overflow-y-auto">
|
|
141
|
+
{signatures.map((signature) => (
|
|
142
|
+
<div
|
|
143
|
+
key={signature.id}
|
|
144
|
+
className={cn(
|
|
145
|
+
"group flex items-center rounded-md text-sm transition-colors",
|
|
146
|
+
selectedId === signature.id
|
|
147
|
+
? "bg-muted font-medium"
|
|
148
|
+
: "hover:bg-muted/50"
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
onClick={() => selectSignature(signature)}
|
|
154
|
+
className="flex min-w-0 flex-1 items-center gap-2 px-2.5 py-2 text-left"
|
|
155
|
+
>
|
|
156
|
+
<span className="min-w-0 flex-1 truncate">{signature.name}</span>
|
|
157
|
+
{signature.isDefault && (
|
|
158
|
+
<Star className="size-3.5 shrink-0 fill-amber-400 text-amber-400" />
|
|
159
|
+
)}
|
|
160
|
+
</button>
|
|
161
|
+
<Button
|
|
162
|
+
variant="ghost"
|
|
163
|
+
size="icon-sm"
|
|
164
|
+
title={`Delete ${signature.name}`}
|
|
165
|
+
aria-label={`Delete ${signature.name}`}
|
|
166
|
+
disabled={busy}
|
|
167
|
+
onClick={() => void handleDelete(signature.id)}
|
|
168
|
+
className="mr-1 shrink-0 text-muted-foreground opacity-60 hover:text-destructive sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100"
|
|
169
|
+
>
|
|
170
|
+
<Trash2 />
|
|
171
|
+
</Button>
|
|
172
|
+
</div>
|
|
173
|
+
))}
|
|
174
|
+
{!signatures.length && (
|
|
175
|
+
<p className="px-2.5 py-2 text-xs text-muted-foreground">
|
|
176
|
+
No signatures yet
|
|
177
|
+
</p>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
<Button variant="outline" size="sm" onClick={startNew}>
|
|
181
|
+
<Plus />
|
|
182
|
+
New
|
|
183
|
+
</Button>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div className="flex flex-col gap-3">
|
|
187
|
+
<div className="space-y-1.5">
|
|
188
|
+
<Label className="text-xs text-muted-foreground">Name</Label>
|
|
189
|
+
<Input
|
|
190
|
+
value={form.name}
|
|
191
|
+
onChange={(e) =>
|
|
192
|
+
setForm((prev) => ({ ...prev, name: e.target.value }))
|
|
193
|
+
}
|
|
194
|
+
placeholder="e.g. Work"
|
|
195
|
+
className="h-9"
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div className="space-y-1.5">
|
|
200
|
+
<Label className="text-xs text-muted-foreground">Content</Label>
|
|
201
|
+
<MailRichEditor
|
|
202
|
+
value={form.content}
|
|
203
|
+
onChange={(content) => setForm((prev) => ({ ...prev, content }))}
|
|
204
|
+
placeholder="Signature content…"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<label className="flex items-center gap-2 text-sm">
|
|
209
|
+
<input
|
|
210
|
+
type="checkbox"
|
|
211
|
+
checked={Boolean(form.isDefault)}
|
|
212
|
+
onChange={(e) =>
|
|
213
|
+
setForm((prev) => ({ ...prev, isDefault: e.target.checked }))
|
|
214
|
+
}
|
|
215
|
+
className="size-4 accent-primary"
|
|
216
|
+
/>
|
|
217
|
+
Default signature (auto-insert on new messages)
|
|
218
|
+
</label>
|
|
219
|
+
|
|
220
|
+
<div className="flex items-center justify-end">
|
|
221
|
+
<div className="flex items-center gap-2">
|
|
222
|
+
{selectedId && (
|
|
223
|
+
<Button
|
|
224
|
+
variant="outline"
|
|
225
|
+
size="sm"
|
|
226
|
+
onClick={() => void handleSetDefault()}
|
|
227
|
+
disabled={busy}
|
|
228
|
+
>
|
|
229
|
+
<Star />
|
|
230
|
+
{selectedIsDefault ? "Unset default" : "Set default"}
|
|
231
|
+
</Button>
|
|
232
|
+
)}
|
|
233
|
+
<Button
|
|
234
|
+
size="sm"
|
|
235
|
+
onClick={() => void handleSave()}
|
|
236
|
+
disabled={!canSave || busy}
|
|
237
|
+
>
|
|
238
|
+
Save
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (embedded) return editor;
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
250
|
+
<DialogContent className="sm:max-w-3xl">
|
|
251
|
+
<DialogHeader>
|
|
252
|
+
<DialogTitle>Signatures</DialogTitle>
|
|
253
|
+
</DialogHeader>
|
|
254
|
+
{editor}
|
|
255
|
+
</DialogContent>
|
|
256
|
+
</Dialog>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const SIGNATURE_ATTR = "data-mail-signature";
|
|
2
|
+
|
|
3
|
+
export function createSignatureId() {
|
|
4
|
+
return `sig-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function stripSignature(html: string): string {
|
|
8
|
+
if (!html) return "";
|
|
9
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
10
|
+
doc.body.querySelectorAll(`[${SIGNATURE_ATTR}]`).forEach((el) => el.remove());
|
|
11
|
+
return doc.body.innerHTML;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function wrapSignature(content: string): string {
|
|
15
|
+
return `<div ${SIGNATURE_ATTR}="true" style="margin-top:16px;padding-top:12px;border-top:1px solid #e4e4e7">${content}</div>`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function applySignature(
|
|
19
|
+
html: string,
|
|
20
|
+
signatureContent: string | null | undefined
|
|
21
|
+
): string {
|
|
22
|
+
const base = stripSignature(html);
|
|
23
|
+
if (!signatureContent) return base;
|
|
24
|
+
return `${base}${wrapSignature(signatureContent)}`;
|
|
25
|
+
}
|