From 884fdeba1034985d3888af4f1215aae4da9f7917 Mon Sep 17 00:00:00 2001 From: rustmailer Date: Thu, 19 Mar 2026 15:44:20 +0800 Subject: [PATCH] feat(search): expand default search scope and support specific field filtering --- src/modules/duckdb/init.rs | 16 ++ src/modules/imap/tests.rs | 2 - src/modules/message/search.rs | 26 +- web/src/features/search/filter-reset.tsx | 4 +- web/src/features/search/table/toolbar.tsx | 4 +- web/src/features/search/text-search-input.tsx | 244 +++++++++--------- web/src/locales/ar.json | 5 +- web/src/locales/da.json | 5 +- web/src/locales/de.json | 5 +- web/src/locales/en.json | 5 +- web/src/locales/es.json | 5 +- web/src/locales/fi.json | 5 +- web/src/locales/fr.json | 5 +- web/src/locales/it.json | 5 +- web/src/locales/jp.json | 5 +- web/src/locales/ko.json | 5 +- web/src/locales/nl.json | 5 +- web/src/locales/no.json | 5 +- web/src/locales/pl.json | 5 +- web/src/locales/pt.json | 5 +- web/src/locales/ru.json | 5 +- web/src/locales/sv.json | 5 +- web/src/locales/zh-tw.json | 5 +- web/src/locales/zh.json | 5 +- 24 files changed, 236 insertions(+), 150 deletions(-) diff --git a/src/modules/duckdb/init.rs b/src/modules/duckdb/init.rs index db05240..8a5e598 100644 --- a/src/modules/duckdb/init.rs +++ b/src/modules/duckdb/init.rs @@ -1168,6 +1168,18 @@ impl DuckDBManager { } } + if let Some(subject) = filter.subject { + let pattern = format!("(?i){}", subject); + base_sql.push_str(" AND regexp_matches(coalesce(e.subject, ''), ?)"); + args.push(pattern.into()); + } + + if let Some(body) = filter.body { + let pattern = format!("(?i){}", body); + base_sql.push_str(" AND regexp_matches(coalesce(e.body, ''), ?)"); + args.push(pattern.into()); + } + if let Some(text) = filter.text { let pattern = format!("(?i){}", text); base_sql.push_str( @@ -1175,10 +1187,14 @@ impl DuckDBManager { AND ( regexp_matches(coalesce(e.subject, ''), ?) OR regexp_matches(coalesce(e.body, ''), ?) + OR regexp_matches(coalesce(e.sender, ''), ?) + OR regexp_matches(array_to_string(e.recipients, ','), ?) ) ", ); args.push(pattern.clone().into()); + args.push(pattern.clone().into()); + args.push(pattern.clone().into()); args.push(pattern.into()); } diff --git a/src/modules/imap/tests.rs b/src/modules/imap/tests.rs index 02a1f30..b761ea3 100644 --- a/src/modules/imap/tests.rs +++ b/src/modules/imap/tests.rs @@ -16,8 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::io::Write; - use mail_parser::{parsers::MessageStream, MessageParser, MimeHeaders}; use crate::{ diff --git a/src/modules/message/search.rs b/src/modules/message/search.rs index 92395fe..c3766ad 100644 --- a/src/modules/message/search.rs +++ b/src/modules/message/search.rs @@ -34,6 +34,8 @@ use crate::{ #[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Object)] pub struct SearchFilter { pub text: Option, + pub subject: Option, + pub body: Option, pub from: Option, pub to: Option, pub cc: Option, @@ -80,15 +82,23 @@ impl SearchRequest { )); } - if let Some(ref pattern) = self.filter.text { - if let Err(_) = duckdb()?.validate_regex(pattern) { - return Err(raise_error!( - "Invalid search pattern: The regular expression is not supported by DuckDB." - .into(), - ErrorCode::InvalidParameter - )); + let validate = |pattern: &Option| -> BichonResult<()> { + if let Some(ref p) = pattern { + if duckdb()?.validate_regex(p).is_err() { + return Err(raise_error!( + "Invalid search pattern: The regular expression is not supported by DuckDB.".into(), + ErrorCode::InvalidParameter + )); + } } - } + Ok(()) + }; + + validate(&self.filter.text)?; + validate(&self.filter.subject)?; + validate(&self.filter.body)?; + validate(&self.filter.from)?; + validate(&self.filter.to)?; Ok(()) } diff --git a/web/src/features/search/filter-reset.tsx b/web/src/features/search/filter-reset.tsx index f3fac81..9540999 100644 --- a/web/src/features/search/filter-reset.tsx +++ b/web/src/features/search/filter-reset.tsx @@ -8,7 +8,7 @@ export function FilterResetButton() { const { filter, setFilter } = useSearchContext(); const { t } = useTranslation() const { q, ...restFilters } = filter; - + const activeFiltersCount = Object.keys(restFilters).filter(key => { const value = restFilters[key]; if (Array.isArray(value)) return value.length > 0; @@ -19,7 +19,7 @@ export function FilterResetButton() { return ( +
+
+ +
+ + setValue(e.target.value)} + onFocus={() => setShowHistory(true)} + onKeyDown={(e) => e.key === "Enter" && handleSearch()} + placeholder={t("search_input.placeholder")} + className="h-9 border-none shadow-none focus-visible:ring-0 pl-9 pr-10 text-sm bg-transparent w-full" + /> + {value && ( + + )}
+
- {showHistory && ( -
-
+
+
- {t('search_input.recent_title')} + {t("search_input.recent_title")}
{history.length > 0 && ( )}
- {history.length > 0 ? ( - history.map((term, idx) => ( - - )) - ) : ( -
- {t('search_input.no_history')} -
- )} +
+ {history.length > 0 ? ( + history.map((term, idx) => ( + + )) + ) : ( +
+ {t("search_input.no_history")} +
+ )} +
)}
diff --git a/web/src/locales/ar.json b/web/src/locales/ar.json index 56b49db..aa1772e 100644 --- a/web/src/locales/ar.json +++ b/web/src/locales/ar.json @@ -1555,6 +1555,9 @@ "recent_title": "عمليات البحث الأخيرة", "clear_history": "مسح السجل", "no_history": "لا يوجد سجل بحث", - "hint": "نطاق البحث الافتراضي: العنوان، المحتوى، وأسماء المرفقات" + "all": "الكل", + "all_fields_desc": "يطابق موضوع الرسالة، النص، المرسل والمستلمين", + "subject": "موضوع الرسالة", + "body": "نص الرسالة" } } \ No newline at end of file diff --git a/web/src/locales/da.json b/web/src/locales/da.json index d6e7e1a..603b781 100644 --- a/web/src/locales/da.json +++ b/web/src/locales/da.json @@ -1555,6 +1555,9 @@ "recent_title": "Seneste søgninger", "clear_history": "Ryd historik", "no_history": "Ingen søgehistorik", - "hint": "Standard søgeområde: emne, indhold og vedhæftningsnavne" + "all": "Alle", + "all_fields_desc": "Matcher emne, indhold, afsender og modtagere", + "subject": "Emne", + "body": "Indhold" } } \ No newline at end of file diff --git a/web/src/locales/de.json b/web/src/locales/de.json index 645d055..b8f2699 100644 --- a/web/src/locales/de.json +++ b/web/src/locales/de.json @@ -1555,6 +1555,9 @@ "recent_title": "Letzte Suchanfragen", "clear_history": "Verlauf löschen", "no_history": "Kein Suchverlauf", - "hint": "Standard-Suchbereich: Betreff, Inhalt und Anhangnamen" + "all": "Alle Felder", + "all_fields_desc": "Matcht Betreff, Text, Absender und Empfänger", + "subject": "Betreff", + "body": "Inhalt" } } \ No newline at end of file diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 26c20f3..464124e 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -1555,6 +1555,9 @@ "recent_title": "Recent searches", "clear_history": "Clear history", "no_history": "No search history", - "hint": "Default scope: subject, body and attachment names" + "all": "All Fields", + "all_fields_desc": "Matches subject, body, sender, and recipients", + "subject": "Subject", + "body": "Body" } } \ No newline at end of file diff --git a/web/src/locales/es.json b/web/src/locales/es.json index 87dc298..c1f6b48 100644 --- a/web/src/locales/es.json +++ b/web/src/locales/es.json @@ -1555,6 +1555,9 @@ "recent_title": "Búsquedas recientes", "clear_history": "Borrar historial", "no_history": "Sin historial de búsqueda", - "hint": "Ámbito de búsqueda predeterminado: asunto, contenido y nombres de archivos adjuntos" + "all": "Todo", + "all_fields_desc": "Coincide con asunto, cuerpo, remitente y destinatarios", + "subject": "Asunto", + "body": "Cuerpo del mensaje" } } \ No newline at end of file diff --git a/web/src/locales/fi.json b/web/src/locales/fi.json index 3aec748..2c8cc1a 100644 --- a/web/src/locales/fi.json +++ b/web/src/locales/fi.json @@ -1555,6 +1555,9 @@ "recent_title": "Viimeisimmät haut", "clear_history": "Tyhjennä historia", "no_history": "Ei hakuhistoriaa", - "hint": "Oletushakualue: otsikko, sisältö ja liitteiden nimet" + "all": "Kaikki", + "all_fields_desc": "Hakee aiheesta, tekstistä, lähettäjästä ja vastaanottajista", + "subject": "Aihe", + "body": "Viestiosa" } } \ No newline at end of file diff --git a/web/src/locales/fr.json b/web/src/locales/fr.json index 22cb3e3..dd3c277 100644 --- a/web/src/locales/fr.json +++ b/web/src/locales/fr.json @@ -1555,6 +1555,9 @@ "recent_title": "Recherches récentes", "clear_history": "Effacer l’historique", "no_history": "Aucun historique de recherche", - "hint": "Portée par défaut : objet, contenu et noms des pièces jointes" + "all": "Tous les champs", + "all_fields_desc": "Recherche dans l'objet, le corps, l'expéditeur et les destinataires", + "subject": "Objet", + "body": "Corps du message" } } \ No newline at end of file diff --git a/web/src/locales/it.json b/web/src/locales/it.json index 0266eb4..c2ad85a 100644 --- a/web/src/locales/it.json +++ b/web/src/locales/it.json @@ -1555,6 +1555,9 @@ "recent_title": "Ricerche recenti", "clear_history": "Cancella cronologia", "no_history": "Nessuna cronologia di ricerca", - "hint": "Ambito predefinito: oggetto, contenuto e nomi degli allegati" + "all": "Tutti i campi", + "all_fields_desc": "Cerca in oggetto, testo, mittente e destinatari", + "subject": "Oggetto", + "body": "Corpo del messaggio" } } \ No newline at end of file diff --git a/web/src/locales/jp.json b/web/src/locales/jp.json index ed55812..cddc769 100644 --- a/web/src/locales/jp.json +++ b/web/src/locales/jp.json @@ -1555,6 +1555,9 @@ "recent_title": "最近の検索", "clear_history": "履歴をクリア", "no_history": "履歴なし", - "hint": "検索対象:件名・本文・添付ファイル名" + "all": "すべて", + "all_fields_desc": "件名、本文、送信者、受信者に一致します", + "subject": "件名", + "body": "本文" } } \ No newline at end of file diff --git a/web/src/locales/ko.json b/web/src/locales/ko.json index 9ac554b..8247935 100644 --- a/web/src/locales/ko.json +++ b/web/src/locales/ko.json @@ -1555,6 +1555,9 @@ "recent_title": "최근 검색", "clear_history": "기록 지우기", "no_history": "검색 기록 없음", - "hint": "기본 검색 범위: 제목, 본문 및 첨부파일 이름" + "all": "전체", + "all_fields_desc": "제목, 본문, 발신자 및 수신자를 검색합니다", + "subject": "제목", + "body": "본문" } } \ No newline at end of file diff --git a/web/src/locales/nl.json b/web/src/locales/nl.json index b33ccf8..00cf53c 100644 --- a/web/src/locales/nl.json +++ b/web/src/locales/nl.json @@ -1555,6 +1555,9 @@ "recent_title": "Recente zoekopdrachten", "clear_history": "Geschiedenis wissen", "no_history": "Geen zoekgeschiedenis", - "hint": "Standaard zoekbereik: onderwerp, inhoud en bijlagenamen" + "all": "Alle velden", + "all_fields_desc": "Zoekt in onderwerp, tekst, afzender en ontvangers", + "subject": "Onderwerp", + "body": "Inhoud" } } \ No newline at end of file diff --git a/web/src/locales/no.json b/web/src/locales/no.json index 6e3e1cb..44e1011 100644 --- a/web/src/locales/no.json +++ b/web/src/locales/no.json @@ -1555,6 +1555,9 @@ "recent_title": "Nylige søk", "clear_history": "Tøm historikk", "no_history": "Ingen søkehistorikk", - "hint": "Standard søkeområde: emne, innhold og vedleggsnavn" + "all": "Alle", + "all_fields_desc": "Søker i emne, tekst, avsender og mottakere", + "subject": "Emne", + "body": "Innhold" } } \ No newline at end of file diff --git a/web/src/locales/pl.json b/web/src/locales/pl.json index 324d59f..f6feb65 100644 --- a/web/src/locales/pl.json +++ b/web/src/locales/pl.json @@ -1555,6 +1555,9 @@ "recent_title": "Ostatnie wyszukiwania", "clear_history": "Wyczyść historię", "no_history": "Brak historii wyszukiwania", - "hint": "Domyślny zakres wyszukiwania: temat, treść i nazwy załączników" + "all": "Wszystkie", + "all_fields_desc": "Dopasowuje temat, treść, nadawcę i odbiorców", + "subject": "Temat", + "body": "Treść" } } \ No newline at end of file diff --git a/web/src/locales/pt.json b/web/src/locales/pt.json index 9994c16..bce369d 100644 --- a/web/src/locales/pt.json +++ b/web/src/locales/pt.json @@ -1555,6 +1555,9 @@ "recent_title": "Pesquisas recentes", "clear_history": "Limpar histórico", "no_history": "Nenhum histórico de pesquisa", - "hint": "Escopo padrão: assunto, conteúdo e nomes dos anexos" + "all": "Todos os campos", + "all_fields_desc": "Corresponde ao assunto, corpo, remetente e destinatários", + "subject": "Assunto", + "body": "Corpo da mensagem" } } \ No newline at end of file diff --git a/web/src/locales/ru.json b/web/src/locales/ru.json index fad2898..5033538 100644 --- a/web/src/locales/ru.json +++ b/web/src/locales/ru.json @@ -1555,6 +1555,9 @@ "recent_title": "Недавние поиски", "clear_history": "Очистить историю", "no_history": "История поиска пуста", - "hint": "По умолчанию поиск выполняется по теме, содержимому и именам вложений" + "all": "Все поля", + "all_fields_desc": "Поиск по теме, тексту, отправителю и получателям", + "subject": "Тема", + "body": "Текст письма" } } \ No newline at end of file diff --git a/web/src/locales/sv.json b/web/src/locales/sv.json index 2c27cf7..13a906a 100644 --- a/web/src/locales/sv.json +++ b/web/src/locales/sv.json @@ -1555,6 +1555,9 @@ "recent_title": "Senaste sökningar", "clear_history": "Rensa historik", "no_history": "Ingen sökhistorik", - "hint": "Standardomfattning: ämne, innehåll och bilagenamn" + "all": "Alla", + "all_fields_desc": "Matchar ämne, innehåll, avsändare och mottagare", + "subject": "Ämne", + "body": "Innehåll" } } \ No newline at end of file diff --git a/web/src/locales/zh-tw.json b/web/src/locales/zh-tw.json index 3e17768..1a152dd 100644 --- a/web/src/locales/zh-tw.json +++ b/web/src/locales/zh-tw.json @@ -1555,6 +1555,9 @@ "recent_title": "最近搜尋", "clear_history": "清除紀錄", "no_history": "尚無搜尋紀錄", - "hint": "預設搜尋範圍:郵件標題、內容與附件名稱" + "all": "全部", + "all_fields_desc": "匹配郵件主題、正文、發件人及收件人", + "subject": "郵件主題", + "body": "郵件正文" } } \ No newline at end of file diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 79dd5fd..d693f3d 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -1555,6 +1555,9 @@ "recent_title": "最近搜索", "clear_history": "清空记录", "no_history": "暂无搜索记录", - "hint": "默认搜索范围:邮件标题、正文及附件名称" + "all": "全部", + "all_fields_desc": "匹配邮件主题、正文、发件人及收件人", + "subject": "邮件主题", + "body": "邮件正文" } } \ No newline at end of file