import { NodeViewWrapper } from "@tiptap/react"; import { useEffect, useState, useCallback, useRef } from "react"; import type { NodeViewProps } from "@tiptap/react"; export function BibliographyView({ editor, node, getPos }: NodeViewProps) { const [html, setHtml] = useState(""); const [count, setCount] = useState(0); const [style, setStyle] = useState("apa"); const cacheRef = useRef(""); const timerRef = useRef>(); // Sync style from collaborative Y.Map("settings") useEffect(() => { const settingsMap = (editor.storage.citation as any)?.settingsMap; if (!settingsMap) return; const sync = () => { const val = settingsMap.get("citationStyle"); if (val && val !== style) setStyle(val as string); }; sync(); settingsMap.observe(sync); return () => settingsMap.unobserve(sync); }, [editor]); const refresh = useCallback(() => { const citationsMap = (editor.storage.citation as any)?.citationsMap; if (!citationsMap) return; const keys = new Set(); editor.state.doc.descendants((node) => { if (node.type.name === "citation" && node.attrs.key) { keys.add(node.attrs.key as string); } }); const entries: any[] = []; for (const key of keys) { const entry = citationsMap.get(key); if (entry) entries.push({ ...entry, id: key }); } setCount(entries.length); if (!entries.length) { setHtml(""); cacheRef.current = ""; return; } const cacheKey = entries .map((e) => e.id) .sort() .join(",") + `:${style}`; if (cacheKey === cacheRef.current) return; cacheRef.current = cacheKey; fetch("/api/citations/format", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ entries, style }), }) .then((r) => r.json()) .then((data) => { if (data.html) { setHtml(data.html); // Persist rendered HTML into node attrs for server-side generation const pos = getPos(); if (typeof pos === "number" && node.attrs.renderedHtml !== data.html) { editor.commands.command(({ tr }) => { tr.setNodeMarkup(pos, undefined, { ...node.attrs, renderedHtml: data.html }); return true; }); } } }) .catch(console.error); }, [editor, style]); useEffect(() => { const debouncedRefresh = () => { clearTimeout(timerRef.current); timerRef.current = setTimeout(refresh, 500); }; refresh(); const citationsMap = (editor.storage.citation as any)?.citationsMap; if (citationsMap) citationsMap.observe(debouncedRefresh); const onUpdate = () => debouncedRefresh(); editor.on("update", onUpdate); return () => { clearTimeout(timerRef.current); if (citationsMap) citationsMap.unobserve(debouncedRefresh); editor.off("update", onUpdate); }; }, [editor, refresh]); return (

References

{count === 0 ? (

No citations yet. Use /Citation to add references.

) : (
)}
); }