mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-21 15:59:15 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
cc2c0c31b2
@ -207,4 +207,11 @@
|
|||||||
<path d="M0.5 7.99988L3 10.4999L3.705 9.79488L2.415 8.49988H6.5C6.77614 8.49988 7 8.27602 7 7.99988C7 7.72374 6.77614 7.49988 6.5 7.49988H2.415L3.705 6.20488L3 5.49988L0.5 7.99988Z"/>
|
<path d="M0.5 7.99988L3 10.4999L3.705 9.79488L2.415 8.49988H6.5C6.77614 8.49988 7 8.27602 7 7.99988C7 7.72374 6.77614 7.49988 6.5 7.49988H2.415L3.705 6.20488L3 5.49988L0.5 7.99988Z"/>
|
||||||
<path d="M12.295 6.20488L13.585 7.49988H9.5C9.22386 7.49988 9 7.72374 9 7.99988C9 8.27602 9.22386 8.49988 9.5 8.49988H13.585L12.295 9.79488L13 10.4999L15.5 7.99988L13 5.49988L12.295 6.20488Z"/>
|
<path d="M12.295 6.20488L13.585 7.49988H9.5C9.22386 7.49988 9 7.72374 9 7.99988C9 8.27602 9.22386 8.49988 9.5 8.49988H13.585L12.295 9.79488L13 10.4999L15.5 7.99988L13 5.49988L12.295 6.20488Z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="union" viewBox="0 0 32 32" fill="none">
|
||||||
|
<path d="M17 3H6C4.34315 3 3 4.34315 3 6V17C3 18.6569 4.34315 20 6 20H12V26C12 27.6569 13.3431 29 15 29H26C27.6569 29 29 27.6569 29 26V15C29 13.3431 27.6569 12 26 12H20V6C20 4.34315 18.6569 3 17 3Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="divide" viewBox="0 0 32 32" fill="none">
|
||||||
|
<path d="M3 6C3 4.34315 4.34315 3 6 3H17C18.6569 3 20 4.34315 20 6V10.5556V17C20 18.6569 18.6569 20 17 20H10.5556H6C4.34314 20 3 18.6569 3 17V6Z" stroke="currentColor" stroke-width="2"/>
|
||||||
|
<path d="M12 15C12 13.3431 13.3431 12 15 12H26C27.6569 12 29 13.3431 29 15V26C29 27.6569 27.6569 29 26 29H15C13.3431 29 12 27.6569 12 26V15Z" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -46,9 +46,12 @@
|
|||||||
"AddRowAfter": "Add after",
|
"AddRowAfter": "Add after",
|
||||||
"DeleteRow": "Delete",
|
"DeleteRow": "Delete",
|
||||||
"DeleteTable": "Delete",
|
"DeleteTable": "Delete",
|
||||||
|
"MergeCells": "Merge cells",
|
||||||
|
"SplitCells": "Split cells",
|
||||||
"Duplicate": "Duplicate",
|
"Duplicate": "Duplicate",
|
||||||
"CategoryRow": "Rows",
|
"CategoryRow": "Rows",
|
||||||
"CategoryColumn": "Columns",
|
"CategoryColumn": "Columns",
|
||||||
|
"CategoryCell": "Cells",
|
||||||
"Table": "Table",
|
"Table": "Table",
|
||||||
"InsertTable": "Insert table",
|
"InsertTable": "Insert table",
|
||||||
"TableOptions": "Customize table",
|
"TableOptions": "Customize table",
|
||||||
|
@ -46,9 +46,12 @@
|
|||||||
"AddRowAfter": "Добавить после",
|
"AddRowAfter": "Добавить после",
|
||||||
"DeleteRow": "Удалить",
|
"DeleteRow": "Удалить",
|
||||||
"DeleteTable": "Удалить",
|
"DeleteTable": "Удалить",
|
||||||
|
"MergeCells": "Объединить ячейки",
|
||||||
|
"SplitCells": "Разделить ячейки",
|
||||||
"Duplicate": "Дублировать",
|
"Duplicate": "Дублировать",
|
||||||
"CategoryRow": "Строки",
|
"CategoryRow": "Строки",
|
||||||
"CategoryColumn": "Колонки",
|
"CategoryColumn": "Колонки",
|
||||||
|
"CategoryCell": "Ячейки",
|
||||||
"Table": "Таблица",
|
"Table": "Таблица",
|
||||||
"InsertTable": "Добавить таблицу",
|
"InsertTable": "Добавить таблицу",
|
||||||
"TableOptions": "Настроить таблицу",
|
"TableOptions": "Настроить таблицу",
|
||||||
|
@ -41,5 +41,7 @@ loadMetadata(textEditor.icon, {
|
|||||||
Download: `${icons}#download`,
|
Download: `${icons}#download`,
|
||||||
Note: `${icons}#note`,
|
Note: `${icons}#note`,
|
||||||
Comment: `${icons}#comment`,
|
Comment: `${icons}#comment`,
|
||||||
SelectTable: `${icons}#move`
|
SelectTable: `${icons}#move`,
|
||||||
|
MergeCells: `${icons}#union`,
|
||||||
|
SplitCells: `${icons}#divide`
|
||||||
})
|
})
|
||||||
|
@ -175,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__col {
|
&__col {
|
||||||
right: -1.5rem;
|
right: calc(var(--table-offscreen-spacing) - 1.5rem);
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin: 1.25rem 0;
|
margin: 1.25rem 0;
|
||||||
@ -188,7 +188,7 @@
|
|||||||
&__row {
|
&__row {
|
||||||
bottom: -0.25rem;
|
bottom: -0.25rem;
|
||||||
left: var(--table-offscreen-spacing);
|
left: var(--table-offscreen-spacing);
|
||||||
right: 0;
|
right: var(--table-offscreen-spacing);
|
||||||
|
|
||||||
.table-button {
|
.table-button {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
|
@ -13,25 +13,76 @@
|
|||||||
// limitations under the License.
|
// 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 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 { TableNodeLocation } from '../types'
|
||||||
|
import { type Editor } from '@tiptap/core'
|
||||||
|
|
||||||
type TableRow = Array<ProseMirrorNode | null>
|
type TableRow = Array<ProseMirrorNode | null>
|
||||||
type TableRows = TableRow[]
|
type TableRows = TableRow[]
|
||||||
|
|
||||||
export function moveColumn (table: TableNodeLocation, from: number, to: number, tr: Transaction): Transaction {
|
export function moveSelectedColumns (
|
||||||
const cols = transpose(tableToCells(table))
|
editor: Editor,
|
||||||
moveRowInplace(cols, from, to)
|
table: TableNodeLocation,
|
||||||
tableFromCells(table, transpose(cols), tr)
|
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
|
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)
|
const rows = tableToCells(table)
|
||||||
moveRowInplace(rows, from, to)
|
const range = rows.splice(rowStart, rowEnd - rowStart)
|
||||||
tableFromCells(table, rows, tr)
|
const offset = to > rowStart ? to - (rowEnd - rowStart - 1) : to
|
||||||
|
rows.splice(offset, 0, ...range)
|
||||||
|
|
||||||
|
tableFromCells(editor, table, rows, tr)
|
||||||
return tr
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,23 +129,17 @@ export function duplicateColumns (table: TableNodeLocation, columnIndices: numbe
|
|||||||
return tr
|
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 {
|
function tableToCells (table: TableNodeLocation): TableRows {
|
||||||
const { map, width, height } = TableMap.get(table.node)
|
const { map, width, height } = TableMap.get(table.node)
|
||||||
|
|
||||||
|
const visitedCells = new Set<number>()
|
||||||
const rows = []
|
const rows = []
|
||||||
for (let row = 0; row < height; row++) {
|
for (let row = 0; row < height; row++) {
|
||||||
const cells = []
|
const cells = []
|
||||||
for (let col = 0; col < width; col++) {
|
for (let col = 0; col < width; col++) {
|
||||||
const pos = map[row * 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)
|
rows.push(cells)
|
||||||
}
|
}
|
||||||
@ -102,23 +147,11 @@ function tableToCells (table: TableNodeLocation): TableRows {
|
|||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
function tableFromCells (table: TableNodeLocation, rows: TableRows, tr: Transaction): void {
|
function tableFromCells (editor: Editor, table: TableNodeLocation, rows: TableRows, tr: Transaction): void {
|
||||||
const { map, width, height } = TableMap.get(table.node)
|
const schema = editor.schema.nodes
|
||||||
const mapStart = tr.mapping.maps.length
|
const newRowNodes = rows.map((row) =>
|
||||||
|
schema.tableRow.create(null, row.filter((cell) => cell !== null) as readonly Node[])
|
||||||
for (let row = 0; row < height; row++) {
|
)
|
||||||
for (let col = 0; col < width; col++) {
|
const newTableNode = table.node.copy(Fragment.from(newRowNodes))
|
||||||
const pos = map[row * width + col]
|
tr.replaceWith(table.pos, table.pos + table.node.nodeSize, newTableNode)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
|
|
||||||
import { type Editor } from '@tiptap/core'
|
import { type Editor } from '@tiptap/core'
|
||||||
import { type EditorState } from '@tiptap/pm/state'
|
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 { Decoration } from '@tiptap/pm/view'
|
||||||
import textEditor from '@hcengineering/text-editor'
|
import textEditor from '@hcengineering/text-editor'
|
||||||
|
|
||||||
import { type TableNodeLocation } from '../types'
|
import { type TableNodeLocation } from '../types'
|
||||||
import { findTable, getSelectedColumns, isColumnSelected, selectColumn } from '../utils'
|
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 DeleteCol from '../../../icons/table/DeleteCol.svelte'
|
||||||
import Duplicate from '../../../icons/table/Duplicate.svelte'
|
import Duplicate from '../../../icons/table/Duplicate.svelte'
|
||||||
import { createCellsHandle, type OptionItem } from './cellsHandle'
|
import { createCellsHandle, type OptionItem } from './cellsHandle'
|
||||||
@ -120,8 +120,10 @@ const handleMouseDown = (
|
|||||||
|
|
||||||
if (col !== dropIndex) {
|
if (col !== dropIndex) {
|
||||||
let tr = editor.state.tr
|
let tr = editor.state.tr
|
||||||
tr = selectColumn(table, dropIndex, tr)
|
const selection = editor.state.selection
|
||||||
tr = moveColumn(table, col, dropIndex, tr)
|
if (selection instanceof CellSelection) {
|
||||||
|
tr = moveSelectedColumns(editor, table, selection, dropIndex, tr)
|
||||||
|
}
|
||||||
editor.view.dispatch(tr)
|
editor.view.dispatch(tr)
|
||||||
}
|
}
|
||||||
window.removeEventListener('mouseup', handleFinish)
|
window.removeEventListener('mouseup', handleFinish)
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
|
|
||||||
import { type Editor } from '@tiptap/core'
|
import { type Editor } from '@tiptap/core'
|
||||||
import { type EditorState } from '@tiptap/pm/state'
|
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 { Decoration } from '@tiptap/pm/view'
|
||||||
import textEditor from '@hcengineering/text-editor'
|
import textEditor from '@hcengineering/text-editor'
|
||||||
|
|
||||||
import { type TableNodeLocation } from '../types'
|
import { type TableNodeLocation } from '../types'
|
||||||
import { findTable, getSelectedRows, isRowSelected, selectRow } from '../utils'
|
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 DeleteRow from '../../../icons/table/DeleteRow.svelte'
|
||||||
import Duplicate from '../../../icons/table/Duplicate.svelte'
|
import Duplicate from '../../../icons/table/Duplicate.svelte'
|
||||||
import { createCellsHandle, type OptionItem } from './cellsHandle'
|
import { createCellsHandle, type OptionItem } from './cellsHandle'
|
||||||
@ -120,8 +120,10 @@ const handleMouseDown = (
|
|||||||
|
|
||||||
if (row !== dropIndex) {
|
if (row !== dropIndex) {
|
||||||
let tr = editor.state.tr
|
let tr = editor.state.tr
|
||||||
tr = selectRow(table, dropIndex, tr)
|
const selection = editor.state.selection
|
||||||
tr = moveRow(table, row, dropIndex, tr)
|
if (selection instanceof CellSelection) {
|
||||||
|
tr = moveSelectedRows(editor, table, selection, dropIndex, tr)
|
||||||
|
}
|
||||||
editor.view.dispatch(tr)
|
editor.view.dispatch(tr)
|
||||||
}
|
}
|
||||||
window.removeEventListener('mouseup', handleFinish)
|
window.removeEventListener('mouseup', handleFinish)
|
||||||
|
@ -60,10 +60,17 @@ function getTableCellBorders (
|
|||||||
const { width, height } = tableMap
|
const { width, height } = tableMap
|
||||||
const cellIndex = tableMap.map.indexOf(cell)
|
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 topCell = cellIndex >= width ? tableMap.map[cellIndex - width] : undefined
|
||||||
const bottomCell = cellIndex < width * height - width ? tableMap.map[cellIndex + width] : undefined
|
const bottomCell = cellIndex < width * height - testBottom ? tableMap.map[cellIndex + testBottom] : undefined
|
||||||
const leftCell = cellIndex % width !== 0 ? tableMap.map[cellIndex - 1] : undefined
|
const leftCell = cellIndex % width > 0 ? tableMap.map[cellIndex - 1] : undefined
|
||||||
const rightCell = cellIndex % width !== width - 1 ? tableMap.map[cellIndex + 1] : undefined
|
const rightCell = cellIndex % width < width - testRight ? tableMap.map[cellIndex + testRight] : undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: topCell === undefined || !selection.includes(topCell),
|
top: topCell === undefined || !selection.includes(topCell),
|
||||||
|
@ -116,6 +116,24 @@ export async function openTableOptions (editor: Editor, event: MouseEvent): Prom
|
|||||||
label: textEditor.string.CategoryRow
|
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',
|
id: '#deleteTable',
|
||||||
icon: DeleteTable,
|
icon: DeleteTable,
|
||||||
|
@ -18,17 +18,19 @@ import TiptapTableCell from '@tiptap/extension-table-cell'
|
|||||||
import { Plugin, PluginKey, type Selection } from '@tiptap/pm/state'
|
import { Plugin, PluginKey, type Selection } from '@tiptap/pm/state'
|
||||||
import { DecorationSet } from '@tiptap/pm/view'
|
import { DecorationSet } from '@tiptap/pm/view'
|
||||||
|
|
||||||
import { findTable } from './utils'
|
import { CellSelection, type Rect, TableMap } from '@tiptap/pm/tables'
|
||||||
import { columnHandlerDecoration } from './decorations/columnHandlerDecoration'
|
import { columnHandlerDecoration } from './decorations/columnHandlerDecoration'
|
||||||
import { columnInsertDecoration } from './decorations/columnInsertDecoration'
|
import { columnInsertDecoration } from './decorations/columnInsertDecoration'
|
||||||
|
import { rowHandlerDecoration } from './decorations/rowHandlerDecoration'
|
||||||
import { rowInsertDecoration } from './decorations/rowInsertDecoration'
|
import { rowInsertDecoration } from './decorations/rowInsertDecoration'
|
||||||
import { tableDragMarkerDecoration } from './decorations/tableDragMarkerDecoration'
|
import { tableDragMarkerDecoration } from './decorations/tableDragMarkerDecoration'
|
||||||
import { tableSelectionDecoration } from './decorations/tableSelectionDecoration'
|
import { tableSelectionDecoration } from './decorations/tableSelectionDecoration'
|
||||||
import { rowHandlerDecoration } from './decorations/rowHandlerDecoration'
|
import { findTable } from './utils'
|
||||||
|
import { type Node } from '@tiptap/pm/model'
|
||||||
|
|
||||||
export const TableCell = TiptapTableCell.extend({
|
export const TableCell = TiptapTableCell.extend({
|
||||||
addProseMirrorPlugins () {
|
addProseMirrorPlugins () {
|
||||||
return [tableCellDecorationPlugin(this.editor)]
|
return [tableCellDecorationPlugin(this.editor), tableSelectionNormalizer()]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -78,3 +80,80 @@ const tableCellDecorationPlugin = (editor: Editor): Plugin<TableCellDecorationPl
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tableSelectionNormalizer = (): Plugin<any> => {
|
||||||
|
return new Plugin({
|
||||||
|
appendTransaction: (transactions, oldState, newState) => {
|
||||||
|
const selection = newState.selection
|
||||||
|
if (selection.eq(oldState.selection) || !(selection instanceof CellSelection)) return
|
||||||
|
|
||||||
|
const table = findTable(newState.selection)
|
||||||
|
if (table === undefined) return
|
||||||
|
|
||||||
|
const tableMap = TableMap.get(table.node)
|
||||||
|
|
||||||
|
let rect: Rect | undefined
|
||||||
|
|
||||||
|
const walkCell = (pos: number): void => {
|
||||||
|
const cell = tableMap.findCell(pos)
|
||||||
|
if (cell === undefined) return
|
||||||
|
|
||||||
|
if (rect === undefined) {
|
||||||
|
rect = { ...cell }
|
||||||
|
} else {
|
||||||
|
rect.left = Math.min(rect.left, cell.left)
|
||||||
|
rect.top = Math.min(rect.top, cell.top)
|
||||||
|
|
||||||
|
rect.right = Math.max(rect.right, cell.right)
|
||||||
|
rect.bottom = Math.max(rect.bottom, cell.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.forEachCell((_node, pos) => {
|
||||||
|
walkCell(pos - table.pos - 1)
|
||||||
|
})
|
||||||
|
if (rect === undefined) return
|
||||||
|
|
||||||
|
const rectSelection: number[] = []
|
||||||
|
for (let row = rect.top; row < rect.bottom; row++) {
|
||||||
|
for (let col = rect.left; col < rect.right; col++) {
|
||||||
|
rectSelection.push(tableMap.map[row * tableMap.width + col])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rectSelection.forEach((pos) => {
|
||||||
|
walkCell(pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (rect === undefined) return
|
||||||
|
|
||||||
|
// Original promemirror implementation of TableMap.positionAt skips rowspawn cells, which leads to unpredictable selection behaviour
|
||||||
|
const firstCellOffset = cellPositionAt(tableMap, rect.bottom - 1, rect.right - 1, table.node)
|
||||||
|
const lastCellOffset = cellPositionAt(tableMap, rect.top, rect.left, table.node)
|
||||||
|
|
||||||
|
const firstCellPos = newState.doc.resolve(table.start + firstCellOffset)
|
||||||
|
const lastCellPos = newState.doc.resolve(table.start + lastCellOffset)
|
||||||
|
|
||||||
|
const reverseOrder = selection.$anchorCell.pos > selection.$headCell.pos
|
||||||
|
const $head = reverseOrder ? lastCellPos : firstCellPos
|
||||||
|
const $anchor = reverseOrder ? firstCellPos : lastCellPos
|
||||||
|
|
||||||
|
const newSelection = new CellSelection($anchor, $head)
|
||||||
|
|
||||||
|
if (newSelection.eq(newState.selection)) return
|
||||||
|
|
||||||
|
return newState.tr.setSelection(new CellSelection($anchor, $head))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellPositionAt (tableMap: TableMap, row: number, col: number, table: Node): number {
|
||||||
|
for (let i = 0, rowStart = 0; ; i++) {
|
||||||
|
const rowEnd = rowStart + table.child(i).nodeSize
|
||||||
|
if (i === row) {
|
||||||
|
const index = col + row * tableMap.width
|
||||||
|
const rowEndIndex = (row + 1) * tableMap.width
|
||||||
|
return index === rowEndIndex ? rowEnd - 1 : tableMap.map[index]
|
||||||
|
}
|
||||||
|
rowStart = rowEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -84,11 +84,14 @@ export default plugin(textEditorId, {
|
|||||||
DeleteColumn: '' as IntlString,
|
DeleteColumn: '' as IntlString,
|
||||||
AddRowBefore: '' as IntlString,
|
AddRowBefore: '' as IntlString,
|
||||||
AddRowAfter: '' as IntlString,
|
AddRowAfter: '' as IntlString,
|
||||||
|
MergeCells: '' as IntlString,
|
||||||
|
SplitCells: '' as IntlString,
|
||||||
DeleteRow: '' as IntlString,
|
DeleteRow: '' as IntlString,
|
||||||
DeleteTable: '' as IntlString,
|
DeleteTable: '' as IntlString,
|
||||||
Duplicate: '' as IntlString,
|
Duplicate: '' as IntlString,
|
||||||
CategoryRow: '' as IntlString,
|
CategoryRow: '' as IntlString,
|
||||||
CategoryColumn: '' as IntlString,
|
CategoryColumn: '' as IntlString,
|
||||||
|
CategoryCell: '' as IntlString,
|
||||||
Table: '' as IntlString,
|
Table: '' as IntlString,
|
||||||
TableOptions: '' as IntlString,
|
TableOptions: '' as IntlString,
|
||||||
SelectTable: '' as IntlString,
|
SelectTable: '' as IntlString,
|
||||||
@ -125,6 +128,8 @@ export default plugin(textEditorId, {
|
|||||||
Download: '' as Asset,
|
Download: '' as Asset,
|
||||||
Note: '' as Asset,
|
Note: '' as Asset,
|
||||||
Comment: '' as Asset,
|
Comment: '' as Asset,
|
||||||
SelectTable: '' as Asset
|
SelectTable: '' as Asset,
|
||||||
|
MergeCells: '' as Asset,
|
||||||
|
SplitCells: '' as Asset
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -553,6 +553,13 @@ export interface Session {
|
|||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
|
findAllRaw: <T extends Doc>(
|
||||||
|
ctx: MeasureContext,
|
||||||
|
pipeline: Pipeline,
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T>
|
||||||
|
) => Promise<FindResult<T>>
|
||||||
searchFulltext: (ctx: ClientSessionCtx, query: SearchQuery, options: SearchOptions) => Promise<void>
|
searchFulltext: (ctx: ClientSessionCtx, query: SearchQuery, options: SearchOptions) => Promise<void>
|
||||||
tx: (ctx: ClientSessionCtx, tx: Tx) => Promise<void>
|
tx: (ctx: ClientSessionCtx, tx: Tx) => Promise<void>
|
||||||
loadChunk: (ctx: ClientSessionCtx, domain: Domain, idx?: number) => Promise<void>
|
loadChunk: (ctx: ClientSessionCtx, domain: Domain, idx?: number) => Promise<void>
|
||||||
|
@ -716,21 +716,38 @@ class TSessionManager implements SessionManager {
|
|||||||
const user = pipeline.context.modelDb.getAccountByEmail(session.getUser())
|
const user = pipeline.context.modelDb.getAccountByEmail(session.getUser())
|
||||||
if (user === undefined) return
|
if (user === undefined) return
|
||||||
|
|
||||||
const status = (await pipeline.findAll(ctx, core.class.UserStatus, { user: user._id }, { limit: 1 }))[0]
|
const clientCtx: ClientSessionCtx = {
|
||||||
|
requestId: undefined,
|
||||||
|
pipeline,
|
||||||
|
sendResponse: async (msg) => {
|
||||||
|
// No response
|
||||||
|
},
|
||||||
|
ctx,
|
||||||
|
sendError: async (msg, error: Status) => {
|
||||||
|
// Assume no error send
|
||||||
|
},
|
||||||
|
sendPong: () => {}
|
||||||
|
}
|
||||||
|
const status = (
|
||||||
|
await session.findAllRaw(ctx, pipeline, core.class.UserStatus, { user: user._id }, { limit: 1 })
|
||||||
|
)[0]
|
||||||
const txFactory = new TxFactory(user._id, true)
|
const txFactory = new TxFactory(user._id, true)
|
||||||
if (status === undefined) {
|
if (status === undefined) {
|
||||||
const tx = txFactory.createTxCreateDoc(core.class.UserStatus, core.space.Space, {
|
const tx = txFactory.createTxCreateDoc(core.class.UserStatus, core.space.Space, {
|
||||||
online,
|
online,
|
||||||
user: user._id
|
user: user._id
|
||||||
})
|
})
|
||||||
await pipeline.tx(ctx, [tx])
|
await session.tx(clientCtx, tx)
|
||||||
} else if (status.online !== online) {
|
} else if (status.online !== online) {
|
||||||
const tx = txFactory.createTxUpdateDoc(status._class, status.space, status._id, {
|
const tx = txFactory.createTxUpdateDoc(status._class, status.space, status._id, {
|
||||||
online
|
online
|
||||||
})
|
})
|
||||||
await pipeline.tx(ctx, [tx])
|
await session.tx(clientCtx, tx)
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch (err: any) {
|
||||||
|
ctx.error('failed to set status', { err })
|
||||||
|
Analytics.handleError(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async close (ctx: MeasureContext, ws: ConnectionSocket, wsid: string): Promise<void> {
|
async close (ctx: MeasureContext, ws: ConnectionSocket, wsid: string): Promise<void> {
|
||||||
|
Loading…
Reference in New Issue
Block a user