Files
edgeever/apps/web/src/lib/notebook-delete.ts
T
tianma-if 968ac71aa6 fix: delete empty child notebooks with their parent
A parent notebook stayed after repeated deletes when it only contained empty children. Remove that empty subtree together, and keep the tree when any note is still inside.
2026-09-22 20:52:50 +08:00

61 lines
2.0 KiB
TypeScript

export class NotebookNotEmptyError extends Error {
readonly code = "notebook_not_empty";
constructor() {
super("notebook_not_empty");
this.name = "NotebookNotEmptyError";
}
}
export const isNotebookNotEmptyError = (error: unknown) => {
if (error instanceof NotebookNotEmptyError) return true;
if (typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "notebook_not_empty") {
return true;
}
return error instanceof Error && error.message === "notebook_not_empty";
};
export const notebookDeleteIdsFromPayload = (
payload: { notebookId?: unknown; notebookIds?: unknown } | null | undefined,
fallbackId: string,
) => {
const listed = Array.isArray(payload?.notebookIds)
? payload.notebookIds.filter((id): id is string => typeof id === "string" && id.length > 0)
: [];
if (listed.length > 0) return listed;
const notebookId = typeof payload?.notebookId === "string" && payload.notebookId.length > 0
? payload.notebookId
: fallbackId;
return notebookId ? [notebookId] : [];
};
// A pending delete of a parent must also hide descendants that the server
// still lists, including when the parent itself is already absent.
export const notebooksBlockedByPendingDeletes = (
notebooks: Array<{ id: string; parentId?: string | null }>,
pendingDeleteIds: ReadonlySet<string>,
) => {
const parentById = new Map(notebooks.map((notebook) => [notebook.id, notebook.parentId ?? null]));
const blocked = new Set<string>();
for (const notebook of notebooks) {
const seen = new Set<string>();
let currentId: string | null = notebook.id;
while (currentId && !seen.has(currentId)) {
if (pendingDeleteIds.has(currentId)) {
blocked.add(notebook.id);
break;
}
seen.add(currentId);
const parentId: string | null = parentById.get(currentId) ?? null;
if (parentId && pendingDeleteIds.has(parentId)) {
blocked.add(notebook.id);
break;
}
currentId = parentId;
}
}
return blocked;
};