File size: 3,266 Bytes
ab6adbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4894c4f
ab6adbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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}>&times;</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>
    </>
  );
}