import { useEffect, useRef, useState } from "react"; import * as Api from "./api"; import { NavigationDialog } from "./navigation-dialog"; import type { NavigationDialogMotion } from "./navigation-dialog"; export type DocumentSearchResult = { project: Api.NavigationProject; channel: Api.Channel; }; export async function searchAvailableDocuments( projects: Api.NavigationProject[], query: string, includeArchived: boolean, load: typeof Api.channels = Api.channels, signal?: AbortSignal, ): Promise<{ results: DocumentSearchResult[]; failedProjectIds: string[] }> { let searched = await Promise.all( projects.filter(project => project.available).map( async project => { let channels: Api.Channel[] = []; let cursor: string | undefined; try { do { let page = await load( project.repositoryOwner, project.repositoryName, { cursor, includeArchived, query: query.trim() || undefined, signal, }, ); channels.push(...page.channels); cursor = page.nextCursor; } while (cursor); return { channels, project }; } catch (error) { if (signal?.aborted) throw error; return { channels: [], error, project }; } }, ), ); return { results: searched.flatMap(({ channels, project }) => channels.map(channel => ({ channel, project })) ), failedProjectIds: searched.filter(result => "error" in result) .map(result => result.project.repositoryId), }; } export function DocumentSearchDialog( { includeArchived, motion, onDismiss, onSelect, projects, }: { includeArchived: boolean; motion: NavigationDialogMotion; onDismiss: () => void; onSelect: (documentId: string) => void; projects: Api.NavigationProject[]; }, ) { let input = useRef(null); let [query, setQuery] = useState(""); let [retry, setRetry] = useState(0); let [search, setSearch] = useState< | { status: "loading" } | { status: "ready"; results: DocumentSearchResult[]; failedProjectIds: string[] } | { status: "error"; message: string } >({ status: "loading" }); useEffect(() => { let active = true; let controller = new AbortController(); let timer = window.setTimeout(() => { setSearch({ status: "loading" }); searchAvailableDocuments( projects, query, includeArchived, Api.channels, controller.signal, ).then(result => { if (active) setSearch({ status: "ready", ...result }); }, error => { if (active && !controller.signal.aborted) { setSearch({ status: "error", message: error instanceof Error ? error.message : "Could not search documents.", }); } }); }, 150); return () => { active = false; window.clearTimeout(timer); controller.abort(); }; }, [includeArchived, projects, query, retry]); let results = search.status === "ready" ? search.results : []; return (
setQuery(event.target.value)} placeholder="Search documents" ref={input} value={query} />
{search.status === "loading" && (

Searching documents…

)} {search.status === "error" && (

{search.message}

)} {search.status === "ready" && search.failedProjectIds.length > 0 && (

Some Projects could not be searched.

)} {search.status === "ready" && results.length === 0 && (

No documents found.

)} {results.map(({ channel, project }) => ( ))}
); }