Andrey Epifancev 93eff7eac0 Веб: правки вики в общей ленте (#140)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:04:40 +04:00

85 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; history_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 interface WikiChange {
at: string; page_code: string; title: string | null; path: string | null; version: string;
author: { login: string | null; name: string | null }; added: number; removed: number;
}
export interface WikiChanges {
since: string; until: string; synced_at: string | null; history_at: string | null; truncated: boolean; changes: WikiChange[];
}
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,
changes: (p: string, period: string) => ["wiki", p, "changes", period] 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<WikiStatus>("/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<WikiPageData>(`/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),
});
// правки за период для ленты; границы считает запрос в момент выполнения (перечитывание берёт «до сейчас»)
export const WIKI_CHANGES_LIMIT = 2000;
export const useWikiChanges = (project: string, period: string, bounds: () => { since: Date; until: Date }, enabled: boolean) =>
useQuery({
queryKey: wikiQk.changes(project, period), enabled, refetchOnWindowFocus: true,
queryFn: () => {
const { since, until } = bounds();
return api.get<WikiChanges>("/wiki/changes", { project, since: since.toISOString(), until: until.toISOString(), limit: WIKI_CHANGES_LIMIT });
},
});