attachments preview overlay, docx/xlsx/odf rendering, composer drag-drop & undo-send UX
Frontend: - Full-screen attachment preview overlay (AttachmentPreview) for image/pdf/txt plus office docs: .docx via docx-preview (rendered live into the sandboxed iframe so header/footer VML images survive), .xlsx via xlsx-preview, and .odt/.ods/.odp/.odg via a JSZip+DOMParser ODF walker (officeConvert). - Type-aware attachment icons (green spreadsheet, blue document, red PDF, image/archive); new document/image/spreadsheet/print SVGs. - Composer: drag-drop attachments (body drop inserts images inline, else attaches); inserted images are selectable/resizable/deletable (RichTextEditor). - Undo-send: "Sending…" banner with live "N seconds to Undo" countdown. - Search result list: pin/archive/delete update correctly; grey-out only for search hits; scoped "Search in Trash/Spam". Backend: - Search: prefix (search-as-you-type) matching, email-address search, and address-first result ordering; Trash/Junk excluded with scoped folder search. - Attachments: RFC 6266 Content-Disposition for non-ASCII filenames; one-time inline image loading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rcj3TpydMTzTSmU7kunzf8
This commit is contained in:
@@ -257,6 +257,17 @@ class PgSearchBackend(SearchBackend):
|
||||
for cond in conditions:
|
||||
stmt = stmt.where(cond)
|
||||
|
||||
# Exclude Trash/Junk from search by default (Gmail-style), so deleting a
|
||||
# message from search results removes it. An explicit `in:trash`/`in:junk`
|
||||
# overrides this to search there on purpose.
|
||||
if not _targets_removal_folder(groups):
|
||||
stmt = stmt.where(
|
||||
or_(
|
||||
Mailbox.special_use.is_(None),
|
||||
Mailbox.special_use.notin_(_REMOVAL_FOLDERS),
|
||||
)
|
||||
)
|
||||
|
||||
decoded = _decode_cursor(cursor)
|
||||
if decoded is not None:
|
||||
cur_hit, cur_date, cur_id = decoded
|
||||
@@ -291,6 +302,23 @@ class PgSearchBackend(SearchBackend):
|
||||
return hits, next_cursor
|
||||
|
||||
|
||||
_REMOVAL_FOLDERS = ("trash", "junk")
|
||||
|
||||
|
||||
def _targets_removal_folder(groups: list[list[dict[str, object]]]) -> bool:
|
||||
"""True if the query explicitly scopes to Trash/Junk via an ``in:`` filter, in
|
||||
which case the default removal-folder exclusion must not apply."""
|
||||
for group in groups:
|
||||
for clause in group:
|
||||
if (
|
||||
clause.get("type") == "filter"
|
||||
and clause.get("key") == "in"
|
||||
and str(clause.get("value", "")).strip().lower() in _REMOVAL_FOLDERS
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _positive_text_terms(groups: list[list[dict[str, object]]]) -> list[str]:
|
||||
terms: list[str] = []
|
||||
for group in groups:
|
||||
|
||||
Reference in New Issue
Block a user