@ckeditor/ckeditor5-engine 29.0.0 → 31.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +1 -1
- package/package.json +24 -21
- package/src/controller/datacontroller.js +50 -1
- package/src/conversion/downcasthelpers.js +21 -18
- package/src/conversion/upcasthelpers.js +10 -17
- package/src/dataprocessor/htmldataprocessor.js +13 -16
- package/src/dataprocessor/xmldataprocessor.js +15 -19
- package/src/dev-utils/view.js +21 -3
- package/src/index.js +1 -0
- package/src/model/markercollection.js +5 -4
- package/src/model/range.js +4 -3
- package/src/model/schema.js +28 -24
- package/src/model/selection.js +1 -1
- package/src/model/utils/deletecontent.js +3 -3
- package/src/model/utils/selection-post-fixer.js +10 -2
- package/src/view/document.js +12 -0
- package/src/view/domconverter.js +286 -73
- package/src/view/matcher.js +89 -13
- package/src/view/observer/selectionobserver.js +48 -1
- package/src/view/rawelement.js +3 -2
- package/src/view/renderer.js +103 -39
- package/src/view/styles/border.js +10 -10
- package/src/view/styles/utils.js +5 -0
- package/src/view/uielement.js +5 -2
- package/src/view/view.js +1 -1
- package/theme/renderer.css +9 -0
- package/CHANGELOG.md +0 -823
package/src/view/domconverter.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* @module engine/view/domconverter
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* globals document, Node, NodeFilter, Text */
|
|
10
|
+
/* globals document, Node, NodeFilter, DOMParser, Text */
|
|
11
11
|
|
|
12
12
|
import ViewText from './text';
|
|
13
13
|
import ViewElement from './element';
|
|
14
|
+
import ViewUIElement from './uielement';
|
|
14
15
|
import ViewPosition from './position';
|
|
15
16
|
import ViewRange from './range';
|
|
16
17
|
import ViewSelection from './selection';
|
|
@@ -25,9 +26,7 @@ import {
|
|
|
25
26
|
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
|
|
26
27
|
import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
|
|
27
28
|
import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
|
|
28
|
-
import getCommonAncestor from '@ckeditor/ckeditor5-utils/src/dom/getcommonancestor';
|
|
29
29
|
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
|
|
30
|
-
import { isElement } from 'lodash-es';
|
|
31
30
|
|
|
32
31
|
const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
|
|
33
32
|
const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
@@ -52,7 +51,12 @@ export default class DomConverter {
|
|
|
52
51
|
*
|
|
53
52
|
* @param {module:engine/view/document~Document} document The view document instance.
|
|
54
53
|
* @param {Object} options An object with configuration options.
|
|
55
|
-
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode
|
|
54
|
+
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode] The type of the block filler to use.
|
|
55
|
+
* Default value depends on the options.renderingMode:
|
|
56
|
+
* 'nbsp' when options.renderingMode == 'data',
|
|
57
|
+
* 'br' when options.renderingMode == 'editing'.
|
|
58
|
+
* @param {'data'|'editing'} [options.renderingMode='editing'] Whether to leave the View-to-DOM conversion result unchanged
|
|
59
|
+
* or improve editing experience by filtering out interactive data.
|
|
56
60
|
*/
|
|
57
61
|
constructor( document, options = {} ) {
|
|
58
62
|
/**
|
|
@@ -61,12 +65,27 @@ export default class DomConverter {
|
|
|
61
65
|
*/
|
|
62
66
|
this.document = document;
|
|
63
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Whether to leave the View-to-DOM conversion result unchanged or improve editing experience by filtering out interactive data.
|
|
70
|
+
*
|
|
71
|
+
* @member {'data'|'editing'} module:engine/view/domconverter~DomConverter#renderingMode
|
|
72
|
+
*/
|
|
73
|
+
this.renderingMode = options.renderingMode || 'editing';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main switch for new rendering approach in the editing view.
|
|
77
|
+
*
|
|
78
|
+
* @protected
|
|
79
|
+
* @member {Boolean}
|
|
80
|
+
*/
|
|
81
|
+
this.experimentalRenderingMode = false;
|
|
82
|
+
|
|
64
83
|
/**
|
|
65
84
|
* The mode of a block filler used by the DOM converter.
|
|
66
85
|
*
|
|
67
86
|
* @member {'br'|'nbsp'|'markedNbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
|
|
68
87
|
*/
|
|
69
|
-
this.blockFillerMode = options.blockFillerMode || 'br';
|
|
88
|
+
this.blockFillerMode = options.blockFillerMode || ( this.renderingMode === 'editing' ? 'br' : 'nbsp' );
|
|
70
89
|
|
|
71
90
|
/**
|
|
72
91
|
* Elements which are considered pre-formatted elements.
|
|
@@ -94,6 +113,23 @@ export default class DomConverter {
|
|
|
94
113
|
'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
|
|
95
114
|
];
|
|
96
115
|
|
|
116
|
+
/**
|
|
117
|
+
* A list of elements that exist inline (in text) but their inner structure cannot be edited because
|
|
118
|
+
* of the way they are rendered by the browser. They are mostly HTML form elements but there are other
|
|
119
|
+
* elements such as `<img>` or `<iframe>` that also have non-editable children or no children whatsoever.
|
|
120
|
+
*
|
|
121
|
+
* Whether an element is considered an inline object has an impact on white space rendering (trimming)
|
|
122
|
+
* around (and inside of it). In short, white spaces in text nodes next to inline objects are not trimmed.
|
|
123
|
+
*
|
|
124
|
+
* You can extend this array if you introduce support for inline object elements which are not yet recognized here.
|
|
125
|
+
*
|
|
126
|
+
* @readonly
|
|
127
|
+
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#inlineObjectElements
|
|
128
|
+
*/
|
|
129
|
+
this.inlineObjectElements = [
|
|
130
|
+
'object', 'iframe', 'input', 'button', 'textarea', 'select', 'option', 'video', 'embed', 'audio', 'img', 'canvas'
|
|
131
|
+
];
|
|
132
|
+
|
|
97
133
|
/**
|
|
98
134
|
* The DOM-to-view mapping.
|
|
99
135
|
*
|
|
@@ -205,6 +241,82 @@ export default class DomConverter {
|
|
|
205
241
|
this._viewToDomMapping.set( viewFragment, domFragment );
|
|
206
242
|
}
|
|
207
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Decides whether given pair of attribute key and value should be passed further down the pipeline.
|
|
246
|
+
*
|
|
247
|
+
* @param {String} attributeKey
|
|
248
|
+
* @param {String} attributeValue
|
|
249
|
+
* @returns {Boolean}
|
|
250
|
+
*/
|
|
251
|
+
shouldRenderAttribute( attributeKey, attributeValue ) {
|
|
252
|
+
if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return !( attributeKey.toLowerCase().startsWith( 'on' ) ||
|
|
257
|
+
attributeValue.match( /(\b)(on\S+)(\s*)=|javascript:|(<\s*)(\/*)script/i ) ||
|
|
258
|
+
attributeValue.match( /data:(?!image\/(png|jpeg|gif|webp))/i )
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Set `domElement`'s content using provided `html` argument. Apply necessary filtering for the editing pipeline.
|
|
264
|
+
*
|
|
265
|
+
* @param {Element} domElement DOM element that should have `html` set as its content.
|
|
266
|
+
* @param {String} html Textual representation of the HTML that will be set on `domElement`.
|
|
267
|
+
*/
|
|
268
|
+
setContentOf( domElement, html ) {
|
|
269
|
+
// For data pipeline we pass the HTML as-is.
|
|
270
|
+
if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
|
|
271
|
+
domElement.innerHTML = html;
|
|
272
|
+
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const document = new DOMParser().parseFromString( html, 'text/html' );
|
|
277
|
+
const fragment = document.createDocumentFragment();
|
|
278
|
+
const bodyChildNodes = document.body.childNodes;
|
|
279
|
+
|
|
280
|
+
while ( bodyChildNodes.length > 0 ) {
|
|
281
|
+
fragment.appendChild( bodyChildNodes[ 0 ] );
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const treeWalker = document.createTreeWalker( fragment, NodeFilter.SHOW_ELEMENT );
|
|
285
|
+
const nodes = [];
|
|
286
|
+
|
|
287
|
+
let currentNode;
|
|
288
|
+
|
|
289
|
+
// eslint-disable-next-line no-cond-assign
|
|
290
|
+
while ( currentNode = treeWalker.nextNode() ) {
|
|
291
|
+
nodes.push( currentNode );
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for ( const currentNode of nodes ) {
|
|
295
|
+
// Go through nodes to remove those that are prohibited in editing pipeline.
|
|
296
|
+
for ( const attributeName of currentNode.getAttributeNames() ) {
|
|
297
|
+
const attributeValue = currentNode.getAttribute( attributeName );
|
|
298
|
+
|
|
299
|
+
if ( !this.shouldRenderAttribute( attributeName, attributeValue ) ) {
|
|
300
|
+
currentNode.removeAttribute( attributeName );
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const elementName = currentNode.tagName.toLowerCase();
|
|
305
|
+
|
|
306
|
+
// There are certain nodes, that should be renamed to <span> in editing pipeline.
|
|
307
|
+
if ( this._shouldRenameElement( elementName ) ) {
|
|
308
|
+
currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Empty the target element.
|
|
313
|
+
while ( domElement.firstChild ) {
|
|
314
|
+
domElement.firstChild.remove();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
domElement.append( fragment );
|
|
318
|
+
}
|
|
319
|
+
|
|
208
320
|
/**
|
|
209
321
|
* Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will
|
|
210
322
|
* be created. For bound elements and document fragments the method will return corresponding items.
|
|
@@ -237,8 +349,12 @@ export default class DomConverter {
|
|
|
237
349
|
this.bindDocumentFragments( domElement, viewNode );
|
|
238
350
|
}
|
|
239
351
|
} else if ( viewNode.is( 'uiElement' ) ) {
|
|
240
|
-
|
|
241
|
-
|
|
352
|
+
if ( viewNode.name === '$comment' ) {
|
|
353
|
+
domElement = domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
|
|
354
|
+
} else {
|
|
355
|
+
// UIElement has its own render() method (see #799).
|
|
356
|
+
domElement = viewNode.render( domDocument, this );
|
|
357
|
+
}
|
|
242
358
|
|
|
243
359
|
if ( options.bind ) {
|
|
244
360
|
this.bindElements( domElement, viewNode );
|
|
@@ -247,7 +363,9 @@ export default class DomConverter {
|
|
|
247
363
|
return domElement;
|
|
248
364
|
} else {
|
|
249
365
|
// Create DOM element.
|
|
250
|
-
if (
|
|
366
|
+
if ( this._shouldRenameElement( viewNode.name ) ) {
|
|
367
|
+
domElement = this._createReplacementDomElement( viewNode.name );
|
|
368
|
+
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
|
|
251
369
|
domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
|
|
252
370
|
} else {
|
|
253
371
|
domElement = domDocument.createElement( viewNode.name );
|
|
@@ -256,7 +374,7 @@ export default class DomConverter {
|
|
|
256
374
|
// RawElement take care of their children in RawElement#render() method which can be customized
|
|
257
375
|
// (see https://github.com/ckeditor/ckeditor5/issues/4469).
|
|
258
376
|
if ( viewNode.is( 'rawElement' ) ) {
|
|
259
|
-
viewNode.render( domElement );
|
|
377
|
+
viewNode.render( domElement, this );
|
|
260
378
|
}
|
|
261
379
|
|
|
262
380
|
if ( options.bind ) {
|
|
@@ -265,7 +383,13 @@ export default class DomConverter {
|
|
|
265
383
|
|
|
266
384
|
// Copy element's attributes.
|
|
267
385
|
for ( const key of viewNode.getAttributeKeys() ) {
|
|
268
|
-
|
|
386
|
+
const value = viewNode.getAttribute( key );
|
|
387
|
+
|
|
388
|
+
if ( !this.shouldRenderAttribute( key, value ) ) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
domElement.setAttribute( key, value );
|
|
269
393
|
}
|
|
270
394
|
}
|
|
271
395
|
|
|
@@ -406,7 +530,9 @@ export default class DomConverter {
|
|
|
406
530
|
* @param {Object} [options] Conversion options.
|
|
407
531
|
* @param {Boolean} [options.bind=false] Determines whether new elements will be bound.
|
|
408
532
|
* @param {Boolean} [options.withChildren=true] If `true`, node's and document fragment's children will be converted too.
|
|
409
|
-
* @param {Boolean} [options.keepOriginalCase=false] If `false`, node's tag name will be
|
|
533
|
+
* @param {Boolean} [options.keepOriginalCase=false] If `false`, node's tag name will be converted to lower case.
|
|
534
|
+
* @param {Boolean} [options.skipComments=false] If `false`, comment nodes will be converted to `$comment`
|
|
535
|
+
* {@link module:engine/view/uielement~UIElement view UI elements}.
|
|
410
536
|
* @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null} Converted node or document fragment
|
|
411
537
|
* or `null` if DOM node is a {@link module:engine/view/filler filler} or the given node is an empty text node.
|
|
412
538
|
*/
|
|
@@ -422,6 +548,10 @@ export default class DomConverter {
|
|
|
422
548
|
return hostElement;
|
|
423
549
|
}
|
|
424
550
|
|
|
551
|
+
if ( this.isComment( domNode ) && options.skipComments ) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
|
|
425
555
|
if ( isText( domNode ) ) {
|
|
426
556
|
if ( isInlineFiller( domNode ) ) {
|
|
427
557
|
return null;
|
|
@@ -430,8 +560,6 @@ export default class DomConverter {
|
|
|
430
560
|
|
|
431
561
|
return textData === '' ? null : new ViewText( this.document, textData );
|
|
432
562
|
}
|
|
433
|
-
} else if ( this.isComment( domNode ) ) {
|
|
434
|
-
return null;
|
|
435
563
|
} else {
|
|
436
564
|
if ( this.mapDomToView( domNode ) ) {
|
|
437
565
|
return this.mapDomToView( domNode );
|
|
@@ -448,8 +576,7 @@ export default class DomConverter {
|
|
|
448
576
|
}
|
|
449
577
|
} else {
|
|
450
578
|
// Create view element.
|
|
451
|
-
|
|
452
|
-
viewElement = new ViewElement( this.document, viewName );
|
|
579
|
+
viewElement = this._createViewElement( domNode, options );
|
|
453
580
|
|
|
454
581
|
if ( options.bind ) {
|
|
455
582
|
this.bindElements( domNode, viewElement );
|
|
@@ -458,13 +585,18 @@ export default class DomConverter {
|
|
|
458
585
|
// Copy element's attributes.
|
|
459
586
|
const attrs = domNode.attributes;
|
|
460
587
|
|
|
461
|
-
|
|
462
|
-
|
|
588
|
+
if ( attrs ) {
|
|
589
|
+
for ( let i = attrs.length - 1; i >= 0; i-- ) {
|
|
590
|
+
viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
|
|
591
|
+
}
|
|
463
592
|
}
|
|
464
593
|
|
|
465
594
|
// Treat this element's content as a raw data if it was registered as such.
|
|
466
|
-
|
|
467
|
-
|
|
595
|
+
// Comment node is also treated as an element with raw data.
|
|
596
|
+
if ( this._isViewElementWithRawContent( viewElement, options ) || this.isComment( domNode ) ) {
|
|
597
|
+
const rawContent = this.isComment( domNode ) ? domNode.data : domNode.innerHTML;
|
|
598
|
+
|
|
599
|
+
viewElement._setCustomProperty( '$rawContent', rawContent );
|
|
468
600
|
|
|
469
601
|
// Store a DOM node to prevent left trimming of the following text node.
|
|
470
602
|
this._encounteredRawContentDomNodes.add( domNode );
|
|
@@ -575,10 +707,10 @@ export default class DomConverter {
|
|
|
575
707
|
* If structures are too different and it is not possible to find corresponding position then `null` will be returned.
|
|
576
708
|
*
|
|
577
709
|
* @param {Node} domParent DOM position parent.
|
|
578
|
-
* @param {Number} domOffset DOM position offset.
|
|
710
|
+
* @param {Number} [domOffset=0] DOM position offset. You can skip it when converting the inline filler node.
|
|
579
711
|
* @returns {module:engine/view/position~Position} viewPosition View position.
|
|
580
712
|
*/
|
|
581
|
-
domPositionToView( domParent, domOffset ) {
|
|
713
|
+
domPositionToView( domParent, domOffset = 0 ) {
|
|
582
714
|
if ( this.isBlockFiller( domParent ) ) {
|
|
583
715
|
return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );
|
|
584
716
|
}
|
|
@@ -1034,8 +1166,8 @@ export default class DomConverter {
|
|
|
1034
1166
|
// 1. Replace the first space with a nbsp if the previous node ends with a space or there is no previous node
|
|
1035
1167
|
// (container element boundary).
|
|
1036
1168
|
if ( data.charAt( 0 ) == ' ' ) {
|
|
1037
|
-
const prevNode = this.
|
|
1038
|
-
const prevEndsWithSpace = prevNode && this._nodeEndsWithSpace( prevNode );
|
|
1169
|
+
const prevNode = this._getTouchingInlineViewNode( node, false );
|
|
1170
|
+
const prevEndsWithSpace = prevNode && prevNode.is( '$textProxy' ) && this._nodeEndsWithSpace( prevNode );
|
|
1039
1171
|
|
|
1040
1172
|
if ( prevEndsWithSpace || !prevNode ) {
|
|
1041
1173
|
data = '\u00A0' + data.substr( 1 );
|
|
@@ -1052,9 +1184,10 @@ export default class DomConverter {
|
|
|
1052
1184
|
//
|
|
1053
1185
|
// More here: https://github.com/ckeditor/ckeditor5-engine/issues/1747.
|
|
1054
1186
|
if ( data.charAt( data.length - 1 ) == ' ' ) {
|
|
1055
|
-
const nextNode = this.
|
|
1187
|
+
const nextNode = this._getTouchingInlineViewNode( node, true );
|
|
1188
|
+
const nextStartsWithSpace = nextNode && nextNode.is( '$textProxy' ) && nextNode.data.charAt( 0 ) == ' ';
|
|
1056
1189
|
|
|
1057
|
-
if ( data.charAt( data.length - 2 ) == ' ' || !nextNode ||
|
|
1190
|
+
if ( data.charAt( data.length - 2 ) == ' ' || !nextNode || nextStartsWithSpace ) {
|
|
1058
1191
|
data = data.substr( 0, data.length - 1 ) + '\u00A0';
|
|
1059
1192
|
}
|
|
1060
1193
|
}
|
|
@@ -1141,14 +1274,17 @@ export default class DomConverter {
|
|
|
1141
1274
|
// ` \u00A0` to ensure proper rendering. Since here we convert back, we recognize those pairs and change them back to ` `.
|
|
1142
1275
|
data = data.replace( / \u00A0/g, ' ' );
|
|
1143
1276
|
|
|
1277
|
+
const isNextNodeInlineObjectElement = nextNode && this.isElement( nextNode ) && nextNode.tagName != 'BR';
|
|
1278
|
+
const isNextNodeStartingWithSpace = nextNode && isText( nextNode ) && nextNode.data.charAt( 0 ) == ' ';
|
|
1279
|
+
|
|
1144
1280
|
// Then, let's change the last nbsp to a space.
|
|
1145
|
-
if ( /( |\u00A0)\u00A0$/.test( data ) || !nextNode ||
|
|
1281
|
+
if ( /( |\u00A0)\u00A0$/.test( data ) || !nextNode || isNextNodeInlineObjectElement || isNextNodeStartingWithSpace ) {
|
|
1146
1282
|
data = data.replace( /\u00A0$/, ' ' );
|
|
1147
1283
|
}
|
|
1148
1284
|
|
|
1149
1285
|
// Then, change character that is at the beginning of the text node to space character.
|
|
1150
1286
|
// We do that replacement only if this is the first node or the previous node ends on whitespace character.
|
|
1151
|
-
if ( shouldLeftTrim ) {
|
|
1287
|
+
if ( shouldLeftTrim || prevNode && this.isElement( prevNode ) && prevNode.tagName != 'BR' ) {
|
|
1152
1288
|
data = data.replace( /^\u00A0/, ' ' );
|
|
1153
1289
|
}
|
|
1154
1290
|
|
|
@@ -1163,15 +1299,15 @@ export default class DomConverter {
|
|
|
1163
1299
|
*
|
|
1164
1300
|
* @private
|
|
1165
1301
|
* @param {Node} node
|
|
1166
|
-
* @param {Node} prevNode
|
|
1302
|
+
* @param {Node} prevNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
|
|
1167
1303
|
*/
|
|
1168
1304
|
_checkShouldLeftTrimDomText( node, prevNode ) {
|
|
1169
1305
|
if ( !prevNode ) {
|
|
1170
1306
|
return true;
|
|
1171
1307
|
}
|
|
1172
1308
|
|
|
1173
|
-
if ( isElement( prevNode ) ) {
|
|
1174
|
-
return
|
|
1309
|
+
if ( this.isElement( prevNode ) ) {
|
|
1310
|
+
return prevNode.tagName === 'BR';
|
|
1175
1311
|
}
|
|
1176
1312
|
|
|
1177
1313
|
// Shouldn't left trim if previous node is a node that was encountered as a raw content node.
|
|
@@ -1188,7 +1324,7 @@ export default class DomConverter {
|
|
|
1188
1324
|
*
|
|
1189
1325
|
* @private
|
|
1190
1326
|
* @param {Node} node
|
|
1191
|
-
* @param {Node} nextNode
|
|
1327
|
+
* @param {Node} nextNode Either DOM text or `<br>` or one of `#inlineObjectElements`.
|
|
1192
1328
|
*/
|
|
1193
1329
|
_checkShouldRightTrimDomText( node, nextNode ) {
|
|
1194
1330
|
if ( nextNode ) {
|
|
@@ -1205,18 +1341,23 @@ export default class DomConverter {
|
|
|
1205
1341
|
* @private
|
|
1206
1342
|
* @param {module:engine/view/text~Text} node Reference node.
|
|
1207
1343
|
* @param {Boolean} getNext
|
|
1208
|
-
* @returns {module:engine/view/text~Text|null} Touching text node
|
|
1344
|
+
* @returns {module:engine/view/text~Text|module:engine/view/element~Element|null} Touching text node, an inline object
|
|
1345
|
+
* or `null` if there is no next or previous touching text node.
|
|
1209
1346
|
*/
|
|
1210
|
-
|
|
1347
|
+
_getTouchingInlineViewNode( node, getNext ) {
|
|
1211
1348
|
const treeWalker = new ViewTreeWalker( {
|
|
1212
1349
|
startPosition: getNext ? ViewPosition._createAfter( node ) : ViewPosition._createBefore( node ),
|
|
1213
1350
|
direction: getNext ? 'forward' : 'backward'
|
|
1214
1351
|
} );
|
|
1215
1352
|
|
|
1216
1353
|
for ( const value of treeWalker ) {
|
|
1354
|
+
// Found an inline object (for example an image).
|
|
1355
|
+
if ( value.item.is( 'element' ) && this.inlineObjectElements.includes( value.item.name ) ) {
|
|
1356
|
+
return value.item;
|
|
1357
|
+
}
|
|
1217
1358
|
// ViewContainerElement is found on a way to next ViewText node, so given `node` was first/last
|
|
1218
1359
|
// text node in its container element.
|
|
1219
|
-
if ( value.item.is( 'containerElement' ) ) {
|
|
1360
|
+
else if ( value.item.is( 'containerElement' ) ) {
|
|
1220
1361
|
return null;
|
|
1221
1362
|
}
|
|
1222
1363
|
// <br> found – it works like a block boundary, so do not scan further.
|
|
@@ -1234,10 +1375,11 @@ export default class DomConverter {
|
|
|
1234
1375
|
|
|
1235
1376
|
/**
|
|
1236
1377
|
* Helper function. For the given text node, it finds the closest touching node which is either
|
|
1237
|
-
* a text
|
|
1238
|
-
* wasn't found so far, `null` is returned.
|
|
1378
|
+
* a text, `<br>` or an {@link #inlineObjectElements inline object}.
|
|
1239
1379
|
*
|
|
1240
|
-
*
|
|
1380
|
+
* If no such node is found, `null` is returned.
|
|
1381
|
+
*
|
|
1382
|
+
* For instance, in the following DOM structure:
|
|
1241
1383
|
*
|
|
1242
1384
|
* <p>foo<b>bar</b><br>bom</p>
|
|
1243
1385
|
*
|
|
@@ -1258,45 +1400,121 @@ export default class DomConverter {
|
|
|
1258
1400
|
return null;
|
|
1259
1401
|
}
|
|
1260
1402
|
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
1263
|
-
const topmostParent = getAncestors( node )[ 0 ];
|
|
1403
|
+
const stepInto = getNext ? 'firstChild' : 'lastChild';
|
|
1404
|
+
const stepOver = getNext ? 'nextSibling' : 'previousSibling';
|
|
1264
1405
|
|
|
1265
|
-
|
|
1266
|
-
acceptNode( node ) {
|
|
1267
|
-
if ( isText( node ) ) {
|
|
1268
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
1269
|
-
}
|
|
1406
|
+
let skipChildren = true;
|
|
1270
1407
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1408
|
+
do {
|
|
1409
|
+
if ( !skipChildren && node[ stepInto ] ) {
|
|
1410
|
+
node = node[ stepInto ];
|
|
1411
|
+
} else if ( node[ stepOver ] ) {
|
|
1412
|
+
node = node[ stepOver ];
|
|
1413
|
+
skipChildren = false;
|
|
1414
|
+
} else {
|
|
1415
|
+
node = node.parentNode;
|
|
1416
|
+
skipChildren = true;
|
|
1417
|
+
}
|
|
1274
1418
|
|
|
1275
|
-
|
|
1419
|
+
if ( !node || this._isBlockElement( node ) ) {
|
|
1420
|
+
return null;
|
|
1276
1421
|
}
|
|
1277
|
-
}
|
|
1422
|
+
} while (
|
|
1423
|
+
!( isText( node ) || node.tagName == 'BR' || this._isInlineObjectElement( node ) )
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
return node;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Returns `true` if a DOM node belongs to {@link #blockElements}. `false` otherwise.
|
|
1431
|
+
*
|
|
1432
|
+
* @private
|
|
1433
|
+
* @param {Node} node
|
|
1434
|
+
* @returns {Boolean}
|
|
1435
|
+
*/
|
|
1436
|
+
_isBlockElement( node ) {
|
|
1437
|
+
return this.isElement( node ) && this.blockElements.includes( node.tagName.toLowerCase() );
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Returns `true` if a DOM node belongs to {@link #inlineObjectElements}. `false` otherwise.
|
|
1442
|
+
*
|
|
1443
|
+
* @private
|
|
1444
|
+
* @param {Node} node
|
|
1445
|
+
* @returns {Boolean}
|
|
1446
|
+
*/
|
|
1447
|
+
_isInlineObjectElement( node ) {
|
|
1448
|
+
return this.isElement( node ) && this.inlineObjectElements.includes( node.tagName.toLowerCase() );
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Creates view element basing on the node type.
|
|
1453
|
+
*
|
|
1454
|
+
* @private
|
|
1455
|
+
* @param {Node} node DOM node to check.
|
|
1456
|
+
* @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
|
|
1457
|
+
* @returns {Element}
|
|
1458
|
+
*/
|
|
1459
|
+
_createViewElement( node, options ) {
|
|
1460
|
+
if ( this.isComment( node ) ) {
|
|
1461
|
+
return new ViewUIElement( this.document, '$comment' );
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const viewName = options.keepOriginalCase ? node.tagName : node.tagName.toLowerCase();
|
|
1465
|
+
|
|
1466
|
+
return new ViewElement( this.document, viewName );
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Checks if view element's content should be treated as a raw data.
|
|
1471
|
+
*
|
|
1472
|
+
* @private
|
|
1473
|
+
* @param {Element} viewElement View element to check.
|
|
1474
|
+
* @param {Object} options Conversion options. See {@link module:engine/view/domconverter~DomConverter#domToView} options parameter.
|
|
1475
|
+
* @returns {Boolean}
|
|
1476
|
+
*/
|
|
1477
|
+
_isViewElementWithRawContent( viewElement, options ) {
|
|
1478
|
+
return options.withChildren !== false && this._rawContentElementMatcher.match( viewElement );
|
|
1479
|
+
}
|
|
1278
1480
|
|
|
1279
|
-
|
|
1481
|
+
/**
|
|
1482
|
+
* Checks whether given element name should be renamed in a current rendering mode.
|
|
1483
|
+
*
|
|
1484
|
+
* @private
|
|
1485
|
+
* @param {String} elementName The name of view element.
|
|
1486
|
+
* @returns {Boolean}
|
|
1487
|
+
*/
|
|
1488
|
+
_shouldRenameElement( elementName ) {
|
|
1489
|
+
return this.experimentalRenderingMode && this.renderingMode == 'editing' && elementName == 'script';
|
|
1490
|
+
}
|
|
1280
1491
|
|
|
1281
|
-
|
|
1492
|
+
/**
|
|
1493
|
+
* Return a <span> element with special attribute holding the name of the original element.
|
|
1494
|
+
* Optionally, copy all the attributes of the original element if that element is provided.
|
|
1495
|
+
*
|
|
1496
|
+
* @private
|
|
1497
|
+
* @param {String} elementName The name of view element.
|
|
1498
|
+
* @param {Element} [originalDomElement] The original DOM element to copy attributes and content from.
|
|
1499
|
+
* @returns {Element}
|
|
1500
|
+
*/
|
|
1501
|
+
_createReplacementDomElement( elementName, originalDomElement = null ) {
|
|
1502
|
+
const newDomElement = document.createElement( 'span' );
|
|
1282
1503
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1504
|
+
// Mark the span replacing a script as hidden.
|
|
1505
|
+
newDomElement.setAttribute( 'data-ck-hidden', elementName );
|
|
1506
|
+
|
|
1507
|
+
if ( originalDomElement ) {
|
|
1508
|
+
while ( originalDomElement.firstChild ) {
|
|
1509
|
+
newDomElement.appendChild( originalDomElement.firstChild );
|
|
1510
|
+
}
|
|
1285
1511
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
// and there are no block elements on a way from next/prev text node to that ancestor...
|
|
1289
|
-
if (
|
|
1290
|
-
lca &&
|
|
1291
|
-
!_hasDomParentOfType( node, this.blockElements, lca ) &&
|
|
1292
|
-
!_hasDomParentOfType( touchingNode, this.blockElements, lca )
|
|
1293
|
-
) {
|
|
1294
|
-
// Then they are in the same container element.
|
|
1295
|
-
return touchingNode;
|
|
1512
|
+
for ( const attributeName of originalDomElement.getAttributeNames() ) {
|
|
1513
|
+
newDomElement.setAttribute( attributeName, originalDomElement.getAttribute( attributeName ) );
|
|
1296
1514
|
}
|
|
1297
1515
|
}
|
|
1298
1516
|
|
|
1299
|
-
return
|
|
1517
|
+
return newDomElement;
|
|
1300
1518
|
}
|
|
1301
1519
|
}
|
|
1302
1520
|
|
|
@@ -1305,14 +1523,9 @@ export default class DomConverter {
|
|
|
1305
1523
|
//
|
|
1306
1524
|
// @param {Node} node
|
|
1307
1525
|
// @param {Array.<String>} types
|
|
1308
|
-
// @param {Boolean} [boundaryParent] Can be given if parents should be checked up to a given element (excluding that element).
|
|
1309
1526
|
// @returns {Boolean} `true` if such parent exists or `false` if it does not.
|
|
1310
|
-
function _hasDomParentOfType( node, types
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
if ( boundaryParent ) {
|
|
1314
|
-
parents = parents.slice( parents.indexOf( boundaryParent ) + 1 );
|
|
1315
|
-
}
|
|
1527
|
+
function _hasDomParentOfType( node, types ) {
|
|
1528
|
+
const parents = getAncestors( node );
|
|
1316
1529
|
|
|
1317
1530
|
return parents.some( parent => parent.tagName && types.includes( parent.tagName.toLowerCase() ) );
|
|
1318
1531
|
}
|
|
@@ -1360,7 +1573,7 @@ function hasBlockParent( domNode, blockElements ) {
|
|
|
1360
1573
|
*
|
|
1361
1574
|
* * `br` – For the `<br data-cke-filler="true">` block filler used in the editing view.
|
|
1362
1575
|
* * `nbsp` – For the ` ` block fillers used in the data.
|
|
1363
|
-
* * `markedNbsp` – For the ` ` block fillers wrapped in
|
|
1576
|
+
* * `markedNbsp` – For the ` ` block fillers wrapped in `<span>` elements: `<span data-cke-filler="true"> </span>`
|
|
1364
1577
|
* used in the data.
|
|
1365
1578
|
*
|
|
1366
1579
|
* @typedef {String} module:engine/view/filler~BlockFillerMode
|