@ckeditor/ckeditor5-engine 47.1.0-alpha.2 → 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 +1 -1
- package/dist/index.js +75 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/conversion/downcastdispatcher.d.ts +6 -1
- package/src/conversion/downcastdispatcher.js +12 -5
- package/src/conversion/downcasthelpers.d.ts +1 -0
- package/src/conversion/downcasthelpers.js +34 -14
- package/src/model/utils/insertcontent.js +15 -4
- package/src/model/utils/insertobject.js +5 -4
- package/src/view/observer/keyobserver.d.ts +5 -4
- package/src/view/placeholder.js +12 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@ CKEditor 5 editing engine
|
|
|
2
2
|
========================================
|
|
3
3
|
|
|
4
4
|
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine)
|
|
5
|
-
[](https://codecov.io/gh/ckeditor/ckeditor5)
|
|
6
6
|
[](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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
35937
|
-
|
|
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
|
}
|