import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import { api } from "../../../api/client.ts"; export interface WikiBatch { id: number; status: string } export interface WikiStatus { root: string; root_title: string | null; synced_at: string | null; pages: number; error: string | null; batch: WikiBatch | null; } export interface WikiNode { code: string; title: string | null; depth: number; parent_code: string | null; size: number | null } export interface WikiAttachment { id: number; name: string | null; state: string; size: number | null; error?: string | null; image?: boolean } export interface WikiPageData { code: string; title: string | null; path: string; url: string | null; synced_at: string | null; markdown: string; attachments: WikiAttachment[]; } export interface WikiHit { code: string; title: string | null; path: string; snippet: string } export interface WikiAttachmentState { id: number; name: string | null; state: string; error: string | null; size: number | null } export const POLL_MS = 3000; // опрос хода синхронизации, пока пакет не завершён export const SEARCH_LIMIT = 20; // ключи начинаются с ["wiki", проект]: одной инвалидацией обновляется всё про проект export const wikiQk = { project: (p: string) => ["wiki", p] as const, status: (p: string) => ["wiki", p, "status"] as const, tree: (p: string) => ["wiki", p, "tree"] as const, page: (p: string, code: string) => ["wiki", p, "page", code] as const, search: (p: string, q: string) => ["wiki", p, "search", q] as const, }; const terminal = (b: WikiBatch | null) => b === null || b.status === "done" || b.status === "cancelled"; // ход синхронизации; пока пакет идёт — опрос раз в 3 с, по его завершении дерево и страницы перечитываются export function useWikiStatus(project: string, enabled = true) { const qc = useQueryClient(); const q = useQuery({ queryKey: wikiQk.status(project), enabled, queryFn: () => api.get("/wiki/status", { project }), refetchInterval: (query) => (terminal(query.state.data?.batch ?? null) ? false : POLL_MS), }); const batch = q.data?.batch ?? null; const wasRunning = useRef(false); useEffect(() => { const running = !terminal(batch); if (wasRunning.current && !running) { for (const key of ["tree", "page", "search"]) void qc.invalidateQueries({ queryKey: [...wikiQk.project(project), key] }); } wasRunning.current = running; }, [batch, project, qc]); return q; } export const useWikiTree = (project: string, enabled: boolean) => useQuery({ queryKey: wikiQk.tree(project), enabled, queryFn: () => api.get<{ pages: WikiNode[] }>("/wiki/tree", { project }).then((r) => r.pages) }); export const useWikiPage = (project: string, code: string | null) => useQuery({ queryKey: wikiQk.page(project, code ?? ""), enabled: code !== null, queryFn: () => api.get(`/wiki/pages/${encodeURIComponent(code!)}`, { project }), }); export const useWikiSearch = (project: string, q: string) => useQuery({ queryKey: wikiQk.search(project, q), enabled: q !== "", queryFn: () => api.get<{ results: WikiHit[] }>("/wiki/search", { project, q, limit: SEARCH_LIMIT }).then((r) => r.results), });