Skip to content
Source
Sign inSign up

useThreadListSearchMatches.ts

58 lines2.3 KB

1import {
2 nextShownOrder,
3 threadSearchMatchKey,
4 threadSearchSubject,
5 type EnvironmentThreadSearchMatch,
6 type ShownOrder,
7} from "@t3tools/client-runtime/state/thread-search";
8import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell";
9import { foldSearchQuery, rankMessageMatches } from "@t3tools/shared/paletteSearch";
10import { useMemo, useState } from "react";
11​
12const NO_MATCHES: ReadonlyMap<string, EnvironmentThreadSearchMatch> = new Map();
13​
14/**
15 * A thread list's message matches by thread key, ranked across environments
16 * as the palette ranks them. Rows already shown keep their places while
17 * slower environments answer. The map keeps its identity while nothing
18 * changes, since lists pass it to every row.
19 */
20export function useThreadListSearchMatches(
21 threads: ReadonlyArray<EnvironmentThreadShell>,
22 search: {
23 readonly matches: ReadonlyArray<EnvironmentThreadSearchMatch>;
24 readonly query: string;
25 },
26): ReadonlyMap<string, EnvironmentThreadSearchMatch> {
27 const [now] = useState(() => Date.now());
28 const [shownOrder, setShownOrder] = useState<ShownOrder | null>(null);
29 const rankedKeys = useMemo(() => {
30 if (search.matches.length === 0) return [];
31 const subjectByKey = new Map(
32 threads.map((thread) => [
33 threadSearchMatchKey({ environmentId: thread.environmentId, threadId: thread.id }),
34 threadSearchSubject(thread, null),
35 ]),
36 );
37 return rankMessageMatches(
38 search.matches,
39 (match) => subjectByKey.get(threadSearchMatchKey(match)),
40 foldSearchQuery(search.query),
41 now,
42 { picks: false },
43 ).map(threadSearchMatchKey);
44 }, [now, search.matches, search.query, threads]);
45 // A list shows every match, so nothing is cut; late ones go below.
46 const order = nextShownOrder(shownOrder, search.query, rankedKeys, Number.POSITIVE_INFINITY);
47 if (order !== shownOrder) setShownOrder(order);
48 return useMemo(() => {
49 if (order.keys.length === 0) return NO_MATCHES;
50 const matchByKey = new Map(search.matches.map((match) => [threadSearchMatchKey(match), match]));
51 return new Map(
52 order.keys.flatMap((key) => {
53 const match = matchByKey.get(key);
54 return match ? [[key, match] as const] : [];
55 }),
56 );
57 }, [order, search.matches]);