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

Added: A view post-fixer for spans in editing view.

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

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

@@ -107,6 +107,8 @@ export default class TableEditing extends Plugin {
 		conversion.for( 'editingDowncast' ).add( downcastTableHeadingRowsChange( { asWidget: true } ) );
 		conversion.for( 'dataDowncast' ).add( downcastTableHeadingRowsChange() );
 
+		injectParagraphInTableCellPostFixer( editor.model, editor.editing );
+
 		// Define all the commands.
 		editor.commands.add( 'insertTable', new InsertTableCommand( editor ) );
 		editor.commands.add( 'insertTableRowAbove', new InsertRowCommand( editor, { order: 'above' } ) );
@@ -251,3 +253,52 @@ export default class TableEditing extends Plugin {
 		} );
 	}
 }
+
+function injectParagraphInTableCellPostFixer( model, editing ) {
+	editing.view.document.registerPostFixer( writer => paragraphInTableCellPostFixer( writer, model, editing.mapper ) );
+}
+
+function paragraphInTableCellPostFixer( writer, model, mapper ) {
+	const changes = model.document.differ.getChanges();
+
+	for ( const entry of changes ) {
+		const tableCell = entry.position.parent;
+
+		if ( tableCell.is( 'tableCell' ) ) {
+			if ( tableCell.childCount > 1 ) {
+				for ( const child of tableCell.getChildren() ) {
+					if ( child.name != 'paragraph' ) {
+						continue;
+					}
+
+					const viewElement = mapper.toViewElement( child );
+
+					if ( viewElement && viewElement.name === 'span' ) {
+						// Unbind table cell as <span> will be renamed to <p>.
+						// mapper.unbindModelElement( tableCell );
+
+						const renamedViewElement = writer.rename( viewElement, 'p' );
+
+						// Re-bind table cell to renamed view element.
+						mapper.bindElements( child, renamedViewElement );
+					}
+				}
+			} else {
+				const singleChild = tableCell.getChild( 0 );
+				if ( !singleChild || !singleChild.is( 'paragraph' ) ) {
+					return;
+				}
+
+				const viewElement = mapper.toViewElement( singleChild );
+
+				// Unbind table cell as <span> will be renamed to <p>.
+				// mapper.unbindModelElement( tableCell );
+
+				const renamedViewElement = writer.rename( viewElement, 'span' );
+
+				// Re-bind table cell to renamed view element.
+				mapper.bindElements( singleChild, renamedViewElement );
+			}
+		}
+	}
+}

+ 22 - 9
packages/ckeditor5-table/tests/_utils/utils.js

@@ -83,22 +83,29 @@ export function modelTable( tableData, attributes ) {
  */
 export function viewTable( tableData, attributes = {} ) {
 	const headingRows = attributes.headingRows || 0;
+	const asWidget = !!attributes.asWidget;
 
 	const thead = headingRows > 0 ? `<thead>${ makeRows( tableData.slice( 0, headingRows ), {
 		cellElement: 'th',
 		rowElement: 'tr',
 		headingElement: 'th',
-		wrappingElement: 'p'
+		wrappingElement: 'p',
+		asWidget
 	} ) }</thead>` : '';
+
 	const tbody = tableData.length > headingRows ?
 		`<tbody>${ makeRows( tableData.slice( headingRows ), {
 			cellElement: 'td',
 			rowElement: 'tr',
 			headingElement: 'th',
-			wrappingElement: 'p'
+			wrappingElement: 'p',
+			asWidget
 		} ) }</tbody>` : '';
 
-	return `<figure class="table"><table>${ thead }${ tbody }</table></figure>`;
+	const figureAttributes = asWidget ? 'class="ck-widget ck-widget_selectable table" contenteditable="false"' : 'class="table"';
+	const widgetHandler = '<div class="ck ck-widget__selection-handler"></div>';
+
+	return `<figure ${ figureAttributes }>${ asWidget ? widgetHandler : '' }<table>${ thead }${ tbody }</table></figure>`;
 }
 
 /**
@@ -202,25 +209,24 @@ function formatAttributes( attributes ) {
 			attributesString = ' ' + entries.map( entry => `${ entry[ 0 ] }="${ entry[ 1 ] }"` ).join( ' ' );
 		}
 	}
+
 	return attributesString;
 }
 
 // Formats passed table data to a set of table rows.
 function makeRows( tableData, options ) {
-	const { cellElement, rowElement, headingElement, wrappingElement, enforceWrapping } = options;
+	const { cellElement, rowElement, headingElement, wrappingElement, enforceWrapping, asWidget } = options;
 
 	return tableData
 		.reduce( ( previousRowsString, tableRow ) => {
 			const tableRowString = tableRow.reduce( ( tableRowString, tableCellData ) => {
-				let contents = tableCellData;
-
 				const isObject = typeof tableCellData === 'object';
 
+				let contents = isObject ? tableCellData.contents : tableCellData;
+
 				let resultingCellElement = cellElement;
 
 				if ( isObject ) {
-					contents = tableCellData.contents;
-
 					// TODO: check...
 					if ( tableCellData.isHeading ) {
 						resultingCellElement = headingElement;
@@ -230,11 +236,18 @@ function makeRows( tableData, options ) {
 					delete tableCellData.isHeading;
 				}
 
+				const attributes = isObject ? tableCellData : {};
+
+				if ( asWidget ) {
+					attributes.class = 'ck-editor__editable ck-editor__nested-editable';
+					attributes.contenteditable = 'true';
+				}
+
 				if ( !( contents.replace( '[', '' ).replace( ']', '' ).startsWith( '<' ) ) && enforceWrapping ) {
 					contents = `<${ wrappingElement }>${ contents }</${ wrappingElement }>`;
 				}
 
-				const formattedAttributes = formatAttributes( isObject ? tableCellData : '' );
+				const formattedAttributes = formatAttributes( attributes );
 				tableRowString += `<${ resultingCellElement }${ formattedAttributes }>${ contents }</${ resultingCellElement }>`;
 
 				return tableRowString;

+ 62 - 0
packages/ckeditor5-table/tests/converters/downcast.js

@@ -11,6 +11,28 @@ import { defaultConversion, defaultSchema, formatTable, formattedViewTable, mode
 import env from '@ckeditor/ckeditor5-utils/src/env';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 
+function paragraphInTableCell() {
+	return dispatcher => dispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
+		const tableCell = data.item.parent;
+
+		if ( tableCell.is( 'tableCell' ) && tableCell.childCount > 1 ) {
+			for ( const child of tableCell.getChildren() ) {
+				if ( child.name != 'paragraph' ) {
+					continue;
+				}
+
+				const viewElement = conversionApi.mapper.toViewElement( child );
+
+				if ( viewElement && viewElement.name === 'span' ) {
+					conversionApi.mapper.unbindModelElement( tableCell );
+					conversionApi.writer.rename( viewElement, 'p' );
+					conversionApi.mapper.bindElements( child, viewElement );
+				}
+			}
+		}
+	}, { converterPriority: 'highest' } );
+}
+
 describe( 'downcast converters', () => {
 	let editor, model, doc, root, viewDocument;
 
@@ -1107,4 +1129,44 @@ describe( 'downcast converters', () => {
 			], { headingRows: 1 } ) );
 		} );
 	} );
+
+	describe( 'options.asWidget=true', () => {
+		beforeEach( () => {
+			return VirtualTestEditor.create()
+				.then( newEditor => {
+					editor = newEditor;
+					model = editor.model;
+					doc = model.document;
+					root = doc.getRoot( 'main' );
+					viewDocument = editor.editing.view;
+
+					defaultSchema( model.schema );
+					defaultConversion( editor.conversion, true );
+
+					editor.conversion.for( 'downcast' ).add( paragraphInTableCell() );
+				} );
+		} );
+
+		it( 'should rename <span> to <p> when more then one block content inside table cell', () => {
+			setModelData( model, modelTable( [
+				[ '00[]' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const nodeByPath = table.getNodeByPath( [ 0, 0, 0 ] );
+
+				const paragraph = writer.createElement( 'paragraph' );
+
+				writer.insert( paragraph, nodeByPath, 'after' );
+
+				writer.setSelection( nodeByPath.nextSibling, 0 );
+			} );
+
+			expect( formatTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
+				[ '<p>00</p><p></p>' ]
+			], { asWidget: true } ) );
+		} );
+	} );
 } );