Files
fusion/packages/dashboard/app/hooks/useProjectMarkdownFiles.ts
Fusion 6453fc771e feat(FN-2315): add hidden markdown toggle for project documents
- Extend dashboard file service and routes to support includeHidden when scanning markdown files
- Update API client, markdown hook, and Documents view UI to expose a hidden files toggle
- Add coverage for hidden-file filtering in file-service, hook, and DocumentsView tests
- Stabilize related SettingsModal and TaskForm tests and document toggle behavior in the dashboard README
2026-04-24 08:55:02 -07:00

90 lines
2.4 KiB
TypeScript

import { useState, useEffect, useRef, useCallback } from "react";
import { fetchProjectMarkdownFiles, type MarkdownFileEntry } from "../api";
export interface ProjectMarkdownFilesVisibilityOptions {
showHidden?: boolean;
}
export interface UseProjectMarkdownFilesResult {
files: MarkdownFileEntry[];
loading: boolean;
error: string | null;
refresh: (options?: ProjectMarkdownFilesVisibilityOptions) => Promise<void>;
}
/**
* Hook for fetching markdown files from the project workspace.
*
* Loading behavior matches useDocuments: loading is true only for the initial
* fetch, not for subsequent refreshes, to avoid content flicker.
*/
export function useProjectMarkdownFiles(
projectId?: string,
options?: ProjectMarkdownFilesVisibilityOptions,
): UseProjectMarkdownFilesResult {
const [files, setFiles] = useState<MarkdownFileEntry[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
const initialLoadCompleteRef = useRef(false);
const showHidden = options?.showHidden ?? false;
const refresh = useCallback(async (refreshOptions?: ProjectMarkdownFilesVisibilityOptions) => {
if (abortRef.current) {
abortRef.current.abort();
}
const requestController = new AbortController();
abortRef.current = requestController;
const isInitial = !initialLoadCompleteRef.current;
if (isInitial) {
setLoading(true);
}
setError(null);
try {
const response = await fetchProjectMarkdownFiles(projectId, {
showHidden: refreshOptions?.showHidden ?? showHidden,
});
if (requestController.signal.aborted) {
return;
}
setFiles(response.files);
initialLoadCompleteRef.current = true;
} catch (err: unknown) {
if (requestController.signal.aborted) {
return;
}
setError(err instanceof Error ? err.message : String(err));
} finally {
if (!requestController.signal.aborted && isInitial) {
setLoading(false);
}
}
}, [projectId, showHidden]);
useEffect(() => {
initialLoadCompleteRef.current = false;
void refresh({ showHidden });
return () => {
if (abortRef.current) {
abortRef.current.abort();
}
};
}, [refresh, showHidden]);
return {
files,
loading,
error,
refresh,
};
}