@ckeditor/ckeditor5-engine 34.1.0 → 35.0.1

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 CHANGED
@@ -11,6 +11,10 @@ Sources of Intellectual Property Included in CKEditor
11
11
 
12
12
  Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
13
13
 
14
+ The following libraries are included in CKEditor under the [MIT license](https://opensource.org/licenses/MIT):
15
+
16
+ * lodash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
17
+
14
18
  Trademarks
15
19
  ----------
16
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-engine",
3
- "version": "34.1.0",
3
+ "version": "35.0.1",
4
4
  "description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
5
5
  "keywords": [
6
6
  "wysiwyg",
@@ -23,30 +23,30 @@
23
23
  ],
24
24
  "main": "src/index.js",
25
25
  "dependencies": {
26
- "@ckeditor/ckeditor5-utils": "^34.1.0",
26
+ "@ckeditor/ckeditor5-utils": "^35.0.1",
27
27
  "lodash-es": "^4.17.15"
28
28
  },
29
29
  "devDependencies": {
30
- "@ckeditor/ckeditor5-basic-styles": "^34.1.0",
31
- "@ckeditor/ckeditor5-block-quote": "^34.1.0",
32
- "@ckeditor/ckeditor5-clipboard": "^34.1.0",
33
- "@ckeditor/ckeditor5-cloud-services": "^34.1.0",
34
- "@ckeditor/ckeditor5-core": "^34.1.0",
35
- "@ckeditor/ckeditor5-editor-classic": "^34.1.0",
36
- "@ckeditor/ckeditor5-enter": "^34.1.0",
37
- "@ckeditor/ckeditor5-essentials": "^34.1.0",
38
- "@ckeditor/ckeditor5-heading": "^34.1.0",
39
- "@ckeditor/ckeditor5-image": "^34.1.0",
40
- "@ckeditor/ckeditor5-link": "^34.1.0",
41
- "@ckeditor/ckeditor5-list": "^34.1.0",
42
- "@ckeditor/ckeditor5-mention": "^34.1.0",
43
- "@ckeditor/ckeditor5-paragraph": "^34.1.0",
44
- "@ckeditor/ckeditor5-table": "^34.1.0",
45
- "@ckeditor/ckeditor5-theme-lark": "^34.1.0",
46
- "@ckeditor/ckeditor5-typing": "^34.1.0",
47
- "@ckeditor/ckeditor5-ui": "^34.1.0",
48
- "@ckeditor/ckeditor5-undo": "^34.1.0",
49
- "@ckeditor/ckeditor5-widget": "^34.1.0",
30
+ "@ckeditor/ckeditor5-basic-styles": "^35.0.1",
31
+ "@ckeditor/ckeditor5-block-quote": "^35.0.1",
32
+ "@ckeditor/ckeditor5-clipboard": "^35.0.1",
33
+ "@ckeditor/ckeditor5-cloud-services": "^35.0.1",
34
+ "@ckeditor/ckeditor5-core": "^35.0.1",
35
+ "@ckeditor/ckeditor5-editor-classic": "^35.0.1",
36
+ "@ckeditor/ckeditor5-enter": "^35.0.1",
37
+ "@ckeditor/ckeditor5-essentials": "^35.0.1",
38
+ "@ckeditor/ckeditor5-heading": "^35.0.1",
39
+ "@ckeditor/ckeditor5-image": "^35.0.1",
40
+ "@ckeditor/ckeditor5-link": "^35.0.1",
41
+ "@ckeditor/ckeditor5-list": "^35.0.1",
42
+ "@ckeditor/ckeditor5-mention": "^35.0.1",
43
+ "@ckeditor/ckeditor5-paragraph": "^35.0.1",
44
+ "@ckeditor/ckeditor5-table": "^35.0.1",
45
+ "@ckeditor/ckeditor5-theme-lark": "^35.0.1",
46
+ "@ckeditor/ckeditor5-typing": "^35.0.1",
47
+ "@ckeditor/ckeditor5-ui": "^35.0.1",
48
+ "@ckeditor/ckeditor5-undo": "^35.0.1",
49
+ "@ckeditor/ckeditor5-widget": "^35.0.1",
50
50
  "webpack": "^5.58.1",
51
51
  "webpack-cli": "^4.9.0"
52
52
  },
@@ -67,6 +67,7 @@
67
67
  "lang",
68
68
  "src",
69
69
  "theme",
70
- "ckeditor5-metadata.json"
70
+ "ckeditor5-metadata.json",
71
+ "CHANGELOG.md"
71
72
  ]
72
73
  }
@@ -465,6 +465,11 @@ export function convertText() {
465
465
  return;
466
466
  }
467
467
 
468
+ // Do not auto-paragraph whitespaces.
469
+ if ( data.viewItem.data.trim().length == 0 ) {
470
+ return;
471
+ }
472
+
468
473
  position = wrapInParagraph( position, writer );
469
474
  }
470
475
 
@@ -7,7 +7,7 @@
7
7
  * @module engine/dataprocessor/htmldataprocessor
8
8
  */
9
9
 
10
- /* globals document, DOMParser */
10
+ /* globals DOMParser */
11
11
 
12
12
  import BasicHtmlWriter from './basichtmlwriter';
13
13
  import DomConverter from '../view/domconverter';
@@ -56,7 +56,7 @@ export default class HtmlDataProcessor {
56
56
  */
57
57
  toData( viewFragment ) {
58
58
  // Convert view DocumentFragment to DOM DocumentFragment.
59
- const domFragment = this.domConverter.viewToDom( viewFragment, document );
59
+ const domFragment = this.domConverter.viewToDom( viewFragment );
60
60
 
61
61
  // Convert DOM DocumentFragment to HTML output.
62
62
  return this.htmlWriter.getHtml( domFragment );
@@ -7,7 +7,7 @@
7
7
  * @module engine/dataprocessor/xmldataprocessor
8
8
  */
9
9
 
10
- /* globals DOMParser, document */
10
+ /* globals DOMParser */
11
11
 
12
12
  import BasicHtmlWriter from './basichtmlwriter';
13
13
  import DomConverter from '../view/domconverter';
@@ -71,7 +71,7 @@ export default class XmlDataProcessor {
71
71
  */
72
72
  toData( viewFragment ) {
73
73
  // Convert view DocumentFragment to DOM DocumentFragment.
74
- const domFragment = this.domConverter.viewToDom( viewFragment, document );
74
+ const domFragment = this.domConverter.viewToDom( viewFragment );
75
75
 
76
76
  // Convert DOM DocumentFragment to XML output.
77
77
  // There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
@@ -7,7 +7,7 @@
7
7
  * @module engine/view/domconverter
8
8
  */
9
9
 
10
- /* globals document, Node, NodeFilter, DOMParser, Text */
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, domDocument, options = {} ) {
368
+ viewToDom( viewNode, options = {} ) {
354
369
  if ( viewNode.is( '$text' ) ) {
355
370
  const textData = this._processDataFromViewText( viewNode );
356
371
 
357
- return domDocument.createTextNode( textData );
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 = domDocument.createDocumentFragment();
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 = domDocument.createComment( viewNode.getCustomProperty( '$rawContent' ) );
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( domDocument, this );
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 = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
407
+ domElement = this._domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );
393
408
  } else {
394
- domElement = domDocument.createElement( viewNode.name );
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, domDocument, options ) ) {
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, domDocument, options = {} ) {
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( domDocument );
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, domDocument, options );
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, domDocument, options );
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( domDocument );
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 = document.createRange();
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 i = attrs.length - 1; i >= 0; i-- ) {
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 = document.createRange();
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( domDocument ) {
1185
+ _getBlockFiller() {
1173
1186
  switch ( this.blockFillerMode ) {
1174
1187
  case 'nbsp':
1175
- return NBSP_FILLER( domDocument ); // eslint-disable-line new-cap
1188
+ return NBSP_FILLER( this._domDocument ); // eslint-disable-line new-cap
1176
1189
  case 'markedNbsp':
1177
- return MARKED_NBSP_FILLER( domDocument ); // eslint-disable-line new-cap
1190
+ return MARKED_NBSP_FILLER( this._domDocument ); // eslint-disable-line new-cap
1178
1191
  case 'br':
1179
- return BR_FILLER( domDocument ); // eslint-disable-line new-cap
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' && UNSAFE_ELEMENTS.includes( name );
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 = document.createElement( 'span' );
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 );
@@ -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
 
@@ -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.innerHTML = '\u00A0';
59
+ span.innerText = '\u00A0';
60
60
 
61
61
  return span;
62
62
  };
@@ -229,11 +229,19 @@ export default class Renderer {
229
229
  this.markedChildren.add( inlineFillerPosition.parent );
230
230
  }
231
231
  }
232
- // Paranoid check: we make sure the inline filler has any parent so it can be mapped to view position
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, domElement.ownerDocument, { withChildren: false } )
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, domText.ownerDocument );
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, domElement.ownerDocument, { bind: true } )
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