import { useState, useCallback, useEffect } from "react"; import type { Editor } from "@tiptap/core"; import type * as Y from "yjs"; interface CitationPanelProps { editor: Editor; citationsMap: Y.Map; onClose: () => void; } function isBibTeX(input: string): boolean { const trimmed = input.trim(); return trimmed.startsWith("@") && /\{[\s\S]*\}/.test(trimmed); } /** * Single smart-field citation panel. * Auto-detects DOI / URL / BibTeX from the input. * Library is always visible below. */ export function CitationPanel({ editor, citationsMap, onClose, }: CitationPanelProps) { const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [libraryEntries, setLibraryEntries] = useState< { key: string; entry: any }[] >([]); useEffect(() => { const refresh = () => setLibraryEntries(getLibraryEntries(citationsMap)); refresh(); citationsMap.observe(refresh); return () => citationsMap.unobserve(refresh); }, [citationsMap]); const ensureBibliography = useCallback(() => { let hasBibliography = false; editor.state.doc.descendants((node) => { if (node.type.name === "bibliography") hasBibliography = true; }); if (!hasBibliography) { const endPos = editor.state.doc.content.size; editor .chain() .insertContentAt(endPos, [ { type: "paragraph" }, { type: "bibliography" }, ]) .run(); } }, [editor]); const insertCitation = useCallback( (entry: any) => { const key = entry["citation-key"] || entry.id || generateKey(entry); citationsMap.set(key, entry); editor.chain().focus().insertCitation(key).run(); ensureBibliography(); onClose(); }, [editor, citationsMap, onClose, ensureBibliography], ); const insertExisting = useCallback( (key: string) => { editor.chain().focus().insertCitation(key).run(); ensureBibliography(); onClose(); }, [editor, onClose, ensureBibliography], ); const resolveInput = useCallback(async () => { if (!input.trim()) return; setLoading(true); setError(""); try { const bibtex = isBibTeX(input); const endpoint = bibtex ? "/api/citations/import-bib" : "/api/citations/resolve"; const body = bibtex ? { bibtex: input } : { input }; const res = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) { setError(data.error || "Resolution failed"); return; } if (data.entries?.length >= 1) { data.entries.forEach((entry: any) => { const key = entry["citation-key"] || entry.id || generateKey(entry); citationsMap.set(key, entry); }); insertCitation(data.entries[0]); } } catch (err: any) { setError(err.message || "Network error"); } finally { setLoading(false); } }, [input, insertCitation, citationsMap]); return (
e.stopPropagation()}>

Add reference