fix: a page-anchored frame no longer crashes the editor while a saved document loads
The image NodeView reads the page grid from the view ProseMirror hands it at construction; editor.view is a throwing proxy until the view is built, and a saved document with such a frame is passed in exactly then. The dom leg loads its band document through the autosave, the path a reload takes. The layout baseline is re-recorded for the two showcase files whose second page was reworked after the baseline was taken.
This commit is contained in:
1 parent
5db1c37468
commit
3af5a00fed
3 files changed
+20
-12
No files matched your search
@@ -3,6 +3,7 @@ import type { Editor } from '@tiptap/core';
|
||||
import type { Node as PMNode } from '@tiptap/pm/model';
|
||||
import { NodeSelection, Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
import { Decoration, DecorationSet } from '@tiptap/pm/view';
|
||||
import type { EditorView } from '@tiptap/pm/view';
|
||||
import { dropCursor } from '@tiptap/pm/dropcursor';
|
||||
import { cmToPx } from '../../storage/pageMargins';
|
||||
import { readVerticalMargins } from './pageBreaks';
|
||||
@@ -287,7 +288,7 @@ export const Image = Node.create({
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ node, editor, getPos }) => new ImageView(node as PMNode, editor, getPos as () => number);
|
||||
return ({ node, editor, getPos, view }) => new ImageView(node as PMNode, editor, getPos as () => number, view);
|
||||
},
|
||||
|
||||
// The drop cursor paints a caret where a dragged-in image *file* lands; moving an
|
||||
@@ -357,11 +358,14 @@ class ImageView {
|
||||
private badge: HTMLElement;
|
||||
private node: PMNode;
|
||||
private editor: Editor;
|
||||
// Handed in with the node: editor.view is not there yet while a saved document builds.
|
||||
private view: EditorView;
|
||||
private getPos: () => number;
|
||||
|
||||
constructor(node: PMNode, editor: Editor, getPos: () => number) {
|
||||
constructor(node: PMNode, editor: Editor, getPos: () => number, view: EditorView) {
|
||||
this.node = node;
|
||||
this.editor = editor;
|
||||
this.view = view;
|
||||
this.getPos = getPos;
|
||||
|
||||
this.dom = document.createElement('span');
|
||||
@@ -511,7 +515,7 @@ class ImageView {
|
||||
d.style.position = 'absolute';
|
||||
d.style.zIndex = this.node.attrs.inFront ? '1' : '-1';
|
||||
d.style.left = `${px(this.node.attrs.wrapOffset)}px`;
|
||||
const grid = readVerticalMargins(this.editor.view.dom as HTMLElement).grid;
|
||||
const grid = readVerticalMargins(this.view.dom as HTMLElement).grid;
|
||||
d.style.top = `${grid.topOf(page) + px(this.node.attrs.wrapOffsetY)}px`;
|
||||
}
|
||||
|
||||
@@ -537,7 +541,7 @@ class ImageView {
|
||||
if (!this.editor.isEditable) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const view = this.editor.view;
|
||||
const view = this.view;
|
||||
const origPos = this.getPos();
|
||||
if (typeof origPos !== 'number') return;
|
||||
const win = this.dom.ownerDocument.defaultView ?? window;
|
||||
@@ -631,7 +635,7 @@ class ImageView {
|
||||
private commit(attrs: Record<string, unknown>): void {
|
||||
const pos = this.getPos();
|
||||
if (typeof pos !== 'number') return;
|
||||
this.editor.view.dispatch(this.editor.state.tr.setNodeMarkup(pos, undefined, { ...this.node.attrs, ...attrs }));
|
||||
this.view.dispatch(this.editor.state.tr.setNodeMarkup(pos, undefined, { ...this.node.attrs, ...attrs }));
|
||||
}
|
||||
|
||||
private startResize(event: MouseEvent, cfg: typeof HANDLES[number]): void {
|
||||
|
||||
+6
-2
@@ -244,16 +244,20 @@ try {
|
||||
const frame = (attrs) => ({ type: 'image', attrs: { src: PNG, width: 120, height: 60, ...attrs } });
|
||||
const block = (...content) => ({ type: 'paragraph', content });
|
||||
const words = (t) => ({ type: 'text', text: t });
|
||||
await page.evaluate((d) => document.querySelector('.tiptap').editor.commands.setContent(d), { type: 'doc', content: [
|
||||
// Out of the autosave, as a reload builds it: the anchored frame is placed while the
|
||||
// view is still being built.
|
||||
await page.evaluate((d) => localStorage.setItem('edentext-doc', JSON.stringify(d)), { type: 'doc', content: [
|
||||
block(words('above')), block(words('with '), frame({ wrap: 'topBottom' })), block(words('below')),
|
||||
block(frame({ wrap: 'topBottom', anchorPage: 1 })), block(words('after anchored')),
|
||||
block(frame({ wrap: 'topBottom' })), block(frame({ wrap: 'topBottom' })), block(words('after two')),
|
||||
] });
|
||||
await page.reload({ waitUntil: 'load' });
|
||||
await page.waitForSelector('.tiptap', { timeout: 15_000 });
|
||||
await settle(page, true);
|
||||
const bands = await page.evaluate(() => Array.from(document.querySelectorAll('.tiptap > p'),
|
||||
(p) => `${p.getAttribute('data-wrap-band') ?? '-'}/${getComputedStyle(p).clear}`).join(' '));
|
||||
check(bands === '-/none true/none -/both anchored/none -/none true/none true/none -/both',
|
||||
`the block after a band frame clears it, after an anchored one it does not (${bands})`);
|
||||
`loaded from the autosave, the block after a band frame clears it, after an anchored one it does not (${bands})`);
|
||||
} catch (err) {
|
||||
check(false, `dom run threw: ${err.message ?? err}`);
|
||||
} finally {
|
||||
|
||||
@@ -219,20 +219,20 @@
|
||||
},
|
||||
"docs/showcase/newsletter.docx": {
|
||||
"pages": 2,
|
||||
"lines": 153,
|
||||
"ms": 200,
|
||||
"lines": 155,
|
||||
"ms": 400,
|
||||
"starts": [
|
||||
"Eden Astronomy Society",
|
||||
"For the"
|
||||
"Observing nights this autumn"
|
||||
]
|
||||
},
|
||||
"docs/showcase/newsletter.odt": {
|
||||
"pages": 2,
|
||||
"lines": 153,
|
||||
"lines": 155,
|
||||
"ms": 100,
|
||||
"starts": [
|
||||
"Eden Astronomy Society",
|
||||
"For the"
|
||||
"Observing nights this autumn"
|
||||
]
|
||||
},
|
||||
"docs/showcase/thesis-review.docx": {
|
||||
|
||||
Reference in new issue
Block a user