diff --git a/packages/editor/src/extensions/paging/index.ts b/packages/editor/src/extensions/paging/index.ts index da5bd11b8..5bf6d0f47 100644 --- a/packages/editor/src/extensions/paging/index.ts +++ b/packages/editor/src/extensions/paging/index.ts @@ -98,17 +98,14 @@ export const Paging = Extension.create({ } }); -export { HeightMap } from "./height-map.js"; export { PAGE_NODE, Page } from "./page.js"; export { getScrollAnchor, restoreScrollAnchor, type ScrollAnchor } from "./anchor.js"; -export { installPagingParser, uninstallPagingParser } from "./parser.js"; export { fromFlatPosition, toFlatPosition } from "./positions.js"; export { serializeDocumentHTML } from "./serialize.js"; -export { installFlatteningSerializer } from "./serializer.js"; export { DEFAULT_PAGE_SIZE, countPages, diff --git a/packages/editor/src/extensions/paging/page-view.ts b/packages/editor/src/extensions/paging/page-view.ts index 6f048a9f8..493f6686b 100644 --- a/packages/editor/src/extensions/paging/page-view.ts +++ b/packages/editor/src/extensions/paging/page-view.ts @@ -69,31 +69,22 @@ function placeholder(node: ProsemirrorNode, heights: HeightMap): NodeView { }; } -/** A page rendered normally, measured so its placeholder is the right size. */ -function rendered(node: ProsemirrorNode, heights: HeightMap): NodeView { +/** A page shown normally, rendered the way the schema says. */ +function rendered(node: ProsemirrorNode): NodeView { const spec = node.type.spec.toDOM?.(node); if (!spec) return { dom: document.createElement("div") }; const { dom, contentDOM } = DOMSerializer.renderSpec(document, spec); - const measure = () => { - if (dom instanceof HTMLElement) heights.record(node, dom.offsetHeight); - }; - return { dom, contentDOM, update(updated, decorations) { - if (updated.type !== node.type) return false; if (!isRendered(decorations)) { profiler.count("paging.pageHidden"); return false; } - if (!node.sameMarkup(updated)) return false; - node = updated; - measure(); - return true; - }, - destroy: measure + return node.sameMarkup(updated); + } }; } @@ -102,7 +93,5 @@ export function createPageView( decorations: readonly Decoration[], heights: HeightMap ): NodeView { - return isRendered(decorations) - ? rendered(node, heights) - : placeholder(node, heights); + return isRendered(decorations) ? rendered(node) : placeholder(node, heights); } diff --git a/packages/editor/src/extensions/paging/parser.ts b/packages/editor/src/extensions/paging/parser.ts index 8e614dde8..4d1e9a812 100644 --- a/packages/editor/src/extensions/paging/parser.ts +++ b/packages/editor/src/extensions/paging/parser.ts @@ -59,8 +59,3 @@ export function installPagingParser( } (cached.domParser as PagingDOMParser).options = options; } - -export function uninstallPagingParser(schema: Schema): void { - const cached = schema.cached as { domParser?: DOMParser }; - if (cached.domParser instanceof PagingDOMParser) delete cached.domParser; -} diff --git a/packages/editor/src/extensions/paging/viewport-plugin.ts b/packages/editor/src/extensions/paging/viewport-plugin.ts index 24d69d0f0..4dbe8e7be 100644 --- a/packages/editor/src/extensions/paging/viewport-plugin.ts +++ b/packages/editor/src/extensions/paging/viewport-plugin.ts @@ -29,11 +29,11 @@ export const viewportKey = new PluginKey("notesnook-paging"); type ViewportState = { visible: Set; selectionIndex: number; - blockCount: number; + pageCount: number; decorations: DecorationSet; }; -type BlockRange = { from: number; to: number; index: number }; +type PageRange = { from: number; to: number; index: number }; const EMPTY_VISIBLE: Set = new Set(); const SHOW_MARGIN = 1; @@ -119,7 +119,7 @@ function buildDecorations( end(); profiler.count("paging.decorationBuilds"); profiler.gauge("paging.renderedPages", decorations.length); - profiler.gauge("paging.blocksInDoc", doc.childCount); + profiler.gauge("paging.pagesInDoc", doc.childCount); return set; } @@ -127,7 +127,7 @@ function buildDecorations( * The pages around the caret, worked out from the selection instead of by * walking the document. */ -function selectionRanges(state: EditorState): BlockRange[] { +function selectionRanges(state: EditorState): PageRange[] { const { $from } = state.selection; const doc = state.doc; const index = $from.index(0); @@ -135,7 +135,7 @@ function selectionRanges(state: EditorState): BlockRange[] { const start = $from.depth > 0 ? $from.before(1) : $from.pos; const node = doc.child(index); - const ranges: BlockRange[] = []; + const ranges: PageRange[] = []; if (index > 0) { const previous = doc.child(index - 1); @@ -157,7 +157,7 @@ function selectionRanges(state: EditorState): BlockRange[] { return ranges; } -function hasRenderDecoration(set: DecorationSet, range: BlockRange): boolean { +function hasRenderDecoration(set: DecorationSet, range: PageRange): boolean { return set .find(range.from, range.to) .some( @@ -198,7 +198,7 @@ export function viewportPlugin(heights: HeightMap): Plugin { return { visible: EMPTY_VISIBLE, selectionIndex, - blockCount: state.doc.childCount, + pageCount: state.doc.childCount, decorations: buildDecorations( state.doc, EMPTY_VISIBLE, @@ -212,21 +212,21 @@ export function viewportPlugin(heights: HeightMap): Plugin { | undefined; const visible = meta?.visible ?? value.visible; const selectionIndex = newState.selection.$from.index(0); - const blockCount = tr.doc.childCount; + const pageCount = tr.doc.childCount; const selectionMoved = selectionIndex !== value.selectionIndex; - const structural = blockCount !== value.blockCount; + const pagesChanged = pageCount !== value.pageCount; - if (!meta && !selectionMoved && !structural && !tr.docChanged) { + if (!meta && !selectionMoved && !pagesChanged && !tr.docChanged) { profiler.count("paging.decorationReuses"); return value; } - if (meta || selectionMoved || structural) { + if (meta || selectionMoved || pagesChanged) { return { visible, selectionIndex, - blockCount, + pageCount, decorations: buildDecorations(tr.doc, visible, selectionIndex) }; } @@ -239,7 +239,7 @@ export function viewportPlugin(heights: HeightMap): Plugin { end(); profiler.count("paging.decorationMaps"); - return { visible, selectionIndex, blockCount, decorations: mapped }; + return { visible, selectionIndex, pageCount, decorations: mapped }; } }, props: { @@ -250,7 +250,6 @@ export function viewportPlugin(heights: HeightMap): Plugin { view(editorView) { let frame = 0; let scrollParent: HTMLElement | null = null; - let visible: Set = EMPTY_VISIBLE; const measuredPages = new Set(); const ensureScrollParent = () => { @@ -305,6 +304,7 @@ export function viewportPlugin(heights: HeightMap): Plugin { const keepTop = bounds.top - height * KEEP_MARGIN; const keepBottom = bounds.top + height * (1 + KEEP_MARGIN); + const shown = viewportKey.getState(editorView.state)?.visible; const next = new Set(); for ( let i = firstPageBelow(children, keepTop); @@ -314,13 +314,13 @@ export function viewportPlugin(heights: HeightMap): Plugin { const element = children[i] as HTMLElement; const rect = element.getBoundingClientRect(); if (rect.top > keepBottom) break; - const blockId = element.getAttribute("data-block-id"); - if (!blockId) continue; + const pageId = element.getAttribute("data-block-id"); + if (!pageId) continue; if ( (rect.bottom >= addTop && rect.top <= addBottom) || - visible.has(blockId) + shown?.has(pageId) ) - next.add(blockId); + next.add(pageId); } return next; }; @@ -372,9 +372,8 @@ export function viewportPlugin(heights: HeightMap): Plugin { return; } - visible = next; profiler.count("paging.visibilityFlushes"); - profiler.gauge("paging.visibleBlocks", next.size); + profiler.gauge("paging.visiblePages", next.size); const pin = pinnedPage(); editorView.dispatch( @@ -384,6 +383,7 @@ export function viewportPlugin(heights: HeightMap): Plugin { .setMeta("addToHistory", false) ); restorePin(pin); + updateMetrics(); measureRenderedPages(); resizePlaceholders(); }; @@ -413,7 +413,8 @@ export function viewportPlugin(heights: HeightMap): Plugin { * Whatever is on screen has a real height. Remembering it means the empty * box left behind is the right size once it scrolls away. */ - const measureRenderedPages = () => { + /** The layout text is wrapped in, read from the editor itself. */ + const updateMetrics = () => { const style = getComputedStyle(editorView.dom); const fontSize = parseFloat(style.fontSize); const lineHeight = parseFloat(style.lineHeight); @@ -422,6 +423,9 @@ export function viewportPlugin(heights: HeightMap): Plugin { fontSize, lineHeight: Number.isFinite(lineHeight) ? lineHeight : fontSize * 1.5 }); + }; + + const measureRenderedPages = () => { const children = editorView.dom.children; const doc = editorView.state.doc; const count = Math.min(children.length, doc.childCount); @@ -432,7 +436,11 @@ export function viewportPlugin(heights: HeightMap): Plugin { heights.record(node, element.offsetHeight); const pageId = node.attrs.blockId as string | undefined; - if (node.type.name !== "page" || !pageId || measuredPages.has(pageId)) + if ( + node.type.name !== PAGE_NODE || + !pageId || + measuredPages.has(pageId) + ) continue; measuredPages.add(pageId); const blocks = element.children;