import { create } from "zustand"; import { persist } from "zustand/middleware"; export interface Page { id: string; title: string; content: string; parentId: string | null; type: "file" | "folder"; children?: Page[]; // For tree view structures (virtual field) createdAt?: string; updatedAt?: string; icon?: string | null; tags?: string[]; order?: number; isLocked?: boolean; } interface EditorState { pages: Page[]; activePageId: string | null; isLoading: boolean; // Actions fetchPages: () => Promise; setActivePageId: (id: string | null) => void; addPage: (parentId?: string | null, type?: "file" | "folder", initialData?: { title: string, content: string }, order?: number) => Promise; updatePage: (id: string, data: Partial) => Promise; deletePage: (id: string) => Promise; movePage: (id: string, parentId: string | null) => Promise; reorderPages: (data: { id: string, order: number }[]) => Promise; } export const useEditorStore = create()( persist( (set, get) => ({ pages: [], activePageId: null, isLoading: false, fetchPages: async () => { set({ isLoading: true }); try { const res = await fetch("/api/pages"); if (res.ok) { const data = await res.json(); set({ pages: data }); // If no active page, maybe select the first one? Or leave null. // Persistence will handle restoring activePageId if it exists. } } catch (e) { console.error("Failed to fetch pages", e); } finally { set({ isLoading: false }); } }, setActivePageId: (id) => set({ activePageId: id }), addPage: async (parentId = null, type = "file", initialData, order) => { try { const res = await fetch("/api/pages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: initialData?.title || "无标题", content: initialData?.content || "", tags: [], parentId, type, order }), }); if (res.ok) { const newPage = await res.json(); set((state) => ({ pages: [newPage, ...state.pages], activePageId: newPage.id // Auto select new page })); } } catch (e) { console.error("Failed to create page", e); } }, updatePage: async (id, data) => { // Optimistic update set((state) => ({ pages: state.pages.map((p) => (p.id === id ? { ...p, ...data, updatedAt: new Date().toISOString() } : p)), })); // Debounce logic could be added here, but for now direct call try { await fetch(`/api/pages/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); } catch (e) { console.error("Failed to update page", e); } }, movePage: async (id, parentId) => { // Reuse updatePage logic await get().updatePage(id, { parentId }); }, deletePage: async (id) => { // Optimistic delete const currentActive = get().activePageId; set((state) => ({ pages: state.pages.filter((p) => p.id !== id), activePageId: currentActive === id ? null : currentActive })); try { await fetch(`/api/pages/${id}`, { method: "DELETE", }); } catch (e) { console.error("Failed to delete page", e); // Rollback could be added here } }, reorderPages: async (updates) => { // Optimistic update set((state) => { const newPages = [...state.pages]; updates.forEach(({ id, order }) => { const page = newPages.find(p => p.id === id); if (page) page.order = order; }); // Re-sort locally? Or just trust UI to sort based on updated order property // It's safer if 'pages' remains the master list return { pages: newPages }; }); try { await fetch("/api/pages/reorder", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ updates }), }); } catch (e) { console.error("Failed to reorder pages", e); } }, }), { name: "editor-storage", partialize: (state) => ({ activePageId: state.activePageId }), } ) );