Set the note left, count its pages, choose its typeface, align its text
The column sat in the middle, so it moved as the window was resized and the start of every line landed somewhere new. It is against the margin now. The toolbar counts pages in the words the PDF reader uses, with a hairline where each ends. Pages are drawn, never stored: the note stays one document, Markdown has no page, and a check asserts none reaches the stored body. The current page is the one under the middle of the window — measuring from the top claimed "page 1" while you stood at the foot of a two-page note, because a window is shorter than a page. The typeface is a per-account setting rather than a per-browser one, because a choice about how your own notes are set should follow you to the phone. The server takes serif, sans or mono and drops anything else; that column is written by a browser, so it is an input like any other. Alignment is a small extension of our own — one attribute, a parse rule, a render rule — so the schema every stored note is validated against stays in files this project owns. Markdown has no alignment: what CommonMark allows is embedded HTML, so non-left blocks travel as `<div align="…">`, the one form most readers still honour, and the importer puts it back on the paragraphs. Left is never written. The full run caught a crash I had put in: PdfWorkspace used `noteFont` without taking it as a prop, which the build cannot see and the type checker does not reach in .jsx. It killed the notes panel beside a PDF, and `offline` failed with it because the whole app was broken. 224 unit tests, 263 browser checks across twelve suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
830af6532a
commit
fed53772aa
21 files changed
+637
-9
No files matched your search
@@ -99,16 +99,34 @@ function inlineNode(n: DocNode, o: MarkdownOptions): string {
|
||||
|
||||
/* ── blocks ── */
|
||||
|
||||
/**
|
||||
* Markdown has no alignment.
|
||||
*
|
||||
* It is often said to "basically be HTML"; it is not. What CommonMark allows is
|
||||
* *embedded* HTML, so a centred paragraph can only travel as a tag. `align` is
|
||||
* used rather than a style attribute because that is the one form GitHub and
|
||||
* most other readers still honour — and a reader that does not simply shows the
|
||||
* text, which is the right way to fail.
|
||||
*
|
||||
* Left-aligned blocks are written plainly. Wrapping every paragraph in a div
|
||||
* would turn an ordinary note into markup for the sake of a default.
|
||||
*/
|
||||
function withAlignment(n: DocNode, lines: string[]): string[] {
|
||||
const value = n.attrs?.textAlign;
|
||||
if (!value || value === "left" || lines.every((l) => !l.trim())) return lines;
|
||||
return [`<div align="${value}">`, "", ...lines, "", "</div>"];
|
||||
}
|
||||
|
||||
function block(n: DocNode, o: MarkdownOptions, indent = ""): string[] {
|
||||
switch (n.type) {
|
||||
case "paragraph": {
|
||||
const text = inline(n.content, o);
|
||||
return text.trim() ? [escapeLineStart(indent + text)] : [""];
|
||||
return text.trim() ? withAlignment(n, [escapeLineStart(indent + text)]) : [""];
|
||||
}
|
||||
|
||||
case "heading": {
|
||||
const level = Math.min(6, Math.max(1, Number(n.attrs?.level) || 1));
|
||||
return [`${"#".repeat(level)} ${inline(n.content, o)}`];
|
||||
return withAlignment(n, [`${"#".repeat(level)} ${inline(n.content, o)}`]);
|
||||
}
|
||||
|
||||
case "blockquote":
|
||||
|
||||
@@ -54,16 +54,23 @@ function blocks(nodes: DocNode[] | undefined, resolveImage: (id: string) => stri
|
||||
return nodes.map((n) => blockHtml(n, resolveImage)).join("\n");
|
||||
}
|
||||
|
||||
/** `style="text-align: …"`, or nothing at all when the block is left aligned. */
|
||||
function alignAttr(n: DocNode): string {
|
||||
const value = n.attrs?.textAlign;
|
||||
return value && value !== "left" ? ` style="text-align: ${esc(value)}"` : "";
|
||||
}
|
||||
|
||||
function blockHtml(n: DocNode, img: (id: string) => string | null): string {
|
||||
const kids = () => blocks(n.content, img);
|
||||
switch (n.type) {
|
||||
case "paragraph": {
|
||||
const body = inline(n.content, img);
|
||||
return body.trim() ? `<p>${body}</p>` : "<p> </p>";
|
||||
const a = alignAttr(n);
|
||||
return body.trim() ? `<p${a}>${body}</p>` : "<p> </p>";
|
||||
}
|
||||
case "heading": {
|
||||
const l = Math.min(6, Math.max(1, Number(n.attrs?.level) || 1));
|
||||
return `<h${l}>${inline(n.content, img)}</h${l}>`;
|
||||
return `<h${l}${alignAttr(n)}>${inline(n.content, img)}</h${l}>`;
|
||||
}
|
||||
case "blockquote":
|
||||
return `<blockquote>${kids()}</blockquote>`;
|
||||
|
||||
Reference in new issue
Block a user