feat: split search query into words for AND semantics
Searching for "foo bar" now matches emails containing both words rather than the exact phrase. Each whitespace-separated term generates its own IMAP criterion (OR SUBJECT "w" TEXT "w") — multiple top-level IMAP criteria are ANDed by the protocol — and its own Drift LIKE clause for the local DB. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0103454e31
commit
2074046bb3
@@ -6,6 +6,17 @@ Tasks get moved from next.md to done.md
|
||||
|
||||
## Tasks
|
||||
|
||||
## Multi-word search uses AND semantics
|
||||
|
||||
Searching for "foo bar" now returns emails that contain **both** words, not the exact
|
||||
phrase. Fixed in `email_repository_impl.dart`:
|
||||
|
||||
- **IMAP search** (`searchEmails`): query is split on whitespace; each word becomes
|
||||
`OR SUBJECT "word" TEXT "word"`, joined by spaces. Multiple top-level IMAP criteria
|
||||
are implicitly ANDed by the protocol.
|
||||
- **Local DB search** (`searchEmailsGlobal`): each word adds
|
||||
`& (subject LIKE '%word%' | preview LIKE '%word%')` to the Drift where-clause.
|
||||
|
||||
## Navigate back to account list from inside an account
|
||||
|
||||
Added an "All accounts" tile (with `Icons.switch_account`) at the top of `FolderDrawer`,
|
||||
|
||||
@@ -1726,13 +1726,21 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
String accountId,
|
||||
String query,
|
||||
) async {
|
||||
final pattern = '%${query.toLowerCase()}%';
|
||||
final words = query
|
||||
.toLowerCase()
|
||||
.split(RegExp(r'\s+'))
|
||||
.where((w) => w.isNotEmpty)
|
||||
.toList();
|
||||
final rows = await (_db.select(_db.emails)
|
||||
..where(
|
||||
(t) =>
|
||||
t.accountId.equals(accountId) &
|
||||
(t.subject.like(pattern) | t.preview.like(pattern)),
|
||||
)
|
||||
..where((t) {
|
||||
Expression<bool> condition = t.accountId.equals(accountId);
|
||||
for (final word in words) {
|
||||
final pattern = '%$word%';
|
||||
condition = condition &
|
||||
(t.subject.like(pattern) | t.preview.like(pattern));
|
||||
}
|
||||
return condition;
|
||||
})
|
||||
..orderBy([(t) => OrderingTerm.desc(t.receivedAt)])
|
||||
..limit(50))
|
||||
.get();
|
||||
@@ -1770,9 +1778,14 @@ class EmailRepositoryImpl implements EmailRepository {
|
||||
await _imapConnect(account, _effectiveUsername(account), password);
|
||||
try {
|
||||
await client.selectMailboxByPath(mailboxPath);
|
||||
final escaped = query.replaceAll('"', '\\"');
|
||||
final terms =
|
||||
query.split(RegExp(r'\s+')).where((t) => t.isNotEmpty).toList();
|
||||
final searchCriteria = terms.map((term) {
|
||||
final escaped = term.replaceAll('"', '\\"');
|
||||
return 'OR SUBJECT "$escaped" TEXT "$escaped"';
|
||||
}).join(' ');
|
||||
final result = await client.uidSearchMessages(
|
||||
searchCriteria: 'OR SUBJECT "$escaped" TEXT "$escaped"',
|
||||
searchCriteria: searchCriteria,
|
||||
);
|
||||
final uids = result.matchingSequence?.toList() ?? [];
|
||||
if (uids.isEmpty) return [];
|
||||
|
||||
@@ -18,11 +18,6 @@ Then commit.
|
||||
|
||||
## Tasks
|
||||
|
||||
I opened a mailbox. I search for "foo bar". I want to see all mails containing foo and bar. Not
|
||||
mails containing "foo bar" exactly.
|
||||
|
||||
---
|
||||
|
||||
I search for "foo". Now I see all mails containing "foo". I want to easily do the common actions on
|
||||
the selected mails: Delete, Archive, Move to Folder, Move to Junk, ...
|
||||
|
||||
|
||||
Reference in New Issue
Block a user