import type { Json } from "@/types/supabase"; type BlockLike = { id: string; type?: string; props?: Record; content?: unknown; children?: BlockLike[]; }; const asBlockArray = (value: unknown): BlockLike[] => { if (!Array.isArray(value)) return []; return value.filter((b) => b && typeof b === "object" && typeof (b as { id?: unknown }).id === "string") as BlockLike[]; }; export const findBlockInTree = ( blocks: BlockLike[], blockId: string, ): { block: BlockLike; parent: BlockLike | null; index: number } | null => { const stack: Array<{ list: BlockLike[]; parent: BlockLike | null }> = [{ list: blocks, parent: null }]; while (stack.length) { const item = stack.pop()!; const list = item.list; for (let i = 0; i < list.length; i += 1) { const b = list[i]!; if (b.id === blockId) { return { block: b, parent: item.parent, index: i }; } if (Array.isArray(b.children) && b.children.length > 0) { stack.push({ list: b.children, parent: b }); } } } return null; }; const cloneBlock = (block: BlockLike): BlockLike => { return { ...block, props: block.props ? { ...block.props } : undefined, content: Array.isArray(block.content) ? [...block.content] : block.content, children: Array.isArray(block.children) ? block.children.map(cloneBlock) : block.children, }; }; export const removeBlockSubtree = ( blocks: BlockLike[], blockId: string, ): { removed: BlockLike | null; nextBlocks: BlockLike[] } => { // 说明:这里不直接修改入参 blocks,返回新的 nextBlocks。 const nextTop = blocks.map(cloneBlock); const hit = findBlockInTree(nextTop, blockId); if (!hit) return { removed: null, nextBlocks: nextTop }; if (hit.parent) { const parent = hit.parent; const nextChildren = asBlockArray(parent.children).map(cloneBlock); const removed = nextChildren.splice(hit.index, 1)[0] ?? null; parent.children = nextChildren; return { removed, nextBlocks: nextTop }; } const removed = nextTop.splice(hit.index, 1)[0] ?? null; return { removed, nextBlocks: nextTop }; }; export const replaceBlockInTree = ( blocks: BlockLike[], blockId: string, nextBlock: BlockLike, ): { ok: boolean; nextBlocks: BlockLike[] } => { const nextTop = blocks.map(cloneBlock); const hit = findBlockInTree(nextTop, blockId); if (!hit) return { ok: false, nextBlocks: nextTop }; const normalized = cloneBlock({ ...nextBlock, id: blockId }); if (hit.parent) { const parent = hit.parent; const nextChildren = asBlockArray(parent.children).map(cloneBlock); nextChildren[hit.index] = normalized; parent.children = nextChildren; return { ok: true, nextBlocks: nextTop }; } nextTop[hit.index] = normalized; return { ok: true, nextBlocks: nextTop }; }; export const extractBlockText = (block: BlockLike): string => { const inline = Array.isArray(block.content) ? (block.content as Array<{ text?: unknown }>) : []; const text = inline.map((n) => (typeof n?.text === "string" ? n.text : "")).join(""); return text.trim(); }; export const getBlocksFromDocumentContent = (content: unknown): BlockLike[] => { if (Array.isArray(content)) return asBlockArray(content); if (content && typeof content === "object") { const blocks = (content as { blocks?: unknown }).blocks; return asBlockArray(blocks); } return []; }; export const withBlocksWrittenBack = (content: unknown, blocks: BlockLike[]): Json => { // 复用现有结构:数组或 {blocks: []} if (Array.isArray(content)) { return blocks as unknown as Json; } if (content && typeof content === "object") { return { ...(content as Record), blocks } as Json; } return { blocks } as Json; };