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
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { PinLinkPicker } from "./PinLinkPicker.jsx";
|
||||
|
||||
/**
|
||||
* The notes for a page, written in the margin beside it.
|
||||
*
|
||||
@@ -21,9 +19,6 @@ export const MARGIN_W = 208;
|
||||
const CARD_H = 74;
|
||||
const CARD_GAP = 6;
|
||||
|
||||
/** A card carrying a link to a note is one row taller. */
|
||||
const heightOf = (note) => CARD_H + (note.noteId ? 20 : 0);
|
||||
|
||||
/** Cards at their anchors, pushed down where they would sit on top of each other. */
|
||||
function layout(notes, pageHeight, cssHeight) {
|
||||
let previousBottom = -Infinity;
|
||||
@@ -33,14 +28,13 @@ function layout(notes, pageHeight, cssHeight) {
|
||||
.map((note) => {
|
||||
const anchor = (note.y / pageHeight) * cssHeight - 12;
|
||||
const top = Math.max(anchor, previousBottom + CARD_GAP);
|
||||
previousBottom = top + heightOf(note);
|
||||
previousBottom = top + CARD_H;
|
||||
return { note, top };
|
||||
});
|
||||
}
|
||||
|
||||
function Card({ T, note, top, number, open, onOpen, onChange, onRemove, link }) {
|
||||
function Card({ T, note, top, number, open, onOpen, onChange, onRemove }) {
|
||||
const [draft, setDraft] = useState(note.text);
|
||||
const [picking, setPicking] = useState(false);
|
||||
const areaRef = useRef(null);
|
||||
|
||||
// Adopt text that arrived from elsewhere — another device, or an undo — but
|
||||
@@ -144,23 +138,6 @@ function Card({ T, note, top, number, open, onOpen, onChange, onRemove, link })
|
||||
>
|
||||
Entfernen
|
||||
</button>
|
||||
{!note.noteId && (
|
||||
<>
|
||||
<button
|
||||
data-pin-make-note
|
||||
onClick={() => {
|
||||
commit();
|
||||
link.onCreate(note.id, draft);
|
||||
}}
|
||||
style={linkAction(T)}
|
||||
>
|
||||
Notiz anlegen
|
||||
</button>
|
||||
<button data-pin-link onClick={() => setPicking((v) => !v)} style={linkAction(T)}>
|
||||
Verknüpfen
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<span style={{ flex: 1 }} />
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -181,18 +158,6 @@ function Card({ T, note, top, number, open, onOpen, onChange, onRemove, link })
|
||||
Fertig
|
||||
</button>
|
||||
</div>
|
||||
{picking && (
|
||||
<PinLinkPicker
|
||||
T={T}
|
||||
search={link.search}
|
||||
onClose={() => setPicking(false)}
|
||||
onPick={(target) => {
|
||||
setPicking(false);
|
||||
commit();
|
||||
link.onLink(note.id, target.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
@@ -211,86 +176,15 @@ function Card({ T, note, top, number, open, onOpen, onChange, onRemove, link })
|
||||
{note.text || "Leer — zum Schreiben antippen"}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The note this remark grew into. Shown in both states: when the card
|
||||
is closed it is the whole point of the card, and when it is open it
|
||||
is what stops a second note being made for the same passage. */}
|
||||
{note.noteId && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 6 }}>
|
||||
<button
|
||||
data-pin-goto={note.noteId}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
link.onOpenNote(note.noteId);
|
||||
}}
|
||||
title="Zur Notiz springen"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
color: T.accent,
|
||||
cursor: "pointer",
|
||||
fontSize: 10.5,
|
||||
fontWeight: 600,
|
||||
padding: 0,
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<span style={{ flexShrink: 0 }}>→</span>
|
||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{link.labelOf(note.noteId) ?? "Notiz gelöscht"}
|
||||
</span>
|
||||
</button>
|
||||
{open && (
|
||||
<button
|
||||
data-pin-unlink
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
link.onLink(note.id, null);
|
||||
}}
|
||||
title="Verweis lösen (die Notiz bleibt)"
|
||||
style={{ ...linkAction(T), color: T.muted }}
|
||||
>
|
||||
Lösen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const linkAction = (T) => ({
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
color: T.accent,
|
||||
cursor: "pointer",
|
||||
fontSize: 10.5,
|
||||
padding: 0,
|
||||
marginRight: 10,
|
||||
});
|
||||
|
||||
export function PdfMargin({
|
||||
T,
|
||||
notes,
|
||||
pageHeight,
|
||||
cssHeight,
|
||||
openId,
|
||||
onOpen,
|
||||
onChange,
|
||||
onRemove,
|
||||
numberOf,
|
||||
link,
|
||||
}) {
|
||||
export function PdfMargin({ T, notes, pageHeight, cssHeight, openId, onOpen, onChange, onRemove, numberOf }) {
|
||||
const placed = layout(notes, pageHeight, cssHeight);
|
||||
// Cards near the foot of a page may need more room than the page is tall.
|
||||
const last = placed[placed.length - 1];
|
||||
const needed = last ? last.top + heightOf(last.note) : 0;
|
||||
const needed = placed.length ? placed[placed.length - 1].top + CARD_H : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -313,7 +207,6 @@ export function PdfMargin({
|
||||
onOpen={onOpen}
|
||||
onChange={(text) => onChange(note.id, text)}
|
||||
onRemove={() => onRemove(note.id)}
|
||||
link={link}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user