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

Revert article manual tests and create slot conversion manual test in the engine package.

Maciej Gołaszewski 5 лет назад
Родитель
Сommit
1c2487466b

+ 63 - 0
packages/ckeditor5-engine/tests/manual/slotconversion.html

@@ -0,0 +1,63 @@
+<head>
+	<style>
+		body {
+			max-width: 800px;
+			margin: 20px auto;
+		}
+
+		.box {
+			border: 1px solid hsl(0, 0%, 20%);
+			padding: 2px;
+			background: hsl(0, 0%, 40%);
+		}
+
+		.box-meta {
+			border: 1px solid hsl(0, 0%, 80%);
+			background: hsl(0, 0%, 60%);
+		}
+
+		.box-content-field {
+			padding: .5em;
+			background: hsl(0, 0%, 100%);
+			border: 1px solid hsl(0, 0%, 80%)
+		}
+
+		.ck.ck-content [data-insert-count]:after {
+			content: attr(data-insert-count);
+			position: relative;
+			font-size: 10px;
+			top: -5px;
+			right: 0;
+			float: right;
+			border: 1px solid hsl(219, 86%, 31%);
+			background: hsl(219, 100%, 91%);
+			padding: 0 2px;
+			opacity: 0.8;
+		}
+	</style>
+</head>
+
+<div id="editor">
+	<div class="box">
+		<div class="box__wrapper">
+			<div class="box-meta box-meta-header">
+				<div class="box-meta-header-icon"></div>
+				<div class="box-meta-header-title">
+					<h2>I'm a title</h2>
+				</div>
+			</div>
+			<div class="box-content">
+				<div class="box-content-field"><p>Foo</p></div>
+				<div class="box-content-field">
+					<ul>
+						<li>Bar</li>
+					</ul>
+				</div>
+				<div class="box-content-field"><p>Baz</p></div>
+			</div>
+			<div class="box-meta box-meta-author">
+				<a href="www.example.com">@john</a>
+			</div>
+		</div>
+	</div>
+</div>

+ 292 - 0
packages/ckeditor5-engine/tests/manual/slotconversion.js

@@ -0,0 +1,292 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+const byClassName = className => element => element.hasClass( className );
+
+const getRandom = () => parseInt( Math.random() * 1000 );
+
+function mapMeta( editor ) {
+	return metaElement => {
+		if ( metaElement.hasClass( 'box-meta-header' ) ) {
+			const title = getChildren( editor, metaElement )
+				.filter( byClassName( 'box-meta-header-title' ) )
+				.pop().getChild( 0 ).getChild( 0 ).data;
+
+			return {
+				header: {
+					title
+				}
+			};
+		}
+
+		if ( metaElement.hasClass( 'box-meta-author' ) ) {
+			const link = metaElement.getChild( 0 );
+
+			return {
+				author: {
+					name: link.getChild( 0 ).data,
+					website: link.getAttribute( 'href' )
+				}
+			};
+		}
+	};
+}
+
+function getChildren( editor, viewElement ) {
+	return [ ...( editor.editing.view.createRangeIn( viewElement ) ) ]
+		.filter( ( { type } ) => type === 'elementStart' )
+		.map( ( { item } ) => item );
+}
+
+function getBoxUpcastConverter( editor ) {
+	return dispatcher => dispatcher.on( 'element:div', ( event, data, conversionApi ) => {
+		const viewElement = data.viewItem;
+		const writer = conversionApi.writer;
+
+		if ( !viewElement.hasClass( 'box' ) ) {
+			return;
+		}
+
+		const box = writer.createElement( 'box' );
+
+		if ( !conversionApi.safeInsert( box, data.modelCursor ) ) {
+			return;
+		}
+
+		const elements = getChildren( editor, viewElement );
+
+		const fields = elements.filter( byClassName( 'box-content-field' ) );
+		const metaElements = elements.filter( byClassName( 'box-meta' ) );
+
+		const meta = metaElements.map( mapMeta( editor ) ).reduce( ( prev, current ) => Object.assign( prev, current ), {} );
+
+		writer.setAttribute( 'meta', meta, box );
+
+		for ( const field of fields ) {
+			const boxField = writer.createElement( 'boxField' );
+
+			conversionApi.safeInsert( boxField, writer.createPositionAt( box, field.index ) );
+			conversionApi.convertChildren( field, boxField );
+		}
+
+		conversionApi.consumable.consume( viewElement, { name: true } );
+		elements.map( element => {
+			conversionApi.consumable.consume( element, { name: true } );
+		} );
+
+		conversionApi.updateConversionResult( box, data );
+	} );
+}
+
+function downcastBox( modelElement, conversionApi ) {
+	const { writer } = conversionApi;
+
+	const viewBox = writer.createContainerElement( 'div', { class: 'box' } );
+	conversionApi.mapper.bindElements( modelElement, viewBox );
+
+	const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } );
+	writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap );
+
+	for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) {
+		if ( meta === 'header' ) {
+			const header = writer.createRawElement( 'div', {
+				class: 'box-meta box-meta-header'
+			}, domElement => {
+				domElement.innerHTML = `<div class="box-meta-header-title"><h2>${ metaValue.title }</h2></div>`;
+			} );
+
+			writer.insert( writer.createPositionBefore( contentWrap ), header );
+		}
+
+		if ( meta === 'author' ) {
+			const author = writer.createRawElement( 'div', {
+				class: 'box-meta box-meta-author'
+			}, domElement => {
+				domElement.innerHTML = `<a href="${ metaValue.website }">${ metaValue.name }</a>`;
+			} );
+
+			writer.insert( writer.createPositionAfter( contentWrap ), author );
+		}
+	}
+
+	for ( const field of modelElement.getChildren() ) {
+		const viewField = writer.createContainerElement( 'div', { class: 'box-content-field' } );
+
+		writer.insert( writer.createPositionAt( contentWrap, field.index ), viewField );
+		conversionApi.mapper.bindSlotElements( field, viewField );
+
+		// Might be simplified to:
+		//
+		// writer.defineSlot( field, viewField, field.index );
+		//
+		// but would require a converter:
+		//
+		// editor.conversion.for( 'downcast' ).elementToElement( {	// .slotToElement()?
+		// 		model: 'viewField',
+		// 		view: { name: 'div', class: 'box-content-field' }
+		// 	} );
+	}
+
+	// At this point we're inserting whole "component". Equivalent to (JSX-like notation):
+	//
+	//	"rendered" view																					Mapping/source
+	//
+	//	<div:container class="box">												<-- top-level			box
+	//		<div:raw class="box-meta box-meta-header">...</div:raw>										box[meta.header]
+	//		<div:container class="box-content">
+	//			<div:container class="box-content-field">...</div:container>	<-- this is "slot"		boxField
+	//			... many
+	//			<div:container class="box-content-field">...</div:container>	<-- this is "slot"		boxField
+	//		</div:container>
+	//		<div:raw class="box-meta box-meta-author">...</div:raw>										box[meta.author]
+	//	</div:container>
+
+	return viewBox;
+}
+
+function addButton( editor, uiName, label, callback ) {
+	editor.ui.componentFactory.add( uiName, locale => {
+		const view = new ButtonView( locale );
+
+		view.set( { label, withText: true } );
+
+		view.listenTo( view, 'execute', () => {
+			const parent = editor.model.document.selection.getFirstPosition().parent;
+			const boxField = parent.findAncestor( 'boxField' );
+
+			if ( !boxField ) {
+				return;
+			}
+
+			editor.model.change( writer => callback( writer, boxField.findAncestor( 'box' ), boxField ) );
+		} );
+
+		return view;
+	} );
+}
+
+function addBoxMetaButton( editor, uiName, label, updateWith ) {
+	addButton( editor, uiName, label, ( writer, box ) => {
+		writer.setAttribute( 'meta', {
+			...box.getAttribute( 'meta' ),
+			...updateWith()
+		}, box );
+	} );
+}
+
+function Box( editor ) {
+	editor.model.schema.register( 'box', {
+		allowIn: '$root',
+		isObject: true,
+		isSelectable: true,
+		allowAttributes: [ 'infoBoxMeta' ]
+	} );
+
+	editor.model.schema.register( 'boxField', {
+		allowContentOf: '$root',
+		allowIn: 'box',
+		isLimit: true
+	} );
+
+	editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) );
+
+	editor.conversion.for( 'downcast' ).elementToElement( {
+		model: 'box',
+		view: downcastBox,
+		triggerBy: [
+			'attribute:meta:box',
+			'insert:boxField',
+			'remove:boxField'
+		]
+	} );
+
+	addBoxMetaButton( editor, 'boxTitle', 'Box title', () => ( {
+		header: { title: `Random title no. ${ getRandom() }.` }
+	} ) );
+
+	addBoxMetaButton( editor, 'boxAuthor', 'Box author', () => ( {
+		author: {
+			website: `www.example.com/${ getRandom() }`,
+			name: `Random author no. ${ getRandom() }`
+		}
+	} ) );
+
+	addButton( editor, 'addBoxField', '+', ( writer, box, boxField ) => {
+		const newBoxField = writer.createElement( 'boxField' );
+		writer.insert( newBoxField, box, boxField.index );
+		writer.insert( writer.createElement( 'paragraph' ), newBoxField, 0 );
+	} );
+
+	addButton( editor, 'removeBoxField', '-', ( writer, box, boxField ) => {
+		writer.remove( boxField );
+	} );
+}
+
+function AddRenderCount( editor ) {
+	let insertCount = 0;
+
+	const nextInsert = () => insertCount++;
+
+	editor.conversion.for( 'downcast' ).add( dispatcher => dispatcher.on( 'insert', ( event, data, conversionApi ) => {
+		const view = conversionApi.mapper.toViewElement( data.item );
+
+		if ( view ) {
+			const insertCount = nextInsert();
+
+			conversionApi.writer.setAttribute( 'data-insert-count', `${ insertCount }`, view );
+			conversionApi.writer.setAttribute( 'title', `Insertion counter: ${ insertCount }`, view );
+		}
+	}, { priority: 'lowest' } ) );
+}
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ ArticlePluginSet, Box, AddRenderCount ],
+		toolbar: [
+			'heading',
+			'|',
+			'boxTitle',
+			'boxAuthor',
+			'addBoxField',
+			'removeBoxField',
+			'|',
+			'bold',
+			'italic',
+			'link',
+			'bulletedList',
+			'numberedList',
+			'|',
+			'outdent',
+			'indent',
+			'|',
+			'blockQuote',
+			'insertTable',
+			'mediaEmbed',
+			'undo',
+			'redo'
+		],
+		image: {
+			toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
+		},
+		table: {
+			contentToolbar: [
+				'tableColumn',
+				'tableRow',
+				'mergeTableCells'
+			]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 12 - 0
packages/ckeditor5-engine/tests/manual/slotconversion.md

@@ -0,0 +1,12 @@
+# Slot conversion
+
+The editor should be loaded with a "box" element that contains multiple "slots" in which user can edit content.
+
+An additional converter adds `"data-insert-count"` attribute to view elements to show when it was rendered. It is displayed with a CSS at the top-right corner of rendered element. If a view element was not re-rendered this attribute should not change. *Note*: it only acts on "insert" changes so it can omit attribute-to-element changes or insertions not passed through dispatcher.
+
+Observe which view elements are re-rendered when using UI-buttons:
+
+* `Box title` - updates title attribute which triggers re-rendering of a "box".
+* `Box title` - updates author attribute which triggers re-rendering of a "box".
+* `+` - adds "slot" to box"  which triggers re-rendering of a "box".
+* `-` - removes "slot" from box" which triggers re-rendering of a "box".

+ 21 - 284
tests/manual/article.html

@@ -4,293 +4,30 @@
 			max-width: 800px;
 			margin: 20px auto;
 		}
-
-		.box {
-			border: 1px solid hsl(0, 0%, 20%);
-			padding: 2px;
-			background: hsl(0, 0%, 40%);
-		}
-
-		.box-meta {
-			border: 1px solid hsl(0, 0%, 80%);
-			background: hsl(0, 0%, 60%);
-		}
-
-		.box-content-field {
-			padding: .5em;
-			background: hsl(0, 0%, 100%);
-			border: 1px solid hsl(0, 0%, 80%)
-		}
 	</style>
 </head>
-
 <div id="editor">
-	<div class="box">
-		<div class="box__wrapper">
-			<div class="box-meta box-meta-header">
-				<div class="box-meta-header-icon"></div>
-				<div class="box-meta-header-title">
-					<h2>I'm a title</h2>
-				</div>
-			</div>
-			<div class="box-content">
-				<div class="box-content-field"><p>Foo</p></div>
-				<div class="box-content-field">
-					<ul>
-						<li>Bar</li>
-					</ul>
-				</div>
-				<div class="box-content-field"><p>Baz</p></div>
-			</div>
-			<div class="box-meta box-meta-author">
-				<a href="www.example.com">@john</a>
-			</div>
-		</div>
-	</div>
-	<figure class="table">
-		<table>
-			<thead>
-				<tr>
-					<th colspan="40"><p>foo bar baz</p></th>
-				</tr>
-				<tr>
-					<th colspan="40"><p>foo bar baz</p></th>
-				</tr>
-				<tr>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-				</tr>
-				<tr>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-					<th><p>foo bar baz</p></th>
-				</tr>
-			</thead>
-			<tbody>
-				<tr>
-					<td colspan="40"><p>foo bar baz</p></td>
-				</tr>
-				<tr>
-					<td colspan="40"><p>foo bar baz</p></td>
-				</tr>
-				<tr>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-				</tr>
-				<tr>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-				</tr>
-				<tr>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-					<td><p>foo bar baz</p></td>
-				</tr>
-			</tbody>
-		</table>
+	<h2>Heading 1</h2>
+	<p>Paragraph</p>
+	<p><strong>Bold</strong> <i>Italic</i> <a href="https://ckeditor.com">Link</a></p>
+	<ul>
+		<li>UL List item 1</li>
+		<li>UL List item 2</li>
+	</ul>
+	<ol>
+		<li>OL List item 1</li>
+		<li>OL List item 2</li>
+	</ol>
+	<figure class="image image-style-side">
+		<img alt="bar" src="sample.jpg">
+		<figcaption>Caption</figcaption>
 	</figure>
-</div>
-
-
-<!--
-
-<box type="foo" icon="icon.png" author="@john" authorWebsite="www.example.com">
-	<boxField>
-		<p>Foo bar</p>
+	<blockquote>
+		<p>Quote</p>
 		<ul>
-			<li>baz</li>
+			<li>Quoted UL List item 1</li>
+			<li>Quoted UL List item 2</li>
 		</ul>
-	</boxField>
-	...
-	<boxField></boxField>
-</box>
-
--->
+		<p>Quote</p>
+	</blockquote>
+</div>

+ 2 - 228
tests/manual/article.js

@@ -6,241 +6,15 @@
 /* globals console, window, document */
 
 import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
-import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-
-const byClassName = className => element => element.hasClass( className );
-
-const getRandom = () => parseInt( Math.random() * 1000 );
-
-function mapMeta( editor ) {
-	return metaElement => {
-		if ( metaElement.hasClass( 'box-meta-header' ) ) {
-			const title = getChildren( editor, metaElement )
-				.filter( byClassName( 'box-meta-header-title' ) )
-				.pop().getChild( 0 ).getChild( 0 ).data;
-
-			return {
-				header: {
-					title
-				}
-			};
-		}
-
-		if ( metaElement.hasClass( 'box-meta-author' ) ) {
-			const link = metaElement.getChild( 0 );
-
-			return {
-				author: {
-					name: link.getChild( 0 ).data,
-					website: link.getAttribute( 'href' )
-				}
-			};
-		}
-	};
-}
-
-function getChildren( editor, viewElement ) {
-	return [ ...( editor.editing.view.createRangeIn( viewElement ) ) ]
-		.filter( ( { type } ) => type === 'elementStart' )
-		.map( ( { item } ) => item );
-}
-
-function getBoxUpcastConverter( editor ) {
-	return dispatcher => dispatcher.on( 'element:div', ( event, data, conversionApi ) => {
-		const viewElement = data.viewItem;
-		const writer = conversionApi.writer;
-
-		if ( !viewElement.hasClass( 'box' ) ) {
-			return;
-		}
-
-		const box = writer.createElement( 'box' );
-
-		if ( !conversionApi.safeInsert( box, data.modelCursor ) ) {
-			return;
-		}
-
-		const elements = getChildren( editor, viewElement );
-
-		const fields = elements.filter( byClassName( 'box-content-field' ) );
-		const metaElements = elements.filter( byClassName( 'box-meta' ) );
-
-		const meta = metaElements.map( mapMeta( editor ) ).reduce( ( prev, current ) => Object.assign( prev, current ), {} );
-
-		writer.setAttribute( 'meta', meta, box );
-
-		for ( const field of fields ) {
-			const boxField = writer.createElement( 'boxField' );
-
-			conversionApi.safeInsert( boxField, writer.createPositionAt( box, field.index ) );
-			conversionApi.convertChildren( field, boxField );
-		}
-
-		conversionApi.consumable.consume( viewElement, { name: true } );
-		elements.map( element => {
-			conversionApi.consumable.consume( element, { name: true } );
-		} );
-
-		conversionApi.updateConversionResult( box, data );
-	} );
-}
-
-function downcastBox( modelElement, conversionApi ) {
-	const { writer } = conversionApi;
-
-	const viewBox = writer.createContainerElement( 'div', { class: 'box' } );
-	conversionApi.mapper.bindElements( modelElement, viewBox );
-
-	const contentWrap = writer.createContainerElement( 'div', { class: 'box-content' } );
-	writer.insert( writer.createPositionAt( viewBox, 0 ), contentWrap );
-
-	for ( const [ meta, metaValue ] of Object.entries( modelElement.getAttribute( 'meta' ) ) ) {
-		if ( meta === 'header' ) {
-			const header = writer.createRawElement( 'div', {
-				class: 'box-meta box-meta-header'
-			}, domElement => {
-				domElement.innerHTML = `<div class="box-meta-header-title"><h2>${ metaValue.title }</h2></div>`;
-			} );
-
-			writer.insert( writer.createPositionBefore( contentWrap ), header );
-		}
 
-		if ( meta === 'author' ) {
-			const author = writer.createRawElement( 'div', {
-				class: 'box-meta box-meta-author'
-			}, domElement => {
-				domElement.innerHTML = `<a href="${ metaValue.website }">${ metaValue.name }</a>`;
-			} );
-
-			writer.insert( writer.createPositionAfter( contentWrap ), author );
-		}
-	}
-
-	for ( const field of modelElement.getChildren() ) {
-		const viewField = writer.createContainerElement( 'div', { class: 'box-content-field' } );
-
-		writer.insert( writer.createPositionAt( contentWrap, field.index ), viewField );
-		conversionApi.mapper.bindSlotElements( field, viewField );
-
-		// Might be simplified to:
-		//
-		// writer.defineSlot( field, viewField, field.index );
-		//
-		// but would require a converter:
-		//
-		// editor.conversion.for( 'downcast' ).elementToElement( {	// .slotToElement()?
-		// 		model: 'viewField',
-		// 		view: { name: 'div', class: 'box-content-field' }
-		// 	} );
-	}
-
-	// At this point we're inserting whole "component". Equivalent to (JSX-like notation):
-	//
-	//	"rendered" view																					Mapping/source
-	//
-	//	<div:container class="box">												<-- top-level			box
-	//		<div:raw class="box-meta box-meta-header">...</div:raw>										box[meta.header]
-	//		<div:container class="box-content">
-	//			<div:container class="box-content-field">...</div:container>	<-- this is "slot"		boxField
-	//			... many
-	//			<div:container class="box-content-field">...</div:container>	<-- this is "slot"		boxField
-	//		</div:container>
-	//		<div:raw class="box-meta box-meta-author">...</div:raw>										box[meta.author]
-	//	</div:container>
-
-	return viewBox;
-}
-
-function addButton( editor, uiName, label, callback ) {
-	editor.ui.componentFactory.add( uiName, locale => {
-		const view = new ButtonView( locale );
-
-		view.set( { label, withText: true } );
-
-		view.listenTo( view, 'execute', () => {
-			const parent = editor.model.document.selection.getFirstPosition().parent;
-			const boxField = parent.findAncestor( 'boxField' );
-
-			if ( !boxField ) {
-				return;
-			}
-
-			editor.model.change( writer => callback( writer, boxField.findAncestor( 'box' ), boxField ) );
-		} );
-
-		return view;
-	} );
-}
-
-function addBoxMetaButton( editor, uiName, label, updateWith ) {
-	addButton( editor, uiName, label, ( writer, box ) => {
-		writer.setAttribute( 'meta', {
-			...box.getAttribute( 'meta' ),
-			...updateWith()
-		}, box );
-	} );
-}
-
-function Box( editor ) {
-	editor.model.schema.register( 'box', {
-		allowIn: '$root',
-		isObject: true,
-		isSelectable: true,
-		allowAttributes: [ 'infoBoxMeta' ]
-	} );
-
-	editor.model.schema.register( 'boxField', {
-		allowContentOf: '$root',
-		allowIn: 'box',
-		isLimit: true
-	} );
-
-	editor.conversion.for( 'upcast' ).add( getBoxUpcastConverter( editor ) );
-
-	editor.conversion.for( 'downcast' ).elementToElement( {
-		model: 'box',
-		view: downcastBox,
-		triggerBy: [
-			'attribute:meta:box',
-			'insert:boxField',
-			'remove:boxField'
-		]
-	} );
-
-	addBoxMetaButton( editor, 'boxTitle', 'Box title', () => ( {
-		header: { title: `Random title no. ${ getRandom() }.` }
-	} ) );
-
-	addBoxMetaButton( editor, 'boxAuthor', 'Box author', () => ( {
-		author: {
-			website: `www.example.com/${ getRandom() }`,
-			name: `Random author no. ${ getRandom() }`
-		}
-	} ) );
-
-	addButton( editor, 'addBoxField', '+', ( writer, box, boxField ) => {
-		const newBoxField = writer.createElement( 'boxField' );
-		writer.insert( newBoxField, box, boxField.index );
-		writer.insert( writer.createElement( 'paragraph' ), newBoxField, 0 );
-	} );
-
-	addButton( editor, 'removeBoxField', '-', ( writer, box, boxField ) => {
-		writer.remove( boxField );
-	} );
-}
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ ArticlePluginSet, Box ],
+		plugins: [ ArticlePluginSet ],
 		toolbar: [
 			'heading',
 			'|',
-			'boxTitle',
-			'boxAuthor',
-			'addBoxField',
-			'removeBoxField',
-			'|',
 			'bold',
 			'italic',
 			'link',