|
|
@@ -34,7 +34,10 @@ import { findAncestor } from './commands/utils';
|
|
|
import TableUtils from '../src/tableutils';
|
|
|
|
|
|
import injectTablePostFixer from './converters/table-post-fixer';
|
|
|
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
|
|
|
import injectTableCellPostFixer from './converters/tablecell-post-fixer';
|
|
|
+import ViewRange from '../../ckeditor5-engine/src/view/range';
|
|
|
+import TableWalker from './tablewalker';
|
|
|
|
|
|
import '../theme/tableediting.css';
|
|
|
|
|
|
@@ -52,6 +55,7 @@ export default class TableEditing extends Plugin {
|
|
|
const model = editor.model;
|
|
|
const schema = model.schema;
|
|
|
const conversion = editor.conversion;
|
|
|
+ const viewDocument = editor.editing.view.document;
|
|
|
|
|
|
schema.register( 'table', {
|
|
|
allowWhere: '$block',
|
|
|
@@ -147,6 +151,70 @@ export default class TableEditing extends Plugin {
|
|
|
this.editor.keystrokes.set( 'Tab', ( ...args ) => this._handleTabOnSelectedTable( ...args ), { priority: 'low' } );
|
|
|
this.editor.keystrokes.set( 'Tab', this._getTabHandler( true ), { priority: 'low' } );
|
|
|
this.editor.keystrokes.set( 'Shift+Tab', this._getTabHandler( false ), { priority: 'low' } );
|
|
|
+
|
|
|
+ const selected = new Set();
|
|
|
+
|
|
|
+ const tableUtils = editor.plugins.get( TableUtils );
|
|
|
+ const tableSelection = new TableSelection( editor, tableUtils );
|
|
|
+
|
|
|
+ this.listenTo( viewDocument, 'mousedown', ( eventInfo, domEventData ) => {
|
|
|
+ const tableCell = getTableCell( domEventData, this.editor );
|
|
|
+
|
|
|
+ if ( !tableCell ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const { column, row } = tableUtils.getCellLocation( tableCell );
|
|
|
+
|
|
|
+ const mode = getSelectionMode( domEventData, column, row );
|
|
|
+
|
|
|
+ tableSelection.startSelection( tableCell, mode );
|
|
|
+
|
|
|
+ domEventData.preventDefault();
|
|
|
+ } );
|
|
|
+
|
|
|
+ this.listenTo( viewDocument, 'mousemove', ( eventInfo, domEventData ) => {
|
|
|
+ if ( !tableSelection.isSelecting ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const tableCell = getTableCell( domEventData, this.editor );
|
|
|
+
|
|
|
+ if ( !tableCell ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ tableSelection.updateSelection( tableCell );
|
|
|
+ } );
|
|
|
+
|
|
|
+ this.listenTo( viewDocument, 'mouseup', ( eventInfo, domEventData ) => {
|
|
|
+ if ( !tableSelection.isSelecting ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const tableCell = getTableCell( domEventData, this.editor );
|
|
|
+
|
|
|
+ if ( !tableCell ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ tableSelection.stopSelection( tableCell );
|
|
|
+ } );
|
|
|
+
|
|
|
+ this.listenTo( viewDocument, 'blur', () => {
|
|
|
+ tableSelection.clearSelection();
|
|
|
+ } );
|
|
|
+
|
|
|
+ viewDocument.selection.on( 'change', () => {
|
|
|
+ for ( const range of viewDocument.selection.getRanges() ) {
|
|
|
+ const node = range.start.nodeAfter;
|
|
|
+
|
|
|
+ if ( node && ( node.is( 'td' ) || node.is( 'th' ) ) ) {
|
|
|
+ editor.editing.view.change( writer => writer.addClass( 'selected', node ) );
|
|
|
+ selected.add( node );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -251,3 +319,163 @@ export default class TableEditing extends Plugin {
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class TableSelection {
|
|
|
+ constructor( editor, tableUtils ) {
|
|
|
+ // block | column | row
|
|
|
+ this._mode = 'block';
|
|
|
+ this._isSelecting = false;
|
|
|
+ this._highlighted = new Set();
|
|
|
+
|
|
|
+ this.editor = editor;
|
|
|
+ this.tableUtils = tableUtils;
|
|
|
+ }
|
|
|
+
|
|
|
+ get isSelecting() {
|
|
|
+ return this._isSelecting;
|
|
|
+ }
|
|
|
+
|
|
|
+ startSelection( tableCell, mode = 'block' ) {
|
|
|
+ this.clearSelection();
|
|
|
+ this._isSelecting = true;
|
|
|
+ this._startElement = tableCell;
|
|
|
+ this._endElement = tableCell;
|
|
|
+ this._mode = mode;
|
|
|
+ this._redrawSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ updateSelection( tableCell ) {
|
|
|
+ this._endElement = tableCell;
|
|
|
+ this._redrawSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ _redrawSelection() {
|
|
|
+ const viewRanges = [];
|
|
|
+
|
|
|
+ const selected = [ ...this.getSelection() ];
|
|
|
+ const previous = [ ...this._highlighted.values() ];
|
|
|
+
|
|
|
+ this._highlighted.clear();
|
|
|
+
|
|
|
+ for ( const tableCell of selected ) {
|
|
|
+ const viewElement = this.editor.editing.mapper.toViewElement( tableCell );
|
|
|
+ viewRanges.push( ViewRange.createOn( viewElement ) );
|
|
|
+
|
|
|
+ this._highlighted.add( viewElement );
|
|
|
+ }
|
|
|
+
|
|
|
+ this.editor.editing.view.change( writer => {
|
|
|
+ for ( const previouslyHighlighted of previous ) {
|
|
|
+ if ( !selected.includes( previouslyHighlighted ) ) {
|
|
|
+ writer.removeClass( 'selected', previouslyHighlighted );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ writer.setSelection( viewRanges, { fake: true, label: 'fake selection over table cell' } );
|
|
|
+ } );
|
|
|
+ }
|
|
|
+
|
|
|
+ stopSelection( tableCell ) {
|
|
|
+ this._isSelecting = false;
|
|
|
+ this._endElement = tableCell;
|
|
|
+ this._redrawSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ clearSelection() {
|
|
|
+ this._startElement = undefined;
|
|
|
+ this._endElement = undefined;
|
|
|
+ this._isSelecting = false;
|
|
|
+ this.updateSelection();
|
|
|
+ this._highlighted.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ * getSelection() {
|
|
|
+ if ( !this._startElement || !this._endElement ) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // return selection according to the mode
|
|
|
+ if ( this._mode == 'block' ) {
|
|
|
+ yield* this._getBlockSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( this._mode == 'row' ) {
|
|
|
+ yield* this._getRowSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( this._mode == 'column' ) {
|
|
|
+ yield* this._getColumnSelection();
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ * _getBlockSelection() {
|
|
|
+ const startLocation = this.tableUtils.getCellLocation( this._startElement );
|
|
|
+ const endLocation = this.tableUtils.getCellLocation( this._endElement );
|
|
|
+
|
|
|
+ const startRow = startLocation.row > endLocation.row ? endLocation.row : startLocation.row;
|
|
|
+ const endRow = startLocation.row > endLocation.row ? startLocation.row : endLocation.row;
|
|
|
+
|
|
|
+ const startColumn = startLocation.column > endLocation.column ? endLocation.column : startLocation.column;
|
|
|
+ const endColumn = startLocation.column > endLocation.column ? startLocation.column : endLocation.column;
|
|
|
+
|
|
|
+ for ( const cellInfo of new TableWalker( this._startElement.parent.parent, { startRow, endRow } ) ) {
|
|
|
+ if ( cellInfo.column >= startColumn && cellInfo.column <= endColumn ) {
|
|
|
+ yield cellInfo.cell;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ * _getRowSelection() {
|
|
|
+ const startLocation = this.tableUtils.getCellLocation( this._startElement );
|
|
|
+ const endLocation = this.tableUtils.getCellLocation( this._endElement );
|
|
|
+
|
|
|
+ const startRow = startLocation.row > endLocation.row ? endLocation.row : startLocation.row;
|
|
|
+ const endRow = startLocation.row > endLocation.row ? startLocation.row : endLocation.row;
|
|
|
+
|
|
|
+ for ( const cellInfo of new TableWalker( this._startElement.parent.parent, { startRow, endRow } ) ) {
|
|
|
+ yield cellInfo.cell;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ * _getColumnSelection() {
|
|
|
+ const startLocation = this.tableUtils.getCellLocation( this._startElement );
|
|
|
+ const endLocation = this.tableUtils.getCellLocation( this._endElement );
|
|
|
+
|
|
|
+ const startColumn = startLocation.column > endLocation.column ? endLocation.column : startLocation.column;
|
|
|
+ const endColumn = startLocation.column > endLocation.column ? startLocation.column : endLocation.column;
|
|
|
+
|
|
|
+ for ( const cellInfo of new TableWalker( this._startElement.parent.parent ) ) {
|
|
|
+ if ( cellInfo.column >= startColumn && cellInfo.column <= endColumn ) {
|
|
|
+ yield cellInfo.cell;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getTableCell( domEventData, editor ) {
|
|
|
+ const element = domEventData.target;
|
|
|
+ const modelElement = editor.editing.mapper.toModelElement( element );
|
|
|
+
|
|
|
+ if ( !modelElement ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ return getParentElement( 'tableCell', Position.createAt( modelElement ) );
|
|
|
+}
|
|
|
+
|
|
|
+function getSelectionMode( domEventData, column, row ) {
|
|
|
+ let mode = 'block';
|
|
|
+
|
|
|
+ const domEvent = domEventData.domEvent;
|
|
|
+ const target = domEvent.target;
|
|
|
+
|
|
|
+ if ( column == 0 && domEvent.offsetX < target.clientWidth / 2 ) {
|
|
|
+ mode = 'row';
|
|
|
+ } else if ( row == 0 && ( domEvent.offsetY < target.clientHeight / 2 ) ) {
|
|
|
+ mode = 'column';
|
|
|
+ }
|
|
|
+
|
|
|
+ return mode;
|
|
|
+}
|