Skip to content

Commit

Permalink
Table cell merge & various related fixes (#7599)
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Ilyushchenko <[email protected]>
  • Loading branch information
mr1name authored Jan 8, 2025
1 parent 9c7ce99 commit bc4c94a
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 55 deletions.
7 changes: 7 additions & 0 deletions plugins/text-editor-assets/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions plugins/text-editor-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@
"AddRowAfter": "Add after",
"DeleteRow": "Delete",
"DeleteTable": "Delete",
"MergeCells": "Merge cells",
"SplitCells": "Split cells",
"Duplicate": "Duplicate",
"CategoryRow": "Rows",
"CategoryColumn": "Columns",
"CategoryCell": "Cells",
"Table": "Table",
"InsertTable": "Insert table",
"TableOptions": "Customize table",
Expand Down
3 changes: 3 additions & 0 deletions plugins/text-editor-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@
"AddRowAfter": "Добавить после",
"DeleteRow": "Удалить",
"DeleteTable": "Удалить",
"MergeCells": "Объединить ячейки",
"SplitCells": "Разделить ячейки",
"Duplicate": "Дублировать",
"CategoryRow": "Строки",
"CategoryColumn": "Колонки",
"CategoryCell": "Ячейки",
"Table": "Таблица",
"InsertTable": "Добавить таблицу",
"TableOptions": "Настроить таблицу",
Expand Down
4 changes: 3 additions & 1 deletion plugins/text-editor-assets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ loadMetadata(textEditor.icon, {
Download: `${icons}#download`,
Note: `${icons}#note`,
Comment: `${icons}#comment`,
SelectTable: `${icons}#move`
SelectTable: `${icons}#move`,
MergeCells: `${icons}#union`,
SplitCells: `${icons}#divide`
})
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
}
&__col {
right: -1.5rem;
right: calc(var(--table-offscreen-spacing) - 1.5rem);
top: 0;
bottom: 0;
margin: 1.25rem 0;
Expand All @@ -188,7 +188,7 @@
&__row {
bottom: -0.25rem;
left: var(--table-offscreen-spacing);
right: 0;
right: var(--table-offscreen-spacing);
.table-button {
height: 1.25rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,76 @@
// limitations under the License.
//

import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Fragment, type Node, type Node as ProseMirrorNode } from '@tiptap/pm/model'
import type { Transaction } from '@tiptap/pm/state'
import { TableMap } from '@tiptap/pm/tables'
import { type CellSelection, TableMap } from '@tiptap/pm/tables'
import type { TableNodeLocation } from '../types'
import { type Editor } from '@tiptap/core'

type TableRow = Array<ProseMirrorNode | null>
type TableRows = TableRow[]

export function moveColumn (table: TableNodeLocation, from: number, to: number, tr: Transaction): Transaction {
const cols = transpose(tableToCells(table))
moveRowInplace(cols, from, to)
tableFromCells(table, transpose(cols), tr)
export function moveSelectedColumns (
editor: Editor,
table: TableNodeLocation,
selection: CellSelection,
to: number,
tr: Transaction
): Transaction {
const tableMap = TableMap.get(table.node)

let columnStart = -1
let columnEnd = -1

selection.forEachCell((node, pos) => {
const cell = tableMap.findCell(pos - table.pos - 1)
for (let i = cell.left; i < cell.right; i++) {
columnStart = columnStart >= 0 ? Math.min(cell.left, columnStart) : cell.left
columnEnd = columnEnd >= 0 ? Math.max(cell.right, columnEnd) : cell.right
}
})

if (to < 0 || to > tableMap.width || (to >= columnStart && to < columnEnd)) return tr

const rows = tableToCells(table)
for (const row of rows) {
const range = row.splice(columnStart, columnEnd - columnStart)
const offset = to > columnStart ? to - (columnEnd - columnStart - 1) : to
row.splice(offset, 0, ...range)
}

tableFromCells(editor, table, rows, tr)
return tr
}

export function moveRow (table: TableNodeLocation, from: number, to: number, tr: Transaction): Transaction {
export function moveSelectedRows (
editor: Editor,
table: TableNodeLocation,
selection: CellSelection,
to: number,
tr: Transaction
): Transaction {
const tableMap = TableMap.get(table.node)

let rowStart = -1
let rowEnd = -1

selection.forEachCell((node, pos) => {
const cell = tableMap.findCell(pos - table.pos - 1)
for (let i = cell.top; i < cell.bottom; i++) {
rowStart = rowStart >= 0 ? Math.min(cell.top, rowStart) : cell.top
rowEnd = rowEnd >= 0 ? Math.max(cell.bottom, rowEnd) : cell.bottom
}
})

if (to < 0 || to > tableMap.height || (to >= rowStart && to < rowEnd)) return tr

const rows = tableToCells(table)
moveRowInplace(rows, from, to)
tableFromCells(table, rows, tr)
const range = rows.splice(rowStart, rowEnd - rowStart)
const offset = to > rowStart ? to - (rowEnd - rowStart - 1) : to
rows.splice(offset, 0, ...range)

tableFromCells(editor, table, rows, tr)
return tr
}

Expand Down Expand Up @@ -78,47 +129,29 @@ export function duplicateColumns (table: TableNodeLocation, columnIndices: numbe
return tr
}

function moveRowInplace (rows: TableRows, from: number, to: number): void {
rows.splice(to, 0, rows.splice(from, 1)[0])
}

function transpose (rows: TableRows): TableRows {
return rows[0].map((_, colIdx) => rows.map((row) => row[colIdx]))
}

function tableToCells (table: TableNodeLocation): TableRows {
const { map, width, height } = TableMap.get(table.node)

const visitedCells = new Set<number>()
const rows = []
for (let row = 0; row < height; row++) {
const cells = []
for (let col = 0; col < width; col++) {
const pos = map[row * width + col]
cells.push(table.node.nodeAt(pos))
cells.push(!visitedCells.has(pos) ? table.node.nodeAt(pos) : null)
visitedCells.add(pos)
}
rows.push(cells)
}

return rows
}

function tableFromCells (table: TableNodeLocation, rows: TableRows, tr: Transaction): void {
const { map, width, height } = TableMap.get(table.node)
const mapStart = tr.mapping.maps.length

for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const pos = map[row * width + col]

const oldCell = table.node.nodeAt(pos)
const newCell = rows[row][col]

if (oldCell !== null && newCell !== null && oldCell !== newCell) {
const start = tr.mapping.slice(mapStart).map(table.start + pos)
const end = start + oldCell.nodeSize

tr.replaceWith(start, end, newCell)
}
}
}
function tableFromCells (editor: Editor, table: TableNodeLocation, rows: TableRows, tr: Transaction): void {
const schema = editor.schema.nodes
const newRowNodes = rows.map((row) =>
schema.tableRow.create(null, row.filter((cell) => cell !== null) as readonly Node[])
)
const newTableNode = table.node.copy(Fragment.from(newRowNodes))
tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTableNode)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

import { type Editor } from '@tiptap/core'
import { type EditorState } from '@tiptap/pm/state'
import { TableMap } from '@tiptap/pm/tables'
import { CellSelection, TableMap } from '@tiptap/pm/tables'
import { Decoration } from '@tiptap/pm/view'
import textEditor from '@hcengineering/text-editor'

import { type TableNodeLocation } from '../types'
import { findTable, getSelectedColumns, isColumnSelected, selectColumn } from '../utils'

import { duplicateColumns, moveColumn } from './actions'
import { duplicateColumns, moveSelectedColumns } from './actions'
import DeleteCol from '../../../icons/table/DeleteCol.svelte'
import Duplicate from '../../../icons/table/Duplicate.svelte'
import { createCellsHandle, type OptionItem } from './cellsHandle'
Expand Down Expand Up @@ -120,8 +120,10 @@ const handleMouseDown = (

if (col !== dropIndex) {
let tr = editor.state.tr
tr = selectColumn(table, dropIndex, tr)
tr = moveColumn(table, col, dropIndex, tr)
const selection = editor.state.selection
if (selection instanceof CellSelection) {
tr = moveSelectedColumns(editor, table, selection, dropIndex, tr)
}
editor.view.dispatch(tr)
}
window.removeEventListener('mouseup', handleFinish)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

import { type Editor } from '@tiptap/core'
import { type EditorState } from '@tiptap/pm/state'
import { TableMap } from '@tiptap/pm/tables'
import { CellSelection, TableMap } from '@tiptap/pm/tables'
import { Decoration } from '@tiptap/pm/view'
import textEditor from '@hcengineering/text-editor'

import { type TableNodeLocation } from '../types'
import { findTable, getSelectedRows, isRowSelected, selectRow } from '../utils'

import { duplicateRows, moveRow } from './actions'
import { duplicateRows, moveSelectedRows } from './actions'
import DeleteRow from '../../../icons/table/DeleteRow.svelte'
import Duplicate from '../../../icons/table/Duplicate.svelte'
import { createCellsHandle, type OptionItem } from './cellsHandle'
Expand Down Expand Up @@ -120,8 +120,10 @@ const handleMouseDown = (

if (row !== dropIndex) {
let tr = editor.state.tr
tr = selectRow(table, dropIndex, tr)
tr = moveRow(table, row, dropIndex, tr)
const selection = editor.state.selection
if (selection instanceof CellSelection) {
tr = moveSelectedRows(editor, table, selection, dropIndex, tr)
}
editor.view.dispatch(tr)
}
window.removeEventListener('mouseup', handleFinish)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,17 @@ function getTableCellBorders (
const { width, height } = tableMap
const cellIndex = tableMap.map.indexOf(cell)

const rect = tableMap.findCell(cell)
const cellW = rect.right - rect.left
const cellH = rect.bottom - rect.top

const testRight = cellW
const testBottom = width * cellH

const topCell = cellIndex >= width ? tableMap.map[cellIndex - width] : undefined
const bottomCell = cellIndex < width * height - width ? tableMap.map[cellIndex + width] : undefined
const leftCell = cellIndex % width !== 0 ? tableMap.map[cellIndex - 1] : undefined
const rightCell = cellIndex % width !== width - 1 ? tableMap.map[cellIndex + 1] : undefined
const bottomCell = cellIndex < width * height - testBottom ? tableMap.map[cellIndex + testBottom] : undefined
const leftCell = cellIndex % width > 0 ? tableMap.map[cellIndex - 1] : undefined
const rightCell = cellIndex % width < width - testRight ? tableMap.map[cellIndex + testRight] : undefined

return {
top: topCell === undefined || !selection.includes(topCell),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ export async function openTableOptions (editor: Editor, event: MouseEvent): Prom
label: textEditor.string.CategoryRow
}
},
{
id: '#mergeCells',
icon: textEditor.icon.MergeCells,
label: textEditor.string.MergeCells,
action: () => editor.commands.mergeCells(),
category: {
label: textEditor.string.CategoryCell
}
},
{
id: '#splitCell',
icon: textEditor.icon.SplitCells,
label: textEditor.string.SplitCells,
action: () => editor.commands.splitCell(),
category: {
label: textEditor.string.CategoryCell
}
},
{
id: '#deleteTable',
icon: DeleteTable,
Expand Down
Loading

0 comments on commit bc4c94a

Please sign in to comment.