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