Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table cell merge & various related fixes #7599

Merged
merged 3 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading