"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { createReactBlockSpec } from "@blocknote/react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { extractBlockText } from "@/lib/blocks"; type RemoteBlock = { id: string; type?: string; props?: Record; content?: unknown; children?: unknown; }; const isTextBlock = (block: RemoteBlock) => block.type === "paragraph" || block.type === "heading"; export const blockReferenceBlock = createReactBlockSpec( { type: "blockReference", propSchema: { sourceDocumentId: { default: "" }, targetBlockId: { default: "" }, display: { default: "embed" }, }, content: "none", }, () => ({ render: ({ block }) => , }), )(); function BlockReferenceContent({ block }: { block: { props: { sourceDocumentId: string; targetBlockId: string } } }) { const router = useRouter(); const sourceDocumentId = block.props.sourceDocumentId; const targetBlockId = block.props.targetBlockId; const [remote, setRemote] = useState(null); const [textDraft, setTextDraft] = useState(""); const [status, setStatus] = useState<"idle" | "loading" | "error">("idle"); const [error, setError] = useState(""); const canEdit = useMemo(() => Boolean(remote && isTextBlock(remote)), [remote]); useEffect(() => { if (!sourceDocumentId || !targetBlockId) { setStatus("error"); setError("引用信息不完整"); return; } let cancelled = false; setStatus("loading"); setError(""); setRemote(null); const run = async () => { try { const res = await fetch( `/api/blocks/get?sourceDocumentId=${encodeURIComponent(sourceDocumentId)}&blockId=${encodeURIComponent(targetBlockId)}`, { method: "GET", credentials: "include" }, ); if (!res.ok) { const payload = await res.json().catch(() => ({})); throw new Error(payload?.error ?? "获取引用块失败"); } const json = await res.json(); const next = (json?.block ?? null) as RemoteBlock | null; if (!cancelled) { setRemote(next); if (next && isTextBlock(next)) { setTextDraft(extractBlockText(next as any)); } setStatus("idle"); } } catch (e) { if (!cancelled) { setStatus("error"); setError(e instanceof Error ? e.message : "获取引用块失败"); } } }; void run(); return () => { cancelled = true; }; }, [sourceDocumentId, targetBlockId]); const openSource = useCallback(() => { if (sourceDocumentId) { router.push(`/documents/${sourceDocumentId}`); } }, [router, sourceDocumentId]); const saveText = useCallback(async () => { if (!remote || !canEdit) return; const nextBlock: RemoteBlock = { ...remote, id: remote.id, content: [{ type: "text", text: textDraft }], }; const res = await fetch("/api/blocks/patch", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ sourceDocumentId, blockId: targetBlockId, nextBlock }), }); if (!res.ok) { const payload = await res.json().catch(() => ({})); const msg = payload?.error ?? "同步编辑失败"; if (typeof window !== "undefined") window.alert(msg); return; } if (typeof window !== "undefined") window.alert("已同步编辑到原块"); }, [canEdit, remote, sourceDocumentId, targetBlockId, textDraft]); return (
{ // 说明:避免点击内部按钮/输入框时误触发编辑器的拖拽/选择。 e.stopPropagation(); }} >
嵌入引用
{status === "loading" ? (
加载中...
) : status === "error" ? (
{error}
) : !remote ? (
引用块不存在
) : canEdit ? (