Let a drawing run over as many pages as the note
The ink was stuck on page one while the writing carried on without it, which stopped making sense the moment a note could run to four pages. Every page is exported anyway, so every page can be drawn on. Pages are numbered the way the PDF reader numbers them, and only pages that carry ink are stored, so the keys are the drawn pages. While drawing there is always one blank page past the end: pages otherwise come from how far the writing runs, and to draw on page three you would first have had to write three pages. A drawing that lives entirely on page one is still written in the old single-page shape. Nothing requires that — it means an ordinary note is stored exactly as it always was, and a device running an older build still shows it. The bin clears the page being looked at. Undo and redo work on the drawing as a whole, because one Undo button that means four different things depending on where you scrolled is not a button anyone can use. And a real bug found on the way out: the exporter has written drawings/<note>.png since the interface was put into English, while the importer was still looking for zeichnungen/<note>.png. A drawing exported by this very app came back from its own archive as nothing at all. Both names are read now, and so are the further pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b8c5c42217
commit
228552d453
9 files changed
+671
-136
No files matched your search
@@ -29,6 +29,11 @@ export interface ImportProgress {
|
||||
label: string;
|
||||
}
|
||||
|
||||
/* How far to look for further pages of one note's drawing. A bound rather than
|
||||
* a limit anyone should reach: without one, a malformed archive would have this
|
||||
* loop asking for filenames forever. */
|
||||
const MAX_DRAWING_PAGES = 200;
|
||||
|
||||
export interface ImportResult {
|
||||
notes: NoteMeta[];
|
||||
/** Notes whose drawing came back as a picture instead of editable strokes. */
|
||||
@@ -140,14 +145,42 @@ async function importOne(
|
||||
else result.missingImages++;
|
||||
}
|
||||
|
||||
/* The drawing, if the archive carried one. Two layouts exist: a notebook
|
||||
* export puts it in `zeichnungen/`, a single-note export writes it beside the
|
||||
* file as `<name>-zeichnung.png`. */
|
||||
/* The drawing, if the archive carried one.
|
||||
*
|
||||
* Four layouts exist. A notebook export files it under `drawings/`, a
|
||||
* single-note export writes it beside the file as `<name>-drawing.png`, and
|
||||
* both had German names — `zeichnungen/`, `-zeichnung` — before the interface
|
||||
* was put into English. All four are read: an archive written last year is
|
||||
* exactly the archive someone reaches for.
|
||||
*
|
||||
* Only the first two were being looked for until now, which meant a drawing
|
||||
* exported by this very app came back as nothing at all.
|
||||
*
|
||||
* A drawing may run over several pages. Page one keeps the plain name and the
|
||||
* rest are numbered after it, so they come back in order.
|
||||
*/
|
||||
const base = baseName(markdownPath);
|
||||
const drawingPath = [`zeichnungen/${base}.png`, `${base}-zeichnung.png`].find((p) => sources.files.has(p));
|
||||
const drawingId = drawingPath ? await uploadPicture(sources.files.get(drawingPath)!, drawingPath) : null;
|
||||
if (drawingId) result.flattenedDrawings++;
|
||||
const drawingIds: string[] = [];
|
||||
for (const suffix of ["", ...Array.from({ length: MAX_DRAWING_PAGES - 1 }, (_, i) => `-${i + 2}`)]) {
|
||||
const path = [
|
||||
`drawings/${base}${suffix}.png`,
|
||||
`${base}-drawing${suffix}.png`,
|
||||
`zeichnungen/${base}${suffix}.png`,
|
||||
`${base}-zeichnung${suffix}.png`,
|
||||
].find((p) => sources.files.has(p));
|
||||
const bytes = path ? sources.files.get(path) : null;
|
||||
if (!path || !bytes) break; // pages are consecutive; the first gap is the end
|
||||
const id = await uploadPicture(bytes, path);
|
||||
if (id) drawingIds.push(id);
|
||||
}
|
||||
if (drawingIds.length) result.flattenedDrawings++;
|
||||
|
||||
const asPicture = (id: string) => ({
|
||||
type: "image",
|
||||
attrs: { attachmentId: id, alt: "Drawing", src: null },
|
||||
});
|
||||
|
||||
let replaced = false;
|
||||
let doc = mapNodes(parsed.doc, (n) => {
|
||||
if (n.type === "image") {
|
||||
const id = n.attrs?.src ? uploaded.get(n.attrs.src) : null;
|
||||
@@ -158,22 +191,20 @@ async function importOne(
|
||||
}
|
||||
// The exporter leaves a note saying the drawing lies beside the file. Now it
|
||||
// is back inside the note, so the note about it would be wrong.
|
||||
if (drawingId && isDrawingMarker(n)) {
|
||||
return { type: "image", attrs: { attachmentId: drawingId, alt: "Zeichnung", src: null } };
|
||||
const first = drawingIds[0];
|
||||
if (first && isDrawingMarker(n)) {
|
||||
replaced = true;
|
||||
return asPicture(first);
|
||||
}
|
||||
return n;
|
||||
})!;
|
||||
|
||||
// No marker to replace (a hand-written archive, or an older export) — the
|
||||
// drawing still belongs in the note, so it goes at the end.
|
||||
if (drawingId && !JSON.stringify(doc).includes(drawingId)) {
|
||||
doc = {
|
||||
...doc,
|
||||
content: [
|
||||
...(doc.content ?? []),
|
||||
{ type: "image", attrs: { attachmentId: drawingId, alt: "Zeichnung", src: null } },
|
||||
],
|
||||
};
|
||||
// The marker stands for the drawing as a whole, so it can only become the
|
||||
// first page; the others follow it at the end. With no marker at all (a
|
||||
// hand-written archive, or an older export) every page goes there.
|
||||
const trailing = replaced ? drawingIds.slice(1) : drawingIds;
|
||||
if (trailing.length) {
|
||||
doc = { ...doc, content: [...(doc.content ?? []), ...trailing.map(asPicture)] };
|
||||
}
|
||||
|
||||
const note = await sync.createNote(folderId);
|
||||
@@ -182,7 +213,7 @@ async function importOne(
|
||||
note.id,
|
||||
serializeNoteContent(doc),
|
||||
[],
|
||||
[...uploaded.values(), ...(drawingId ? [drawingId] : [])]
|
||||
[...uploaded.values(), ...drawingIds]
|
||||
);
|
||||
return { ...note, title };
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user