From d11fc065f8d09187d3c13d64c62d7661ab2fd3e1 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Tue, 7 Jul 2026 19:57:19 +0200 Subject: [PATCH] docs: clean up comments --- src/lib/editor/extensions/columns.ts | 15 ++++----- src/lib/editor/extensions/columnsFlow.ts | 39 ++++++++---------------- src/lib/editor/extensions/pageBreaks.ts | 19 +++++------- src/lib/export/odt.ts | 14 ++++----- src/lib/import/docx.ts | 3 +- 5 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/lib/editor/extensions/columns.ts b/src/lib/editor/extensions/columns.ts index 020cb64..383c46a 100644 --- a/src/lib/editor/extensions/columns.ts +++ b/src/lib/editor/extensions/columns.ts @@ -3,10 +3,9 @@ import type { Node as PMNode } from '@tiptap/pm/model'; import { TextSelection } from '@tiptap/pm/state'; import type { EditorState, Transaction } from '@tiptap/pm/state'; -// A multi-column (newspaper) section: a block region whose content flows through -// balanced CSS columns. The document may hold it as several adjacent fragments with -// equal attrs — a "chain" — so the flow can continue across page breaks -// (columnsFlow.ts splits/joins fragments); export merges a chain into one section. +// A multi-column (newspaper) section: content flows through balanced CSS columns. +// The doc may hold it as adjacent equal-attr fragments (a "chain") so columnsFlow.ts +// can split/join across page breaks; export merges a chain into one section. export const DEFAULT_COLUMN_GAP_CM = 0.5; export const MAX_COLUMN_GAP_CM = 5; @@ -26,11 +25,9 @@ declare module '@tiptap/core' { } } -// Shared fit margin for the "does the first block fit the remaining page space" -// test: the balanced-height estimate (block height / count) undershoots the real -// fragment height by margins + line quantization. pageBreaks.ts (push vs. wait for -// the flow) and columnsFlow.ts (split vs. leave) MUST use the same margin, or the -// two disagree at the boundary and split/push/join cycle forever. +// Fit margin for "does the first block fit the remaining page space": the +// balanced-height estimate undershoots real height by margins + line quantization. +// pageBreaks.ts and columnsFlow.ts MUST share this or split/push/join cycles forever. export const COLUMNS_FIT_MARGIN_PX = 32; export function clampColumnCount(n: number): number { diff --git a/src/lib/editor/extensions/columnsFlow.ts b/src/lib/editor/extensions/columnsFlow.ts index f453e3f..b3e4a1e 100644 --- a/src/lib/editor/extensions/columnsFlow.ts +++ b/src/lib/editor/extensions/columnsFlow.ts @@ -7,20 +7,9 @@ import type { Node as PMNode } from '@tiptap/pm/model'; import { readVerticalMargins, FORCE_PAGE_RECALC } from './pageBreaks'; import { sameColumnsAttrs, COLUMNS_FIT_MARGIN_PX } from './columns'; -// Cross-page column flow: the document holds a columns section as adjacent -// equal-attr fragments ("chain"). This layout pass keeps the fragmentation in sync -// with the page grid — an overflowing fragment is split at a block boundary (or, -// for a paragraph taller than the page slot, at a LINE boundary: the second part -// carries the layout-internal joinPrev attr and is re-merged by joins/export), and -// a fragment with room pulls the continuation back. Splits/joins are pure layout: -// they never enter the undo history, and export merges a chain into one section. -// -// Fill order is sequential like Word (column 1 fills to the page bottom, then -// column 2, then the next page): fragments get a node decoration with an explicit -// height + column-fill:auto — full page slot for fragments with a continuation, -// content height for the open last fragment of a document-final chain (so typing -// grows column 1 first). A chain followed by real content keeps CSS balance on its -// last fragment (Word balances the end of a continuous section). +// Cross-page column flow: keeps a columns chain's fragmentation in sync with the +// page grid (split an overflowing fragment at a block or line boundary, pull a +// fragment with room back) via layout-only transactions that never enter undo history. const flowKey = new PluginKey('columnsFlow'); const FLOW_TX = 'columnsFlowTx'; @@ -208,11 +197,9 @@ export const ColumnsFlow = Extension.create({ return open; } - // Split the paragraph at child `blockIndex` at the line whose prefix - // consumes `budgetPx` of content flow (Word's mid-paragraph page break); - // the second part is marked joinPrev so joins/export restore the original. - // `force` takes at least one line even when the budget is tiny (progress - // guard for a paragraph taller than the whole page slot). + // Split the paragraph at `blockIndex` at the line consuming `budgetPx` (Word's + // mid-paragraph page break); the second part is marked joinPrev so joins/export + // restore the original. `force` takes at least one line for an over-tall paragraph. function splitParagraphInBlock( frag: Fragment, children: Element[], blockIndex: number, budgetPx: number, scale: number, force: boolean, @@ -346,10 +333,9 @@ export const ColumnsFlow = Extension.create({ const headIsJoinPrev = headBlock.type.name === 'paragraph' && headBlock.attrs.joinPrev && node.child(node.childCount - 1).type.name === 'paragraph'; - // A joinPrev head needs only one more line row to make progress; a - // multi-line paragraph head can be pulled PARTIALLY (the overflow - // split hands the excess back via a line split), so ~two line rows - // of room justify the pull; any other block needs its full share. + // A joinPrev head needs only one more line row; a multi-line head can be + // pulled PARTIALLY (the overflow split hands the excess back), so ~two + // line rows justify the pull — any other block needs its full share. const headLines = headBlock.type.name === 'paragraph' ? lineRects(nextFirst) : []; const headAdvance = (lineAdvance(headLines) || DEFAULT_LINE_PX * scale) / scale; const need = headIsJoinPrev @@ -373,10 +359,9 @@ export const ColumnsFlow = Extension.create({ return false; } - // Sequential-fill heights: a fragment with a continuation spans its page - // slot; the open last fragment of a document-final chain hugs its content - // (plus slack) so typing fills column 1 first. A chain followed by real - // content keeps its last fragment undecorated (CSS balance). + // Sequential-fill heights: a fragment with a continuation spans its page slot; + // an open last fragment hugs its content (plus slack) so typing fills column 1 + // first. Otherwise the last fragment stays undecorated (CSS balance). function updateDecorations(): void { const frags = fragments(); const vm = readVerticalMargins(editorView.dom); diff --git a/src/lib/editor/extensions/pageBreaks.ts b/src/lib/editor/extensions/pageBreaks.ts index 3c6c5be..5ad3a6b 100644 --- a/src/lib/editor/extensions/pageBreaks.ts +++ b/src/lib/editor/extensions/pageBreaks.ts @@ -130,9 +130,8 @@ export type VMargins = { }; // Reads vertical page margins + page height (px) from the --user-* props the Layout -// panel writes. Document-px values, so they pair directly with offsetTop below. Side -// margins don't affect pagination (only line wrapping), so they're ignored. -// Exported for columnsFlow.ts, which shares this geometry. +// panel writes (document-px, pairing directly with offsetTop below); side margins +// are ignored (they affect line wrapping, not pagination). Shared with columnsFlow.ts. export function readVerticalMargins(dom: HTMLElement): VMargins { const cs = getComputedStyle(dom); const top = parseFloat(cs.getPropertyValue('--user-margin-top')); @@ -190,10 +189,9 @@ type Leaf = { cellStart?: boolean; rowStart?: boolean; rowEnd?: boolean; - // A multi-column section fragment (columns.ts). columnsFlow.ts splits an - // overflowing fragment at a block boundary, so pagination must NOT push it when - // the flow can act: it only pushes when the first block alone can't fit the - // remaining page space (then the flow splits it at the next page top). + // A multi-column section fragment (columns.ts): pagination only pushes it when + // the first block alone can't fit the remaining space (else columnsFlow.ts splits + // the overflow at a block boundary). columnsFragment?: { blockCount: number; firstBlockNeededPx: number }; }; @@ -817,10 +815,9 @@ export const PageBreaks = Extension.create({ // Set when a leaf overflows but no spacer can bridge it (block > one page). let noPushReason: string | null = null; - // The empty trailing paragraph after a document-final columns chain: its - // fragment box may run to the page bottom (sequential fill), which would - // shove this invisible paragraph onto a phantom extra page. Leave it in - // place and keep it out of the page count; typed text re-enables it. + // A sequential-fill chain's fragment box may run to the page bottom, which + // would shove this trailing empty paragraph onto a phantom extra page — + // leave it in place and out of the page count; typed text re-enables it. if ( i === leaves.length - 1 && leaf.el.tagName === 'P' && diff --git a/src/lib/export/odt.ts b/src/lib/export/odt.ts index 37a32e5..cb5e92b 100644 --- a/src/lib/export/odt.ts +++ b/src/lib/export/odt.ts @@ -83,10 +83,9 @@ const TOC_SENT = ''; // / or . U+E008. const TBX = ''; -// Sentinel bracketing a hoisted multi-column section's blocks (marker paragraphs -// COL S{i} COL … COL E{i} COL): replaceColumns hoists like replaceTextBoxes; -// applyColumns wraps the region into a with a minted section style -// carrying . U+E009. +// Sentinel bracketing a hoisted multi-column section's blocks, same mechanism as +// TBX above; applyColumns wraps the region into a with a minted +// section style carrying . U+E009. const COL = ''; const EXT_BY_MIME: Record = { @@ -346,10 +345,9 @@ export function mergeJoinedParagraphsJson(blocks: TiptapNode[]): TiptapNode[] { return out; } -// Swap each top-level columns section for marker paragraphs bracketing its hoisted -// child blocks (same mechanism as replaceTextBoxes); applyColumns re-wraps the -// serialized region into a . Adjacent equal-attr fragments are a -// columnsFlow.ts page-split chain — one section, so they coalesce here. +// Swap each top-level columns section for marker paragraphs (same mechanism as +// replaceTextBoxes). Adjacent equal-attr fragments are a columnsFlow.ts page-split +// chain — one section, so they coalesce here. function replaceColumns(doc: TiptapNode, cols: ColumnsExport[]): TiptapNode { if (!doc.content?.length) return doc; const content: TiptapNode[] = []; diff --git a/src/lib/import/docx.ts b/src/lib/import/docx.ts index 006114a..6377068 100644 --- a/src/lib/import/docx.ts +++ b/src/lib/import/docx.ts @@ -859,8 +859,7 @@ function flattenTable(tbl: Element, ctx: Ctx): Node[] { // ---- section: page geometry + headers/footers ------------------------------ // Split body children into sections: a whose pPr carries a sectPr ends the -// group before it (a continuous section break). The marker paragraph is dropped -// unless it has run content (ours and Word's are empty). The trailing group has +// group before it (dropped unless it has run content). The trailing group has // sectPr null — the caller pairs it with the body-final . function splitBodySections(children: Element[]): { groups: { els: Element[]; sectPr: Element | null }[];