8
0
Просмотр исходного кода

Other: Initial 'insert:tableRow' downcast converter implementation.

Maciej Gołaszewski 7 лет назад
Родитель
Сommit
7c9bf200fb

+ 68 - 5
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -30,13 +30,14 @@ export default function downcastTable() {
 		const tableElement = conversionApi.writer.createContainerElement( 'table' );
 		const headingRows = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
 		const tableRows = Array.from( table.getChildren() );
-		const cellSpans = new CellSpans();
+
+		const cellSpans = ensureCellSpans( table, 0, conversionApi );
 
 		for ( const tableRow of tableRows ) {
 			const rowIndex = tableRows.indexOf( tableRow );
 			const tableSectionElement = getTableSection( rowIndex, headingRows, tableElement, conversionApi );
 
-			downcastTableRow( tableRow, rowIndex, cellSpans, tableSectionElement, conversionApi );
+			downcastTableRow( tableRow, rowIndex, tableSectionElement, conversionApi );
 
 			// Drop table cell spans information for downcasted row.
 			cellSpans.drop( rowIndex );
@@ -66,6 +67,64 @@ export default function downcastTable() {
 	}, { priority: 'normal' } );
 }
 
+export function downcastInsertRow() {
+	return dispatcher => dispatcher.on( 'insert:tableRow', ( evt, data, conversionApi ) => {
+		const tableRow = data.item;
+
+		if ( !conversionApi.consumable.consume( tableRow, 'insert' ) ) {
+			return;
+		}
+
+		const table = tableRow.parent;
+
+		const tableElement = conversionApi.mapper.toViewElement( table );
+
+		const headingRows = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
+
+		const rowIndex = table.getChildIndex( tableRow );
+		const isHeadingRow = rowIndex < headingRows;
+
+		const tableSection = Array.from( tableElement.getChildren() )
+			.filter( child => child.name === isHeadingRow ? 'thead' : 'tbody' )[ 0 ];
+
+		ensureCellSpans( table, rowIndex, conversionApi );
+
+		downcastTableRow( tableRow, rowIndex, tableSection, conversionApi );
+	}, { priority: 'normal' } );
+}
+
+function ensureCellSpans( table, currentRowIndex, conversionApi ) {
+	if ( !conversionApi.store ) {
+		conversionApi.store = {};
+	}
+
+	if ( !conversionApi.store.cellSpans ) {
+		const cellSpans = new CellSpans();
+
+		conversionApi.store.cellSpans = cellSpans;
+
+		for ( let rowIndex = 0; rowIndex < currentRowIndex; rowIndex++ ) {
+			const row = table.getChild( rowIndex );
+
+			let columnIndex = 0;
+
+			for ( const tableCell of Array.from( row.getChildren() ) ) {
+				columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
+
+				const colspan = tableCell.hasAttribute( 'colspan' ) ? parseInt( tableCell.getAttribute( 'colspan' ) ) : 1;
+				const rowspan = tableCell.hasAttribute( 'rowspan' ) ? parseInt( tableCell.getAttribute( 'rowspan' ) ) : 1;
+
+				cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
+
+				// Skip to next "free" column index.
+				columnIndex += colspan;
+			}
+		}
+	}
+
+	return conversionApi.store.cellSpans;
+}
+
 // Downcast converter for tableRow model element. Converts tableCells as well.
 //
 // @param {module:engine/model/element~Element} tableRow
@@ -73,13 +132,17 @@ export default function downcastTable() {
 // @param {CellSpans} cellSpans
 // @param {module:engine/view/containerelement~ContainerElement} tableSection
 // @param {Object} conversionApi
-function downcastTableRow( tableRow, rowIndex, cellSpans, tableSection, conversionApi ) {
+function downcastTableRow( tableRow, rowIndex, tableSection, conversionApi ) {
 	// Will always consume since we're converting <tableRow> element from a parent <table>.
 	conversionApi.consumable.consume( tableRow, 'insert' );
-	const trElement = conversionApi.writer.createContainerElement( 'tr' );
 
+	const trElement = conversionApi.writer.createContainerElement( 'tr' );
 	conversionApi.mapper.bindElements( tableRow, trElement );
-	conversionApi.writer.insert( ViewPosition.createAt( tableSection, 'end' ), trElement );
+
+	const position = ViewPosition.createAt( tableSection, 'end' );
+	conversionApi.writer.insert( position, trElement );
+
+	const cellSpans = conversionApi.store.cellSpans;
 
 	// Defines tableCell horizontal position in table.
 	// Might be different then position of tableCell in parent tableRow

+ 1 - 1
packages/ckeditor5-table/src/insertcolumncommand.js

@@ -9,7 +9,7 @@
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import { CellSpans } from './converters/downcasttable';
-import Position from '../../ckeditor5-engine/src/model/position';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
 
 /**
  * The insert column command.

+ 3 - 4
packages/ckeditor5-table/src/tableediting.js

@@ -9,9 +9,8 @@
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
-import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
 import upcastTable from './converters/upcasttable';
-import downcastTable from './converters/downcasttable';
+import downcastTable, { downcastInsertRow } from './converters/downcasttable';
 import InsertTableCommand from './inserttablecommand';
 import InsertRowCommand from './insertrowcommand';
 import InsertColumnCommand from './insertcolumncommand';
@@ -56,8 +55,8 @@ export default class TablesEditing extends Plugin {
 		conversion.for( 'upcast' ).add( upcastTable() );
 		conversion.for( 'downcast' ).add( downcastTable() );
 
-		conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'tableRow', view: 'tr' } ) );
-		conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'tableCell', view: 'td' } ) );
+		// Insert conversion
+		conversion.for( 'downcast' ).add( downcastInsertRow() );
 
 		// Table cell conversion.
 		conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableCell', view: 'td' } ) );

+ 29 - 10
packages/ckeditor5-table/tests/_utils/utils.js

@@ -14,14 +14,7 @@ function formatAttributes( attributes ) {
 	return attributesString;
 }
 
-/**
- * @param {Number} columns
- * @param {Array.<String>} tableData
- * @param {Object} [attributes]
- *
- * @returns {String}
- */
-export function modelTable( tableData, attributes ) {
+function makeRows( tableData, cellElement, rowElement ) {
 	const tableRows = tableData
 		.reduce( ( previousRowsString, tableRow ) => {
 			const tableRowString = tableRow.reduce( ( tableRowString, tableCellData ) => {
@@ -34,17 +27,43 @@ export function modelTable( tableData, attributes ) {
 					delete tableCellData.contents;
 				}
 
-				tableRowString += `<tableCell${ formatAttributes( isObject ? tableCellData : '' ) }>${ tableCell }</tableCell>`;
+				const formattedAttributes = formatAttributes( isObject ? tableCellData : '' );
+				tableRowString += `<${ cellElement }${ formattedAttributes }>${ tableCell }</${ cellElement }>`;
 
 				return tableRowString;
 			}, '' );
 
-			return `${ previousRowsString }<tableRow>${ tableRowString }</tableRow>`;
+			return `${ previousRowsString }<${ rowElement }>${ tableRowString }</${ rowElement }>`;
 		}, '' );
+	return tableRows;
+}
+
+/**
+ * @param {Number} columns
+ * @param {Array.<String>} tableData
+ * @param {Object} [attributes]
+ *
+ * @returns {String}
+ */
+export function modelTable( tableData, attributes ) {
+	const tableRows = makeRows( tableData, 'tableCell', 'tableRow' );
 
 	return `<table${ formatAttributes( attributes ) }>${ tableRows }</table>`;
 }
 
+/**
+ * @param {Number} columns
+ * @param {Array.<String>} tableData
+ * @param {Object} [attributes]
+ *
+ * @returns {String}
+ */
+export function viewTable( tableData, attributes ) {
+	const tableRows = makeRows( tableData, 'td', 'tr' );
+
+	return `<table${ formatAttributes( attributes ) }><tbody>${ tableRows }</tbody></table>`;
+}
+
 export function formatModelTable( tableString ) {
 	return tableString
 		.replace( /<tableRow>/g, '\n<tableRow>\n    ' )

+ 65 - 5
packages/ckeditor5-table/tests/converters/downcasttable.js

@@ -5,18 +5,21 @@
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
-
 import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
-import downcastTable from '../../src/converters/downcasttable';
+
+import downcastTable, { downcastInsertRow } from '../../src/converters/downcasttable';
+import { modelTable, viewTable } from '../_utils/utils';
 
 describe( 'downcastTable()', () => {
-	let editor, model, viewDocument;
+	let editor, model, doc, root, viewDocument;
 
 	beforeEach( () => {
 		return VirtualTestEditor.create()
 			.then( newEditor => {
 				editor = newEditor;
 				model = editor.model;
+				doc = model.document;
+				root = doc.getRoot( 'main' );
 				viewDocument = editor.editing.view;
 
 				const conversion = editor.conversion;
@@ -47,6 +50,9 @@ describe( 'downcastTable()', () => {
 				conversion.for( 'downcast' ).add( downcastTable() );
 				conversion.attributeToAttribute( { model: 'colspan', view: 'colspan' } );
 				conversion.attributeToAttribute( { model: 'rowspan', view: 'rowspan' } );
+
+				// Insert conversion
+				conversion.for( 'downcast' ).add( downcastInsertRow() );
 			} );
 	} );
 
@@ -129,8 +135,8 @@ describe( 'downcastTable()', () => {
 	} );
 
 	it( 'should be possible to overwrite', () => {
-		editor.conversion.elementToElement( { model: 'tableRow', view: 'tr' } );
-		editor.conversion.elementToElement( { model: 'tableCell', view: 'td' } );
+		editor.conversion.elementToElement( { model: 'tableRow', view: 'tr', priority: 'high' } );
+		editor.conversion.elementToElement( { model: 'tableCell', view: 'td', priority: 'high' } );
 		editor.conversion.for( 'downcast' ).add( dispatcher => {
 			dispatcher.on( 'insert:table', ( evt, data, conversionApi ) => {
 				conversionApi.consumable.consume( data.item, 'insert' );
@@ -236,4 +242,58 @@ describe( 'downcastTable()', () => {
 			);
 		} );
 	} );
+
+	describe( 'model change', () => {
+		it( 'should react to changed rows', () => {
+			setModelData( model, modelTable( [
+				[ '11', '12' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = writer.createElement( 'tableRow' );
+
+				writer.insert( row, table, 1 );
+
+				for ( let i = 0; i < 2; i++ ) {
+					const cell = writer.createElement( 'tableCell' );
+
+					writer.insert( cell, row, 'end' );
+				}
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ '11', '12' ],
+				[ '', '' ]
+			] ) );
+		} );
+
+		it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
+			setModelData( model, modelTable( [
+				[ { rowspan: 3, contents: '11' }, '12' ],
+				[ '22' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = writer.createElement( 'tableRow' );
+
+				writer.insert( row, table, 2 );
+
+				for ( let i = 0; i < 1; i++ ) {
+					const cell = writer.createElement( 'tableCell' );
+
+					writer.insert( cell, row, 'end' );
+				}
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ { rowspan: 3, contents: '11' }, '12' ],
+				[ '22' ],
+				[ '' ]
+			] ) );
+		} );
+	} );
 } );

+ 1 - 1
packages/ckeditor5-table/tests/converters/upcasttable.js

@@ -5,8 +5,8 @@
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
-
 import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
 import upcastTable from '../../src/converters/upcasttable';
 
 describe( 'upcastTable()', () => {