refactor: a field round reads the page grid once
Both indexes and the cross-references each read it for themselves, twice per round in the index's case: 59 readings of it over a load where 37 do. The round hands one to every reader instead, so they all resolve their pages against the same grid, and the lazy holders they kept go with it. No measurable time — the style pass a reading forces is owed to the geometry read that follows it either way.
This commit is contained in:
1 parent
fed2ccedad
commit
2f51292587
4 files changed
+17
-19
No files matched your search
@@ -3,7 +3,7 @@ import type { Editor } from '@tiptap/core';
|
||||
import type { Node as PMNode } from '@tiptap/pm/model';
|
||||
import type { EditorView } from '@tiptap/pm/view';
|
||||
import { bookmarks, findBookmark, type BookmarkRef } from './bookmark';
|
||||
import { readVerticalMargins, pageOfElement, scheduleFieldRound, type FieldWrite, type PageGrid } from './pageBreaks';
|
||||
import { pageOfElement, scheduleFieldRound, type FieldWrite, type PageGrid, type VMargins } from './pageBreaks';
|
||||
|
||||
// A cross-reference: an inline atom showing either the text of a bookmark or the page it
|
||||
// sits on, kept live by the node view the way the TOC keeps its page numbers. Round-trips
|
||||
@@ -119,10 +119,10 @@ class CrossRefBatch {
|
||||
|
||||
schedule(): void {
|
||||
if (this.editor.isDestroyed) return;
|
||||
scheduleFieldRound(this.editor.view, this, () => this.measure());
|
||||
scheduleFieldRound(this.editor.view, this, (vm) => this.measure(vm));
|
||||
}
|
||||
|
||||
private measure(): FieldWrite | void {
|
||||
private measure(vm: VMargins): FieldWrite | void {
|
||||
const { editor } = this;
|
||||
if (editor.isDestroyed) return;
|
||||
const view = editor.view;
|
||||
@@ -132,8 +132,7 @@ class CrossRefBatch {
|
||||
}
|
||||
const targets = new Map<string, BookmarkRef>();
|
||||
for (const b of bookmarks(editor.state.doc)) if (!targets.has(b.name)) targets.set(b.name, b);
|
||||
let grid: PageGrid | null = null;
|
||||
const gridOf = () => (grid ??= readVerticalMargins(view.dom as HTMLElement).grid);
|
||||
const gridOf = () => vm.grid;
|
||||
const jobs: { ref: CrossRefView; node: PMNode; pos: number; text: string }[] = [];
|
||||
for (const ref of this.views) {
|
||||
const pos = ref.pos();
|
||||
|
||||
@@ -333,7 +333,7 @@ export const FORCE_PAGE_RECALC = 'forcePageBreakRecalc';
|
||||
// It measures, and what it returns writes: its own DOM, and its cached result on the
|
||||
// round's transaction. A write may return one more phase, run after every other write.
|
||||
export type FieldWrite = (tr: Transaction) => FieldWrite | void;
|
||||
export type FieldRead = () => FieldWrite | void;
|
||||
export type FieldRead = (vm: VMargins) => FieldWrite | void;
|
||||
|
||||
type FieldRound = { jobs: Map<object, FieldRead>; raf: number | null };
|
||||
const fieldRounds = new WeakMap<EditorView, FieldRound>();
|
||||
@@ -352,7 +352,10 @@ function runFieldRound(view: EditorView): void {
|
||||
const round = fieldRounds.get(view);
|
||||
if (!round) return;
|
||||
round.raf = null;
|
||||
let phase = Array.from(round.jobs.values(), (read) => read());
|
||||
// One reading of the page grid for the round: getComputedStyle is a style pass over
|
||||
// the whole document, and every field resolves its pages against the same grid.
|
||||
const vm = readVerticalMargins(view.dom as HTMLElement);
|
||||
let phase = Array.from(round.jobs.values(), (read) => read(vm));
|
||||
round.jobs.clear();
|
||||
const tr = view.state.tr.setMeta('addToHistory', false);
|
||||
// Bounded: measure, write, and the one read-back a write can ask for.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { indexEntries, indexRows } from './indexEntry';
|
||||
import { bibliographyEntries, bibliographyRows } from './bibliographyEntry';
|
||||
import { isCitationStyle, type CitationStyle } from '../../utils/citationStyle';
|
||||
import type { Transaction } from '@tiptap/pm/state';
|
||||
import { readVerticalMargins, pageOfElement, topInEditor, scheduleFieldRound, FORCE_PAGE_RECALC, type FieldWrite, type PageGrid } from './pageBreaks';
|
||||
import { pageOfElement, topInEditor, scheduleFieldRound, FORCE_PAGE_RECALC, type FieldWrite, type PageGrid, type VMargins } from './pageBreaks';
|
||||
|
||||
// A generated index: a block atom listing every source with its live page number — the
|
||||
// headings for a table of contents, the captions of one category for a list of figures
|
||||
@@ -232,7 +232,7 @@ class TocView {
|
||||
|
||||
private schedule(): void {
|
||||
if (this.editor.isDestroyed) return;
|
||||
scheduleFieldRound(this.editor.view, this, () => this.measure());
|
||||
scheduleFieldRound(this.editor.view, this, (vm) => this.measure(vm));
|
||||
}
|
||||
|
||||
// What the index lists, in document order: the headings down to its own level, or —
|
||||
@@ -295,19 +295,15 @@ class TocView {
|
||||
return out;
|
||||
}
|
||||
|
||||
private grid(): PageGrid {
|
||||
return readVerticalMargins(this.editor.view.dom as HTMLElement).grid;
|
||||
}
|
||||
|
||||
private pageOf(pos: number, grid: PageGrid): number {
|
||||
const el = this.editor.view.nodeDOM(pos) as HTMLElement | null;
|
||||
if (!el || el.nodeType !== 1) return 1;
|
||||
return pageOfElement(this.editor.view, el, grid);
|
||||
}
|
||||
|
||||
private measure(): FieldWrite | void {
|
||||
private measure(vm: VMargins): FieldWrite | void {
|
||||
if (this.editor.isDestroyed || !this.dom.isConnected) return;
|
||||
const grid = this.grid();
|
||||
const grid = vm.grid;
|
||||
let heads = this.sources();
|
||||
let entries: TocEntry[];
|
||||
if (indexKindOf(this.node()?.attrs?.index) === 'alphabetical') {
|
||||
@@ -335,18 +331,17 @@ class TocView {
|
||||
}
|
||||
// The rows carry their page numbers now, so where they fall is read once every
|
||||
// field of the round has written.
|
||||
return (last) => this.paginate(last);
|
||||
return (last) => this.paginate(last, vm);
|
||||
};
|
||||
}
|
||||
|
||||
// The index is a block atom: it has no inner document positions for pagination to put
|
||||
// a spacer at, so one longer than a page breaks itself — the row that would cross the
|
||||
// boundary takes the gap to the next page's content top as its margin.
|
||||
private paginate(tr: Transaction): void {
|
||||
private paginate(tr: Transaction, vm: VMargins): void {
|
||||
const rows = Array.from(this.dom.querySelectorAll<HTMLElement>('.toc-entry'));
|
||||
if (!rows.length) return;
|
||||
const view = this.editor.view;
|
||||
const vm = readVerticalMargins(view.dom as HTMLElement);
|
||||
for (const row of rows) row.style.marginTop = '';
|
||||
// Read every natural top and height first: applying a gap moves each row below it,
|
||||
// and one reflow for the whole index beats one per row.
|
||||
|
||||
Reference in new issue
Block a user