1- import { chainCommands } from 'prosemirror-commands' ;
2- import type { Node } from 'prosemirror-model' ;
3- import { type Command , TextSelection } from 'prosemirror-state' ;
4-
5- import {
6- findChildTableCells ,
7- findChildTableRows ,
8- findParentTableBodyFromPos ,
9- findParentTableCellFromPos ,
10- findParentTableFromPos ,
11- isTableCellNode ,
12- isTableRowNode ,
13- } from '../../../../table-utils' ;
14- import { isNodeSelection , isTextSelection } from '../../../../utils/selection' ;
1+ import { chainCommands } from '#pm/commands' ;
2+ import type { Node , NodeType , ResolvedPos } from '#pm/model' ;
3+ import { type Command , TextSelection } from '#pm/state' ;
4+ import { pType } from 'src/extensions/base/specs' ;
5+ import { range } from 'src/lodash' ;
6+ import { isTableCellNode , isTableNode , isTableRowNode } from 'src/table-utils' ;
7+ import { TableDesc } from 'src/table-utils/table-desc' ;
8+ import { isNodeSelection , isTextSelection } from 'src/utils/selection' ;
9+
10+ import { yfmTableBodyType , yfmTableRowType , yfmTableType } from '../utils' ;
1511
1612const removeCellNodeContent : Command = ( state , dispatch ) => {
1713 const sel = state . selection ;
@@ -36,114 +32,79 @@ const removeCellNodeContent: Command = (state, dispatch) => {
3632 return false ;
3733} ;
3834
39- // eslint-disable-next-line complexity
4035export const clearSelectedCells : Command = ( state , dispatch ) => {
4136 const sel = state . selection ;
4237 if ( ! isTextSelection ( sel ) ) return false ;
4338 const { $from, $to} = sel ;
4439
45- const fromCell = findParentTableCellFromPos ( $from ) ;
46- const toCell = findParentTableCellFromPos ( $to ) ;
47- const fromTBody = findParentTableBodyFromPos ( $from ) ;
48- const toTBody = findParentTableBodyFromPos ( $to ) ;
49- const fromTable = findParentTableFromPos ( $to ) ;
50-
51- if ( ! fromCell || ! toCell || ! fromTBody || ! toTBody || ! fromTable ) return false ;
52-
53- if ( fromCell . node === toCell . node ) {
54- // selection inside table cell
55- return false ; // should executes default command
56- }
57-
58- if ( fromTBody && toTBody && fromTBody . pos === toTBody . pos ) {
59- if ( dispatch ) {
60- const table = fromTable ;
61- const tBody = fromTBody ;
62-
63- const fromCellIndexInRow = $from . index ( fromCell . depth - 1 ) ;
64- const toCellIndexInRow = $to . index ( toCell . depth - 1 ) ;
65-
66- const fromRowIndexInBody = $from . index ( fromCell . depth - 2 ) ;
67- const toRowIndexInBody = $to . index ( toCell . depth - 2 ) ;
68-
69- const bodyRows = findChildTableRows ( tBody . node ) ;
70-
71- let tr = state . tr ;
72- tr = tr . delete ( toCell . start , $to . pos ) ;
73-
74- let rowIndex = toRowIndexInBody ;
75- while ( rowIndex >= fromRowIndexInBody ) {
76- const row = bodyRows [ rowIndex ] ;
77- const rowCells = findChildTableCells ( row . node ) ;
78-
79- let cellIndex =
80- rowIndex === toRowIndexInBody ? toCellIndexInRow - 1 : rowCells . length - 1 ;
81- while ( cellIndex > ( rowIndex === fromRowIndexInBody ? fromCellIndexInRow : - 1 ) ) {
82- const cell = rowCells [ cellIndex ] ;
83-
84- const from = tBody . pos + row . pos + cell . pos + 3 ;
85- const to = from + cell . node . nodeSize - 2 ;
86-
87- tr = tr . delete ( from , to ) ;
88-
89- cellIndex -- ;
90- }
91-
92- if ( rowIndex !== fromRowIndexInBody ) {
93- const rowPos = tBody . pos + row . pos + 1 ;
94- const trRow = tr . doc . nodeAt ( rowPos ) ;
95- if ( trRow && isEmptyTableRow ( trRow ) ) {
96- tr = tr . delete ( rowPos , rowPos + trRow . nodeSize ) ;
97- }
98- }
99-
100- rowIndex -- ;
101- }
102-
103- tr = tr . delete ( $from . pos , fromCell . pos + fromCell . node . nodeSize ) ;
104-
105- const fromRowPos = tBody . pos + bodyRows [ fromRowIndexInBody ] . pos + 1 ;
106- const trFromRow = tr . doc . nodeAt ( fromRowPos ) ;
107- if ( fromCellIndexInRow === 0 && trFromRow && isEmptyTableRow ( trFromRow ) ) {
108- const rowsCountBeforeDelete = tr . doc . nodeAt ( tBody . pos ) ! . childCount ;
109- tr = tr . delete ( fromRowPos , fromRowPos + tr . doc . nodeAt ( fromRowPos ) ! . nodeSize ) ;
110-
111- const trTable = tr . doc . nodeAt ( table . pos ) ;
112- if ( rowsCountBeforeDelete <= 1 ) {
113- if ( trTable ) {
114- if ( trTable . childCount <= 1 ) {
115- tr = tr . delete ( table . pos , trTable . nodeSize ) ;
116- }
117- } else {
118- tr = tr . delete ( tBody . pos , tBody . pos + tr . doc . nodeAt ( tBody . pos ) ! . nodeSize ) ;
119- }
120- }
40+ const sharedDepth = $from . sharedDepth ( $to . pos ) ;
41+ const commonAncestor = $from . node ( sharedDepth ) ;
42+ const { schema} = commonAncestor . type ;
43+
44+ if (
45+ ! isAnyOfTypes ( commonAncestor , [
46+ yfmTableType ( schema ) ,
47+ yfmTableBodyType ( schema ) ,
48+ yfmTableRowType ( schema ) ,
49+ ] )
50+ )
51+ return false ;
52+
53+ const tablePos = findTablePos ( $from , sharedDepth ) ;
54+ if ( typeof tablePos !== 'number' ) return false ;
55+ const tableNode = $from . doc . nodeAt ( tablePos ) ;
56+ if ( ! tableNode ) return false ;
57+ const tableDesc = TableDesc . create ( tableNode ) ?. bind ( tablePos ) ;
58+ if ( ! tableDesc ) return false ;
59+
60+ if ( dispatch ) {
61+ const tr = state . tr ;
62+
63+ const cells = range ( 0 , tableDesc . rows )
64+ . flatMap ( ( rowIdx ) => tableDesc . getPosForRowCells ( rowIdx ) )
65+ . filter ( ( cell ) => cell . type === 'real' ) ;
66+
67+ const isAllSelected =
68+ $from . pos <= cells [ 0 ] . from + 2 && $to . pos >= cells [ cells . length - 1 ] . to - 2 ;
69+
70+ if ( isAllSelected ) {
71+ // all table content is selected, we should remove table
72+ tr . replaceWith ( tablePos , tablePos + tableNode . nodeSize , pType ( schema ) . create ( ) ) ;
73+ tr . setSelection ( TextSelection . create ( tr . doc , tablePos + 1 ) ) ;
74+ } else {
75+ for ( const cell of cells ) {
76+ if ( $from . pos > cell . to ) continue ;
77+ if ( $to . pos < cell . from ) break ;
78+
79+ const from = Math . max ( $from . pos , cell . from + 1 ) ;
80+ const to = Math . min ( $to . pos , cell . to - 1 ) ;
81+
82+ tr . delete ( tr . mapping . map ( from ) , tr . mapping . map ( to ) ) ;
83+ tr . setSelection ( TextSelection . near ( tr . doc . resolve ( tr . mapping . map ( to ) ) , - 1 ) ) ;
12184 }
122- tr = tr . setSelection ( TextSelection . create ( tr . doc , tr . mapping . map ( sel . head ) ) ) ;
123-
124- dispatch ( tr ) ;
12585 }
12686
127- return true ;
87+ dispatch ( tr . scrollIntoView ( ) ) ;
12888 }
12989
130- return false ;
90+ return true ;
13191} ;
13292
13393export const backspaceCommand = chainCommands ( removeCellNodeContent , clearSelectedCells ) ;
13494
135- function isEmptyTableRow ( node : Node ) {
136- if ( ! isTableRowNode ( node ) ) return false ;
137- if ( node . childCount === 0 ) return true ;
138- let isRowEmpty = true ;
139- node . forEach ( ( cellNode ) => {
140- isRowEmpty = isEmptyTableCell ( cellNode ) ;
141- } ) ;
142- return isRowEmpty ;
95+ function isAnyOfTypes ( node : Node , types : NodeType [ ] ) : boolean {
96+ return types . some ( ( type ) => type === node . type ) ;
14397}
14498
145- function isEmptyTableCell ( node : Node ) : boolean {
146- if ( ! isTableCellNode ( node ) ) return false ;
147- if ( node . childCount === 0 ) return true ;
148- return node . childCount === 1 && node . child ( 0 ) . isTextblock && node . child ( 0 ) . childCount === 0 ;
99+ function findTablePos ( $pos : ResolvedPos , startDepth : number ) : number | null {
100+ const ASCENTS = 5 ;
101+ let depth = startDepth ;
102+ while ( depth >= 0 && startDepth - depth <= ASCENTS ) {
103+ const node = $pos . node ( depth ) ;
104+ if ( isTableNode ( node ) ) {
105+ return $pos . before ( depth ) ;
106+ }
107+ depth -- ;
108+ }
109+ return null ;
149110}
0 commit comments