@ckeditor/ckeditor5-engine 27.1.0 → 29.2.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.
Files changed (43) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/package.json +22 -21
  4. package/src/controller/datacontroller.js +28 -7
  5. package/src/controller/editingcontroller.js +1 -1
  6. package/src/conversion/conversion.js +4 -4
  7. package/src/conversion/downcastdispatcher.js +6 -2
  8. package/src/conversion/downcasthelpers.js +48 -39
  9. package/src/conversion/mapper.js +1 -0
  10. package/src/conversion/modelconsumable.js +10 -5
  11. package/src/conversion/upcastdispatcher.js +6 -6
  12. package/src/conversion/upcasthelpers.js +34 -30
  13. package/src/dataprocessor/dataprocessor.jsdoc +5 -5
  14. package/src/dataprocessor/htmldataprocessor.js +38 -9
  15. package/src/dataprocessor/xmldataprocessor.js +5 -5
  16. package/src/index.js +1 -0
  17. package/src/model/element.js +3 -3
  18. package/src/model/liveposition.js +1 -1
  19. package/src/model/model.js +5 -5
  20. package/src/model/node.js +3 -3
  21. package/src/model/range.js +5 -3
  22. package/src/model/schema.js +103 -39
  23. package/src/model/selection.js +1 -1
  24. package/src/model/treewalker.js +3 -4
  25. package/src/model/utils/deletecontent.js +17 -4
  26. package/src/model/utils/insertcontent.js +15 -15
  27. package/src/model/utils/selection-post-fixer.js +1 -1
  28. package/src/view/documentselection.js +2 -2
  29. package/src/view/domconverter.js +150 -72
  30. package/src/view/downcastwriter.js +2 -1
  31. package/src/view/element.js +3 -2
  32. package/src/view/filler.js +4 -4
  33. package/src/view/matcher.js +419 -93
  34. package/src/view/observer/focusobserver.js +7 -3
  35. package/src/view/observer/mouseobserver.js +1 -1
  36. package/src/view/renderer.js +9 -1
  37. package/src/view/selection.js +2 -2
  38. package/src/view/styles/background.js +2 -0
  39. package/src/view/styles/border.js +107 -21
  40. package/src/view/styles/utils.js +1 -1
  41. package/src/view/stylesmap.js +45 -5
  42. package/src/view/upcastwriter.js +12 -11
  43. package/src/view/view.js +5 -0
@@ -7,7 +7,6 @@ import Matcher from '../view/matcher';
7
7
  import ConversionHelpers from './conversionhelpers';
8
8
 
9
9
  import { cloneDeep } from 'lodash-es';
10
- import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
11
10
 
12
11
  import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
13
12
  import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
@@ -173,7 +172,7 @@ export default class UpcastHelpers extends ConversionHelpers {
173
172
  * View attribute to model attribute conversion helper.
174
173
  *
175
174
  * This conversion results in setting an attribute on a model node. For example, view `<img src="foo.jpg"></img>` becomes
176
- * `<image source="foo.jpg"></image>` in the model.
175
+ * `<imageBlock source="foo.jpg"></imageBlock>` in the model.
177
176
  *
178
177
  * This helper is meant to convert view attributes from view elements which got converted to the model, so the view attribute
179
178
  * is set only on the corresponding model node:
@@ -294,13 +293,17 @@ export default class UpcastHelpers extends ConversionHelpers {
294
293
  /**
295
294
  * View element to model marker conversion helper.
296
295
  *
297
- * **Note**: This method was deprecated. Please use {@link #dataToMarker} instead.
298
- *
299
296
  * This conversion results in creating a model marker. For example, if the marker was stored in a view as an element:
300
297
  * `<p>Fo<span data-marker="comment" data-comment-id="7"></span>o</p><p>B<span data-marker="comment" data-comment-id="7"></span>ar</p>`,
301
298
  * after the conversion is done, the marker will be available in
302
299
  * {@link module:engine/model/model~Model#markers model document markers}.
303
300
  *
301
+ * **Note**: When this helper is used in the data upcast in combination with
302
+ * {@link module:engine/conversion/downcasthelpers~DowncastHelpers#markerToData `#markerToData()`} in the data downcast,
303
+ * then invalid HTML code (e.g. a span between table cells) may be produced by the latter converter.
304
+ *
305
+ * In most of the cases, the {@link #dataToMarker} should be used instead.
306
+ *
304
307
  * editor.conversion.for( 'upcast' ).elementToMarker( {
305
308
  * view: 'marker-search',
306
309
  * model: 'search'
@@ -330,7 +333,6 @@ export default class UpcastHelpers extends ConversionHelpers {
330
333
  * See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
331
334
  * to the conversion process.
332
335
  *
333
- * @deprecated
334
336
  * @method #elementToMarker
335
337
  * @param {Object} config Conversion configuration.
336
338
  * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
@@ -340,15 +342,6 @@ export default class UpcastHelpers extends ConversionHelpers {
340
342
  * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
341
343
  */
342
344
  elementToMarker( config ) {
343
- /**
344
- * The {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToMarker `UpcastHelpers#elementToMarker()`}
345
- * method was deprecated and will be removed in the near future.
346
- * Please use {@link module:engine/conversion/upcasthelpers~UpcastHelpers#dataToMarker `UpcastHelpers#dataToMarker()`} instead.
347
- *
348
- * @error upcast-helpers-element-to-marker-deprecated
349
- */
350
- logWarning( 'upcast-helpers-element-to-marker-deprecated' );
351
-
352
345
  return this.add( upcastElementToMarker( config ) );
353
346
  }
354
347
 
@@ -389,7 +382,7 @@ export default class UpcastHelpers extends ConversionHelpers {
389
382
  *
390
383
  * // Model:
391
384
  * <paragraph>Foo[bar</paragraph>
392
- * <image src="abc.jpg"></image>]
385
+ * <imageBlock src="abc.jpg"></imageBlock>]
393
386
  *
394
387
  * Where `[]` are boundaries of a marker that will receive the `comment:commentId:uid` name.
395
388
  *
@@ -651,11 +644,11 @@ function upcastDataToMarker( config ) {
651
644
  // Below is a hack that is needed to properly handle `converterPriority` for both elements and attributes.
652
645
  // Attribute conversion needs to be performed *after* element conversion.
653
646
  // This converter handles both element conversion and attribute conversion, which means that if a single
654
- // `config.converterPriority` is used, it will lead to problems. For example, if `'high'` priority is used,
655
- // then attribute conversion will be performed before a lot of element upcast converters.
656
- // On the other hand we want to support `config.converterPriority` and overwriting conveters.
647
+ // `config.converterPriority` is used, it will lead to problems. For example, if the `'high'` priority is used,
648
+ // the attribute conversion will be performed before a lot of element upcast converters.
649
+ // On the other hand, we want to support `config.converterPriority` and converter overwriting.
657
650
  //
658
- // To have it work, we need to do some extra processing for priority for attribute converter.
651
+ // To make it work, we need to do some extra processing for priority for attribute converter.
659
652
  // Priority `'low'` value should be the base value and then we will change it depending on `config.converterPriority` value.
660
653
  //
661
654
  // This hack probably would not be needed if attributes are upcasted separately.
@@ -682,12 +675,23 @@ function upcastAttributeToMarker( config ) {
682
675
  return ( evt, data, conversionApi ) => {
683
676
  const attrName = `data-${ config.view }`;
684
677
 
678
+ // Check if any attribute for the given view item can be consumed before changing the conversion data
679
+ // and consuming view items with these attributes.
680
+ if (
681
+ !conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-end-after' } ) &&
682
+ !conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-start-after' } ) &&
683
+ !conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-end-before' } ) &&
684
+ !conversionApi.consumable.test( data.viewItem, { attributes: attrName + '-start-before' } )
685
+ ) {
686
+ return;
687
+ }
688
+
685
689
  // This converter wants to add a model element, marking a marker, before/after an element (or maybe even group of elements).
686
690
  // To do that, we can use `data.modelRange` which is set on an element (or a group of elements) that has been upcasted.
687
691
  // But, if the processed view element has not been upcasted yet (it does not have been converted), we need to
688
692
  // fire conversion for its children first, then we will have `data.modelRange` available.
689
693
  if ( !data.modelRange ) {
690
- data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
694
+ Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
691
695
  }
692
696
 
693
697
  if ( conversionApi.consumable.consume( data.viewItem, { attributes: attrName + '-end-after' } ) ) {
@@ -868,15 +872,6 @@ function prepareToAttributeConverter( config, shallow ) {
868
872
  return;
869
873
  }
870
874
 
871
- const modelKey = config.model.key;
872
- const modelValue = typeof config.model.value == 'function' ?
873
- config.model.value( data.viewItem, conversionApi ) : config.model.value;
874
-
875
- // Do not convert if attribute building function returned falsy value.
876
- if ( modelValue === null ) {
877
- return;
878
- }
879
-
880
875
  if ( onlyViewNameIsDefined( config.view, data.viewItem ) ) {
881
876
  match.match.name = true;
882
877
  } else {
@@ -889,11 +884,20 @@ function prepareToAttributeConverter( config, shallow ) {
889
884
  return;
890
885
  }
891
886
 
887
+ const modelKey = config.model.key;
888
+ const modelValue = typeof config.model.value == 'function' ?
889
+ config.model.value( data.viewItem, conversionApi ) : config.model.value;
890
+
891
+ // Do not convert if attribute building function returned falsy value.
892
+ if ( modelValue === null ) {
893
+ return;
894
+ }
895
+
892
896
  // Since we are converting to attribute we need a range on which we will set the attribute.
893
897
  // If the range is not created yet, let's create it by converting children of the current node first.
894
898
  if ( !data.modelRange ) {
895
899
  // Convert children and set conversion result as a current data.
896
- data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
900
+ Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
897
901
  }
898
902
 
899
903
  // Set attribute on current `output`. `Schema` is checked inside this helper function.
@@ -51,14 +51,14 @@
51
51
  */
52
52
 
53
53
  /**
54
- * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
55
- * (`<span data-cke-filler="true">&nbsp;</span>`), instead of regular nbsp characters (`&nbsp;`).
54
+ * If the processor is set to use marked fillers, it will insert `&nbsp;` fillers wrapped in `<span>` elements
55
+ * (`<span data-cke-filler="true">&nbsp;</span>`) instead of regular `&nbsp;` characters.
56
56
  *
57
- * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
58
- * data with additional markup.
57
+ * This mode allows for more precise handling of block fillers (so they do not leak into the editor content) but bloats the
58
+ * editor data with additional markup.
59
59
  *
60
60
  * This mode may be required by some features and will be turned on by them automatically.
61
61
  *
62
62
  * @method #useFillerType
63
- * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
63
+ * @param {'default'|'marked'} type Whether to use the default or marked `&nbsp;` block fillers.
64
64
  */
@@ -7,7 +7,7 @@
7
7
  * @module engine/dataprocessor/htmldataprocessor
8
8
  */
9
9
 
10
- /* globals document, DOMParser */
10
+ /* globals document, DOMParser, Node */
11
11
 
12
12
  import BasicHtmlWriter from './basichtmlwriter';
13
13
  import DomConverter from '../view/domconverter';
@@ -94,15 +94,15 @@ export default class HtmlDataProcessor {
94
94
  }
95
95
 
96
96
  /**
97
- * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
98
- * (`<span data-cke-filler="true">&nbsp;</span>`), instead of regular nbsp characters (`&nbsp;`).
97
+ * If the processor is set to use marked fillers, it will insert `&nbsp;` fillers wrapped in `<span>` elements
98
+ * (`<span data-cke-filler="true">&nbsp;</span>`) instead of regular `&nbsp;` characters.
99
99
  *
100
- * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
101
- * data with additional markup.
100
+ * This mode allows for a more precise handling of the block fillers (so they do not leak into the editor content) but
101
+ * bloats the editor data with additional markup.
102
102
  *
103
103
  * This mode may be required by some features and will be turned on by them automatically.
104
104
  *
105
- * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
105
+ * @param {'default'|'marked'} type Whether to use the default or the marked `&nbsp;` block fillers.
106
106
  */
107
107
  useFillerType( type ) {
108
108
  this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
@@ -119,10 +119,39 @@ export default class HtmlDataProcessor {
119
119
  _toDom( data ) {
120
120
  const document = this._domParser.parseFromString( data, 'text/html' );
121
121
  const fragment = document.createDocumentFragment();
122
- const nodes = document.body.childNodes;
123
122
 
124
- while ( nodes.length > 0 ) {
125
- fragment.appendChild( nodes[ 0 ] );
123
+ // The rules for parsing an HTML string can be read on https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhtml.
124
+ //
125
+ // In short, parsing tokens in an HTML string starts with the so-called "initial" insertion mode. When a DOM parser is in this
126
+ // state and encounters a comment node, it inserts this comment node as the last child of the newly-created `HTMLDocument` object.
127
+ // The parser then proceeds to successive insertion modes during parsing subsequent tokens and appends in the `HTMLDocument` object
128
+ // other nodes (like <html>, <head>, <body>). This causes that the first leading comments from HTML string become the first nodes
129
+ // in the `HTMLDocument` object, but not in the <body> collection, because they are ultimately located before the <html> element.
130
+ //
131
+ // Therefore, so that such leading comments do not disappear, they all are moved from the `HTMLDocument` object to the document
132
+ // fragment, until the <html> element is encountered.
133
+ //
134
+ // See: https://github.com/ckeditor/ckeditor5/issues/9861.
135
+ let documentChildNode = document.firstChild;
136
+
137
+ while ( !documentChildNode.isSameNode( document.documentElement ) ) {
138
+ const node = documentChildNode;
139
+
140
+ documentChildNode = documentChildNode.nextSibling;
141
+
142
+ // It seems that `DOMParser#parseFromString()` adds only comment nodes directly to the `HTMLDocument` object, before the <html>
143
+ // node. The condition below is just to be sure we are moving only comment nodes.
144
+
145
+ /* istanbul ignore else */
146
+ if ( node.nodeType == Node.COMMENT_NODE ) {
147
+ fragment.appendChild( node );
148
+ }
149
+ }
150
+
151
+ const bodyChildNodes = document.body.childNodes;
152
+
153
+ while ( bodyChildNodes.length > 0 ) {
154
+ fragment.appendChild( bodyChildNodes[ 0 ] );
126
155
  }
127
156
 
128
157
  return fragment;
@@ -111,15 +111,15 @@ export default class XmlDataProcessor {
111
111
  }
112
112
 
113
113
  /**
114
- * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
115
- * (`<span data-cke-filler="true">&nbsp;</span>`), instead of regular nbsp characters (`&nbsp;`).
114
+ * If the processor is set to use marked fillers, it will insert `&nbsp;` fillers wrapped in `<span>` elements
115
+ * (`<span data-cke-filler="true">&nbsp;</span>`) instead of regular `&nbsp;` characters.
116
116
  *
117
- * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
118
- * data with additional markup.
117
+ * This mode allows for a more precise handling of block fillers (so they do not leak into editor content) but
118
+ * bloats the editor data with additional markup.
119
119
  *
120
120
  * This mode may be required by some features and will be turned on by them automatically.
121
121
  *
122
- * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
122
+ * @param {'default'|'marked'} type Whether to use the default or the marked `&nbsp;` block fillers.
123
123
  */
124
124
  useFillerType( type ) {
125
125
  this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
package/src/index.js CHANGED
@@ -30,6 +30,7 @@ export { default as TreeWalker } from './model/treewalker';
30
30
  export { default as Element } from './model/element';
31
31
 
32
32
  export { default as DomConverter } from './view/domconverter';
33
+ export { default as Renderer } from './view/renderer';
33
34
  export { default as ViewDocument } from './view/document';
34
35
 
35
36
  export { getFillerOffset } from './view/containerelement';
@@ -104,9 +104,9 @@ export default class Element extends Node {
104
104
  * Assuming that the object being checked is an element, you can also check its
105
105
  * {@link module:engine/model/element~Element#name name}:
106
106
  *
107
- * element.is( 'element', 'image' ); // -> true if this is an <image> element
108
- * element.is( 'element', 'image' ); // -> same as above
109
- * text.is( 'element', 'image' ); -> false
107
+ * element.is( 'element', 'imageBlock' ); // -> true if this is an <imageBlock> element
108
+ * element.is( 'element', 'imageBlock' ); // -> same as above
109
+ * text.is( 'element', 'imageBlock' ); -> false
110
110
  *
111
111
  * {@link module:engine/model/node~Node#is Check the entire list of model objects} which implement the `is()` method.
112
112
  *
@@ -96,7 +96,7 @@ export default class LivePosition extends Position {
96
96
  *
97
97
  * @param {module:engine/model/position~Position} position
98
98
  * @param {module:engine/model/position~PositionStickiness} [stickiness]
99
- * @returns {module:engine/model/position~Position}
99
+ * @returns {module:engine/model/liveposition~LivePosition}
100
100
  */
101
101
  static fromPosition( position, stickiness ) {
102
102
  return new this( position.root, position.path.slice(), stickiness ? stickiness : position.stickiness );
@@ -109,15 +109,15 @@ export default class Model {
109
109
 
110
110
  this.schema.register( '$clipboardHolder', {
111
111
  allowContentOf: '$root',
112
+ allowChildren: '$text',
112
113
  isLimit: true
113
114
  } );
114
- this.schema.extend( '$text', { allowIn: '$clipboardHolder' } );
115
115
 
116
116
  this.schema.register( '$documentFragment', {
117
117
  allowContentOf: '$root',
118
+ allowChildren: '$text',
118
119
  isLimit: true
119
120
  } );
120
- this.schema.extend( '$text', { allowIn: '$documentFragment' } );
121
121
 
122
122
  // An element needed by the `upcastElementToMarker` converter.
123
123
  // This element temporarily represents a marker boundary during the conversion process and is removed
@@ -463,14 +463,14 @@ export default class Model {
463
463
  * @param {Boolean} [options.doNotAutoparagraph=false] Whether to create a paragraph if after content deletion selection is moved
464
464
  * to a place where text cannot be inserted.
465
465
  *
466
- * For example `<paragraph>x</paragraph>[<image src="foo.jpg"></image>]` will become:
466
+ * For example `<paragraph>x</paragraph>[<imageBlock src="foo.jpg"></imageBlock>]` will become:
467
467
  *
468
468
  * * `<paragraph>x</paragraph><paragraph>[]</paragraph>` with the option disabled (`doNotAutoparagraph == false`)
469
469
  * * `<paragraph>x[]</paragraph>` with the option enabled (`doNotAutoparagraph == true`).
470
470
  *
471
471
  * **Note:** if there is no valid position for the selection, the paragraph will always be created:
472
472
  *
473
- * `[<image src="foo.jpg"></image>]` -> `<paragraph>[]</paragraph>`.
473
+ * `[<imageBlock src="foo.jpg"></imageBlock>]` -> `<paragraph>[]</paragraph>`.
474
474
  *
475
475
  * @param {'forward'|'backward'} [options.direction='backward'] The direction in which the content is being consumed.
476
476
  * Deleting backward corresponds to using the <kbd>Backspace</kbd> key, while deleting content forward corresponds to
@@ -559,7 +559,7 @@ export default class Model {
559
559
  * {@link module:engine/model/markercollection~Marker#_affectsData affects data}.
560
560
  *
561
561
  * This means that a range containing an empty `<paragraph></paragraph>` is not considered to have a meaningful content.
562
- * However, a range containing an `<image></image>` (which would normally be marked in the schema as an object element)
562
+ * However, a range containing an `<imageBlock></imageBlock>` (which would normally be marked in the schema as an object element)
563
563
  * is considered non-empty.
564
564
  *
565
565
  * @param {module:engine/model/range~Range|module:engine/model/element~Element} rangeOrElement Range or element to check.
package/src/model/node.js CHANGED
@@ -406,9 +406,9 @@ export default class Node {
406
406
  *
407
407
  * By using this method it is also possible to check a name of an element:
408
408
  *
409
- * imageElement.is( 'element', 'image' ); // -> true
410
- * imageElement.is( 'element', 'image' ); // -> same as above
411
- * imageElement.is( 'model:element', 'image' ); // -> same as above, but more precise
409
+ * imageElement.is( 'element', 'imageBlock' ); // -> true
410
+ * imageElement.is( 'element', 'imageBlock' ); // -> same as above
411
+ * imageElement.is( 'model:element', 'imageBlock' ); // -> same as above, but more precise
412
412
  *
413
413
  * The list of model objects which implement the `is()` method:
414
414
  *
@@ -25,8 +25,9 @@ export default class Range {
25
25
  /**
26
26
  * Creates a range spanning from `start` position to `end` position.
27
27
  *
28
- * @param {module:engine/model/position~Position} start Start position.
29
- * @param {module:engine/model/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
28
+ * @param {module:engine/model/position~Position} start The start position.
29
+ * @param {module:engine/model/position~Position} [end] The end position. If not set,
30
+ * the range will be collapsed at the `start` position.
30
31
  */
31
32
  constructor( start, end = null ) {
32
33
  /**
@@ -426,6 +427,7 @@ export default class Range {
426
427
  * @param {Boolean} [options.singleCharacters=false]
427
428
  * @param {Boolean} [options.shallow=false]
428
429
  * @param {Boolean} [options.ignoreElementEnd=false]
430
+ * @returns {module:engine/model/treewalker~TreeWalker}
429
431
  */
430
432
  getWalker( options = {} ) {
431
433
  options.boundaries = this;
@@ -1031,7 +1033,7 @@ export default class Range {
1031
1033
  *
1032
1034
  * @param {Object} json Plain object to be converted to `Range`.
1033
1035
  * @param {module:engine/model/document~Document} doc Document object that will be range owner.
1034
- * @returns {module:engine/model/element~Element} `Range` instance created using given plain object.
1036
+ * @returns {module:engine/model/range~Range} `Range` instance created using given plain object.
1035
1037
  */
1036
1038
  static fromJSON( json, doc ) {
1037
1039
  return new this( Position.fromJSON( json.start, doc ), Position.fromJSON( json.end, doc ) );