diff --git a/packages/theme/styles/_colors.scss b/packages/theme/styles/_colors.scss index 5003092d101..4f721ddef72 100644 --- a/packages/theme/styles/_colors.scss +++ b/packages/theme/styles/_colors.scss @@ -65,6 +65,7 @@ --highlight-red-hover: #ff967e; --highlight-red-press: #f96f50bd; + --text-editor-selected-node-background: rgba(43, 81, 144, 0.1); --text-editor-selected-node-color: #93CAF3; --text-editor-highlighted-node-warning-active-background-color: #F2D7AE; diff --git a/packages/theme/styles/prose.scss b/packages/theme/styles/prose.scss index 927528fa0e4..e1ec6028320 100644 --- a/packages/theme/styles/prose.scss +++ b/packages/theme/styles/prose.scss @@ -390,6 +390,12 @@ table.proseTable { } } +.table-node-selected { + .proseTable { + background-color: var(--text-editor-selected-node-background); + } +} + .proseBlockQuote { margin-inline: 1px 0; padding-left: 1.5em; diff --git a/plugins/text-editor-resources/src/components/extension/table/table.ts b/plugins/text-editor-resources/src/components/extension/table/table.ts index 0ce0cc0c54a..24aa37ad0d4 100644 --- a/plugins/text-editor-resources/src/components/extension/table/table.ts +++ b/plugins/text-editor-resources/src/components/extension/table/table.ts @@ -29,23 +29,66 @@ import AddRowBefore from '../../icons/table/AddRowBefore.svelte' import DeleteCol from '../../icons/table/DeleteCol.svelte' import DeleteRow from '../../icons/table/DeleteRow.svelte' import DeleteTable from '../../icons/table/DeleteTable.svelte' +import { Plugin } from '@tiptap/pm/state' +import { TableSelection } from './types' +import { Decoration, DecorationSet } from '@tiptap/pm/view' export const Table = TiptapTable.extend({ draggable: true, addKeyboardShortcuts () { return { - 'Mod-Backspace': () => handleDelete(this.editor), - 'Mod-Delete': () => handleDelete(this.editor) + Backspace: () => handleDelete(this.editor), + Delete: () => handleDelete(this.editor), + 'Mod-Backspace': () => handleModDelete(this.editor), + 'Mod-Delete': () => handleModDelete(this.editor) } }, addNodeView () { return SvelteNodeViewRenderer(TableNodeView, {}) + }, + addProseMirrorPlugins () { + return [...(this.parent?.() ?? []), tableSelectionHighlight()] } }) function handleDelete (editor: Editor): boolean { - const { selection } = editor.state + const { selection } = editor.state.tr + if (selection instanceof TableSelection && isTableSelected(selection)) { + return editor.commands.deleteTable() + } + return false +} + +export const tableSelectionHighlight = (): Plugin => { + return new Plugin({ + props: { + decorations (state) { + return this.getState(state) + } + }, + state: { + init: () => { + return DecorationSet.empty + }, + apply (tr, value, oldState, newState) { + const selection = newState.selection + if (!(selection instanceof TableSelection)) return DecorationSet.empty + + const table = findTable(newState.selection) + if (table === undefined) return DecorationSet.empty + + const decorations: Decoration[] = [ + Decoration.node(table.pos, table.pos + table.node.nodeSize, { class: 'table-node-selected' }) + ] + return DecorationSet.create(newState.doc, decorations) + } + } + }) +} + +function handleModDelete (editor: Editor): boolean { + const { selection } = editor.state.tr if (selection instanceof CellSelection) { if (isTableSelected(selection)) { return editor.commands.deleteTable() diff --git a/plugins/text-editor-resources/src/components/extension/table/types.ts b/plugins/text-editor-resources/src/components/extension/table/types.ts index 13e64f5c5ae..462948d58c9 100644 --- a/plugins/text-editor-resources/src/components/extension/table/types.ts +++ b/plugins/text-editor-resources/src/components/extension/table/types.ts @@ -14,9 +14,15 @@ // import { type Node as ProseMirrorNode } from '@tiptap/pm/model' +import { CellSelection } from '@tiptap/pm/tables' export interface TableNodeLocation { pos: number start: number node: ProseMirrorNode } + +// This subclass serves as a tag to distinguish between situations where +// the table is selected as a node and when all cells in the table are selected, +// the deletion behavior depends on this. +export class TableSelection extends CellSelection {} diff --git a/plugins/text-editor-resources/src/components/extension/table/utils.ts b/plugins/text-editor-resources/src/components/extension/table/utils.ts index adcdf878c43..3783b3d6495 100644 --- a/plugins/text-editor-resources/src/components/extension/table/utils.ts +++ b/plugins/text-editor-resources/src/components/extension/table/utils.ts @@ -17,7 +17,7 @@ import { findParentNode } from '@tiptap/core' import { type Selection, type Transaction } from '@tiptap/pm/state' import { CellSelection, type Rect, TableMap, addColumn, addRow } from '@tiptap/pm/tables' -import { type TableNodeLocation } from './types' +import { TableSelection, type TableNodeLocation } from './types' export function insertColumn (table: TableNodeLocation, index: number, tr: Transaction): Transaction { const map = TableMap.get(table.node) @@ -71,7 +71,7 @@ export function selectTable (table: TableNodeLocation, tr: Transaction): Transac const $head = tr.doc.resolve(table.start + map[0]) const $anchor = tr.doc.resolve(table.start + map[map.length - 1]) - return tr.setSelection(new CellSelection($anchor, $head)) + return tr.setSelection(new TableSelection($anchor, $head)) } export const isColumnSelected = (columnIndex: number, selection: Selection): boolean => {