feat(unified-mailbox): enable search in the unified views
Enable text AND advanced search in all Unified Mailbox views (the per-role mailboxes and the folder-selected All mail / Unread / Starred cross views). The search input was hard-disabled for every unified view; the store fan-out already supported text search. - page.tsx: the search text input and the advanced-filter toggle are enabled for all unified views (only the scheduled view stays disabled). Clear-search also restores a cross view (not just per-role). - Advanced filters now apply in cross views too: new advancedSearchCrossViewEmails ANDs the advanced filter (text + field conditions from buildJMAPFilter, built without an inMailbox clause) onto the cross-view membership. Per-role unified views keep using advancedSearchUnifiedEmails. Both honor the filter on the first page, on load-more, and on the folder-switch re-run. Fixes: an active Starred filter not applying after switching into a cross view, and the Unread filter in the Unread view returning nothing. - Search persistence on folder switch: an active search is kept and re-run in the target view, preserving advanced filters. handleMailboxSelect picks advancedSearch when filters are set (normal, per-role unified, and cross views, after setting the unified state), text searchEmails when only a query is set, and browses otherwise. The scheduled view is the only view that resets the search on enter (unavailable there; setScheduledView clears searchQuery + searchFilters). Account scope is intentionally left unrestricted in search (it already fanned out across all accounts); the per-view folder selection still applies via crossIncludedMailboxIds.
This commit is contained in:
+17
-5
@@ -7,7 +7,7 @@ import { useCalendarStore } from "@/stores/calendar-store";
|
||||
import { SearchFilters, DEFAULT_SEARCH_FILTERS, buildJMAPFilter, isFilterEmpty } from "@/lib/jmap/search-utils";
|
||||
import { emailHooks } from "@/lib/plugin-hooks";
|
||||
import type { ExternalSearchResult } from "@/lib/plugin-types";
|
||||
import { fetchUnifiedEmails, fetchUnifiedMailboxCounts, searchUnifiedEmails, advancedSearchUnifiedEmails, fetchCrossViewEmails, searchCrossViewEmails, getCrossUnreadTotal, type UnifiedAccountClient, type UnifiedMailboxCounts } from "@/lib/unified-mailbox";
|
||||
import { fetchUnifiedEmails, fetchUnifiedMailboxCounts, searchUnifiedEmails, advancedSearchUnifiedEmails, fetchCrossViewEmails, searchCrossViewEmails, advancedSearchCrossViewEmails, getCrossUnreadTotal, type UnifiedAccountClient, type UnifiedMailboxCounts } from "@/lib/unified-mailbox";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { useAccountStore } from "@/stores/account-store";
|
||||
|
||||
@@ -959,9 +959,12 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const includeGroup = useSettingsStore.getState().includeGroupInUnified;
|
||||
const position = emails.length;
|
||||
const built = await buildUnifiedAccountClients({ includeGroup });
|
||||
const result = searchQuery
|
||||
? await searchCrossViewEmails(built, crossView, searchQuery, emailsPerPage, position)
|
||||
: await fetchCrossViewEmails(built, crossView, emailsPerPage, position);
|
||||
const hasFilters = !isFilterEmpty(get().searchFilters);
|
||||
const result = hasFilters
|
||||
? await advancedSearchCrossViewEmails(built, crossView, buildJMAPFilter(searchQuery, get().searchFilters, undefined), emailsPerPage, position)
|
||||
: searchQuery
|
||||
? await searchCrossViewEmails(built, crossView, searchQuery, emailsPerPage, position)
|
||||
: await fetchCrossViewEmails(built, crossView, emailsPerPage, position);
|
||||
const currentEmails = get().emails;
|
||||
const existingIds = new Set(currentEmails.map(e => e.id));
|
||||
const newEmails = result.emails.filter(e => !existingIds.has(e.id));
|
||||
@@ -1796,7 +1799,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
if (isUnifiedView && crossView) {
|
||||
const includeGroup = useSettingsStore.getState().includeGroupInUnified;
|
||||
const built = await buildUnifiedAccountClients({ includeGroup });
|
||||
const result = await searchCrossViewEmails(built, crossView, searchQuery, emailsPerPage, 0);
|
||||
// Cross views apply the advanced filter (text + fields) on top of the
|
||||
// view membership; an empty filter degrades to a plain membership query.
|
||||
const result = await advancedSearchCrossViewEmails(
|
||||
built, crossView, buildJMAPFilter(searchQuery, searchFilters, undefined), emailsPerPage, 0,
|
||||
);
|
||||
if (controller.signal.aborted) return;
|
||||
const externals = await emailHooks.onProvideSearchResults.transform([] as ExternalSearchResult[], { query: searchQuery, filters: searchFilters });
|
||||
set({
|
||||
@@ -3225,6 +3232,11 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
selectedMailbox: isScheduledView ? VIRTUAL_SCHEDULED_MAILBOX_ID : leavingScheduled ? "" : state.selectedMailbox,
|
||||
selectedEmail: leavingScheduled ? null : state.selectedEmail,
|
||||
selectedEmailIds: leavingScheduled ? new Set<string>() : state.selectedEmailIds,
|
||||
// Search is unavailable in the scheduled view (the input is disabled there).
|
||||
// Reset any active search when entering it so a stale query can't linger or
|
||||
// re-run when the user leaves again.
|
||||
searchQuery: isScheduledView ? "" : state.searchQuery,
|
||||
searchFilters: isScheduledView ? { ...DEFAULT_SEARCH_FILTERS } : state.searchFilters,
|
||||
};
|
||||
}),
|
||||
clearPendingUndoSend: () => set({ pendingUndoSend: null }),
|
||||
|
||||
Reference in New Issue
Block a user