import React, { useState, useMemo, useRef } from 'react'; import Icon from './CanvasIcon'; import { WIDGET_CATALOG, CATEGORIES } from './canvasData'; const fireToast = (msg, kind = 'success') => window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg, kind } })); // ---------- Add citation (with DOI lookup via CrossRef) ---------- export function AddCitationModal({ data, onClose }) { const init = data.initial || {}; const [authors, setA] = useState(init.authors || ''); const [title, setT] = useState(init.title || ''); const [journal, setJ] = useState(init.journal || ''); const [year, setY] = useState(init.year || new Date().getFullYear()); const [doi, setDoi] = useState(init.doi || ''); const [lookingUp, setLookingUp] = useState(false); const [bibtexInput, setBibtexInput] = useState(''); const [showBibtex, setShowBibtex] = useState(false); const valid = authors && title && journal; const editing = !!init.key; const lookupDoi = async () => { const cleaned = doi.trim().replace(/^https?:\/\/(dx\.)?doi\.org\//, ''); if (!cleaned) return; setLookingUp(true); try { const res = await fetch(`https://api.crossref.org/works/${encodeURIComponent(cleaned)}`); if (!res.ok) throw new Error('Not found'); const json = await res.json(); const w = json.message; const authorList = (w.author || []).map(a => `${a.family || ''}, ${(a.given || '').charAt(0)}.`).join('; '); setA(authorList || 'Unknown'); setT((w.title && w.title[0]) || 'Untitled'); setJ((w['container-title'] && w['container-title'][0]) || ''); setY((w.issued && w.issued['date-parts'] && w.issued['date-parts'][0][0]) || new Date().getFullYear()); fireToast('DOI resolved · fields filled'); } catch (e) { fireToast(`DOI lookup failed: ${e.message}`, 'danger'); } finally { setLookingUp(false); } }; const importBibtex = () => { // Minimal BibTeX parser: pull author/title/journal/year out of the first @entry block. const get = (field) => { const m = bibtexInput.match(new RegExp(`${field}\\s*=\\s*[{"]([^}"]+)`, 'i')); return m ? m[1].trim() : ''; }; const a = get('author'); const t = get('title'); const j = get('journal') || get('booktitle'); const y = get('year'); if (!t) { fireToast('Could not parse BibTeX', 'danger'); return; } setA(a); setT(t); setJ(j); setY(y || new Date().getFullYear()); setShowBibtex(false); fireToast('BibTeX imported'); }; const submit = () => { if (!valid) return; const key = init.key || (authors.split(',')[0] || 'cite').toLowerCase().replace(/[^a-z]/g, '') + year; data.onAdd({ key, authors, title, journal, year: +year, cited: init.cited || 0, doi: doi || init.doi }); fireToast(`${editing ? 'Updated' : 'Added'} @${key}`); onClose(); }; return (
e.stopPropagation()}>
{editing ? 'Edit citation' : 'Add citation'}
{editing ? `@${init.key}` : 'Paste a DOI or BibTeX, or fill in manually.'}
setDoi(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') lookupDoi(); }}/>
{showBibtex && (