157 lines
5.7 KiB
TypeScript
157 lines
5.7 KiB
TypeScript
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<void>;
|
|
setActivePageId: (id: string | null) => void;
|
|
addPage: (parentId?: string | null, type?: "file" | "folder", initialData?: { title: string, content: string }, order?: number) => Promise<void>;
|
|
updatePage: (id: string, data: Partial<Page>) => Promise<void>;
|
|
deletePage: (id: string) => Promise<void>;
|
|
movePage: (id: string, parentId: string | null) => Promise<void>;
|
|
reorderPages: (data: { id: string, order: number }[]) => Promise<void>;
|
|
}
|
|
|
|
export const useEditorStore = create<EditorState>()(
|
|
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 }),
|
|
}
|
|
)
|
|
);
|