Ask what a single note's export should contain, and fix drawings never being in one
Two questions instead of one, because the answers depend on each other: Markdown holds text only, a PNG holds the drawing only, PDF holds both. Listing every format flat would have offered combinations that cannot exist. A note without a drawing skips the first step rather than showing it mostly greyed out. While testing that, the drawings turned out never to have made it into any export at all — not the ZIP, not the PDF. strokeDocOf() *builds* a stroke document from a bare array, and it was being handed one that was already a document, so the strokes ended up nested one level too deep and every length check read undefined. Nothing complained; the export simply came out without the drawing. It went unnoticed because the notes the earlier suite exported had none. Now uses docStrokes(), and the suite draws on a note before exporting it, checks the PNG's magic bytes, and checks it is 159 KB rather than an empty white page — a check on the file merely existing would have passed against the broken version too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2611d87f8c
commit
8c36c6121b
5 files changed
+284
-57
No files matched your search
@@ -10,7 +10,7 @@
|
||||
import * as sync from "../syncEngine";
|
||||
import { parseNoteContent } from "../editor/content";
|
||||
import { parsingExtensions } from "../editor/extensions.js";
|
||||
import { CANON_W, CANON_H, paintStrokeDoc, sizeCanvas, strokeDocOf } from "../features/drawing/strokes.js";
|
||||
import { CANON_W, CANON_H, docStrokes, paintStrokeDoc, sizeCanvas } from "../features/drawing/strokes.js";
|
||||
import type { NoteContent, NoteMeta, Notebook } from "../types";
|
||||
import { noteToMarkdown, safeFilename } from "./markdown";
|
||||
import { printNotes, type PrintableNote } from "./print";
|
||||
@@ -30,9 +30,10 @@ type Loaded = { meta: NoteMeta; content: NoteContent };
|
||||
/* ── pieces ── */
|
||||
|
||||
/** Renders a stroke document to PNG bytes, so a drawing survives as a picture. */
|
||||
async function drawingToPng(strokes: unknown): Promise<Uint8Array | null> {
|
||||
const doc = strokeDocOf(strokes);
|
||||
if (!doc?.strokes?.length) return null;
|
||||
async function drawingToPng(doc: unknown): Promise<Uint8Array | null> {
|
||||
// `doc` is already a stroke document as stored — strokeDocOf() builds one from
|
||||
// a bare array and would wrap this one a second time, hiding the strokes.
|
||||
if (docStrokes(doc).length === 0) return null;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const height = Math.round((DRAWING_EXPORT_W * CANON_H) / CANON_W);
|
||||
@@ -81,10 +82,31 @@ function nameFor(meta: NoteMeta, taken: Set<string>): string {
|
||||
|
||||
/* ── single note ── */
|
||||
|
||||
/** What a single-note export should contain. */
|
||||
export type ExportParts = "text" | "drawing" | "both";
|
||||
|
||||
/** Whether this note has a drawing — decides which options the wizard offers. */
|
||||
export async function noteHasDrawing(meta: NoteMeta): Promise<boolean> {
|
||||
try {
|
||||
const { content } = await loadNote(meta);
|
||||
return docStrokes(content.drawingStrokes).length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The drawing on its own, as a PNG on white paper. */
|
||||
export async function exportDrawingAsPng(meta: NoteMeta): Promise<void> {
|
||||
const { content } = await loadNote(meta);
|
||||
const png = await drawingToPng(content.drawingStrokes);
|
||||
if (!png) throw new Error("Diese Notiz enthält keine Zeichnung.");
|
||||
download(bytesToBlob(png, "image/png"), `${safeFilename(meta.title)}.png`);
|
||||
}
|
||||
|
||||
export async function exportNoteAsMarkdown(meta: NoteMeta): Promise<void> {
|
||||
const { content } = await loadNote(meta);
|
||||
const doc = parseNoteContent(content.content, parsingExtensions);
|
||||
const hasDrawing = !!strokeDocOf(content.drawingStrokes)?.strokes?.length;
|
||||
const hasDrawing = docStrokes(content.drawingStrokes).length > 0;
|
||||
const ids = attachmentIds(doc);
|
||||
|
||||
const md = noteToMarkdown(doc, {
|
||||
@@ -133,7 +155,8 @@ const blobToDataUrl = (blob: Blob): Promise<string> =>
|
||||
export async function exportNotesAsPdf(
|
||||
metas: NoteMeta[],
|
||||
title: string,
|
||||
onProgress?: (p: ExportProgress) => void
|
||||
onProgress?: (p: ExportProgress) => void,
|
||||
parts: ExportParts = "both"
|
||||
): Promise<void> {
|
||||
const printable: PrintableNote[] = [];
|
||||
const total = metas.length;
|
||||
@@ -153,10 +176,10 @@ export async function exportNotesAsPdf(
|
||||
}
|
||||
}
|
||||
|
||||
const png = await drawingToPng(content.drawingStrokes);
|
||||
const png = parts === "text" ? null : await drawingToPng(content.drawingStrokes);
|
||||
const drawing = png ? await blobToDataUrl(bytesToBlob(png, "image/png")) : null;
|
||||
|
||||
printable.push({ meta, content, drawing, images });
|
||||
printable.push({ meta, content, drawing, images, includeText: parts !== "drawing" });
|
||||
} catch {
|
||||
/* an unreadable note is left out rather than aborting the whole print */
|
||||
}
|
||||
@@ -225,7 +248,7 @@ export async function exportNotesAsZip({
|
||||
try {
|
||||
const { content } = await loadNote(meta);
|
||||
const doc = parseNoteContent(content.content, parsingExtensions);
|
||||
const hasDrawing = !!strokeDocOf(content.drawingStrokes)?.strokes?.length;
|
||||
const hasDrawing = docStrokes(content.drawingStrokes).length > 0;
|
||||
|
||||
entries.push({
|
||||
name: `${name}.md`,
|
||||
|
||||
Reference in new issue
Block a user