From 9da04057b957b2ef9d82389f9d15427f0c4379df Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Mon, 20 Jul 2026 10:39:16 +0200 Subject: [PATCH] feat: mail list font size, remove Termin toolbar, rename Flagge to Markierung with symbol picker - Mail list: title and subject font text-xs -> text-sm - Remove Termin (create-event) toolbar button - Rename Flagge -> Markierung with select dropdown (star, flag, bookmark, important, question) - Backend: add flag_type column to Mail model + migration 0006 - Backend: flag_type in MailFlagsUpdate schema, update_flags route, mail_to_response - Frontend: flag_type in Mail interface, FlagUpdatePayload, MailList symbol display --- .../mail/migrations/0006_flag_type.sql | 2 + app/plugins/builtins/mail/models.py | 1 + app/plugins/builtins/mail/routes.py | 2 + app/plugins/builtins/mail/schemas.py | 2 + app/plugins/builtins/mail/services.py | 1 + frontend/src/api/mail.ts | 2 + frontend/src/components/mail/MailList.tsx | 19 +++++-- frontend/src/pages/Mail.tsx | 51 +++++++++++-------- 8 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 app/plugins/builtins/mail/migrations/0006_flag_type.sql diff --git a/app/plugins/builtins/mail/migrations/0006_flag_type.sql b/app/plugins/builtins/mail/migrations/0006_flag_type.sql new file mode 100644 index 0000000..5b5858e --- /dev/null +++ b/app/plugins/builtins/mail/migrations/0006_flag_type.sql @@ -0,0 +1,2 @@ +-- Add flag_type column to mails table for multiple symbol types +ALTER TABLE mails ADD COLUMN IF NOT EXISTS flag_type VARCHAR(32); diff --git a/app/plugins/builtins/mail/models.py b/app/plugins/builtins/mail/models.py index 3f32610..29458f0 100644 --- a/app/plugins/builtins/mail/models.py +++ b/app/plugins/builtins/mail/models.py @@ -130,6 +130,7 @@ class Mail(Base, TenantMixin): body_html_sanitized: Mapped[str] = mapped_column(Text, nullable=False, default="") is_seen: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_flagged: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + flag_type: Mapped[str | None] = mapped_column(String(32), nullable=True, default=None) is_draft: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_answered: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_forwarded: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) diff --git a/app/plugins/builtins/mail/routes.py b/app/plugins/builtins/mail/routes.py index 685f87f..c4446cd 100644 --- a/app/plugins/builtins/mail/routes.py +++ b/app/plugins/builtins/mail/routes.py @@ -1290,6 +1290,8 @@ async def update_flags( mail.is_seen = data.is_seen if data.is_flagged is not None: mail.is_flagged = data.is_flagged + if data.flag_type is not None: + mail.flag_type = data.flag_type if data.flag_type else None if data.is_draft is not None: mail.is_draft = data.is_draft if data.is_answered is not None: diff --git a/app/plugins/builtins/mail/schemas.py b/app/plugins/builtins/mail/schemas.py index c0e0813..96bcc4c 100644 --- a/app/plugins/builtins/mail/schemas.py +++ b/app/plugins/builtins/mail/schemas.py @@ -145,6 +145,7 @@ class MailForwardRequest(BaseModel): class MailFlagsUpdate(BaseModel): is_seen: bool | None = None is_flagged: bool | None = None + flag_type: str | None = None is_draft: bool | None = None is_answered: bool | None = None is_forwarded: bool | None = None @@ -166,6 +167,7 @@ class MailResponse(BaseModel): body_html_sanitized: str is_seen: bool is_flagged: bool + flag_type: str | None = None is_draft: bool is_answered: bool is_forwarded: bool diff --git a/app/plugins/builtins/mail/services.py b/app/plugins/builtins/mail/services.py index 91ab2c6..7f62175 100644 --- a/app/plugins/builtins/mail/services.py +++ b/app/plugins/builtins/mail/services.py @@ -1562,6 +1562,7 @@ def mail_to_response( "date": mail.received_at.isoformat() if mail.received_at else (mail.sent_at.isoformat() if mail.sent_at else None), "is_seen": mail.is_seen, "is_flagged": mail.is_flagged, + "flag_type": mail.flag_type, "is_draft": mail.is_draft, "is_answered": mail.is_answered, "is_forwarded": mail.is_forwarded, diff --git a/frontend/src/api/mail.ts b/frontend/src/api/mail.ts index c269c9c..ba24594 100644 --- a/frontend/src/api/mail.ts +++ b/frontend/src/api/mail.ts @@ -65,6 +65,7 @@ export interface Mail { date: string; is_seen: boolean; is_flagged: boolean; + flag_type?: string | null; is_draft: boolean; is_answered: boolean; has_attachments: boolean; @@ -263,6 +264,7 @@ export interface ForwardPayload { export interface FlagUpdatePayload { is_seen?: boolean; is_flagged?: boolean; + flag_type?: string | null; is_draft?: boolean; is_answered?: boolean; is_forwarded?: boolean; diff --git a/frontend/src/components/mail/MailList.tsx b/frontend/src/components/mail/MailList.tsx index b3f83c9..3ce1c9c 100644 --- a/frontend/src/components/mail/MailList.tsx +++ b/frontend/src/components/mail/MailList.tsx @@ -173,9 +173,18 @@ export function MailList({ )} {mail.is_flagged && ( - - - + + {mail.flag_type === 'star' && } + {mail.flag_type === 'flag' && 🚩} + {mail.flag_type === 'bookmark' && 🔖} + {mail.flag_type === 'important' && } + {mail.flag_type === 'question' && } + {(!mail.flag_type || mail.flag_type === '') && ( + + + + )} + )} {mail.has_attachments && (
- + {decodeMimeHeader(mail.from_name) || mail.from_address} {formatDate(mail.date)}
-

+

{decodeMimeHeader(mail.subject) || t('mail.noSubject')}

{mail.labels && mail.labels.length > 0 && ( diff --git a/frontend/src/pages/Mail.tsx b/frontend/src/pages/Mail.tsx index ef92c55..2039abc 100644 --- a/frontend/src/pages/Mail.tsx +++ b/frontend/src/pages/Mail.tsx @@ -340,6 +340,21 @@ export function MailPage() { [toast], ); + // Handle set flag type (symbol picker) + const handleSetFlagType = useCallback( + async (mail: Mail, flagType: string) => { + try { + const isFlagged = flagType !== ''; + await updateFlags(mail.id, { is_flagged: isFlagged, flag_type: flagType || null }); + setSelectedMail((prev) => (prev ? { ...prev, is_flagged: isFlagged, flag_type: flagType || null } : prev)); + setMails((prev) => prev.map((m) => (m.id === mail.id ? { ...m, is_flagged: isFlagged, flag_type: flagType || null } : m))); + } catch (err) { + toast.error(err instanceof Error ? err.message : String(err)); + } + }, + [toast], + ); + // Handle toggle select for bulk operations const handleToggleSelect = useCallback((mailId: string) => { setSelectedMailIds((prev) => { @@ -532,29 +547,21 @@ export function MailPage() { { id: 'flag', plugin: 'mail', - label: 'Flagge', + label: 'Markierung', group: 'mail-actions', disabled: !hasMail, - active: selectedMailFlagged, - icon: ( - - - - ), - onClick: () => selectedMail && handleToggleFlag(selectedMail), - }, - { - id: 'create-event', - plugin: 'mail', - label: 'Termin', - group: 'mail-actions', - disabled: !hasMail, - icon: ( - - - - ), - onClick: () => selectedMail && handleCreateEvent(selectedMail), + type: 'select' as const, + selectValue: selectedMail?.flag_type || '', + selectOptions: [ + { value: '', label: 'Keine' }, + { value: 'star', label: '⭐ Stern' }, + { value: 'flag', label: '🚩 Flagge' }, + { value: 'bookmark', label: '🔖 Lesezeichen' }, + { value: 'important', label: '❗ Wichtig' }, + { value: 'question', label: '❓ Frage' }, + ], + onSelect: (value: string) => selectedMail && handleSetFlagType(selectedMail, value), + onClick: () => {}, }, { id: 'delete', @@ -667,7 +674,7 @@ export function MailPage() { } registerItems('mail', items); return () => unregisterPlugin('mail'); - }, [selectedMailId, selectedMailFlagged, selectedMailIds, handleSearch, handleCompose, handleReply, handleForward, handleToggleFlag, handleCreateEvent, handleDeleteMail, handleBulkDelete, handleBulkMarkRead, handleBulkMarkUnread, t]); + }, [selectedMailId, selectedMailFlagged, selectedMailIds, handleSearch, handleCompose, handleReply, handleForward, handleToggleFlag, handleSetFlagType, handleDeleteMail, handleBulkDelete, handleBulkMarkRead, handleBulkMarkUnread, t]); if (loadingAccounts) { return (