Link the margin notes from the notes, and let them be hidden
The linking went the wrong way round. A remark in the margin is short by design; when there is more to say about that passage it belongs in the notes beside the document — and it has to be possible to say *which* passage. So the pins are offered to the editor's `@` menu exactly like notes and notebooks are, labelled with their page, and following one scrolls the document to the spot and opens the remark. Pins are unhidden first: a link leading to something invisible would read as broken. A pin link is the same node with kind "pin", and is deliberately kept out of the links reported to the server. Its target is a marker inside this note's own overlay, so no note has that id, and reporting it would fill the backlink table with rows matching nothing. They can also be hidden altogether. Reading a document as it was printed is a normal thing to want; the toolbar takes the markers and the column away together and the page takes the width back. The other direction — making a note out of a remark — is removed, along with its picker and the pin's noteId: 357 lines fewer than before. The unmount flush it turned up stays, since a drawing save was genuinely being dropped by navigating away inside its debounce. 218 unit tests, 231 browser checks across eleven suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
502aecfc92
commit
4c1b632029
13 files changed
+239
-484
No files matched your search
@@ -135,7 +135,6 @@ function PdfPage({ doc, index, width, height, size, visible, ink, pins, selectab
|
||||
onChange={(id, text) => pins.onChange(index + 1, size, id, text)}
|
||||
onRemove={(id) => pins.onRemove(index + 1, size, id)}
|
||||
numberOf={pins.numberOf}
|
||||
link={pins.link}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -150,12 +149,13 @@ function PdfPage({ doc, index, width, height, size, visible, ink, pins, selectab
|
||||
* proportions before anything is drawn, so the scrollbar is honest from the
|
||||
* first frame instead of jumping as pages arrive.
|
||||
*/
|
||||
export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pinLinks }) {
|
||||
export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, focusPin, onFocusHandled }) {
|
||||
const [state, setState] = useState({ status: "loading" });
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [drawing, setDrawing] = useState(false);
|
||||
const [placing, setPlacing] = useState(false);
|
||||
const [openPin, setOpenPin] = useState(null);
|
||||
const [showPins, setShowPins] = useState(true);
|
||||
const [hideInk, setHideInk] = useState(false);
|
||||
const [tool, setTool] = useState("pen");
|
||||
const [color, setColor] = useState(COLORS[1]);
|
||||
@@ -313,34 +313,24 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
[writeNotes]
|
||||
);
|
||||
|
||||
/** Points a pin at a note, or lets go of one — the note itself is untouched. */
|
||||
const linkNote = useCallback(
|
||||
(pageNumber, size, id, noteId) =>
|
||||
writeNotes(pageNumber, size, (notes) => notes.map((n) => (n.id === id ? { ...n, noteId } : n))),
|
||||
[writeNotes]
|
||||
);
|
||||
|
||||
/**
|
||||
* Makes a note out of a remark and jumps to it.
|
||||
* Someone followed a link to a pinned note from the notes beside the document.
|
||||
*
|
||||
* The pin keeps the new note's id, so the passage leads there ever after; the
|
||||
* note is created with a link back, so the document lists it in its backlinks.
|
||||
* Scrolls its page into view and opens it. The pins are unhidden first: a link
|
||||
* that led to something invisible would read as broken.
|
||||
*/
|
||||
const makeNoteFor = useCallback(
|
||||
async (pageNumber, size, id, text) => {
|
||||
if (!pinLinks?.onCreate) return;
|
||||
const title = (text || "").trim().split("\n")[0]?.slice(0, 60) || `Notiz zu Seite ${pageNumber}`;
|
||||
const created = await pinLinks.onCreate(title);
|
||||
if (!created) return;
|
||||
// Linked before leaving, and the pending write is flushed on unmount.
|
||||
linkNote(pageNumber, size, id, created.id);
|
||||
setOpenPin(null);
|
||||
// Not `onOpenNote`: that one looks the note up in a list this note is not
|
||||
// in yet, and would quietly go nowhere.
|
||||
pinLinks.onOpenCreated?.(created.id, created.folderId);
|
||||
},
|
||||
[pinLinks, linkNote]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!focusPin || state.status !== "ready") return;
|
||||
const target = allPinnedNotes(ink).find((n) => n.id === focusPin);
|
||||
onFocusHandled?.();
|
||||
if (!target) return;
|
||||
setShowPins(true);
|
||||
setOpenPin(target.id);
|
||||
const row = innerRef.current?.querySelector(`[data-pdf-row="${target.page}"]`);
|
||||
if (row && scrollRef.current) {
|
||||
scrollRef.current.scrollTo({ top: row.offsetTop - 8, behavior: "smooth" });
|
||||
}
|
||||
}, [focusPin, state.status, ink, onFocusHandled]);
|
||||
|
||||
const [pageNo, setPageNo] = useState(1);
|
||||
const [visible, setVisible] = useState(() => new Set([0, 1]));
|
||||
@@ -429,7 +419,7 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
* is being placed — so it is already there before the first note exists and
|
||||
* the page does not jump sideways at the moment of writing it. */
|
||||
const roomForMargin = containerWidth >= MARGIN_W + 380;
|
||||
const withMargin = roomForMargin && (placing || totalPinnedNotes(ink) > 0);
|
||||
const withMargin = showPins && roomForMargin && (placing || totalPinnedNotes(ink) > 0);
|
||||
|
||||
/* The open note, with the page it belongs to — the sheet needs the page size
|
||||
to write a change back into the right page's overlay. */
|
||||
@@ -599,6 +589,31 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
{I.pen} {drawing ? "Zeichnen" : "Lesen"}
|
||||
</button>
|
||||
|
||||
{/* Reading the document as it was printed is a normal thing to want, and
|
||||
so is having every remark in view. Both are one click away. */}
|
||||
{totalPinnedNotes(ink) > 0 && (
|
||||
<button
|
||||
data-pdf-show-pins={showPins ? "on" : "off"}
|
||||
onClick={() => {
|
||||
setShowPins((v) => !v);
|
||||
setOpenPin(null);
|
||||
}}
|
||||
title={showPins ? "Randnotizen ausblenden" : "Randnotizen einblenden"}
|
||||
style={{
|
||||
...barButton,
|
||||
width: "auto",
|
||||
padding: "0 10px",
|
||||
gap: 6,
|
||||
border: `1px solid ${showPins ? T.border : T.accent + "66"}`,
|
||||
background: showPins ? "transparent" : T.accent + "1a",
|
||||
color: showPins ? T.muted : T.accent,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{showPins ? I.eye : I.eyeOff} Randnotizen
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* A note about a passage belongs on the passage. */}
|
||||
<button
|
||||
data-pdf-pin-mode={placing ? "on" : "off"}
|
||||
@@ -785,7 +800,7 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
notes: notesForPage(ink, i + 1),
|
||||
placing,
|
||||
openId: openPin,
|
||||
hidden: hideInk,
|
||||
hidden: hideInk || !showPins,
|
||||
withMargin,
|
||||
onOpen: setOpenPin,
|
||||
onPlace: placeNote,
|
||||
@@ -793,13 +808,6 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
onMove: moveNote,
|
||||
onRemove: removeNote,
|
||||
numberOf: (id) => pinNumbers.get(id) ?? "",
|
||||
link: {
|
||||
search: pinLinks?.search,
|
||||
labelOf: pinLinks?.labelOf,
|
||||
onOpenNote: pinLinks?.onOpenNote,
|
||||
onCreate: (pinId, text) => makeNoteFor(i + 1, state.sizes[i], pinId, text),
|
||||
onLink: (pinId, noteId) => linkNote(i + 1, state.sizes[i], pinId, noteId),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -817,13 +825,6 @@ export function PdfViewer({ note, dark, onUpdate, onPagesReady, notesToggle, pin
|
||||
onChange={(text) => changeNote(openSheet.page, openSheet.size, openSheet.note.id, text)}
|
||||
onRemove={() => removeNote(openSheet.page, openSheet.size, openSheet.note.id)}
|
||||
onClose={() => setOpenPin(null)}
|
||||
link={{
|
||||
search: pinLinks?.search,
|
||||
labelOf: pinLinks?.labelOf,
|
||||
onOpenNote: pinLinks?.onOpenNote,
|
||||
onCreate: (text) => makeNoteFor(openSheet.page, openSheet.size, openSheet.note.id, text),
|
||||
onLink: (noteId) => linkNote(openSheet.page, openSheet.size, openSheet.note.id, noteId),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user