- New stores/signature-store.ts: Zustand persist with CRUD, default/reply signature IDs, per-identity signature mapping - New signature-settings.tsx: list management with add/edit/delete/duplicate - New signature-editor-modal.tsx: TipTap rich text editor for signatures - email-composer.tsx: auto-insert signature based on mode (compose/reply) + signature selector dropdown in toolbar - identity-form.tsx: per-identity default/reply signature dropdowns - settings/page.tsx: Signatures tab in Mail settings group
132 lines
4.2 KiB
TypeScript
132 lines
4.2 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
import { generateUUID } from '@/lib/utils';
|
|
|
|
export interface Signature {
|
|
id: string;
|
|
name: string;
|
|
body: string;
|
|
plainText: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
interface SignatureState {
|
|
signatures: Signature[];
|
|
defaultSignatureId: string | null;
|
|
replySignatureId: string | null;
|
|
identitySignatureMap: Record<string, { defaultId?: string; replyId?: string }>;
|
|
addSignature: (sig: Omit<Signature, 'id' | 'createdAt' | 'updatedAt'>) => Signature;
|
|
updateSignature: (id: string, updates: Partial<Pick<Signature, 'name' | 'body' | 'plainText'>>) => void;
|
|
deleteSignature: (id: string) => void;
|
|
duplicateSignature: (id: string) => Signature;
|
|
setDefaultSignatureId: (id: string | null) => void;
|
|
setReplySignatureId: (id: string | null) => void;
|
|
getSignatureById: (id: string) => Signature | undefined;
|
|
setIdentitySignature: (identityId: string, type: 'default' | 'reply', signatureId: string | null) => void;
|
|
getIdentityDefaultSignatureId: (identityId: string) => string | null;
|
|
getIdentityReplySignatureId: (identityId: string) => string | null;
|
|
}
|
|
|
|
export const useSignatureStore = create<SignatureState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
signatures: [],
|
|
defaultSignatureId: null,
|
|
replySignatureId: null,
|
|
identitySignatureMap: {},
|
|
|
|
addSignature: (sig) => {
|
|
const now = new Date().toISOString();
|
|
const newSig: Signature = {
|
|
...sig,
|
|
id: generateUUID(),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
set((state) => ({
|
|
signatures: [...state.signatures, newSig],
|
|
}));
|
|
return newSig;
|
|
},
|
|
|
|
updateSignature: (id, updates) => {
|
|
set((state) => ({
|
|
signatures: state.signatures.map((s) =>
|
|
s.id === id
|
|
? { ...s, ...updates, updatedAt: new Date().toISOString() }
|
|
: s
|
|
),
|
|
}));
|
|
},
|
|
|
|
deleteSignature: (id) => {
|
|
set((state) => ({
|
|
signatures: state.signatures.filter((s) => s.id !== id),
|
|
defaultSignatureId: state.defaultSignatureId === id ? null : state.defaultSignatureId,
|
|
replySignatureId: state.replySignatureId === id ? null : state.replySignatureId,
|
|
}));
|
|
},
|
|
|
|
duplicateSignature: (id) => {
|
|
const original = get().signatures.find((s) => s.id === id);
|
|
if (!original) {
|
|
throw new Error(`Signature with id ${id} not found`);
|
|
}
|
|
const now = new Date().toISOString();
|
|
const duplicate: Signature = {
|
|
...original,
|
|
id: generateUUID(),
|
|
name: `${original.name} (copy)`,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
set((state) => ({
|
|
signatures: [...state.signatures, duplicate],
|
|
}));
|
|
return duplicate;
|
|
},
|
|
|
|
setDefaultSignatureId: (id) => {
|
|
set({ defaultSignatureId: id });
|
|
},
|
|
|
|
setReplySignatureId: (id) => {
|
|
set({ replySignatureId: id });
|
|
},
|
|
|
|
getSignatureById: (id) => {
|
|
return get().signatures.find((s) => s.id === id);
|
|
},
|
|
|
|
setIdentitySignature: (identityId, type, signatureId) => {
|
|
set((state) => {
|
|
const current = state.identitySignatureMap[identityId] ?? {};
|
|
const updated = {
|
|
...current,
|
|
[type === 'default' ? 'defaultId' : 'replyId']: signatureId ?? undefined,
|
|
};
|
|
if (updated.defaultId === undefined && updated.replyId === undefined) {
|
|
const { [identityId]: _, ...rest } = state.identitySignatureMap;
|
|
return { identitySignatureMap: rest };
|
|
}
|
|
return { identitySignatureMap: { ...state.identitySignatureMap, [identityId]: updated } };
|
|
});
|
|
},
|
|
|
|
getIdentityDefaultSignatureId: (identityId) => {
|
|
const entry = get().identitySignatureMap[identityId];
|
|
return entry?.defaultId ?? get().defaultSignatureId;
|
|
},
|
|
|
|
getIdentityReplySignatureId: (identityId) => {
|
|
const entry = get().identitySignatureMap[identityId];
|
|
return entry?.replyId ?? get().replySignatureId ?? get().defaultSignatureId;
|
|
},
|
|
}),
|
|
{
|
|
name: 'signature-storage',
|
|
}
|
|
)
|
|
);
|