2024-05-31 19:49:46 +05:30
|
|
|
import {
|
|
|
|
|
IEventWithRoomId,
|
|
|
|
|
IResultContext,
|
|
|
|
|
ISearchRequestBody,
|
|
|
|
|
ISearchResponse,
|
|
|
|
|
ISearchResult,
|
|
|
|
|
SearchOrderBy,
|
|
|
|
|
} from 'matrix-js-sdk';
|
|
|
|
|
import { useCallback } from 'react';
|
|
|
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
|
|
|
|
|
|
|
|
export type ResultItem = {
|
|
|
|
|
rank: number;
|
|
|
|
|
event: IEventWithRoomId;
|
|
|
|
|
context: IResultContext;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type ResultGroup = {
|
|
|
|
|
roomId: string;
|
|
|
|
|
items: ResultItem[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type SearchResult = {
|
|
|
|
|
nextToken?: string;
|
|
|
|
|
highlights: string[];
|
|
|
|
|
groups: ResultGroup[];
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-28 14:22:08 -04:00
|
|
|
// Client-side msgtype post-filter. The Matrix search API cannot filter by
|
|
|
|
|
// msgtype server-side, so this is applied to already-returned results.
|
|
|
|
|
export type MsgTypeFilter = 'm.image' | 'm.file' | 'm.video';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter result groups to items whose event msgtype is in `msgTypes` (OR/union).
|
|
|
|
|
* Empty/absent filter returns groups unchanged. Now-empty groups are dropped.
|
|
|
|
|
*/
|
|
|
|
|
export const filterGroupsByMsgType = (
|
|
|
|
|
groups: ResultGroup[],
|
|
|
|
|
msgTypes: MsgTypeFilter[],
|
|
|
|
|
): ResultGroup[] => {
|
|
|
|
|
if (msgTypes.length === 0) return groups;
|
|
|
|
|
const allowed = new Set<string>(msgTypes);
|
|
|
|
|
return groups
|
|
|
|
|
.map((group) => ({
|
|
|
|
|
...group,
|
|
|
|
|
items: group.items.filter((item) => {
|
|
|
|
|
const msgtype = item.event.content?.msgtype;
|
|
|
|
|
return typeof msgtype === 'string' && allowed.has(msgtype);
|
|
|
|
|
}),
|
|
|
|
|
}))
|
|
|
|
|
.filter((group) => group.items.length > 0);
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-28 16:47:50 -04:00
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-31 19:49:46 +05:30
|
|
|
const groupSearchResult = (results: ISearchResult[]): ResultGroup[] => {
|
|
|
|
|
const groups: ResultGroup[] = [];
|
|
|
|
|
|
|
|
|
|
results.forEach((item) => {
|
|
|
|
|
const roomId = item.result.room_id;
|
|
|
|
|
const resultItem: ResultItem = {
|
|
|
|
|
rank: item.rank,
|
|
|
|
|
event: item.result,
|
|
|
|
|
context: item.context,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const lastAddedGroup: ResultGroup | undefined = groups[groups.length - 1];
|
|
|
|
|
if (lastAddedGroup && roomId === lastAddedGroup.roomId) {
|
|
|
|
|
lastAddedGroup.items.push(resultItem);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
groups.push({
|
|
|
|
|
roomId,
|
|
|
|
|
items: [resultItem],
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return groups;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const parseSearchResult = (result: ISearchResponse): SearchResult => {
|
|
|
|
|
const roomEvents = result.search_categories.room_events;
|
|
|
|
|
|
|
|
|
|
const searchResult: SearchResult = {
|
|
|
|
|
nextToken: roomEvents?.next_batch,
|
|
|
|
|
highlights: roomEvents?.highlights ?? [],
|
|
|
|
|
groups: groupSearchResult(roomEvents?.results ?? []),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return searchResult;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type MessageSearchParams = {
|
|
|
|
|
term?: string;
|
|
|
|
|
order?: string;
|
|
|
|
|
rooms?: string[];
|
|
|
|
|
senders?: string[];
|
2026-05-30 22:22:40 -04:00
|
|
|
fromTs?: number;
|
|
|
|
|
toTs?: number;
|
2026-06-18 19:14:42 -04:00
|
|
|
containsUrl?: boolean;
|
2024-05-31 19:49:46 +05:30
|
|
|
};
|
|
|
|
|
export const useMessageSearch = (params: MessageSearchParams) => {
|
|
|
|
|
const mx = useMatrixClient();
|
2026-06-18 19:14:42 -04:00
|
|
|
const { term, order, rooms, senders, fromTs, toTs, containsUrl } = params;
|
2024-05-31 19:49:46 +05:30
|
|
|
|
|
|
|
|
const searchMessages = useCallback(
|
|
|
|
|
async (nextBatch?: string) => {
|
|
|
|
|
if (!term)
|
|
|
|
|
return {
|
|
|
|
|
highlights: [],
|
|
|
|
|
groups: [],
|
|
|
|
|
};
|
2026-05-28 20:01:21 -04:00
|
|
|
const limit = 50;
|
2024-05-31 19:49:46 +05:30
|
|
|
|
|
|
|
|
const requestBody: ISearchRequestBody = {
|
|
|
|
|
search_categories: {
|
|
|
|
|
room_events: {
|
|
|
|
|
event_context: {
|
|
|
|
|
before_limit: 0,
|
|
|
|
|
after_limit: 0,
|
|
|
|
|
include_profile: false,
|
|
|
|
|
},
|
|
|
|
|
filter: {
|
|
|
|
|
limit,
|
|
|
|
|
rooms,
|
|
|
|
|
senders,
|
2026-06-18 19:14:42 -04:00
|
|
|
// from_ts / to_ts and contains_url are valid Matrix spec fields not yet in SDK types
|
2026-05-30 22:22:40 -04:00
|
|
|
...(fromTs !== undefined && { from_ts: fromTs }),
|
|
|
|
|
...(toTs !== undefined && { to_ts: toTs }),
|
2026-06-18 19:14:42 -04:00
|
|
|
...(containsUrl !== undefined && { contains_url: containsUrl }),
|
2026-05-30 23:40:08 -04:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2026-05-30 22:22:40 -04:00
|
|
|
} as any,
|
2024-05-31 19:49:46 +05:30
|
|
|
include_state: false,
|
|
|
|
|
order_by: order as SearchOrderBy.Recent,
|
|
|
|
|
search_term: term,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const r = await mx.search({
|
|
|
|
|
body: requestBody,
|
|
|
|
|
next_batch: nextBatch === '' ? undefined : nextBatch,
|
|
|
|
|
});
|
|
|
|
|
return parseSearchResult(r);
|
|
|
|
|
},
|
2026-06-18 19:14:42 -04:00
|
|
|
[mx, term, order, rooms, senders, fromTs, toTs, containsUrl],
|
2024-05-31 19:49:46 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return searchMessages;
|
|
|
|
|
};
|