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

Commit

Permalink
Merge d370947 into f073ad5
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator committed Sep 17, 2019
2 parents f073ad5 + d370947 commit 17bcf87
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 104 deletions.
3 changes: 1 addition & 2 deletions src/dataprocessor/htmldataprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import BasicHtmlWriter from './basichtmlwriter';
import DomConverter from '../view/domconverter';
import { NBSP_FILLER } from '../view/filler';

/**
* The HTML data processor class.
Expand All @@ -38,7 +37,7 @@ export default class HtmlDataProcessor {
* @private
* @member {module:engine/view/domconverter~DomConverter}
*/
this._domConverter = new DomConverter( { blockFiller: NBSP_FILLER } );
this._domConverter = new DomConverter( { blockFillerMode: 'nbsp' } );

/**
* A basic HTML writer instance used to convert DOM elements to an HTML string.
Expand Down
3 changes: 1 addition & 2 deletions src/dataprocessor/xmldataprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import BasicHtmlWriter from './basichtmlwriter';
import DomConverter from '../view/domconverter';
import { NBSP_FILLER } from '../view/filler';

/**
* The XML data processor class.
Expand Down Expand Up @@ -54,7 +53,7 @@ export default class XmlDataProcessor {
* @private
* @member {module:engine/view/domconverter~DomConverter}
*/
this._domConverter = new DomConverter( { blockFiller: NBSP_FILLER } );
this._domConverter = new DomConverter( { blockFillerMode: 'nbsp' } );

/**
* A basic HTML writer instance used to convert DOM elements to an XML string.
Expand Down
37 changes: 27 additions & 10 deletions src/view/domconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ import ViewRange from './range';
import ViewSelection from './selection';
import ViewDocumentFragment from './documentfragment';
import ViewTreeWalker from './treewalker';
import { BR_FILLER, INLINE_FILLER_LENGTH, isBlockFiller, isInlineFiller, startsWithFiller, getDataWithoutFiller } from './filler';
import {
BR_FILLER,
NBSP_FILLER,
INLINE_FILLER_LENGTH,
isBlockFiller,
isInlineFiller,
startsWithFiller,
getDataWithoutFiller
} from './filler';

import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
Expand All @@ -42,7 +50,7 @@ export default class DomConverter {
* Creates DOM converter.
*
* @param {Object} options Object with configuration options.
* @param {Function} [options.blockFiller=module:engine/view/filler~BR_FILLER] Block filler creator.
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode='br'] The type of the block filler to use.
*/
constructor( options = {} ) {
// Using WeakMap prevent memory leaks: when the converter will be destroyed all referenced between View and DOM
Expand All @@ -56,13 +64,12 @@ export default class DomConverter {
// I've been here. Seen stuff. Afraid of code now.

/**
* Block {@link module:engine/view/filler filler} creator, which is used to create all block fillers during the
* view to DOM conversion and to recognize block fillers during the DOM to view conversion.
* The mode of a block filler used by DOM converter.
*
* @readonly
* @member {Function} module:engine/view/domconverter~DomConverter#blockFiller
* @member {'br'|'nbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
*/
this.blockFiller = options.blockFiller || BR_FILLER;
this.blockFillerMode = options.blockFillerMode || 'br';

/**
* Tag names of DOM `Element`s which are considered pre-formatted elements.
Expand All @@ -80,6 +87,16 @@ export default class DomConverter {
*/
this.blockElements = [ 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];

/**
* Block {@link module:engine/view/filler filler} creator, which is used to create all block fillers during the
* view to DOM conversion and to recognize block fillers during the DOM to view conversion.
*
* @readonly
* @private
* @member {Function} module:engine/view/domconverter~DomConverter#_blockFiller
*/
this._blockFiller = this.blockFillerMode == 'br' ? BR_FILLER : NBSP_FILLER;

/**
* DOM to View mapping.
*
Expand Down Expand Up @@ -255,7 +272,7 @@ export default class DomConverter {

for ( const childView of viewElement.getChildren() ) {
if ( fillerPositionOffset === offset ) {
yield this.blockFiller( domDocument );
yield this._blockFiller( domDocument );
}

yield this.viewToDom( childView, domDocument, options );
Expand All @@ -264,7 +281,7 @@ export default class DomConverter {
}

if ( fillerPositionOffset === offset ) {
yield this.blockFiller( domDocument );
yield this._blockFiller( domDocument );
}
}

Expand Down Expand Up @@ -371,7 +388,7 @@ export default class DomConverter {
* or `null` if DOM node is a {@link module:engine/view/filler filler} or the given node is an empty text node.
*/
domToView( domNode, options = {} ) {
if ( isBlockFiller( domNode, this.blockFiller ) ) {
if ( isBlockFiller( domNode, this.blockFillerMode ) ) {
return null;
}

Expand Down Expand Up @@ -529,7 +546,7 @@ export default class DomConverter {
* @returns {module:engine/view/position~Position} viewPosition View position.
*/
domPositionToView( domParent, domOffset ) {
if ( isBlockFiller( domParent, this.blockFiller ) ) {
if ( isBlockFiller( domParent, this.blockFillerMode ) ) {
return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );
}

Expand Down
76 changes: 56 additions & 20 deletions src/view/filler.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export const BR_FILLER = domDocument => {
return fillerBr;
};

// eslint-disable-next-line new-cap
const BR_FILLER_REF = BR_FILLER( window.document );

/**
* Non-breaking space filler creator. This is a function which creates ` ` text node.
* It defines how the filler is created.
Expand All @@ -65,13 +68,17 @@ export const NBSP_FILLER = domDocument => domDocument.createTextNode( '\u00A0' )
export const INLINE_FILLER_LENGTH = 7;

/**
* Inline filler which is sequence of the zero width spaces.
* Inline filler which is a sequence of the zero width spaces.
*/
export let INLINE_FILLER = '';
export const INLINE_FILLER = ( () => {
let inlineFiller = '';

for ( let i = 0; i < INLINE_FILLER_LENGTH; i++ ) {
INLINE_FILLER += '\u200b';
}
for ( let i = 0; i < INLINE_FILLER_LENGTH; i++ ) {
inlineFiller += '\u200b';
}

return inlineFiller;
} )(); // Usu IIF so the INLINE_FILLER appears as a constant in the docs.

/**
* Checks if the node is a text node which starts with the {@link module:engine/view/filler~INLINE_FILLER inline filler}.
Expand Down Expand Up @@ -119,28 +126,21 @@ export function getDataWithoutFiller( domText ) {
}
}

// Cache block fillers templates to improve performance.
const templateBlockFillers = new WeakMap();

/**
* Checks if the node is an instance of the block filler of the given type.
*
* const brFillerInstance = BR_FILLER( document );
* isBlockFiller( brFillerInstance, BR_FILLER ); // true
* isBlockFiller( brFillerInstance, 'br' ); // true
* isBlockFiller( brFillerInstance, 'nbsp' ); // false
*
* **Note:**: For the `'nbsp'` mode the method also checks context of a node so it cannot be a detached node.
*
* @param {Node} domNode DOM node to check.
* @param {Function} blockFiller Block filler creator.
* @returns {Boolean} True if text node contains only {@link module:engine/view/filler~INLINE_FILLER inline filler}.
* @param {module:engine/view/filler~BlockFillerMode} mode Mode of a block filler.
* @returns {Boolean} True if a node is considered a block filler for given mode.
*/
export function isBlockFiller( domNode, blockFiller ) {
let templateBlockFiller = templateBlockFillers.get( blockFiller );

if ( !templateBlockFiller ) {
templateBlockFiller = blockFiller( window.document );
templateBlockFillers.set( blockFiller, templateBlockFiller );
}

return domNode.isEqualNode( templateBlockFiller );
export function isBlockFiller( domNode, mode ) {
return mode == 'br' ? domNode.isEqualNode( BR_FILLER_REF ) : isNbspBlockFiller( domNode );
}

/**
Expand Down Expand Up @@ -168,3 +168,39 @@ function jumpOverInlineFiller( evt, data ) {
}
}
}

// Checks if given node is a nbsp block filler.
//
// A &nbsp; is a block filler only if it is a single child of a block element.
//
// @param {Node} domNode DOM node.
// @returns {Boolean}
function isNbspBlockFiller( domNode ) {
const isNBSP = isText( domNode ) && domNode.data == '\u00A0';

return isNBSP && hasBlockParent( domNode ) && domNode.parentNode.childNodes.length === 1;
}

// Name of DOM nodes that are considered a block.
const blockElements = [ 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];

// Checks if domNode has block parent.
//
// @param {Node} domNode DOM node.
// @returns {Boolean}
function hasBlockParent( domNode ) {
const parent = domNode.parentNode;

return parent && parent.tagName && blockElements.includes( parent.tagName.toLowerCase() );
}

/**
* Enum representing type of the block filler.
*
* Possible values:
*
* * `br` - for `<br>` block filler used in editing view,
* * `nbsp` - for `&nbsp;` block fillers used in the data.
*
* @typedef {String} module:engine/view/filler~BlockFillerMode
*/
10 changes: 5 additions & 5 deletions src/view/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ export default class Renderer {
_diffNodeLists( actualDomChildren, expectedDomChildren ) {
actualDomChildren = filterOutFakeSelectionContainer( actualDomChildren, this._fakeSelectionContainer );

return diff( actualDomChildren, expectedDomChildren, sameNodes.bind( null, this.domConverter.blockFiller ) );
return diff( actualDomChildren, expectedDomChildren, sameNodes.bind( null, this.domConverter.blockFillerMode ) );
}

/**
Expand Down Expand Up @@ -938,11 +938,11 @@ function areSimilar( node1, node2 ) {
// * Two block filler elements.
//
// @private
// @param {Function} blockFiller Block filler creator function, see {@link module:engine/view/domconverter~DomConverter#blockFiller}.
// @param {String} blockFillerMode Block filler mode, see {@link module:engine/view/domconverter~DomConverter#blockFillerMode}.
// @param {Node} node1
// @param {Node} node2
// @returns {Boolean}
function sameNodes( blockFiller, actualDomChild, expectedDomChild ) {
function sameNodes( blockFillerMode, actualDomChild, expectedDomChild ) {
// Elements.
if ( actualDomChild === expectedDomChild ) {
return true;
Expand All @@ -952,8 +952,8 @@ function sameNodes( blockFiller, actualDomChild, expectedDomChild ) {
return actualDomChild.data === expectedDomChild.data;
}
// Block fillers.
else if ( isBlockFiller( actualDomChild, blockFiller ) &&
isBlockFiller( expectedDomChild, blockFiller ) ) {
else if ( isBlockFiller( actualDomChild, blockFillerMode ) &&
isBlockFiller( expectedDomChild, blockFillerMode ) ) {
return true;
}

Expand Down
53 changes: 26 additions & 27 deletions tests/dataprocessor/htmldataprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,32 @@ describe( 'HtmlDataProcessor', () => {
} );
}

// Uncomment this test after fixing #404.
// describe( 'https://github.com/ckeditor/ckeditor5-clipboard/issues/2#issuecomment-310417731 + #404', () => {
// it( 'does not lose whitespaces in Chrome\'s paste-like content', () => {
// const fragment = dataProcessor.toView(
// '<meta charset=\'utf-8\'>' +
// '<span>This is the<span>\u00a0</span></span>' +
// '<a href="url">third developer preview</a>' +
// '<span><span>\u00a0</span>of<span>\u00a0</span></span>' +
// '<strong>CKEditor\u00a05</strong>' +
// '<span>.</span>'
// );

// expect( stringify( fragment ) ).to.equal(
// '<span>This is the<span>\u00a0</span></span>' +
// '<a href="url">third developer preview</a>' +
// '<span><span>\u00a0</span>of<span>\u00a0</span></span>' +
// '<strong>CKEditor\u00a05</strong>' +
// '<span>.</span>'
// );

// // Just to be sure... stringify() uses conversion and the browser extensively,
// // so it's not entirely safe.
// expect( fragment.getChild( 0 ).getChild( 1 ).getChild( 0 ).data ).to.equal( '\u00a0' );
// expect( fragment.getChild( 2 ).getChild( 0 ).getChild( 0 ).data ).to.equal( '\u00a0' );
// expect( fragment.getChild( 2 ).getChild( 2 ).getChild( 0 ).data ).to.equal( '\u00a0' );
// } );
// } );
describe( 'https://github.com/ckeditor/ckeditor5-clipboard/issues/2#issuecomment-310417731 + #404', () => {
it( 'does not lose whitespaces in Chrome\'s paste-like content', () => {
const fragment = dataProcessor.toView(
'<meta charset=\'utf-8\'>' +
'<span>This is the<span>\u00a0</span></span>' +
'<a href="url">third developer preview</a>' +
'<span><span>\u00a0</span>of<span>\u00a0</span></span>' +
'<strong>CKEditor\u00a05</strong>' +
'<span>.</span>'
);

expect( stringify( fragment ) ).to.equal(
'<span>This is the<span>\u00a0</span></span>' +
'<a href="url">third developer preview</a>' +
'<span><span>\u00a0</span>of<span>\u00a0</span></span>' +
'<strong>CKEditor\u00a05</strong>' +
'<span>.</span>'
);

// Just to be sure... stringify() uses conversion and the browser extensively,
// so it's not entirely safe.
expect( fragment.getChild( 0 ).getChild( 1 ).getChild( 0 ).data ).to.equal( '\u00a0' );
expect( fragment.getChild( 2 ).getChild( 0 ).getChild( 0 ).data ).to.equal( '\u00a0' );
expect( fragment.getChild( 2 ).getChild( 2 ).getChild( 0 ).data ).to.equal( '\u00a0' );
} );
} );
} );

describe( 'toData()', () => {
Expand Down
10 changes: 6 additions & 4 deletions tests/view/domconverter/dom-to-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ViewElement from '../../../src/view/element';
import ViewDocumentSelection from '../../../src/view/documentselection';
import DomConverter from '../../../src/view/domconverter';
import ViewDocumentFragment from '../../../src/view/documentfragment';
import { INLINE_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER } from '../../../src/view/filler';
import { INLINE_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER, BR_FILLER } from '../../../src/view/filler';

import { parse, stringify } from '../../../src/dev-utils/view';

Expand Down Expand Up @@ -157,7 +157,8 @@ describe( 'DomConverter', () => {
} );

it( 'should return null for block filler', () => {
const domFiller = converter.blockFiller( document );
// eslint-disable-next-line new-cap
const domFiller = BR_FILLER( document );

expect( converter.domToView( domFiller ) ).to.be.null;
} );
Expand Down Expand Up @@ -682,7 +683,8 @@ describe( 'DomConverter', () => {
} );

it( 'should skip filler', () => {
const domFiller = converter.blockFiller( document );
// eslint-disable-next-line new-cap
const domFiller = BR_FILLER( document );
const domP = createElement( document, 'p', null, domFiller );

const viewChildren = Array.from( converter.domChildrenToView( domP ) );
Expand Down Expand Up @@ -761,7 +763,7 @@ describe( 'DomConverter', () => {
} );

it( 'should converter position inside block filler', () => {
const converter = new DomConverter( { blockFiller: NBSP_FILLER } );
const converter = new DomConverter( { blockFillerMode: 'nbsp' } );
const domFiller = NBSP_FILLER( document ); // eslint-disable-line new-cap
const domP = createElement( document, 'p', null, domFiller );

Expand Down
12 changes: 6 additions & 6 deletions tests/view/domconverter/domconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ViewEditable from '../../../src/view/editableelement';
import ViewDocument from '../../../src/view/document';
import ViewUIElement from '../../../src/view/uielement';
import ViewContainerElement from '../../../src/view/containerelement';
import { BR_FILLER, NBSP_FILLER, INLINE_FILLER, INLINE_FILLER_LENGTH } from '../../../src/view/filler';
import { INLINE_FILLER, INLINE_FILLER_LENGTH } from '../../../src/view/filler';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';

Expand All @@ -24,13 +24,13 @@ describe( 'DomConverter', () => {
} );

describe( 'constructor()', () => {
it( 'should create converter with BR block filler by default', () => {
expect( converter.blockFiller ).to.equal( BR_FILLER );
it( 'should create converter with BR block filler mode by default', () => {
expect( converter.blockFillerMode ).to.equal( 'br' );
} );

it( 'should create converter with defined block filler', () => {
converter = new DomConverter( { blockFiller: NBSP_FILLER } );
expect( converter.blockFiller ).to.equal( NBSP_FILLER );
it( 'should create converter with defined block mode filler', () => {
converter = new DomConverter( { blockFillerMode: 'nbsp' } );
expect( converter.blockFillerMode ).to.equal( 'nbsp' );
} );
} );

Expand Down
Loading

0 comments on commit 17bcf87

Please sign in to comment.