docs: clean up comments
This commit is contained in:
1 parent
64ae976ecf
commit
20eb94d7c0
9 files changed
+49
-79
No files matched your search
@@ -685,9 +685,8 @@
|
||||
}
|
||||
|
||||
// --- Indent (Einzug) ---
|
||||
// In a list, step the list point one level via indentListForward/Backward (the same
|
||||
// commands the Tab keymap uses — see indent.ts); outside a list, step the paragraph's
|
||||
// left indent.
|
||||
// In a list, step the list point one level (indentListForward/Backward — the Tab
|
||||
// keymap's commands, see indent.ts); outside a list, step the paragraph's left indent.
|
||||
function changeIndent(dir: 1 | -1) {
|
||||
if (!editor) return;
|
||||
const c = editor.chain().focus();
|
||||
|
||||
@@ -4,10 +4,8 @@ import type { Node as PMNode } from '@tiptap/pm/model';
|
||||
import { dropCursor } from '@tiptap/pm/dropcursor';
|
||||
|
||||
// Inline, as-character image (Word's "in line with text"). width/height are
|
||||
// unscaled document px @96dpi (like tableRow.ts rowHeight); export converts them
|
||||
// to cm and import back, so size round-trips exactly. Position is fully determined
|
||||
// by the paragraph + text offset + the paragraph's alignment — no free coordinates.
|
||||
// The node view adds corner resize handles (aspect-locked, zoom-aware).
|
||||
// unscaled doc px @96dpi (like tableRow.ts rowHeight); export converts to cm and
|
||||
// back, so size round-trips exactly. The node view adds aspect-locked resize handles.
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
|
||||
@@ -402,10 +402,9 @@ export const PageBreaks = Extension.create({
|
||||
}
|
||||
gaps.sort((a, b) => a - b);
|
||||
const advance = gaps.length ? gaps[gaps.length >> 1] : 0;
|
||||
// Group rects into lines by top proximity, not vertical overlap: decorative
|
||||
// fonts (e.g. Trattatello) render ink boxes taller than the line advance, so
|
||||
// an overlap test chains whole paragraphs into one phantom line. Only the top
|
||||
// is used downstream; same-line runs sit within a fraction of the advance.
|
||||
// Group rects into lines by top proximity, not overlap: decorative fonts (e.g.
|
||||
// Trattatello) make ink boxes taller than the advance, so an overlap test would
|
||||
// chain paragraphs into one phantom line. Tolerance is a fraction of the advance.
|
||||
const tol = advance > 0 ? advance * 0.6 : 3;
|
||||
const lines: { top: number; bottom: number }[] = [];
|
||||
let lineTop = -Infinity;
|
||||
@@ -980,11 +979,9 @@ export const PageBreaks = Extension.create({
|
||||
|
||||
isUpdating = false;
|
||||
|
||||
// This pass measured against the spacers present at its start. When the result
|
||||
// changes — e.g. after an orientation switch, where the prior orientation's
|
||||
// spacers still sat in the DOM and suppressed a heading's top-margin collapse,
|
||||
// shifting its measured top — re-run so the next pass measures with the new
|
||||
// spacers and the layout settles. Bounded against a two-layout ping-pong.
|
||||
// Each pass measures against the spacers present at its start, so a changed
|
||||
// result needs another pass to re-measure and settle (e.g. after an orientation
|
||||
// switch). Bounded by MAX_CONVERGE_PASSES against a two-layout ping-pong.
|
||||
if (placementsChanged && convergePasses < MAX_CONVERGE_PASSES) {
|
||||
convergePasses++;
|
||||
schedule();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Node } from '@tiptap/core';
|
||||
|
||||
// Inline atom fields for header/footer content: current page number and total
|
||||
// page count. The DOM text is a placeholder — HeaderFooterLayer.svelte patches
|
||||
// every [data-page-field] span with the real per-page value after rendering.
|
||||
// Round-trips to ODF <text:page-number>/<text:page-count> (export/import odt.ts).
|
||||
// Inline atom fields for header/footer: current page number and total page count. The
|
||||
// DOM text is a placeholder — HeaderFooterLayer.svelte patches each [data-page-field]
|
||||
// span per page. Round-trips to ODF <text:page-number>/<text:page-count>.
|
||||
|
||||
function pageFieldNode(name: string, kind: 'number' | 'count') {
|
||||
return Node.create({
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
|
||||
// Keep an empty paragraph at the end of the document when the last block is a
|
||||
// table. A table is an isolating block, so if it sits at the very end there is
|
||||
// nowhere to click or type below it — the cursor gets trapped inside the table
|
||||
// and the user can't continue the document. When the doc ends with a table we
|
||||
// append a paragraph; once it's there the last child is a paragraph, so the
|
||||
// check no-ops on the next round (no loop).
|
||||
// Keep an empty paragraph after a trailing table: a table is isolating, so at the very
|
||||
// end the cursor gets trapped with nowhere to type below. Appending a paragraph fixes
|
||||
// it and self-terminates (once the last child is a paragraph the check no-ops).
|
||||
const trailingNodeKey = new PluginKey('trailingNode');
|
||||
|
||||
// Block types that can't sit at the document end without trapping the cursor.
|
||||
|
||||
+18
-27
@@ -74,10 +74,9 @@ const EXT_BY_MIME: Record<string, string> = {
|
||||
const CELL_LIST_BULLET_STYLE = 'TblListBullet';
|
||||
const CELL_LIST_NUMBER_STYLE = 'TblListNumber';
|
||||
|
||||
// Heading sizes/margins shown in the editor (editor.css); odf-kit's Heading_20_N
|
||||
// defaults are larger, so we rewrite them on export. Margins are the editor's em
|
||||
// values (top 1.5em, bottom 0.5em) resolved against each heading's font size, in cm.
|
||||
// Exported: import/odt.ts uses these as the defaults to suppress on re-import.
|
||||
// Heading sizes/margins shown in the editor (editor.css); odf-kit's larger Heading_20_N
|
||||
// defaults are rewritten to these on export. Margins are the em values (top 1.5em,
|
||||
// bottom 0.5em) in cm; import/odt.ts uses them as defaults to suppress on re-import.
|
||||
export const HEADING_STYLE_OVERRIDES: { name: string; fontSize: string; marginTop: string; marginBottom: string }[] = [
|
||||
{ name: 'Heading_20_1', fontSize: '20pt', marginTop: '1.058cm', marginBottom: '0.353cm' },
|
||||
{ name: 'Heading_20_2', fontSize: '16pt', marginTop: '0.847cm', marginBottom: '0.282cm' },
|
||||
@@ -453,11 +452,9 @@ function applyOrderedListFormats(odtBytes: Uint8Array, formats: (OrderedFmt | nu
|
||||
return rezipOdt(files);
|
||||
}
|
||||
|
||||
// Whole-list indent: the `indent` attr on a top-level bulletList/orderedList (cm)
|
||||
// shifts the entire list. odf-kit emits each level in label-alignment mode (the
|
||||
// paragraph margin is ignored), so the shift must be added to the L# list-style's
|
||||
// per-level fo:margin-left and text:list-tab-stop-position. One entry per top-level
|
||||
// list in odf-kit's L# order (0 = no shift).
|
||||
// Whole-list indent (cm) on a top-level bulletList/orderedList. odf-kit uses
|
||||
// label-alignment mode (ignores the paragraph margin), so the shift goes onto the L#
|
||||
// list-style's per-level fo:margin-left + text:list-tab-stop-position. One per list (0=none).
|
||||
function collectListIndents(node: TiptapNode, result: number[]): void {
|
||||
for (const child of node.content ?? []) {
|
||||
if (child.type === 'bulletList' || child.type === 'orderedList') {
|
||||
@@ -494,10 +491,9 @@ function applyListIndents(odtBytes: Uint8Array, indents: number[]): Uint8Array {
|
||||
return rezipOdt(files);
|
||||
}
|
||||
|
||||
// odf-kit emits nested lists as bare <text:list> sharing the top-level L# style,
|
||||
// so a nested list of a *different* kind/format (e.g. ordered inside bullets)
|
||||
// loses its type. Give those nested lists their own minted 6-level list style —
|
||||
// the same pattern applyCellBlocks uses for in-cell lists.
|
||||
// odf-kit emits nested lists as bare <text:list> sharing the top-level L# style, so a
|
||||
// nested list of a different kind/format (e.g. ordered inside bullets) loses its type.
|
||||
// Mint it its own 6-level list style (same pattern applyCellBlocks uses for cell lists).
|
||||
type ListDef = { ordered: boolean; numFormat: string; numSuffix: string };
|
||||
|
||||
function listDefOf(node: TiptapNode): ListDef {
|
||||
@@ -1033,10 +1029,9 @@ function imageFrameXml(img: ImageExport, index: number): string {
|
||||
);
|
||||
}
|
||||
|
||||
// Resolve image sentinels: swap each IMG{i}IMG for its <draw:frame>, add the
|
||||
// binary picture files, and register each in META-INF/manifest.xml. content.xml
|
||||
// already declares the draw/svg/xlink namespaces (odf-kit), and rezipOdt re-zips
|
||||
// arbitrary binary entries, so no other plumbing is needed.
|
||||
// Resolve image sentinels: swap each IMG{i}IMG for its <draw:frame>, add the binary
|
||||
// picture files, and register each in META-INF/manifest.xml. content.xml already
|
||||
// declares the draw/svg/xlink namespaces and rezipOdt handles binary entries.
|
||||
function applyImages(odtBytes: Uint8Array, images: ImageExport[]): Uint8Array {
|
||||
if (!images.length) return odtBytes;
|
||||
const files = unzipSync(odtBytes);
|
||||
@@ -1106,11 +1101,9 @@ export async function buildOdt(docJson: TiptapNode, margins: PageMargins = DEFAU
|
||||
// Orientation comes from the Layout panel; odf-kit swaps the A4 dimensions
|
||||
// automatically (29.7×21cm) and writes style:print-orientation accordingly.
|
||||
orientation,
|
||||
// Margins (cm) come from the Layout panel via App state. They match the
|
||||
// editor's on-screen padding so exported line wrapping / page flow is identical.
|
||||
// With a header/footer, ODF's vertical margin is the page-edge→header distance;
|
||||
// applyHfPostProcess sizes the header box so the body still starts at the
|
||||
// editor's margin (Word-style mapping).
|
||||
// Margins (cm) from the Layout panel, matching the editor's padding so line
|
||||
// wrapping / page flow is identical. With a header/footer the vertical margin is
|
||||
// the edge→zone distance; applyHfPostProcess keeps the body starting at the margin.
|
||||
marginTop: `${headerPara ? headerDist : margins.top}cm`,
|
||||
marginBottom: `${footerPara ? footerDist : margins.bottom}cm`,
|
||||
marginLeft: `${margins.left}cm`,
|
||||
@@ -1204,11 +1197,9 @@ function hfAlign(para: TiptapNode): AlignValue | null {
|
||||
return ta === 'center' || ta === 'right' || ta === 'justify' ? ta : null;
|
||||
}
|
||||
|
||||
// Header/footer post-processing on styles.xml: resolve the LBR/PGC sentinels in the
|
||||
// emitted runs, rewrite odf-kit's fixed header/footer geometry (0.6/0.5cm) to the
|
||||
// Word-style mapping (page margin = HF distance, min-height fills up to the body
|
||||
// margin so the body starts exactly at the editor's margin), and apply the
|
||||
// paragraph alignment to the named Header/Footer styles.
|
||||
// Header/footer post-processing on styles.xml: resolve LBR/PGC sentinels, apply the
|
||||
// paragraph alignment to the Header/Footer styles, and rewrite the geometry to the
|
||||
// Word-style mapping (page margin = HF distance, min-height fills up to the body margin).
|
||||
function applyHfPostProcess(odtBytes: Uint8Array, margins: PageMargins, headerPara: TiptapNode | null, footerPara: TiptapNode | null, headerDist: number, footerDist: number): Uint8Array {
|
||||
if (!headerPara && !footerPara) return odtBytes;
|
||||
|
||||
|
||||
+6
-14
@@ -1,11 +1,6 @@
|
||||
// PDF export that matches the editor exactly AND keeps selectable/searchable text.
|
||||
//
|
||||
// Why this shape: letting the browser re-render at print time drifts the text (true
|
||||
// vector metrics ≠ on-screen hinted metrics), which broke the page-break band masks.
|
||||
// Instead we RASTER the editor's own on-screen render (where content, big text, tables,
|
||||
// page-break bands and headers/footers are already correct) and overlay an INVISIBLE
|
||||
// text layer positioned from the same on-screen measurements — so the PDF looks like a
|
||||
// screenshot of the editor but its text can still be selected and searched.
|
||||
// PDF export that matches the editor exactly AND keeps selectable text. The browser's
|
||||
// print-time re-render drifts text (vector ≠ hinted metrics) and broke the band masks,
|
||||
// so we raster the on-screen render and overlay an invisible, positioned text layer.
|
||||
|
||||
import { generateHTML } from '@tiptap/core';
|
||||
import { PAGE_W_PORTRAIT, PAGE_H_PORTRAIT, type Orientation } from '../storage/pageOrientation';
|
||||
@@ -190,12 +185,9 @@ export async function exportPdf(opts: PdfOptions): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vector path: native browser print. Crisp, tiny, fonts embedded by the browser
|
||||
// (incl. system fonts). The browser paginates natively (breaks tables cleanly),
|
||||
// and headers/footers are emitted as CSS @page margin boxes (basic text + page
|
||||
// numbers; rich inline formatting isn't expressible there).
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vector path: native browser print. Crisp, tiny, fonts embedded by the browser. It
|
||||
// paginates natively (clean table breaks); headers/footers become CSS @page margin
|
||||
// boxes (basic text + page numbers; rich inline formatting isn't expressible there).
|
||||
|
||||
export interface PrintPdfOptions {
|
||||
json: Json;
|
||||
|
||||
@@ -6,10 +6,9 @@ import { PX_PER_CM, cmToPx, type PageMargins } from '../storage/pageMargins';
|
||||
import type { Orientation } from '../storage/pageOrientation';
|
||||
import { languageFromOdf, NO_LANGUAGE, type DocumentLanguage } from '../storage/documentLanguage';
|
||||
|
||||
// .odt → TipTap JSON, inverting export/odt.ts. Anything the editor can express
|
||||
// becomes its native node/mark/attr; values matching the editor's defaults are
|
||||
// suppressed so a round trip (editor → LibreOffice → editor) doesn't accrete
|
||||
// explicit attrs. Unsupported content degrades gracefully and is reported.
|
||||
// .odt → TipTap JSON, inverting export/odt.ts. Editor-expressible content becomes its
|
||||
// native node/mark/attr; values matching the editor's defaults are suppressed so round
|
||||
// trips don't accrete explicit attrs. Unsupported content degrades gracefully (reported).
|
||||
|
||||
type Mark = { type: string; attrs?: Record<string, unknown> };
|
||||
type Node = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { PageMargins } from '../storage/pageMargins';
|
||||
import type { Orientation } from '../storage/pageOrientation';
|
||||
|
||||
// Resolves ODF style indirection for the importer: every producer (our export,
|
||||
// LibreOffice re-saves, Word) spreads formatting across named styles, automatic
|
||||
// styles, and parent-style-name chains. This module flattens those chains into
|
||||
// per-style property maps so import/odt.ts can read effective values directly.
|
||||
// Resolves ODF style indirection for the importer: producers (our export, LibreOffice,
|
||||
// Word) spread formatting across named/automatic styles and parent-style-name chains.
|
||||
// This flattens those into per-style property maps so import/odt.ts reads effective values.
|
||||
|
||||
export const NS = {
|
||||
office: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
|
||||
@@ -304,10 +303,9 @@ export class StyleResolver {
|
||||
};
|
||||
}
|
||||
|
||||
// Page margins + orientation from the master page's layout. With a header/footer,
|
||||
// ODF's vertical margin ends at the zone — the editor's body margin is margin +
|
||||
// zone height + spacing (inverse of the export's Word-style mapping). Page *size*
|
||||
// is intentionally ignored — the editor is A4-only.
|
||||
// Page margins + orientation from the master page's layout. With a header/footer the
|
||||
// body margin is page margin + zone height + spacing (inverse of the export mapping).
|
||||
// Page size is intentionally ignored — the editor is A4-only.
|
||||
pageGeometry(): { margins: PageMargins; orientation: Orientation } | null {
|
||||
const props = this.pageLayoutEl()?.getElementsByTagNameNS(NS.style, 'page-layout-properties')[0] ?? null;
|
||||
if (!props) return null;
|
||||
|
||||
Reference in new issue
Block a user