Files
mnote/wolai-frontend/src/components/editor/blocks/PageReferenceBlock.tsx
T

115 lines
3.0 KiB
TypeScript
Raw Normal View History

2025-11-23 10:55:04 +08:00
"use client";
import { useEffect, useState } from "react";
import { createReactBlockSpec } from "@blocknote/react";
import { RiFileTextFill } from "react-icons/ri";
import { useRouter } from "next/navigation";
import { supabaseBrowser } from "@/lib/supabase/client";
const normalizeTitle = (value?: string | null) => {
if (!value || !value.trim()) {
return "未命名页面";
}
return value;
};
const PageReferenceContent = ({ pageId, title }: { pageId: string; title: string }) => {
const router = useRouter();
const fallbackTitle = normalizeTitle(title);
const [resolvedTitle, setResolvedTitle] = useState(fallbackTitle);
useEffect(() => {
setResolvedTitle(fallbackTitle);
}, [fallbackTitle]);
useEffect(() => {
if (!pageId) {
return;
}
let cancelled = false;
const applyTitle = (nextTitle?: string | null) => {
if (!cancelled) {
setResolvedTitle(normalizeTitle(nextTitle));
}
};
const fetchTitle = async () => {
try {
const { data } = await supabaseBrowser
.from("documents")
.select("title")
.eq("id", pageId)
.single();
if (data) {
applyTitle(data.title);
}
} catch {
// ignore fetch errors,等待后续订阅同步
}
};
void fetchTitle();
const channel = supabaseBrowser
.channel(`page-ref-${pageId}`)
.on(
"postgres_changes",
{ event: "UPDATE", schema: "public", table: "documents", filter: `id=eq.${pageId}` },
(payload) => {
const nextTitle = (payload.new as { title?: string } | null)?.title ?? null;
applyTitle(nextTitle);
},
)
.subscribe();
return () => {
cancelled = true;
supabaseBrowser.removeChannel(channel);
};
}, [pageId]);
const navigate = () => {
if (pageId) {
router.push(`/documents/${pageId}`);
}
};
return (
<div
role="button"
tabIndex={0}
onClick={navigate}
onKeyDown={(event) => {
if ((event.key === "Enter" || event.key === " ") && pageId) {
event.preventDefault();
navigate();
}
}}
className="group mt-2 flex items-center gap-3 rounded-[4px] px-4 py-3 text-[#2563eb] hover:bg-[#f5f5f5]"
style={{ fontFamily: "Inter, system-ui, sans-serif" }}
>
<RiFileTextFill className="text-xl" aria-hidden />
<span className="text-base font-medium leading-none">{resolvedTitle}</span>
<span className="ml-auto text-sm opacity-0 transition-opacity duration-150 group-hover:opacity-100">
点击进入
</span>
</div>
);
};
export const pageReferenceBlock = createReactBlockSpec(
{
type: "pageReference",
propSchema: {
pageId: { default: "" },
title: { default: "未命名页面" },
},
content: "none",
},
() => ({
render: ({ block }) => (
<PageReferenceContent pageId={block.props.pageId} title={block.props.title} />
),
}),
)();