Przeglądaj źródła

Added: Fake table selection POC.

Maciej Gołaszewski 7 lat temu
rodzic
commit
8690b99dba

+ 228 - 0
packages/ckeditor5-table/src/tableediting.js

@@ -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;
+}

+ 7 - 0
packages/ckeditor5-table/theme/tableediting.css

@@ -8,3 +8,10 @@
  * it acts as a message to the builder telling that it should look for the corresponding styles
  * **in the theme** when compiling the editor.
  */
+
+.ck-content .table table {
+	& td.selected,
+	& th.selected {
+		background: hsl(216, 100%, 67%);
+	}
+}