fix: Exporting document with table causes crash (#11422)

* fix: Exporting document with table causes crash

* fix: Same issue for checkbox lists
This commit is contained in:
Tom Moor
2026-02-12 18:27:42 -05:00
committed by GitHub
parent a52391842f
commit 66f9113975
2 changed files with 65 additions and 22 deletions
+24 -6
View File
@@ -1,6 +1,7 @@
import type { Node as ProsemirrorNode } from "prosemirror-model";
import type { EditorView, NodeView } from "prosemirror-view";
import type { Dictionary } from "../../../app/hooks/useDictionary";
import { isBrowser } from "../../utils/browser";
import Storage from "../../utils/Storage";
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
@@ -11,23 +12,20 @@ import { EditorStyleHelper } from "../styles/EditorStyleHelper";
export class CheckboxListView implements NodeView {
dom: HTMLElement;
contentDOM: HTMLElement;
private toggleControl: HTMLButtonElement;
private node: ProsemirrorNode;
private view: EditorView;
private getPos: () => number | undefined;
private userIdentifier: string;
private dictionary: Dictionary;
constructor(
node: ProsemirrorNode,
view: EditorView,
getPos: () => number | undefined,
_view: EditorView,
_getPos: () => number | undefined,
userIdentifier: string,
dictionary: Dictionary
) {
this.node = node;
this.view = view;
this.getPos = getPos;
this.userIdentifier = userIdentifier;
this.dictionary = dictionary;
@@ -40,7 +38,10 @@ export class CheckboxListView implements NodeView {
EditorStyleHelper.checklistCompletedToggle
);
this.toggleControl.contentEditable = "false";
if (isBrowser) {
this.toggleControl.addEventListener("click", this.handleToggleClick);
}
this.contentDOM = document.createElement("ul");
this.contentDOM.classList.add("checkbox_list");
@@ -49,10 +50,16 @@ export class CheckboxListView implements NodeView {
wrapperElement.appendChild(this.contentDOM);
this.dom = wrapperElement;
if (isBrowser) {
this.updateToggleState();
}
}
private handleToggleClick = (clickEvent: Event) => {
if (!isBrowser) {
return;
}
clickEvent.preventDefault();
clickEvent.stopPropagation();
@@ -68,6 +75,10 @@ export class CheckboxListView implements NodeView {
};
private updateToggleState() {
if (!isBrowser) {
return;
}
const listId = this.node.attrs.id;
if (!listId) {
this.toggleControl.style.display = "none";
@@ -104,15 +115,22 @@ export class CheckboxListView implements NodeView {
}
update(node: ProsemirrorNode) {
if (!isBrowser) {
return false;
}
if (node.type.name !== "checkbox_list") {
return false;
}
this.node = node;
this.updateToggleState();
return true;
}
destroy() {
if (!isBrowser) {
return;
}
this.toggleControl.removeEventListener("click", this.handleToggleClick);
}
}
+25
View File
@@ -2,6 +2,7 @@ import type { Node } from "prosemirror-model";
import { TableView as ProsemirrorTableView } from "prosemirror-tables";
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
import { TableLayout } from "../types";
import { isBrowser } from "../../utils/browser";
export class TableView extends ProsemirrorTableView {
public constructor(
@@ -18,6 +19,7 @@ export class TableView extends ProsemirrorTableView {
this.scrollable.appendChild(this.table);
this.scrollable.classList.add(EditorStyleHelper.tableScrollable);
if (isBrowser) {
this.scrollable.addEventListener(
"scroll",
() => {
@@ -27,15 +29,18 @@ export class TableView extends ProsemirrorTableView {
passive: true,
}
);
}
this.updateClassList(node);
// We need to wait for the next tick to ensure dom is rendered and scroll shadows are correct.
if (isBrowser) {
setTimeout(() => {
if (this.dom) {
this.updateClassList(node);
}
}, 0);
}
// Set up sticky header handling
this.setupStickyHeader();
@@ -66,6 +71,10 @@ export class TableView extends ProsemirrorTableView {
}
private updateClassList(node: Node) {
if (!isBrowser) {
return;
}
this.dom.classList.toggle(
EditorStyleHelper.tableFullWidth,
node.attrs.layout === TableLayout.fullWidth
@@ -108,6 +117,10 @@ export class TableView extends ProsemirrorTableView {
* Sets up the scroll listener for sticky header behavior.
*/
private setupStickyHeader() {
if (!isBrowser) {
return;
}
// Defer setup to ensure DOM is fully rendered
setTimeout(() => {
this.scrollHandler = () => {
@@ -129,6 +142,10 @@ export class TableView extends ProsemirrorTableView {
* Cleans up the scroll listener and resets header styles.
*/
private cleanupStickyHeader() {
if (!isBrowser) {
return;
}
if (this.scrollHandler) {
document.removeEventListener("scroll", this.scrollHandler, {
capture: true,
@@ -145,6 +162,10 @@ export class TableView extends ProsemirrorTableView {
* Updates the header row transform to create a sticky effect.
*/
private updateStickyHeader() {
if (!isBrowser) {
return;
}
const headerRow = this.table.querySelector("tr") as HTMLElement | null;
if (!headerRow) {
return;
@@ -179,6 +200,10 @@ export class TableView extends ProsemirrorTableView {
* @returns the offset in pixels from the top of the viewport.
*/
private getHeaderOffset(): number {
if (!isBrowser) {
return TableView.HEADER_HEIGHT;
}
const value = getComputedStyle(document.documentElement).getPropertyValue(
"--header-offset"
);