| import React from 'react'; |
|
|
| export default function NotebookDrawer({ |
| open, |
| onClose, |
| paper, |
| userNotes, |
| paperNote, |
| onSavePaperNote, |
| onScrollToPara, |
| }) { |
| const [draft, setDraft] = React.useState(paperNote || ''); |
| const timerRef = React.useRef(null); |
|
|
| React.useEffect(() => { setDraft(paperNote || ''); }, [paperNote]); |
| React.useEffect(() => { return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, []); |
|
|
| const handlePaperNoteChange = (e) => { |
| const val = e.target.value; |
| setDraft(val); |
| if (timerRef.current) clearTimeout(timerRef.current); |
| timerRef.current = setTimeout(() => onSavePaperNote && onSavePaperNote(val), 400); |
| }; |
|
|
| const paras = paper?.paragraphs || []; |
| const notedParas = paras.filter((p) => userNotes[p.id]?.text); |
|
|
| const handleExport = () => { |
| const title = paper?.title || 'paper'; |
| const date = new Date().toISOString().slice(0, 10); |
| const lines = [`# Notes: ${title}`, `*${date}*`, '']; |
| if (draft.trim()) { |
| lines.push('## General', '', draft.trim(), ''); |
| } |
| notedParas.forEach((p) => { |
| lines.push(`## ${p.section}`, '', `> ${p.text.slice(0, 120)}…`, '', |
| userNotes[p.id].text, ''); |
| }); |
| const blob = new Blob([lines.join('\n')], { type: 'text/markdown' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `${title.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}-notes.md`; |
| a.click(); |
| URL.revokeObjectURL(url); |
| }; |
|
|
| React.useEffect(() => { |
| const onKey = (e) => { if (e.key === 'Escape') onClose(); }; |
| window.addEventListener('keydown', onKey); |
| return () => window.removeEventListener('keydown', onKey); |
| }, [onClose]); |
|
|
| return ( |
| <> |
| <div className={'ap-notebook-backdrop' + (open ? ' is-open' : '')} onClick={onClose} /> |
| <aside className={'ap-notebook' + (open ? ' is-open' : '')}> |
| <div className="ap-notebook-head"> |
| <span className="ap-notebook-title">Notebook</span> |
| <button className="ap-notebook-export" onClick={handleExport}>Export .md</button> |
| <button className="ap-notebook-close" onClick={onClose}>×</button> |
| </div> |
| <div className="ap-notebook-paper-note"> |
| <div className="ap-notebook-label">General note</div> |
| <textarea |
| className="ap-notebook-paper-text" |
| value={draft} |
| placeholder="notes about this paper…" |
| onChange={handlePaperNoteChange} |
| rows={3} |
| /> |
| </div> |
| <div className="ap-notebook-entries"> |
| {notedParas.length === 0 && ( |
| <div className="ap-notebook-empty">No paragraph notes yet. Use the ✎ button in a margin card.</div> |
| )} |
| {notedParas.map((p) => ( |
| <div key={p.id} className="ap-notebook-entry" onClick={() => onScrollToPara && onScrollToPara(p.id)}> |
| <div className="ap-notebook-entry-section">{p.section}</div> |
| <div className="ap-notebook-entry-snippet">{p.text.slice(0, 90)}…</div> |
| <div className="ap-notebook-entry-note">{userNotes[p.id].text}</div> |
| </div> |
| ))} |
| </div> |
| </aside> |
| </> |
| ); |
| } |
|
|