Files
SRCmail/stores/signature-store.ts
Bernd Rodler 67f61f18d0 feat: P2.1 extended signatures — multiple per identity + TipTap editor
- 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
2026-08-07 12:55:14 +02:00

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',
}
)
);