mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
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:
@@ -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";
|
||||
this.toggleControl.addEventListener("click", this.handleToggleClick);
|
||||
|
||||
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;
|
||||
|
||||
this.updateToggleState();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,24 +19,28 @@ export class TableView extends ProsemirrorTableView {
|
||||
this.scrollable.appendChild(this.table);
|
||||
this.scrollable.classList.add(EditorStyleHelper.tableScrollable);
|
||||
|
||||
this.scrollable.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
this.updateClassList(this.node);
|
||||
},
|
||||
{
|
||||
passive: true,
|
||||
}
|
||||
);
|
||||
if (isBrowser) {
|
||||
this.scrollable.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
this.updateClassList(this.node);
|
||||
},
|
||||
{
|
||||
passive: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.updateClassList(node);
|
||||
|
||||
// We need to wait for the next tick to ensure dom is rendered and scroll shadows are correct.
|
||||
setTimeout(() => {
|
||||
if (this.dom) {
|
||||
this.updateClassList(node);
|
||||
}
|
||||
}, 0);
|
||||
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"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user