feat(search): "Pinned only" filter (composes with msgtype + local results)

Adds a "Pinned" toggle chip that narrows results to messages currently in
their room's m.room.pinned_events. Client-side post-filter mirroring the
has:image/file/video pattern: a pure filterGroupsByPinned(groups, enabled,
isPinned) helper consumes a predicate; MessageSearch builds a per-room
Map<roomId, Set<eventId>> from StateEvent.RoomPinnedEvents.

Review fix: the msgtype + pinned filters are now applied to BOTH the server
results AND the encrypted/local-cache results (via a shared applyResultFilters
useCallback), so the chips narrow the whole UI consistently — previously the
local/E2EE section bypassed them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:47:50 -04:00
co-authored by Claude Opus 4.8
parent da545ba9b9
commit de6cecaffc
3 changed files with 101 additions and 6 deletions
@@ -51,6 +51,26 @@ export const filterGroupsByMsgType = (
.filter((group) => group.items.length > 0);
};
/**
* Filter result groups to items whose event is currently pinned in its room.
* `isPinned(roomId, eventId)` returns whether the event is in the room's
* `m.room.pinned_events` set. When `enabled` is false, groups are returned
* unchanged. Now-empty groups are dropped.
*/
export const filterGroupsByPinned = (
groups: ResultGroup[],
enabled: boolean,
isPinned: (roomId: string, eventId: string) => boolean,
): ResultGroup[] => {
if (!enabled) return groups;
return groups
.map((group) => ({
...group,
items: group.items.filter((item) => isPinned(group.roomId, item.event.event_id)),
}))
.filter((group) => group.items.length > 0);
};
const groupSearchResult = (results: ISearchResult[]): ResultGroup[] => {
const groups: ResultGroup[] = [];