@ckeditor/ckeditor5-engine 47.1.0 → 47.2.0-alpha.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/README.md CHANGED
@@ -2,7 +2,7 @@ CKEditor 5 editing engine
2
2
  ========================================
3
3
 
4
4
  [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-engine.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)
5
- [![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
5
+ [![codecov](https://codecov.io/gh/ckeditor/ckeditor5/branch/master/graph/badge.svg)](https://codecov.io/gh/ckeditor/ckeditor5)
6
6
  [![CircleCI](https://circleci.com/gh/ckeditor/ckeditor5.svg?style=shield)](https://app.circleci.com/pipelines/github/ckeditor/ckeditor5?branch=master)
7
7
 
8
8
  The CKEditor 5 editing engine implements a flexible MVC-based architecture for creating rich text editing features.
package/dist/index.js CHANGED
@@ -194,6 +194,18 @@ let hasDisplayedPlaceholderDeprecationWarning = false;
194
194
  continue;
195
195
  }
196
196
  const hostElement = getChildPlaceholderHostSubstitute(element);
197
+ // If host element changed, remove the placeholder from the previous one.
198
+ // This can happen when user replaces the first child element of the parent element
199
+ // with new one, but the previous one is still in the view tree.
200
+ // See:
201
+ // https://github.com/ckeditor/ckeditor5/issues/14354
202
+ // https://github.com/ckeditor/ckeditor5/issues/18149
203
+ if (hostElement !== config.hostElement && config.hostElement) {
204
+ writer.removeAttribute('data-placeholder', config.hostElement);
205
+ hideViewPlaceholder(writer, config.hostElement);
206
+ config.hostElement = null;
207
+ wasViewModified = true;
208
+ }
197
209
  // When not a direct host, it could happen that there is no child element
198
210
  // capable of displaying a placeholder.
199
211
  if (!hostElement) {
@@ -16810,13 +16822,14 @@ ModelRange.prototype.is = function(type) {
16810
16822
  * @param markers Markers related to the model fragment to convert.
16811
16823
  * @param writer The view writer that should be used to modify the view document.
16812
16824
  */ convertChanges(differ, markers, writer) {
16813
- const conversionApi = this._createConversionApi(writer, differ.getRefreshedItems());
16825
+ const refreshedItems = differ.getRefreshedItems();
16826
+ const conversionApi = this._createConversionApi(writer, refreshedItems);
16814
16827
  // Before the view is updated, remove markers which have changed.
16815
16828
  for (const change of differ.getMarkersToRemove()){
16816
16829
  this._convertMarkerRemove(change.name, change.range, conversionApi);
16817
16830
  }
16818
16831
  // Let features modify the change list (for example to allow reconversion).
16819
- const changes = this._reduceChanges(differ.getChanges());
16832
+ const changes = this._reduceChanges(differ.getChanges(), refreshedItems);
16820
16833
  // Convert changes that happened on model tree.
16821
16834
  for (const entry of changes){
16822
16835
  if (entry.type === 'insert') {
@@ -16988,7 +17001,7 @@ ModelRange.prototype.is = function(type) {
16988
17001
  }
16989
17002
  }
16990
17003
  /**
16991
- * Fires re-insertion conversion (with a `reconversion` flag passed to `insert` events)
17004
+ * Fires re-insertion conversion (with a `reconversion` flag passed to `remove` and `insert` events)
16992
17005
  * of a range of elements (only elements on the range depth, without children).
16993
17006
  *
16994
17007
  * For each node in the range on its depth (without children), {@link #event:insert `insert` event} is fired.
@@ -17007,6 +17020,13 @@ ModelRange.prototype.is = function(type) {
17007
17020
  this._addConsumablesForInsert(conversionApi.consumable, walkerValues);
17008
17021
  // Fire a separate insert event for each node and text fragment contained shallowly in the range.
17009
17022
  for (const data of walkerValues.map(walkerValueToEventData)){
17023
+ // For backward compatibility and handlers that does not recognize reconversion.
17024
+ this.fire(`remove:${data.item.is('element') ? data.item.name : '$text'}`, {
17025
+ position: data.range.start,
17026
+ length: data.item.offsetSize,
17027
+ reconversion: true
17028
+ }, conversionApi);
17029
+ // Reinsert the view element.
17010
17030
  this._testAndFire('insert', {
17011
17031
  ...data,
17012
17032
  reconversion: true
@@ -17086,9 +17106,10 @@ ModelRange.prototype.is = function(type) {
17086
17106
  * `DowncastHelpers.elementToStructure()`} is using this event to trigger reconversion.
17087
17107
  *
17088
17108
  * @fires reduceChanges
17089
- */ _reduceChanges(changes) {
17109
+ */ _reduceChanges(changes, refreshedItems) {
17090
17110
  const data = {
17091
- changes
17111
+ changes,
17112
+ refreshedItems
17092
17113
  };
17093
17114
  this.fire('reduceChanges', data);
17094
17115
  return data.changes;
@@ -20771,6 +20792,10 @@ function cloneNodes(nodes) {
20771
20792
  * @internal
20772
20793
  */ function remove() {
20773
20794
  return (evt, data, conversionApi)=>{
20795
+ // Ignore reconversion related remove as it is handled in the `insert` of reconversion.
20796
+ if (data.reconversion) {
20797
+ return;
20798
+ }
20774
20799
  // Find the view range start position by mapping the model position at which the remove happened.
20775
20800
  const viewStart = conversionApi.mapper.toViewPosition(data.position);
20776
20801
  const modelEnd = data.position.getShiftedBy(data.length);
@@ -20779,14 +20804,7 @@ function cloneNodes(nodes) {
20779
20804
  });
20780
20805
  const viewRange = conversionApi.writer.createRange(viewStart, viewEnd);
20781
20806
  // Trim the range to remove in case some UI elements are on the view range boundaries.
20782
- const removed = conversionApi.writer.remove(viewRange.getTrimmed());
20783
- // After the range is removed, unbind all view elements from the model.
20784
- // Range inside view document fragment is used to unbind deeply.
20785
- for (const child of conversionApi.writer.createRangeIn(removed).getItems()){
20786
- conversionApi.mapper.unbindViewElement(child, {
20787
- defer: true
20788
- });
20789
- }
20807
+ removeRangeAndUnbind(viewRange.getTrimmed(), conversionApi);
20790
20808
  };
20791
20809
  }
20792
20810
  /**
@@ -21030,7 +21048,7 @@ function cloneNodes(nodes) {
21030
21048
  }
21031
21049
  // Consume an element insertion and all present attributes that are specified as a reconversion triggers.
21032
21050
  consumer(data.item, conversionApi.consumable);
21033
- const viewPosition = conversionApi.mapper.toViewPosition(data.range.start);
21051
+ const viewPosition = data.reconversion && removeElementAndUnbind(data.item, conversionApi) || conversionApi.mapper.toViewPosition(data.range.start);
21034
21052
  conversionApi.mapper.bindElements(data.item, viewElement);
21035
21053
  conversionApi.writer.insert(viewPosition, viewElement);
21036
21054
  // Convert attributes before converting children.
@@ -21073,7 +21091,7 @@ function cloneNodes(nodes) {
21073
21091
  validateSlotsChildren(data.item, slotsMap, conversionApi);
21074
21092
  // Consume an element insertion and all present attributes that are specified as a reconversion triggers.
21075
21093
  consumer(data.item, conversionApi.consumable);
21076
- const viewPosition = conversionApi.mapper.toViewPosition(data.range.start);
21094
+ const viewPosition = data.reconversion && removeElementAndUnbind(data.item, conversionApi) || conversionApi.mapper.toViewPosition(data.range.start);
21077
21095
  conversionApi.mapper.bindElements(data.item, viewElement);
21078
21096
  conversionApi.writer.insert(viewPosition, viewElement);
21079
21097
  // Convert attributes before converting children.
@@ -21134,6 +21152,25 @@ function cloneNodes(nodes) {
21134
21152
  evt.stop();
21135
21153
  };
21136
21154
  }
21155
+ /**
21156
+ * Removes given view range content and unbinds removed elements.
21157
+ */ function removeRangeAndUnbind(viewRange, conversionApi) {
21158
+ const removed = conversionApi.writer.remove(viewRange);
21159
+ // After the range is removed, unbind all view elements from the model.
21160
+ // Range inside view document fragment is used to unbind deeply.
21161
+ for (const child of conversionApi.writer.createRangeIn(removed).getItems()){
21162
+ conversionApi.mapper.unbindViewElement(child, {
21163
+ defer: true
21164
+ });
21165
+ }
21166
+ return viewRange.start;
21167
+ }
21168
+ /**
21169
+ * Removes view element for given model element and unbinds removed view elements.
21170
+ */ function removeElementAndUnbind(modelElement, conversionApi) {
21171
+ const viewElement = conversionApi.mapper.toViewElement(modelElement);
21172
+ return viewElement && removeRangeAndUnbind(conversionApi.writer.createRangeOn(viewElement), conversionApi);
21173
+ }
21137
21174
  /**
21138
21175
  * Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~ViewUIElement UI element}
21139
21176
  * based on marker remove change.
@@ -21949,10 +21986,14 @@ function getFromAttributeCreator(config) {
21949
21986
  // For attribute use node affected by the change.
21950
21987
  // For insert or remove use parent element because we need to check if it's added/removed child.
21951
21988
  const node = change.type == 'attribute' ? change.range.start.nodeAfter : change.position.parent;
21952
- if (!node || !shouldReplace(node, change)) {
21989
+ if (!node || !shouldReplace(node, change) || change.type == 'reinsert') {
21953
21990
  reducedChanges.push(change);
21954
21991
  continue;
21955
21992
  }
21993
+ // Force to not-reuse view elements renamed in model.
21994
+ if (change.type == 'insert' && change.action == 'rename') {
21995
+ data.refreshedItems.add(change.position.nodeAfter);
21996
+ }
21956
21997
  // If it's already marked for reconversion, so skip this change, otherwise add the diff items.
21957
21998
  if (!data.reconvertedElements.has(node)) {
21958
21999
  data.reconvertedElements.add(node);
@@ -21971,11 +22012,6 @@ function getFromAttributeCreator(config) {
21971
22012
  changeIndex = i;
21972
22013
  }
21973
22014
  reducedChanges.splice(changeIndex, 0, {
21974
- type: 'remove',
21975
- name: node.name,
21976
- position,
21977
- length: 1
21978
- }, {
21979
22015
  type: 'reinsert',
21980
22016
  name: node.name,
21981
22017
  position,
@@ -22080,6 +22116,7 @@ function getFromAttributeCreator(config) {
22080
22116
  // Fill slots with nested view nodes.
22081
22117
  for ([currentSlot, currentSlotNodes] of slotsMap){
22082
22118
  reinsertOrConvertNodes(viewElement, currentSlotNodes, conversionApi, options);
22119
+ conversionApi.writer.setCustomProperty('$structureSlotParent', true, currentSlot.parent);
22083
22120
  conversionApi.writer.move(conversionApi.writer.createRangeIn(currentSlot), conversionApi.writer.createPositionBefore(currentSlot));
22084
22121
  conversionApi.writer.remove(currentSlot);
22085
22122
  }
@@ -35460,7 +35497,10 @@ function removeRangeContent(range, writer) {
35460
35497
  * @param nodes Nodes to insert.
35461
35498
  */ handleNodes(nodes) {
35462
35499
  for (const node of Array.from(nodes)){
35463
- this._handleNode(node);
35500
+ // Ignore empty nodes, especially empty text nodes.
35501
+ if (node.offsetSize > 0) {
35502
+ this._handleNode(node);
35503
+ }
35464
35504
  }
35465
35505
  // Insert nodes collected in temporary ModelDocumentFragment.
35466
35506
  this._insertPartialFragment();
@@ -35536,7 +35576,7 @@ function removeRangeContent(range, writer) {
35536
35576
  return;
35537
35577
  }
35538
35578
  // Add node to the current temporary ModelDocumentFragment.
35539
- this._appendToFragment(node);
35579
+ node = this._appendToFragment(node);
35540
35580
  // Store the first and last nodes for easy access for merging with sibling nodes.
35541
35581
  if (!this._firstNode) {
35542
35582
  this._firstNode = node;
@@ -35598,6 +35638,11 @@ function removeRangeContent(range, writer) {
35598
35638
  }
35599
35639
  this.writer.insert(node, this._documentFragmentPosition);
35600
35640
  this._documentFragmentPosition = this._documentFragmentPosition.getShiftedBy(node.offsetSize);
35641
+ // In case text node was merged with already inserted text node, we need to get the actual node that is in the document.
35642
+ // This happens when there is a non-allowed object between text nodes.
35643
+ if (!node.parent) {
35644
+ node = this._documentFragmentPosition.nodeBefore;
35645
+ }
35601
35646
  // The last inserted object should be selected because we can't put a collapsed selection after it.
35602
35647
  if (this.schema.isObject(node) && !this.schema.checkChild(this.position, '$text')) {
35603
35648
  this._nodeToSelect = node;
@@ -35605,6 +35650,7 @@ function removeRangeContent(range, writer) {
35605
35650
  this._nodeToSelect = null;
35606
35651
  }
35607
35652
  this._filterAttributesOf.push(node);
35653
+ return node;
35608
35654
  }
35609
35655
  /**
35610
35656
  * Sets `_affectedStart` and `_affectedEnd` to the given `position`. Should be used before a change is done during insertion process to
@@ -35848,12 +35894,13 @@ function removeRangeContent(range, writer) {
35848
35894
  * @param contextElement The element in which context the node should be checked.
35849
35895
  * @param childNode The node to check.
35850
35896
  */ _getAllowedIn(contextElement, childNode) {
35897
+ const context = this.schema.createContext(contextElement);
35851
35898
  // Check if a node can be inserted in the given context...
35852
- if (this.schema.checkChild(contextElement, childNode)) {
35899
+ if (this.schema.checkChild(context, childNode)) {
35853
35900
  return contextElement;
35854
35901
  }
35855
35902
  // ...or it would be accepted if a paragraph would be inserted.
35856
- if (this.schema.checkChild(contextElement, 'paragraph') && this.schema.checkChild('paragraph', childNode)) {
35903
+ if (this.schema.checkChild(context, 'paragraph') && this.schema.checkChild(context.push('paragraph'), childNode)) {
35857
35904
  return contextElement;
35858
35905
  }
35859
35906
  // If the child wasn't allowed in the context element and the element is a limit there's no point in
@@ -35933,8 +35980,9 @@ function removeRangeContent(range, writer) {
35933
35980
  }
35934
35981
  let elementToInsert = object;
35935
35982
  const insertionPositionParent = insertionSelection.anchor.parent;
35936
- // Autoparagraphing of an inline objects.
35937
- if (!model.schema.checkChild(insertionPositionParent, object) && model.schema.checkChild(insertionPositionParent, 'paragraph') && model.schema.checkChild('paragraph', object)) {
35983
+ const context = model.schema.createContext(insertionPositionParent);
35984
+ // Auto-paragraphing of an inline objects.
35985
+ if (!model.schema.checkChild(context, object) && model.schema.checkChild(context, 'paragraph') && model.schema.checkChild(context.push('paragraph'), object)) {
35938
35986
  elementToInsert = writer.createElement('paragraph');
35939
35987
  writer.insert(object, elementToInsert);
35940
35988
  }