import { useEffect, useState, useCallback, useRef } from "react"; import type { Editor as TiptapEditor } from "@tiptap/core"; import type { FrontmatterStore, FrontmatterData } from "./frontmatter/frontmatter-store"; function extractYear(dateStr?: string): number | undefined { if (!dateStr) return undefined; const d = new Date(dateStr); if (!Number.isNaN(d.getTime())) return d.getFullYear(); const m = dateStr.match(/(19|20)\d{2}/); return m ? Number(m[0]) : undefined; } function buildCitationText(data: FrontmatterData): string { const names = data.authors.map((a) => a.name).filter(Boolean); const year = extractYear(data.published); const title = (data.title || "Untitled").replace(/\s+/g, " ").trim(); return `${names.join(", ")}${year ? ` (${year})` : ""}. "${title}".`; } function buildBibtex(data: FrontmatterData): string { const names = data.authors.map((a) => a.name).filter(Boolean); const title = (data.title || "Untitled").replace(/\s+/g, " ").trim(); const year = extractYear(data.published); const keyAuthor = (names[0] || "article").split(/\s+/).slice(-1)[0].toLowerCase(); const keyTitle = title.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, ""); const bibKey = `${keyAuthor}${year ?? ""}_${keyTitle}`; const parts = [` title={${title}}`, ` author={${names.join(" and ")}}`]; if (year) parts.push(` year={${year}}`); if (data.doi) parts.push(` doi={${data.doi}}`); return `@misc{${bibKey},\n${parts.join(",\n")}\n}`; } interface FootnoteItem { index: number; content: string; } function collectFootnotes(editor: TiptapEditor): FootnoteItem[] { const items: FootnoteItem[] = []; let i = 0; editor.state.doc.descendants((node) => { if (node.type.name === "footnote") { i++; items.push({ index: i, content: (node.attrs.content as string) || "" }); } }); return items; } function getBibliographyHtml(editor: TiptapEditor): string { let html = ""; editor.state.doc.descendants((node) => { if (node.type.name === "bibliography" && node.attrs.renderedHtml) { html = node.attrs.renderedHtml as string; } }); return html; } interface EditorFooterProps { store: FrontmatterStore | null; editor: TiptapEditor | null; } export function EditorFooter({ store, editor }: EditorFooterProps) { const [data, setData] = useState(null); const [biblioHtml, setBiblioHtml] = useState(""); const [footnotes, setFootnotes] = useState([]); useEffect(() => { if (!store) return; const sync = () => setData(store.getAll()); sync(); return store.observe(sync); }, [store]); const refreshEditorData = useCallback(() => { if (!editor) return; setBiblioHtml(getBibliographyHtml(editor)); setFootnotes(collectFootnotes(editor)); }, [editor]); const footerTimerRef = useRef(0); useEffect(() => { if (!editor) return; refreshEditorData(); const debouncedRefresh = () => { clearTimeout(footerTimerRef.current); footerTimerRef.current = window.setTimeout(refreshEditorData, 500); }; editor.on("update", debouncedRefresh); return () => { editor.off("update", debouncedRefresh); clearTimeout(footerTimerRef.current); }; }, [editor, refreshEditorData]); if (!data) return null; const citation = buildCitationText(data); const bibtex = buildBibtex(data); return ( ); }