mirror of
https://github.com/makeplane/plane.git
synced 2026-02-04 21:21:03 -06:00
refactor: table drag preview using decorations
This commit is contained in:
@@ -139,8 +139,6 @@ ATTRIBUTES = {
|
||||
"rowspan",
|
||||
"colwidth",
|
||||
"background",
|
||||
"hideContent",
|
||||
"hidecontent",
|
||||
"style",
|
||||
},
|
||||
"td": {
|
||||
@@ -150,8 +148,6 @@ ATTRIBUTES = {
|
||||
"background",
|
||||
"textColor",
|
||||
"textcolor",
|
||||
"hideContent",
|
||||
"hidecontent",
|
||||
"style",
|
||||
},
|
||||
"tr": {"background", "textColor", "textcolor", "style"},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
* @param {Editor} editor - The editor instance.
|
||||
* @param {boolean} hideContent - Whether to hide the content.
|
||||
* @returns {boolean} Whether the content visibility was updated.
|
||||
* @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 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 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 {number[]} cellPositions - Array of cell positions to hide.
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -53,9 +53,6 @@ export const TableCell = Node.create<TableCellOptions>({
|
||||
textColor: {
|
||||
default: null,
|
||||
},
|
||||
hideContent: {
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -116,7 +113,6 @@ export const TableCell = Node.create<TableCellOptions>({
|
||||
return [
|
||||
"td",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
class: node.attrs.hideContent ? "content-hidden" : "",
|
||||
style: `background-color: ${node.attrs.background}; color: ${node.attrs.textColor};`,
|
||||
}),
|
||||
0,
|
||||
|
||||
@@ -45,9 +45,6 @@ export const TableHeader = Node.create<TableHeaderOptions>({
|
||||
background: {
|
||||
default: "none",
|
||||
},
|
||||
hideContent: {
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -63,7 +60,6 @@ export const TableHeader = Node.create<TableHeaderOptions>({
|
||||
return [
|
||||
"th",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
class: node.attrs.hideContent ? "content-hidden" : "",
|
||||
style: `background-color: ${node.attrs.background};`,
|
||||
}),
|
||||
0,
|
||||
|
||||
@@ -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<TableOptions>({
|
||||
tableEditing({
|
||||
allowTableNodeSelection: this.options.allowTableNodeSelection,
|
||||
}),
|
||||
TableDragStatePlugin,
|
||||
TableInsertPlugin(this.editor),
|
||||
TableColumnDragHandlePlugin(this.editor),
|
||||
TableRowDragHandlePlugin(this.editor),
|
||||
|
||||
Reference in New Issue
Block a user