@ckeditor/ckeditor5-engine 34.0.0 → 35.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/CHANGELOG.md +823 -0
- package/LICENSE.md +6 -2
- package/package.json +24 -23
- package/src/conversion/upcastdispatcher.js +4 -4
- package/src/conversion/upcasthelpers.js +23 -1
- package/src/dataprocessor/htmldataprocessor.js +2 -2
- package/src/dataprocessor/xmldataprocessor.js +2 -2
- package/src/index.js +5 -0
- package/src/model/document.js +20 -28
- package/src/model/history.js +160 -22
- package/src/model/model.js +2 -2
- package/src/model/schema.js +2 -2
- package/src/model/utils/findoptimalinsertionrange.js +5 -5
- package/src/model/utils/insertobject.js +5 -5
- package/src/view/domconverter.js +43 -30
- package/src/view/element.js +3 -0
- package/src/view/filler.js +1 -1
- package/src/view/observer/tabobserver.js +2 -2
- package/src/view/renderer.js +13 -5
package/src/view/domconverter.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module engine/view/domconverter
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* globals
|
|
10
|
+
/* globals Node, NodeFilter, DOMParser, Text */
|
|
11
11
|
|
|
12
12
|
import ViewText from './text';
|
|
13
13
|
import ViewElement from './element';
|
|
@@ -30,12 +30,11 @@ import getAncestors from '@ckeditor/ckeditor5-utils/src/dom/getancestors';
|
|
|
30
30
|
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
|
|
31
31
|
import isComment from '@ckeditor/ckeditor5-utils/src/dom/iscomment';
|
|
32
32
|
|
|
33
|
-
const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
|
|
34
|
-
const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
35
|
-
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
|
|
33
|
+
const BR_FILLER_REF = BR_FILLER( global.document ); // eslint-disable-line new-cap
|
|
34
|
+
const NBSP_FILLER_REF = NBSP_FILLER( global.document ); // eslint-disable-line new-cap
|
|
35
|
+
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( global.document ); // eslint-disable-line new-cap
|
|
36
36
|
const UNSAFE_ATTRIBUTE_NAME_PREFIX = 'data-ck-unsafe-attribute-';
|
|
37
37
|
const UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE = 'data-ck-unsafe-element';
|
|
38
|
-
const UNSAFE_ELEMENTS = [ 'script', 'style' ];
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
|
|
@@ -127,6 +126,23 @@ export default class DomConverter {
|
|
|
127
126
|
'object', 'iframe', 'input', 'button', 'textarea', 'select', 'option', 'video', 'embed', 'audio', 'img', 'canvas'
|
|
128
127
|
];
|
|
129
128
|
|
|
129
|
+
/**
|
|
130
|
+
* A list of elements which may affect the editing experience. To avoid this, those elements are replaced with
|
|
131
|
+
* `<span data-ck-unsafe-element="[element name]"></span>` while rendering in the editing mode.
|
|
132
|
+
*
|
|
133
|
+
* @readonly
|
|
134
|
+
* @member {Array.<String>} module:engine/view/domconverter~DomConverter#unsafeElements
|
|
135
|
+
*/
|
|
136
|
+
this.unsafeElements = [ 'script', 'style' ];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The DOM Document used to create DOM nodes.
|
|
140
|
+
*
|
|
141
|
+
* @type {Document}
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
this._domDocument = this.renderingMode === 'editing' ? global.document : global.document.implementation.createHTMLDocument( '' );
|
|
145
|
+
|
|
130
146
|
/**
|
|
131
147
|
* The DOM-to-view mapping.
|
|
132
148
|
*
|
|
@@ -344,17 +360,16 @@ export default class DomConverter {
|
|
|
344
360
|
*
|
|
345
361
|
* @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} viewNode
|
|
346
362
|
* View node or document fragment to transform.
|
|
347
|
-
* @param {Document} domDocument Document which will be used to create DOM nodes.
|
|
348
363
|
* @param {Object} [options] Conversion options.
|
|
349
364
|
* @param {Boolean} [options.bind=false] Determines whether new elements will be bound.
|
|
350
365
|
* @param {Boolean} [options.withChildren=true] If `true`, node's and document fragment's children will be converted too.
|
|
351
366
|
* @returns {Node|DocumentFragment} Converted node or DocumentFragment.
|
|
352
367
|
*/
|
|
353
|
-
viewToDom( viewNode,
|
|
368
|
+
viewToDom( viewNode, options = {} ) {
|
|
354
369
|
if ( viewNode.is( '$text' ) ) {
|
|
355
370
|
const textData = this._processDataFromViewText( viewNode );
|
|
356
371
|
|
|
357
|
-
return
|
|
372
|
+
return this._domDocument.createTextNode( textData );
|
|
358
373
|
} else {
|
|
359
374
|
if ( this.mapViewToDom( viewNode ) ) {
|
|
360
375
|
return this.mapViewToDom( viewNode );
|
|
@@ -364,17 +379,17 @@ export default class DomConverter {
|
|
|
364
379
|
|
|
365
380
|
if ( viewNode.is( 'documentFragment' ) ) {
|
|
366
381
|
// Create DOM document fragment.
|
|
367
|
-
domElement =
|
|
382
|
+
domElement = this._domDocument.createDocumentFragment();
|
|
368
383
|
|
|
369
384
|
if ( options.bind ) {
|
|
370
385
|
this.bindDocumentFragments( domElement, viewNode );
|
|
371
386
|
}
|
|
372
387
|
} else if ( viewNode.is( 'uiElement' ) ) {
|
|
373
388
|
if ( viewNode.name === '$comment' ) {
|
|
374
|
-
domElement =
|
|
389
|
+
domElement = this._domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
|
|
375
390
|
} else {
|
|
376
391
|
// UIElement has its own render() method (see #799).
|
|
377
|
-
domElement = viewNode.render(
|
|
392
|
+
domElement = viewNode.render( this._domDocument, this );
|
|
378
393
|
}
|
|
379
394
|
|
|
380
395
|
if ( options.bind ) {
|
|
@@ -389,9 +404,9 @@ export default class DomConverter {
|
|
|
389
404
|
|
|
390
405
|
domElement = this._createReplacementDomElement( viewNode.name );
|
|
391
406
|
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
|
|
392
|
-
domElement =
|
|
407
|
+
domElement = this._domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
|
|
393
408
|
} else {
|
|
394
|
-
domElement =
|
|
409
|
+
domElement = this._domDocument.createElement( viewNode.name );
|
|
395
410
|
}
|
|
396
411
|
|
|
397
412
|
// RawElement take care of their children in RawElement#render() method which can be customized
|
|
@@ -411,7 +426,7 @@ export default class DomConverter {
|
|
|
411
426
|
}
|
|
412
427
|
|
|
413
428
|
if ( options.withChildren !== false ) {
|
|
414
|
-
for ( const child of this.viewChildrenToDom( viewNode,
|
|
429
|
+
for ( const child of this.viewChildrenToDom( viewNode, options ) ) {
|
|
415
430
|
domElement.appendChild( child );
|
|
416
431
|
}
|
|
417
432
|
}
|
|
@@ -480,23 +495,22 @@ export default class DomConverter {
|
|
|
480
495
|
* Additionally, this method adds block {@link module:engine/view/filler filler} to the list of children, if needed.
|
|
481
496
|
*
|
|
482
497
|
* @param {module:engine/view/element~Element|module:engine/view/documentfragment~DocumentFragment} viewElement Parent view element.
|
|
483
|
-
* @param {Document} domDocument Document which will be used to create DOM nodes.
|
|
484
498
|
* @param {Object} options See {@link module:engine/view/domconverter~DomConverter#viewToDom} options parameter.
|
|
485
499
|
* @returns {Iterable.<Node>} DOM nodes.
|
|
486
500
|
*/
|
|
487
|
-
* viewChildrenToDom( viewElement,
|
|
501
|
+
* viewChildrenToDom( viewElement, options = {} ) {
|
|
488
502
|
const fillerPositionOffset = viewElement.getFillerOffset && viewElement.getFillerOffset();
|
|
489
503
|
let offset = 0;
|
|
490
504
|
|
|
491
505
|
for ( const childView of viewElement.getChildren() ) {
|
|
492
506
|
if ( fillerPositionOffset === offset ) {
|
|
493
|
-
yield this._getBlockFiller(
|
|
507
|
+
yield this._getBlockFiller();
|
|
494
508
|
}
|
|
495
509
|
|
|
496
510
|
const transparentRendering = childView.is( 'element' ) && childView.getCustomProperty( 'dataPipeline:transparentRendering' );
|
|
497
511
|
|
|
498
512
|
if ( transparentRendering && this.renderingMode == 'data' ) {
|
|
499
|
-
yield* this.viewChildrenToDom( childView,
|
|
513
|
+
yield* this.viewChildrenToDom( childView, options );
|
|
500
514
|
} else {
|
|
501
515
|
if ( transparentRendering ) {
|
|
502
516
|
/**
|
|
@@ -507,14 +521,14 @@ export default class DomConverter {
|
|
|
507
521
|
logWarning( 'domconverter-transparent-rendering-unsupported-in-editing-pipeline', { viewElement: childView } );
|
|
508
522
|
}
|
|
509
523
|
|
|
510
|
-
yield this.viewToDom( childView,
|
|
524
|
+
yield this.viewToDom( childView, options );
|
|
511
525
|
}
|
|
512
526
|
|
|
513
527
|
offset++;
|
|
514
528
|
}
|
|
515
529
|
|
|
516
530
|
if ( fillerPositionOffset === offset ) {
|
|
517
|
-
yield this._getBlockFiller(
|
|
531
|
+
yield this._getBlockFiller();
|
|
518
532
|
}
|
|
519
533
|
}
|
|
520
534
|
|
|
@@ -529,7 +543,7 @@ export default class DomConverter {
|
|
|
529
543
|
const domStart = this.viewPositionToDom( viewRange.start );
|
|
530
544
|
const domEnd = this.viewPositionToDom( viewRange.end );
|
|
531
545
|
|
|
532
|
-
const domRange =
|
|
546
|
+
const domRange = this._domDocument.createRange();
|
|
533
547
|
domRange.setStart( domStart.parent, domStart.offset );
|
|
534
548
|
domRange.setEnd( domEnd.parent, domEnd.offset );
|
|
535
549
|
|
|
@@ -672,7 +686,7 @@ export default class DomConverter {
|
|
|
672
686
|
const attrs = domNode.attributes;
|
|
673
687
|
|
|
674
688
|
if ( attrs ) {
|
|
675
|
-
for ( let
|
|
689
|
+
for ( let l = attrs.length, i = 0; i < l; i++ ) {
|
|
676
690
|
viewElement._setAttribute( attrs[ i ].name, attrs[ i ].value );
|
|
677
691
|
}
|
|
678
692
|
}
|
|
@@ -1091,7 +1105,7 @@ export default class DomConverter {
|
|
|
1091
1105
|
|
|
1092
1106
|
// Since it takes multiple lines of code to check whether a "DOM Position" is before/after another "DOM Position",
|
|
1093
1107
|
// we will use the fact that range will collapse if it's end is before it's start.
|
|
1094
|
-
const range =
|
|
1108
|
+
const range = this._domDocument.createRange();
|
|
1095
1109
|
|
|
1096
1110
|
range.setStart( selection.anchorNode, selection.anchorOffset );
|
|
1097
1111
|
range.setEnd( selection.focusNode, selection.focusOffset );
|
|
@@ -1166,17 +1180,16 @@ export default class DomConverter {
|
|
|
1166
1180
|
* Returns the block {@link module:engine/view/filler filler} node based on the current {@link #blockFillerMode} setting.
|
|
1167
1181
|
*
|
|
1168
1182
|
* @private
|
|
1169
|
-
* @params {Document} domDocument
|
|
1170
1183
|
* @returns {Node} filler
|
|
1171
1184
|
*/
|
|
1172
|
-
_getBlockFiller(
|
|
1185
|
+
_getBlockFiller() {
|
|
1173
1186
|
switch ( this.blockFillerMode ) {
|
|
1174
1187
|
case 'nbsp':
|
|
1175
|
-
return NBSP_FILLER(
|
|
1188
|
+
return NBSP_FILLER( this._domDocument ); // eslint-disable-line new-cap
|
|
1176
1189
|
case 'markedNbsp':
|
|
1177
|
-
return MARKED_NBSP_FILLER(
|
|
1190
|
+
return MARKED_NBSP_FILLER( this._domDocument ); // eslint-disable-line new-cap
|
|
1178
1191
|
case 'br':
|
|
1179
|
-
return BR_FILLER(
|
|
1192
|
+
return BR_FILLER( this._domDocument ); // eslint-disable-line new-cap
|
|
1180
1193
|
}
|
|
1181
1194
|
}
|
|
1182
1195
|
|
|
@@ -1564,7 +1577,7 @@ export default class DomConverter {
|
|
|
1564
1577
|
_shouldRenameElement( elementName ) {
|
|
1565
1578
|
const name = elementName.toLowerCase();
|
|
1566
1579
|
|
|
1567
|
-
return this.renderingMode === 'editing' &&
|
|
1580
|
+
return this.renderingMode === 'editing' && this.unsafeElements.includes( name );
|
|
1568
1581
|
}
|
|
1569
1582
|
|
|
1570
1583
|
/**
|
|
@@ -1577,7 +1590,7 @@ export default class DomConverter {
|
|
|
1577
1590
|
* @returns {Element}
|
|
1578
1591
|
*/
|
|
1579
1592
|
_createReplacementDomElement( elementName, originalDomElement = null ) {
|
|
1580
|
-
const newDomElement =
|
|
1593
|
+
const newDomElement = this._domDocument.createElement( 'span' );
|
|
1581
1594
|
|
|
1582
1595
|
// Mark the span replacing a script as hidden.
|
|
1583
1596
|
newDomElement.setAttribute( UNSAFE_ELEMENT_REPLACEMENT_ATTRIBUTE, elementName );
|
package/src/view/element.js
CHANGED
|
@@ -608,6 +608,9 @@ export default class Element extends Node {
|
|
|
608
608
|
// is changed by e.g. toWidget() function from ckeditor5-widget. Perhaps this should be one of custom props.
|
|
609
609
|
cloned.getFillerOffset = this.getFillerOffset;
|
|
610
610
|
|
|
611
|
+
// Clone unsafe attributes list.
|
|
612
|
+
cloned._unsafeAttributesToRender = this._unsafeAttributesToRender;
|
|
613
|
+
|
|
611
614
|
return cloned;
|
|
612
615
|
}
|
|
613
616
|
|
package/src/view/filler.js
CHANGED
|
@@ -56,7 +56,7 @@ export const NBSP_FILLER = domDocument => domDocument.createTextNode( '\u00A0' )
|
|
|
56
56
|
export const MARKED_NBSP_FILLER = domDocument => {
|
|
57
57
|
const span = domDocument.createElement( 'span' );
|
|
58
58
|
span.dataset.ckeFiller = true;
|
|
59
|
-
span.
|
|
59
|
+
span.innerText = '\u00A0';
|
|
60
60
|
|
|
61
61
|
return span;
|
|
62
62
|
};
|
|
@@ -16,7 +16,7 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
|
16
16
|
* Tab observer introduces the {@link module:engine/view/document~Document#event:tab `Document#tab`} event.
|
|
17
17
|
*
|
|
18
18
|
* Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
|
|
19
|
-
* {@link module:engine/view/view~View} this event is available by default.
|
|
19
|
+
* {@link module:engine/view/view~View}, this event is available by default.
|
|
20
20
|
*
|
|
21
21
|
* @extends module:engine/view/observer/observer~Observer
|
|
22
22
|
*/
|
|
@@ -60,7 +60,7 @@ export default class TabObserver extends Observer {
|
|
|
60
60
|
* Introduced by {@link module:engine/view/observer/tabobserver~TabObserver}.
|
|
61
61
|
*
|
|
62
62
|
* Note that because {@link module:engine/view/observer/tabobserver~TabObserver} is attached by the
|
|
63
|
-
* {@link module:engine/view/view~View} this event is available by default.
|
|
63
|
+
* {@link module:engine/view/view~View}, this event is available by default.
|
|
64
64
|
*
|
|
65
65
|
* @event module:engine/view/document~Document#event:tab
|
|
66
66
|
*
|
package/src/view/renderer.js
CHANGED
|
@@ -229,11 +229,19 @@ export default class Renderer {
|
|
|
229
229
|
this.markedChildren.add( inlineFillerPosition.parent );
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
//
|
|
233
|
-
// by DomConverter.
|
|
232
|
+
// Make sure the inline filler has any parent, so it can be mapped to view position by DomConverter.
|
|
234
233
|
else if ( this._inlineFiller && this._inlineFiller.parentNode ) {
|
|
235
234
|
// While the user is making selection, preserve the inline filler at its original position.
|
|
236
235
|
inlineFillerPosition = this.domConverter.domPositionToView( this._inlineFiller );
|
|
236
|
+
|
|
237
|
+
// While down-casting the document selection attributes, all existing empty
|
|
238
|
+
// attribute elements (for selection position) are removed from the view and DOM,
|
|
239
|
+
// so make sure that we were able to map filler position.
|
|
240
|
+
// https://github.com/ckeditor/ckeditor5/issues/12026
|
|
241
|
+
if ( inlineFillerPosition && inlineFillerPosition.parent.is( '$text' ) ) {
|
|
242
|
+
// The inline filler position is expected to be before the text node.
|
|
243
|
+
inlineFillerPosition = ViewPosition._createBefore( inlineFillerPosition.parent );
|
|
244
|
+
}
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
for ( const element of this.markedAttributes ) {
|
|
@@ -314,7 +322,7 @@ export default class Renderer {
|
|
|
314
322
|
this.domConverter.mapViewToDom( viewElement ).childNodes
|
|
315
323
|
);
|
|
316
324
|
const expectedDomChildren = Array.from(
|
|
317
|
-
this.domConverter.viewChildrenToDom( viewElement,
|
|
325
|
+
this.domConverter.viewChildrenToDom( viewElement, { withChildren: false } )
|
|
318
326
|
);
|
|
319
327
|
const diff = this._diffNodeLists( actualDomChildren, expectedDomChildren );
|
|
320
328
|
const actions = this._findReplaceActions( diff, actualDomChildren, expectedDomChildren );
|
|
@@ -510,7 +518,7 @@ export default class Renderer {
|
|
|
510
518
|
*/
|
|
511
519
|
_updateText( viewText, options ) {
|
|
512
520
|
const domText = this.domConverter.findCorrespondingDomText( viewText );
|
|
513
|
-
const newDomText = this.domConverter.viewToDom( viewText
|
|
521
|
+
const newDomText = this.domConverter.viewToDom( viewText );
|
|
514
522
|
|
|
515
523
|
const actualText = domText.data;
|
|
516
524
|
let expectedText = newDomText.data;
|
|
@@ -589,7 +597,7 @@ export default class Renderer {
|
|
|
589
597
|
const inlineFillerPosition = options.inlineFillerPosition;
|
|
590
598
|
const actualDomChildren = this.domConverter.mapViewToDom( viewElement ).childNodes;
|
|
591
599
|
const expectedDomChildren = Array.from(
|
|
592
|
-
this.domConverter.viewChildrenToDom( viewElement,
|
|
600
|
+
this.domConverter.viewChildrenToDom( viewElement, { bind: true } )
|
|
593
601
|
);
|
|
594
602
|
|
|
595
603
|
// Inline filler element has to be created as it is present in the DOM, but not in the view. It is required
|