text: /**
   * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md.
   */
  
  /**
   * @module table/tableediting
   */
  
  import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
  
  import upcastTable, { upcastTableCell } from './converters/upcasttable';
  import {
  	downcastInsertCell,
  	downcastInsertRow,
  	downcastInsertTable,
  	downcastRemoveRow,
  	downcastTableHeadingColumnsChange,
  	downcastTableHeadingRowsChange
  } from './converters/downcast';
  
  import InsertTableCommand from './commands/inserttablecommand';
  import InsertRowCommand from './commands/insertrowcommand';
  import InsertColumnCommand from './commands/insertcolumncommand';
  import SplitCellCommand from './commands/splitcellcommand';
  import MergeCellCommand from './commands/mergecellcommand';
  import RemoveRowCommand from './commands/removerowcommand';
  import RemoveColumnCommand from './commands/removecolumncommand';
  import SetHeaderRowCommand from './commands/setheaderrowcommand';
  import SetHeaderColumnCommand from './commands/setheadercolumncommand';
  import AnnotateTableCommand from './commands/annotatetablecommand';
  import SplitTableCommand from './commands/splittablecommand';
  import { findAncestor } from './commands/utils';
  import TableUtils from './tableutils';
  
  import injectTableLayoutPostFixer from './converters/table-layout-post-fixer';
  import injectTableCellContentsPostFixer from './converters/table-cell-content-post-fixer';
  import injectTableCellPostFixer from './converters/tablecell-post-fixer';
  
  import '../../theme/tableediting.css';
  
  /**
   * The table editing feature.
   *
   * @extends module:core/plugin~Plugin
   */
  export default class TableEditing extends Plugin {
  	/**
  	 * @inheritDoc
  	 */
  	init() {
  		const editor = this.editor;
  		const model = editor.model;
  		const schema = model.schema;
  		const conversion = editor.conversion;
  
  		schema.register( 'table', {
  			allowWhere: '$block',
  			allowAttributes: [ 'headingRows', 'headingColumns', 'tableType' ],
  			isLimit: true,
  			isObject: true
  		} );
  
  		schema.register( 'tableRow', {
  			allowIn: 'table',
  			isLimit: true
  		} );
  
  		schema.register( 'tableCell', {
  			allowIn: 'tableRow',
  			allowAttributes: [ 'colspan', 'rowspan' ],
  			isLimit: true
  		} );
  
  		// Allow all $block content inside table cell.
  		schema.extend( '$block', { allowIn: 'tableCell' } );
  
  		// Disallow table in table.
  		schema.addChildCheck( ( context, childDefinition ) => {
  			if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
  				return false;
  			}
  		} );
  
  		// Disallow image and media in table cell.
  		schema.addChildCheck( ( context, childDefinition ) => {
  			if ( !Array.from( context.getNames() ).includes( 'table' ) ) {
  				return;
  			}
  
  			if ( childDefinition.name == 'image' || childDefinition.name == 'media' ) {
  				return false;
  			}
  		} );
  
  		// Table conversion.
  		conversion.for( 'upcast' ).add( upcastTable() );
  
  		conversion.for( 'editingDowncast' ).add( downcastInsertTable( { asWidget: true } ) );
  		conversion.for( 'dataDowncast' ).add( downcastInsertTable() );
  
  		// Table row conversion.
  		conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableRow', view: 'tr' } ) );
  
  		conversion.for( 'editingDowncast' ).add( downcastInsertRow( { asWidget: true } ) );
  		conversion.for( 'dataDowncast' ).add( downcastInsertRow() );
  		conversion.for( 'downcast' ).add( downcastRemoveRow() );
  
  		// Table cell conversion.
  		conversion.for( 'upcast' ).add( upcastTableCell( 'td' ) );
  		conversion.for( 'upcast' ).add( upcastTableCell( 'th' ) );
  
  		conversion.for( 'editingDowncast' ).add( downcastInsertCell( { asWidget: true } ) );
  		conversion.for( 'dataDowncast' ).add( downcastInsertCell() );
  
  		// Table annotation conversion
  		// const annotateTableOptions = editor.config.get( 'annotateTable.options' );
  
  		conversion.for( 'downcast' ).add( dispatcher => {
  			dispatcher.on( 'attribute:tableType', ( evt, data, conversionApi ) => {
  				if ( !data.attributeNewValue ) {
  					return;
  				}
  				if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
  					return;
  				}
  				let viewElement = conversionApi.mapper.toViewElement( data.item );
  				if ( !viewElement ) {
  					return;
  				}
  				if ( viewElement && viewElement.childCount === 2 ) {
  					// figure > selector, table
  					viewElement = viewElement.getChild( 1 );
  				} else if ( viewElement && viewElement.childCount === 1 ) {
  					viewElement = viewElement.getChild( 0 );
  				} else {
  					return;
  				}
  				const viewWriter = conversionApi.writer;
  				viewWriter.removeClass('ck-annotated-table', viewElement );
  				if (data.attributeOldValue) {
  					viewWriter.removeClass( 'ck-annotated-table--' + data.attributeOldValue, viewElement );
  				}
  				viewWriter.addClass( 'ck-annotated-table', viewElement );
  				viewWriter.addClass( 'ck-annotated-table--' + data.attributeNewValue, viewElement );
  			} );
  		} );
  
  		// Table attributes conversion.
  		conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
  		conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
  
  		// Table heading rows and cols conversion.
  		conversion.for( 'editingDowncast' ).add( downcastTableHeadingColumnsChange( { asWidget: true } ) );
  		conversion.for( 'dataDowncast' ).add( downcastTableHeadingColumnsChange() );
  		conversion.for( 'editingDowncast' ).add( downcastTableHeadingRowsChange( { asWidget: true } ) );
  		conversion.for( 'dataDowncast' ).add( downcastTableHeadingRowsChange() );
  
  		injectTableCellPostFixer( editor.model, editor.editing );
  
  		// Define all the commands.
  		editor.commands.add( 'insertTable', new InsertTableCommand( editor ) );
  		editor.commands.add( 'annotateTable', new AnnotateTableCommand( editor ) );
  		editor.commands.add( 'insertTableRowAbove', new InsertRowCommand( editor, { order: 'above' } ) );
  		editor.commands.add( 'insertTableRowBelow', new InsertRowCommand( editor, { order: 'below' } ) );
  		editor.commands.add( 'insertTableColumnLeft', new InsertColumnCommand( editor, { order: 'left' } ) );
  		editor.commands.add( 'insertTableColumnRight', new InsertColumnCommand( editor, { order: 'right' } ) );
  
  		editor.commands.add( 'removeTableRow', new RemoveRowCommand( editor ) );
  		editor.commands.add( 'removeTableColumn', new RemoveColumnCommand( editor ) );
  
  		editor.commands.add( 'splitTableCellVertically', new SplitCellCommand( editor, { direction: 'vertically' } ) );
  		editor.commands.add( 'splitTableCellHorizontally', new SplitCellCommand( editor, { direction: 'horizontally' } ) );
  
  		editor.commands.add( 'mergeTableCellRight', new MergeCellCommand( editor, { direction: 'right' } ) );
  		editor.commands.add( 'mergeTableCellLeft', new MergeCellCommand( editor, { direction: 'left' } ) );
  		editor.commands.add( 'mergeTableCellDown', new MergeCellCommand( editor, { direction: 'down' } ) );
  		editor.commands.add( 'mergeTableCellUp', new MergeCellCommand( editor, { direction: 'up' } ) );
  
  		editor.commands.add( 'setTableColumnHeader', new SetHeaderColumnCommand( editor ) );
  		editor.commands.add( 'setTableRowHeader', new SetHeaderRowCommand( editor ) );
  
  		editor.commands.add( 'splitTableHorizontally', new SplitTableCommand( editor, { direction: 'horizontally' } ) );
  		editor.commands.add( 'splitTableVertically', new SplitTableCommand( editor, { direction: 'vertically' } ) );
  
  		injectTableLayoutPostFixer( model );
  		injectTableCellContentsPostFixer( model );
  
  		// Handle tab key navigation.
  		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' } );
  	}
  
  	/**
  	 * @inheritDoc
  	 */
  	static get requires() {
  		return [ TableUtils ];
  	}
  
  	/**
  	 * Handles {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
  	 * when the table widget is selected.
  	 *
  	 * @private
  	 * @param {module:utils/eventinfo~EventInfo} eventInfo
  	 * @param {module:engine/view/observer/domeventdata~DomEventData} domEventData
  	 */
  	_handleTabOnSelectedTable( domEventData, cancel ) {
  		const editor = this.editor;
  		const selection = editor.model.document.selection;
  
  		if ( !selection.isCollapsed && selection.rangeCount === 1 && selection.getFirstRange().isFlat ) {
  			const selectedElement = selection.getSelectedElement();
  
  			if ( !selectedElement || !selectedElement.is( 'table' ) ) {
  				return;
  			}
  
  			cancel();
  
  			editor.model.change( writer => {
  				writer.setSelection( writer.createRangeIn( selectedElement.getChild( 0 ).getChild( 0 ) ) );
  			} );
  		}
  	}
  
  	/**
  	 * Returns a handler for {@link module:engine/view/document~Document#event:keydown keydown} events for the <kbd>Tab</kbd> key executed
  	 * inside table cell.
  	 *
  	 * @private
  	 * @param {Boolean} isForward Whether this handler will move selection to the next cell or previous.
  	 */
  	_getTabHandler( isForward ) {
  		const editor = this.editor;
  
  		return ( domEventData, cancel ) => {
  			const selection = editor.model.document.selection;
  
  			const firstPosition = selection.getFirstPosition();
  
  			const tableCell = findAncestor( 'tableCell', firstPosition );
  
  			if ( !tableCell ) {
  				return;
  			}
  
  			cancel();
  
  			const tableRow = tableCell.parent;
  			const table = tableRow.parent;
  
  			const currentRowIndex = table.getChildIndex( tableRow );
  			const currentCellIndex = tableRow.getChildIndex( tableCell );
  
  			const isFirstCellInRow = currentCellIndex === 0;
  
  			if ( !isForward && isFirstCellInRow && currentRowIndex === 0 ) {
  				// It's the first cell of a table - don't do anything (stay in current position).
  				return;
  			}
  
  			const isLastCellInRow = currentCellIndex === tableRow.childCount - 1;
  			const isLastRow = currentRowIndex === table.childCount - 1;
  
  			if ( isForward && isLastRow && isLastCellInRow ) {
  				editor.plugins.get( TableUtils ).insertRows( table, { at: table.childCount } );
  			}
  
  			let cellToFocus;
  
  			// Move to first cell in next row.
  			if ( isForward && isLastCellInRow ) {
  				const nextRow = table.getChild( currentRowIndex + 1 );
  
  				cellToFocus = nextRow.getChild( 0 );
  			}
  			// Move to last cell in a previous row.
  			else if ( !isForward && isFirstCellInRow ) {
  				const previousRow = table.getChild( currentRowIndex - 1 );
  
  				cellToFocus = previousRow.getChild( previousRow.childCount - 1 );
  			}
  			// Move to next/previous cell.
  			else {
  				cellToFocus = tableRow.getChild( currentCellIndex + ( isForward ? 1 : -1 ) );
  			}
  
  			editor.model.change( writer => {
  				writer.setSelection( writer.createRangeIn( cellToFocus ) );
  			} );
  		};
  	}
  }
  
  // Converts the options array to a converter definition.
  //
  // An array with configured options.
  // @returns {module:engine/conversion/conversion~ConverterDefinition}
  // function _buildTableAnnotationConversionConfig( options ) {
  // 	const definition = {
  // 		model: {
  // 			key: 'tableType',
  // 			values: []
  // 		},
  // 		view: {}
  // 	};
  
  // 	for ( const option of options ) {
  // 		definition.model.values.push( option.value );
  // 		definition.view[ option.value ] = {
  // 			name: 'table',
  // 			key: 'class',
  // 			value: `ck-annotated-table--${ option.value } ck-annotated-table`
  // 		};
  // 	}
  // 	return definition;
  // }
  
  