Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Introduced deep children text nodes rerendering #1366

Merged
merged 11 commits into from
Mar 28, 2018
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@ckeditor/ckeditor5-typing": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-undo": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-widget": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-link": "^1.0.0-beta.1",
"eslint": "^4.15.0",
"eslint-config-ckeditor5": "^1.0.7",
"husky": "^0.14.3",
Expand Down
2 changes: 1 addition & 1 deletion src/view/observer/mutationobserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export default class MutationObserver extends Observer {
for ( const viewElement of mutatedElements ) {
const domElement = domConverter.mapViewToDom( viewElement );
const viewChildren = Array.from( viewElement.getChildren() );
const newViewChildren = Array.from( domConverter.domChildrenToView( domElement ) );
const newViewChildren = Array.from( domConverter.domChildrenToView( domElement, { withChildren: false } ) );

// It may happen that as a result of many changes (sth was inserted and then removed),
// both elements haven't really changed. #1031
Expand Down
36 changes: 30 additions & 6 deletions src/view/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,6 @@ export default class Renderer {
this.markedChildren.add( inlineFillerPosition.parent );
}

for ( const node of this.markedTexts ) {
if ( !this.markedChildren.has( node.parent ) && this.domConverter.mapViewToDom( node.parent ) ) {
this._updateText( node, { inlineFillerPosition } );
}
}

for ( const element of this.markedAttributes ) {
this._updateAttrs( element );
}
Expand All @@ -212,6 +206,12 @@ export default class Renderer {
this._updateChildren( element, { inlineFillerPosition } );
}

for ( const node of this.markedTexts ) {
if ( !this.markedChildren.has( node.parent ) && this.domConverter.mapViewToDom( node.parent ) ) {
this._updateText( node, { inlineFillerPosition } );
}
}

// Check whether the inline filler is required and where it really is in the DOM.
// At this point in most cases it will be in the DOM, but there are exceptions.
// For example, if the inline filler was deep in the created DOM structure, it will not be created.
Expand Down Expand Up @@ -498,6 +498,8 @@ export default class Renderer {
nodesToUnbind.add( actualDomChildren[ i ] );
remove( actualDomChildren[ i ] );
} else { // 'equal'
// Force updating text nodes inside elements which did not change and do not need to be re-rendered (#1125).
this._markDescendantTextToSync( domConverter.domToView( expectedDomChildren[ i ] ) );
i++;
}
}
Expand Down Expand Up @@ -531,6 +533,28 @@ export default class Renderer {
}
}

/**
* Marks text nodes to be synced.
*
* If a text node is passed, it will be marked. If an element is passed, all descendant text nodes inside it will be marked.
*
* @private
* @param {module:engine/view/node~Node} viewNode View node to sync.
*/
_markDescendantTextToSync( viewNode ) {
if ( !viewNode ) {
return;
}

if ( viewNode.is( 'text' ) ) {
this.markedTexts.add( viewNode );
} else if ( viewNode.is( 'element' ) ) {
for ( const child of viewNode.getChildren() ) {
this._markDescendantTextToSync( child );
}
}
}

/**
* Checks if selection needs to be updated and possibly updates it.
*
Expand Down
9 changes: 9 additions & 0 deletions tests/view/manual/deep-render.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div id="editor">
<p>Foo<u> bar</u></p>
<p><u>Foo</u><i> buz</i></p>
<p>Foo<a href="#"><b> biz</b></a></p>
<p>Text <a href="#">Link <b>Bold</b></a> Text</p>
</div>

<p>Editor HTML preview:</p>
<code id="preview"></code>
34 changes: 34 additions & 0 deletions tests/view/manual/deep-render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/* global console, document, window */

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Link from '@ckeditor/ckeditor5-link/src/link';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Essentials, Paragraph, Underline, Bold, Italic, Link ],
toolbar: [ 'undo', 'redo', '|', 'bold', 'underline', 'italic', 'link' ]
} )
.then( editor => {
window.editor = editor;

const preview = document.querySelector( '#preview' );

preview.innerText = editor.getData();

editor.editing.view.on( 'render', () => {
preview.innerText = editor.getData();
} );
} )
.catch( err => {
console.error( err.stack );
} );
15 changes: 15 additions & 0 deletions tests/view/manual/deep-render.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Text nodes deep rendering ([#1125](https://github.com/ckeditor/ckeditor5-engine/issues/1125))

### Deep space rerender ([#1093](https://github.com/ckeditor/ckeditor5-engine/issues/1093))

1. Put caret after each `Foo` text (`Foo^`).
1. Press space.

**Expected**: Inserted space is visible. Space on the beginning of the next text node is replaced with `&nbsp;`.

### Link ending with styles ([ckeditor5-typing#120](https://github.com/ckeditor/ckeditor5-typing/issues/120))

1. Put caret on the end of the link (`Link Bold^`).
1. Insert some text.

**Expected**: Bold text is inserted as a part of the link. No errors thrown in the browser dev console.
Loading