Download frontend/src/components/NotebookDrawer.jsx from kiyer/beacon: direct link, hf CLI and curl.
- Browser
- Download file 3.27 kB
-
https://huggingface.co/spaces/kiyer/beacon/resolve/main/frontend/src/components/NotebookDrawer.jsx
- Command line
-
hf download hf://spaces/kiyer/beacon/frontend/src/components/NotebookDrawer.jsx
-
curl -L -o NotebookDrawer.jsx https://huggingface.co/spaces/kiyer/beacon/resolve/main/frontend/src/components/NotebookDrawer.jsx
3.27 kB
| 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> | |
| </> | |
| ); | |
| } | |