From 2b3185d6cc1efe12ae434f6493495445bd015a1b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Thu, 29 Jan 2026 16:14:27 +0530 Subject: [PATCH 1/2] refactor: table drag preview using decorations --- apps/api/plane/utils/content_validator.py | 4 -- .../drag-handles/column/drag-handle.tsx | 5 +- .../plugins/drag-handles/column/utils.ts | 6 +- .../plugins/drag-handles/row/drag-handle.tsx | 5 +- .../table/plugins/drag-handles/row/utils.ts | 6 +- .../table/plugins/drag-handles/utils.ts | 59 ++++++++++++----- .../extensions/table/plugins/drag-state.ts | 64 +++++++++++++++++++ .../src/core/extensions/table/table-cell.ts | 4 -- .../src/core/extensions/table/table-header.ts | 4 -- .../src/core/extensions/table/table/table.ts | 2 + 10 files changed, 123 insertions(+), 36 deletions(-) create mode 100644 packages/editor/src/core/extensions/table/plugins/drag-state.ts diff --git a/apps/api/plane/utils/content_validator.py b/apps/api/plane/utils/content_validator.py index 9dd52b26e5d..1b4ede2626f 100644 --- a/apps/api/plane/utils/content_validator.py +++ b/apps/api/plane/utils/content_validator.py @@ -139,8 +139,6 @@ def validate_binary_data(data): "rowspan", "colwidth", "background", - "hideContent", - "hidecontent", "style", }, "td": { @@ -150,8 +148,6 @@ def validate_binary_data(data): "background", "textColor", "textcolor", - "hideContent", - "hidecontent", "style", }, "tr": {"background", "textColor", "textcolor", "style"}, diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx index 6e39e44b6ab..a3d31b8dd23 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx @@ -42,7 +42,7 @@ import { updateColDragMarker, updateColDropMarker, } from "../marker-utils"; -import { updateCellContentVisibility } from "../utils"; +import { showCellContent } from "../utils"; import { ColumnOptionsDropdown } from "./dropdown"; import { calculateColumnDropIndex, constructColumnDragPreview, getTableColumnNodesInfo } from "./utils"; @@ -152,8 +152,9 @@ export function ColumnDragHandle(props: ColumnDragHandleProps) { hideDropMarker(dropMarker); hideDragMarker(dragMarker); + // Show cell content by clearing decorations if (isCellSelection(editor.state.selection)) { - updateCellContentVisibility(editor, false); + showCellContent(editor); } if (col !== dropIndex) { diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/utils.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/utils.ts index c367ee6ecdb..ec3717d88d1 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/column/utils.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/column/utils.ts @@ -11,7 +11,7 @@ import { TableMap } from "@tiptap/pm/tables"; import { getSelectedRect, isCellSelection } from "@/extensions/table/table/utilities/helpers"; import type { TableNodeLocation } from "@/extensions/table/table/utilities/helpers"; // local imports -import { cloneTableCell, constructDragPreviewTable, updateCellContentVisibility } from "../utils"; +import { cloneTableCell, constructDragPreviewTable, getSelectedCellPositions, hideCellContent } from "../utils"; type TableColumn = { left: number; @@ -151,7 +151,9 @@ export const constructColumnDragPreview = ( } }); - updateCellContentVisibility(editor, true); + // Hide the selected cells using decorations (local only, not persisted) + const cellPositions = getSelectedCellPositions(selection, table); + hideCellContent(editor, cellPositions); return tableElement; }; diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx index e88a1f2bc92..0c069c81f8e 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx @@ -42,7 +42,7 @@ import { updateRowDragMarker, updateRowDropMarker, } from "../marker-utils"; -import { updateCellContentVisibility } from "../utils"; +import { showCellContent } from "../utils"; import { RowOptionsDropdown } from "./dropdown"; import { calculateRowDropIndex, constructRowDragPreview, getTableRowNodesInfo } from "./utils"; @@ -152,8 +152,9 @@ export function RowDragHandle(props: RowDragHandleProps) { hideDropMarker(dropMarker); hideDragMarker(dragMarker); + // Show cell content by clearing decorations if (isCellSelection(editor.state.selection)) { - updateCellContentVisibility(editor, false); + showCellContent(editor); } if (row !== dropIndex) { diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/utils.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/utils.ts index 5b6d80c1dab..d10ec4015e0 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/row/utils.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/row/utils.ts @@ -11,7 +11,7 @@ import { TableMap } from "@tiptap/pm/tables"; import { getSelectedRect, isCellSelection } from "@/extensions/table/table/utilities/helpers"; import type { TableNodeLocation } from "@/extensions/table/table/utilities/helpers"; // local imports -import { cloneTableCell, constructDragPreviewTable, updateCellContentVisibility } from "../utils"; +import { cloneTableCell, constructDragPreviewTable, getSelectedCellPositions, hideCellContent } from "../utils"; type TableRow = { top: number; @@ -150,7 +150,9 @@ export const constructRowDragPreview = ( } }); - updateCellContentVisibility(editor, true); + // Hide the selected cells using decorations (local only, not persisted) + const cellPositions = getSelectedCellPositions(selection, table); + hideCellContent(editor, cellPositions); return tableElement; }; diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts index 4c8f996d3c6..f1cf7893ffb 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts @@ -5,9 +5,15 @@ */ import type { Editor } from "@tiptap/core"; +import type { Selection } from "@tiptap/pm/state"; +import { TableMap } from "@tiptap/pm/tables"; // constants -import { CORE_EXTENSIONS } from "@/constants/extension"; import { CORE_EDITOR_META } from "@/constants/meta"; +// extensions +import { getSelectedRect, isCellSelection } from "@/extensions/table/table/utilities/helpers"; +import type { TableNodeLocation } from "@/extensions/table/table/utilities/helpers"; +// local imports +import { updateTransactionMeta } from "../drag-state"; /** * @description Construct a pseudo table element which will act as a parent for column and row drag previews. @@ -47,20 +53,41 @@ export const cloneTableCell = ( }; /** - * @description This function updates the `hideContent` attribute of the table cells and headers. + * @description Get positions of all cells in the current selection. + * @param {Selection} selection - The selection. + * @param {TableNodeLocation} table - The table node location. + * @returns {number[]} Array of cell positions. + */ +export const getSelectedCellPositions = (selection: Selection, table: TableNodeLocation): number[] => { + if (!isCellSelection(selection)) return []; + + const tableMap = TableMap.get(table.node); + const selectedRect = getSelectedRect(selection, tableMap); + const cellsInSelection = tableMap.cellsInRect(selectedRect); + + // Convert relative positions to absolute document positions + return cellsInSelection.map((cellPos) => table.start + cellPos); +}; + +/** + * @description Hide cell content using decorations (local only, not persisted). * @param {Editor} editor - The editor instance. - * @param {boolean} hideContent - Whether to hide the content. - * @returns {boolean} Whether the content visibility was updated. + * @param {number[]} cellPositions - Array of cell positions to hide. */ -export const updateCellContentVisibility = (editor: Editor, hideContent: boolean): boolean => - editor - .chain() - .focus() - .setMeta(CORE_EDITOR_META.ADD_TO_HISTORY, false) - .updateAttributes(CORE_EXTENSIONS.TABLE_CELL, { - hideContent, - }) - .updateAttributes(CORE_EXTENSIONS.TABLE_HEADER, { - hideContent, - }) - .run(); +export const hideCellContent = (editor: Editor, cellPositions: number[]): void => { + const tr = editor.view.state.tr; + updateTransactionMeta(tr, cellPositions); + tr.setMeta(CORE_EDITOR_META.ADD_TO_HISTORY, false); + editor.view.dispatch(tr); +}; + +/** + * @description Show cell content by clearing decorations. + * @param {Editor} editor - The editor instance. + */ +export const showCellContent = (editor: Editor): void => { + const tr = editor.view.state.tr; + updateTransactionMeta(tr, null); + tr.setMeta(CORE_EDITOR_META.ADD_TO_HISTORY, true); + editor.view.dispatch(tr); +}; diff --git a/packages/editor/src/core/extensions/table/plugins/drag-state.ts b/packages/editor/src/core/extensions/table/plugins/drag-state.ts new file mode 100644 index 00000000000..d7afd269113 --- /dev/null +++ b/packages/editor/src/core/extensions/table/plugins/drag-state.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import type { Transaction } from "@tiptap/pm/state"; +import { Plugin, PluginKey } from "@tiptap/pm/state"; +import { Decoration, DecorationSet } from "@tiptap/pm/view"; + +const TABLE_DRAG_STATE_PLUGIN_KEY = new PluginKey("tableDragState"); + +export const updateTransactionMeta = (tr: Transaction, hiddenCellPositions: number[] | null) => { + tr.setMeta(TABLE_DRAG_STATE_PLUGIN_KEY, hiddenCellPositions); +}; + +/** + * @description Plugin to manage table drag state using decorations. + * This allows hiding cell content during drag operations without modifying the document. + * Decorations are local to each user and not persisted or shared. + */ +export const TableDragStatePlugin = new Plugin({ + key: TABLE_DRAG_STATE_PLUGIN_KEY, + state: { + init() { + return DecorationSet.empty; + }, + apply(tr, oldState) { + // Get metadata about which cells to hide + const hiddenCellPositions = tr.getMeta(TABLE_DRAG_STATE_PLUGIN_KEY) as number[] | null; + + if (hiddenCellPositions === undefined) { + // No change, map decorations through the transaction + return oldState.map(tr.mapping, tr.doc); + } + + if (hiddenCellPositions === null || !Array.isArray(hiddenCellPositions) || hiddenCellPositions.length === 0) { + // Clear all decorations + return DecorationSet.empty; + } + + // Create decorations for hidden cells + const decorations: Decoration[] = []; + hiddenCellPositions.forEach((pos) => { + if (typeof pos !== "number") return; + const node = tr.doc.nodeAt(pos); + if (node) { + decorations.push( + Decoration.node(pos, pos + node.nodeSize, { + class: "content-hidden", + }) + ); + } + }); + + return DecorationSet.create(tr.doc, decorations); + }, + }, + props: { + decorations(state) { + return this.getState(state); + }, + }, +}); diff --git a/packages/editor/src/core/extensions/table/table-cell.ts b/packages/editor/src/core/extensions/table/table-cell.ts index 2870ac894de..3bc7c2299e6 100644 --- a/packages/editor/src/core/extensions/table/table-cell.ts +++ b/packages/editor/src/core/extensions/table/table-cell.ts @@ -53,9 +53,6 @@ export const TableCell = Node.create({ textColor: { default: null, }, - hideContent: { - default: false, - }, }; }, @@ -116,7 +113,6 @@ export const TableCell = Node.create({ return [ "td", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { - class: node.attrs.hideContent ? "content-hidden" : "", style: `background-color: ${node.attrs.background}; color: ${node.attrs.textColor};`, }), 0, diff --git a/packages/editor/src/core/extensions/table/table-header.ts b/packages/editor/src/core/extensions/table/table-header.ts index e3c3725636e..9f939c2a41a 100644 --- a/packages/editor/src/core/extensions/table/table-header.ts +++ b/packages/editor/src/core/extensions/table/table-header.ts @@ -45,9 +45,6 @@ export const TableHeader = Node.create({ background: { default: "none", }, - hideContent: { - default: false, - }, }; }, @@ -63,7 +60,6 @@ export const TableHeader = Node.create({ return [ "th", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { - class: node.attrs.hideContent ? "content-hidden" : "", style: `background-color: ${node.attrs.background};`, }), 0, diff --git a/packages/editor/src/core/extensions/table/table/table.ts b/packages/editor/src/core/extensions/table/table/table.ts index 08cfb7aa9bb..fe8d3e36e19 100644 --- a/packages/editor/src/core/extensions/table/table/table.ts +++ b/packages/editor/src/core/extensions/table/table/table.ts @@ -28,6 +28,7 @@ import { // constants import { CORE_EXTENSIONS } from "@/constants/extension"; // local imports +import { TableDragStatePlugin } from "../plugins/drag-state"; import { TableColumnDragHandlePlugin } from "../plugins/drag-handles/column/plugin"; import { TableRowDragHandlePlugin } from "../plugins/drag-handles/row/plugin"; import { TableInsertPlugin } from "../plugins/insert-handlers/plugin"; @@ -281,6 +282,7 @@ export const Table = Node.create({ tableEditing({ allowTableNodeSelection: this.options.allowTableNodeSelection, }), + TableDragStatePlugin, TableInsertPlugin(this.editor), TableColumnDragHandlePlugin(this.editor), TableRowDragHandlePlugin(this.editor), From c549398c014d9fd67af31d02137ee57e68cb9aea Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 13 Feb 2026 15:23:33 +0530 Subject: [PATCH 2/2] fix: history meta for table drag state --- .../src/core/extensions/table/plugins/drag-handles/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts b/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts index f1cf7893ffb..9d7e8df8309 100644 --- a/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts +++ b/packages/editor/src/core/extensions/table/plugins/drag-handles/utils.ts @@ -88,6 +88,6 @@ export const hideCellContent = (editor: Editor, cellPositions: number[]): void = export const showCellContent = (editor: Editor): void => { const tr = editor.view.state.tr; updateTransactionMeta(tr, null); - tr.setMeta(CORE_EDITOR_META.ADD_TO_HISTORY, true); + tr.setMeta(CORE_EDITOR_META.ADD_TO_HISTORY, false); editor.view.dispatch(tr); };