Two-way draft sync, signatures, sent/bcc fixes, and mail-list UI
Backend - Sent copies: persist locally on send + periodic multi-folder CONDSTORE poll (Sent/Archive/Trash/Junk) so they thread and show without an IDLE-only INBOX. - Bcc: transmitted copy omits Bcc, Sent copy keeps it; store/display bcc_addr. - Drafts: mirror local drafts to the IMAP Drafts folder (append/replace/expunge); inbound sync of foreign drafts (un-threaded) with adopt-to-edit + dedup by UID. - Per-user storage quota (5 GiB default) and manual "sync all folders" action. - Account editing (display/color/server/password) and per-account HTML signature. - Account view scoped to that account's inbox; new migrations 0006–0009. Frontend - Mail-list pagination, folder filters (All/Unread/Pinned), sort toggle, refresh. - Aligned + widened mail-list margins; reading-pane bars fixed height. - Rich-text composer: color palette, highlight, image; attachments; expand/pop-out. - Signatures: auto-insert for new mail, replace on From change, reply insert. - Restore/Delete actions per folder; drafts view merges local + remote, date-sorted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import datetime as dt
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
@@ -54,6 +55,11 @@ class User(Base):
|
||||
settings: Mapped[dict[str, object]] = mapped_column(
|
||||
JSONB, nullable=False, server_default=text("'{}'")
|
||||
)
|
||||
# Storage quota in bytes; per-user so an admin can override it later.
|
||||
# Default 5 GiB (5 * 1024^3).
|
||||
quota_bytes: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=False, server_default=text("5368709120")
|
||||
)
|
||||
created_at: Mapped[dt.datetime] = _now()
|
||||
|
||||
|
||||
@@ -68,6 +74,8 @@ class Account(Base):
|
||||
email: Mapped[str] = mapped_column(CITEXT, nullable=False)
|
||||
display_name: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
color: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("'#888888'"))
|
||||
# Per-account HTML signature, inserted into new messages (and via compose menu).
|
||||
signature: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("''"))
|
||||
imap_config: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False)
|
||||
smtp_config: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False)
|
||||
secret_ct: Mapped[bytes] = mapped_column(BYTEA, nullable=False)
|
||||
@@ -158,6 +166,9 @@ class Message(Base):
|
||||
cc_addr: Mapped[list[dict[str, str]]] = mapped_column(
|
||||
JSONB, nullable=False, server_default=text("'[]'")
|
||||
)
|
||||
bcc_addr: Mapped[list[dict[str, str]]] = mapped_column(
|
||||
JSONB, nullable=False, server_default=text("'[]'")
|
||||
)
|
||||
date_hdr: Mapped[dt.datetime | None] = mapped_column(TIMESTAMP(timezone=True))
|
||||
internal_date: Mapped[dt.datetime] = mapped_column(TIMESTAMP(timezone=True), nullable=False)
|
||||
flags: Mapped[list[str]] = mapped_column(
|
||||
@@ -233,6 +244,9 @@ class Draft(Base):
|
||||
)
|
||||
kind: Mapped[str] = mapped_column(Text, nullable=False, server_default=text("'reply'"))
|
||||
payload: Mapped[dict[str, object]] = mapped_column(JSONB, nullable=False)
|
||||
# UID of the mirrored copy in the account's IMAP Drafts folder (so the draft
|
||||
# is visible/continuable on other mail clients). Replaced on each edit.
|
||||
imap_uid: Mapped[int | None] = mapped_column(Integer)
|
||||
updated_at: Mapped[dt.datetime] = _now()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user