diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts
index 23be85aa1..095adecb9 100644
--- a/apps/client/src/features/editor/extensions/extensions.ts
+++ b/apps/client/src/features/editor/extensions/extensions.ts
@@ -56,6 +56,7 @@ import {
Status,
TransclusionSource,
TransclusionReference,
+ TableView,
} from "@docmost/editor-ext";
import {
randomElement,
@@ -259,6 +260,7 @@ export const mainExtensions = [
resizable: true,
lastColumnResizable: true,
allowTableNodeSelection: true,
+ View: TableView,
}),
TableRow,
TableCell,
diff --git a/packages/editor-ext/src/lib/table/index.ts b/packages/editor-ext/src/lib/table/index.ts
index 9e5a92651..784b71928 100644
--- a/packages/editor-ext/src/lib/table/index.ts
+++ b/packages/editor-ext/src/lib/table/index.ts
@@ -2,4 +2,5 @@ export * from "./row";
export * from "./cell";
export * from "./header";
export * from "./table";
-export * from "./dnd";
\ No newline at end of file
+export * from "./dnd";
+export * from "./table-view";
diff --git a/packages/editor-ext/src/lib/table/table-view.ts b/packages/editor-ext/src/lib/table/table-view.ts
new file mode 100644
index 000000000..733b04089
--- /dev/null
+++ b/packages/editor-ext/src/lib/table/table-view.ts
@@ -0,0 +1,145 @@
+import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
+import type { NodeView, ViewMutationRecord } from '@tiptap/pm/view';
+import { getColStyleDeclaration } from './utils/col-style';
+
+export function updateColumns(
+ node: ProseMirrorNode,
+ colgroup: HTMLTableColElement, //
has the same prototype as
+ table: HTMLTableElement,
+ cellMinWidth: number,
+ overrideCol?: number,
+ overrideValue?: number,
+) {
+ let totalWidth = 0;
+ let fixedWidth = true;
+ let nextDOM = colgroup.firstChild;
+ const row = node.firstChild;
+
+ if (row !== null) {
+ for (let i = 0, col = 0; i < row.childCount; i += 1) {
+ const { colspan, colwidth } = row.child(i).attrs;
+
+ for (let j = 0; j < colspan; j += 1, col += 1) {
+ const hasWidth =
+ overrideCol === col
+ ? overrideValue
+ : ((colwidth && colwidth[j]) as number | undefined);
+ const cssWidth = hasWidth ? `${hasWidth}px` : '';
+
+ totalWidth += hasWidth || cellMinWidth;
+
+ if (!hasWidth) {
+ fixedWidth = false;
+ }
+
+ if (!nextDOM) {
+ const colElement = document.createElement('col');
+
+ const [propertyKey, propertyValue] = getColStyleDeclaration(
+ cellMinWidth,
+ hasWidth,
+ );
+
+ colElement.style.setProperty(propertyKey, propertyValue);
+
+ colgroup.appendChild(colElement);
+ } else {
+ if ((nextDOM as HTMLTableColElement).style.width !== cssWidth) {
+ const [propertyKey, propertyValue] = getColStyleDeclaration(
+ cellMinWidth,
+ hasWidth,
+ );
+
+ (nextDOM as HTMLTableColElement).style.setProperty(
+ propertyKey,
+ propertyValue,
+ );
+ }
+
+ nextDOM = nextDOM.nextSibling;
+ }
+ }
+ }
+ }
+
+ while (nextDOM) {
+ const after = nextDOM.nextSibling;
+
+ nextDOM.parentNode?.removeChild(nextDOM);
+ nextDOM = after;
+ }
+
+ // Check if user has set a width style on the table node
+ const hasUserWidth =
+ node.attrs.style &&
+ typeof node.attrs.style === 'string' &&
+ /\bwidth\s*:/i.test(node.attrs.style);
+
+ if (fixedWidth && !hasUserWidth) {
+ table.style.width = `${totalWidth}px`;
+ table.style.minWidth = '';
+ } else {
+ table.style.width = '';
+ table.style.minWidth = `${totalWidth}px`;
+ }
+}
+
+export class TableView implements NodeView {
+ node: ProseMirrorNode;
+
+ cellMinWidth: number;
+
+ dom: HTMLDivElement;
+
+ table: HTMLTableElement;
+
+ colgroup: HTMLTableColElement;
+
+ contentDOM: HTMLTableSectionElement;
+
+ constructor(node: ProseMirrorNode, cellMinWidth: number) {
+ this.node = node;
+ this.cellMinWidth = cellMinWidth;
+ this.dom = document.createElement('div');
+ this.dom.className = 'tableWrapper';
+ this.table = this.dom.appendChild(document.createElement('table'));
+
+ // Apply user styles to the table element
+ if (node.attrs.style) {
+ this.table.style.cssText = node.attrs.style;
+ }
+
+ this.colgroup = this.table.appendChild(document.createElement('colgroup'));
+ updateColumns(node, this.colgroup, this.table, cellMinWidth);
+ this.contentDOM = this.table.appendChild(document.createElement('tbody'));
+ }
+
+ update(node: ProseMirrorNode) {
+ if (node.type !== this.node.type) {
+ return false;
+ }
+
+ this.node = node;
+ updateColumns(node, this.colgroup, this.table, this.cellMinWidth);
+
+ return true;
+ }
+
+ ignoreMutation(mutation: ViewMutationRecord) {
+ const target = mutation.target as Node;
+ const isInsideWrapper = this.dom.contains(target);
+ const isInsideContent = this.contentDOM.contains(target);
+
+ if (isInsideWrapper && !isInsideContent) {
+ if (
+ mutation.type === 'attributes' ||
+ mutation.type === 'childList' ||
+ mutation.type === 'characterData'
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/packages/editor-ext/src/lib/table/table.ts b/packages/editor-ext/src/lib/table/table.ts
index f1436c28d..e93a1836c 100644
--- a/packages/editor-ext/src/lib/table/table.ts
+++ b/packages/editor-ext/src/lib/table/table.ts
@@ -32,6 +32,7 @@ function handleListOutdent(editor: Editor): boolean {
}
export const CustomTable = Table.extend({
+
addKeyboardShortcuts() {
return {
...this.parent?.(),
diff --git a/packages/editor-ext/src/lib/table/utils/col-style.ts b/packages/editor-ext/src/lib/table/utils/col-style.ts
new file mode 100644
index 000000000..d54a259fd
--- /dev/null
+++ b/packages/editor-ext/src/lib/table/utils/col-style.ts
@@ -0,0 +1,9 @@
+export function getColStyleDeclaration(minWidth: number, width: number | undefined): [string, string] {
+ if (width) {
+ // apply the stored width unless it is below the configured minimum cell width
+ return ['width', `${Math.max(width, minWidth)}px`]
+ }
+
+ // set the minimum with on the column if it has no stored width
+ return ['min-width', `${minWidth}px`]
+}