@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "47.
|
|
3
|
+
"version": "47.2.0-alpha.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "src/index.js",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@ckeditor/ckeditor5-utils": "47.
|
|
27
|
+
"@ckeditor/ckeditor5-utils": "47.2.0-alpha.0",
|
|
28
28
|
"es-toolkit": "1.39.5"
|
|
29
29
|
},
|
|
30
30
|
"author": "CKSource (http://cksource.com/)",
|
|
@@ -210,7 +210,7 @@ export declare class DowncastDispatcher extends /* #__PURE__ */ DowncastDispatch
|
|
|
210
210
|
*/
|
|
211
211
|
private _convertAttribute;
|
|
212
212
|
/**
|
|
213
|
-
* Fires re-insertion conversion (with a `reconversion` flag passed to `insert` events)
|
|
213
|
+
* Fires re-insertion conversion (with a `reconversion` flag passed to `remove` and `insert` events)
|
|
214
214
|
* of a range of elements (only elements on the range depth, without children).
|
|
215
215
|
*
|
|
216
216
|
* For each node in the range on its depth (without children), {@link #event:insert `insert` event} is fired.
|
|
@@ -328,6 +328,10 @@ export type DowncastReduceChangesEventData = {
|
|
|
328
328
|
* A buffered changes to get reduced.
|
|
329
329
|
*/
|
|
330
330
|
changes: Iterable<DifferItem | DifferItemReinsert>;
|
|
331
|
+
/**
|
|
332
|
+
* Set of items marked for rebuild without reusing view nodes.
|
|
333
|
+
*/
|
|
334
|
+
refreshedItems: Set<ModelItem>;
|
|
331
335
|
};
|
|
332
336
|
export type DowncastDispatcherEventMap<TItem = ModelItem> = {
|
|
333
337
|
insert: {
|
|
@@ -338,6 +342,7 @@ export type DowncastDispatcherEventMap<TItem = ModelItem> = {
|
|
|
338
342
|
remove: {
|
|
339
343
|
position: ModelPosition;
|
|
340
344
|
length: number;
|
|
345
|
+
reconversion?: boolean;
|
|
341
346
|
};
|
|
342
347
|
attribute: {
|
|
343
348
|
item: TItem;
|
|
@@ -138,13 +138,14 @@ export class DowncastDispatcher extends /* #__PURE__ */ EmitterMixin() {
|
|
|
138
138
|
* @param writer The view writer that should be used to modify the view document.
|
|
139
139
|
*/
|
|
140
140
|
convertChanges(differ, markers, writer) {
|
|
141
|
-
const
|
|
141
|
+
const refreshedItems = differ.getRefreshedItems();
|
|
142
|
+
const conversionApi = this._createConversionApi(writer, refreshedItems);
|
|
142
143
|
// Before the view is updated, remove markers which have changed.
|
|
143
144
|
for (const change of differ.getMarkersToRemove()) {
|
|
144
145
|
this._convertMarkerRemove(change.name, change.range, conversionApi);
|
|
145
146
|
}
|
|
146
147
|
// Let features modify the change list (for example to allow reconversion).
|
|
147
|
-
const changes = this._reduceChanges(differ.getChanges());
|
|
148
|
+
const changes = this._reduceChanges(differ.getChanges(), refreshedItems);
|
|
148
149
|
// Convert changes that happened on model tree.
|
|
149
150
|
for (const entry of changes) {
|
|
150
151
|
if (entry.type === 'insert') {
|
|
@@ -315,7 +316,7 @@ export class DowncastDispatcher extends /* #__PURE__ */ EmitterMixin() {
|
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
318
|
/**
|
|
318
|
-
* Fires re-insertion conversion (with a `reconversion` flag passed to `insert` events)
|
|
319
|
+
* Fires re-insertion conversion (with a `reconversion` flag passed to `remove` and `insert` events)
|
|
319
320
|
* of a range of elements (only elements on the range depth, without children).
|
|
320
321
|
*
|
|
321
322
|
* For each node in the range on its depth (without children), {@link #event:insert `insert` event} is fired.
|
|
@@ -333,6 +334,9 @@ export class DowncastDispatcher extends /* #__PURE__ */ EmitterMixin() {
|
|
|
333
334
|
this._addConsumablesForInsert(conversionApi.consumable, walkerValues);
|
|
334
335
|
// Fire a separate insert event for each node and text fragment contained shallowly in the range.
|
|
335
336
|
for (const data of walkerValues.map(walkerValueToEventData)) {
|
|
337
|
+
// For backward compatibility and handlers that does not recognize reconversion.
|
|
338
|
+
this.fire(`remove:${data.item.is('element') ? data.item.name : '$text'}`, { position: data.range.start, length: data.item.offsetSize, reconversion: true }, conversionApi);
|
|
339
|
+
// Reinsert the view element.
|
|
336
340
|
this._testAndFire('insert', { ...data, reconversion: true }, conversionApi);
|
|
337
341
|
}
|
|
338
342
|
}
|
|
@@ -401,8 +405,11 @@ export class DowncastDispatcher extends /* #__PURE__ */ EmitterMixin() {
|
|
|
401
405
|
*
|
|
402
406
|
* @fires reduceChanges
|
|
403
407
|
*/
|
|
404
|
-
_reduceChanges(changes) {
|
|
405
|
-
const data = {
|
|
408
|
+
_reduceChanges(changes, refreshedItems) {
|
|
409
|
+
const data = {
|
|
410
|
+
changes,
|
|
411
|
+
refreshedItems
|
|
412
|
+
};
|
|
406
413
|
this.fire('reduceChanges', data);
|
|
407
414
|
return data.changes;
|
|
408
415
|
}
|
|
@@ -800,6 +800,7 @@ export declare function insertAttributesAndChildren(): (evt: unknown, data: {
|
|
|
800
800
|
export declare function remove(): (evt: unknown, data: {
|
|
801
801
|
position: ModelPosition;
|
|
802
802
|
length: number;
|
|
803
|
+
reconversion?: boolean;
|
|
803
804
|
}, conversionApi: DowncastConversionApi) => void;
|
|
804
805
|
/**
|
|
805
806
|
* Creates a `<span>` {@link module:engine/view/attributeelement~ViewAttributeElement view attribute element} from the information
|
|
@@ -761,18 +761,17 @@ export function insertAttributesAndChildren() {
|
|
|
761
761
|
*/
|
|
762
762
|
export function remove() {
|
|
763
763
|
return (evt, data, conversionApi) => {
|
|
764
|
+
// Ignore reconversion related remove as it is handled in the `insert` of reconversion.
|
|
765
|
+
if (data.reconversion) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
764
768
|
// Find the view range start position by mapping the model position at which the remove happened.
|
|
765
769
|
const viewStart = conversionApi.mapper.toViewPosition(data.position);
|
|
766
770
|
const modelEnd = data.position.getShiftedBy(data.length);
|
|
767
771
|
const viewEnd = conversionApi.mapper.toViewPosition(modelEnd, { isPhantom: true });
|
|
768
772
|
const viewRange = conversionApi.writer.createRange(viewStart, viewEnd);
|
|
769
773
|
// Trim the range to remove in case some UI elements are on the view range boundaries.
|
|
770
|
-
|
|
771
|
-
// After the range is removed, unbind all view elements from the model.
|
|
772
|
-
// Range inside view document fragment is used to unbind deeply.
|
|
773
|
-
for (const child of conversionApi.writer.createRangeIn(removed).getItems()) {
|
|
774
|
-
conversionApi.mapper.unbindViewElement(child, { defer: true });
|
|
775
|
-
}
|
|
774
|
+
removeRangeAndUnbind(viewRange.getTrimmed(), conversionApi);
|
|
776
775
|
};
|
|
777
776
|
}
|
|
778
777
|
/**
|
|
@@ -1019,7 +1018,8 @@ export function insertElement(elementCreator, consumer = defaultConsumer) {
|
|
|
1019
1018
|
}
|
|
1020
1019
|
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
|
|
1021
1020
|
consumer(data.item, conversionApi.consumable);
|
|
1022
|
-
const viewPosition =
|
|
1021
|
+
const viewPosition = data.reconversion && removeElementAndUnbind(data.item, conversionApi) ||
|
|
1022
|
+
conversionApi.mapper.toViewPosition(data.range.start);
|
|
1023
1023
|
conversionApi.mapper.bindElements(data.item, viewElement);
|
|
1024
1024
|
conversionApi.writer.insert(viewPosition, viewElement);
|
|
1025
1025
|
// Convert attributes before converting children.
|
|
@@ -1059,7 +1059,8 @@ export function insertStructure(elementCreator, consumer) {
|
|
|
1059
1059
|
validateSlotsChildren(data.item, slotsMap, conversionApi);
|
|
1060
1060
|
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
|
|
1061
1061
|
consumer(data.item, conversionApi.consumable);
|
|
1062
|
-
const viewPosition =
|
|
1062
|
+
const viewPosition = data.reconversion && removeElementAndUnbind(data.item, conversionApi) ||
|
|
1063
|
+
conversionApi.mapper.toViewPosition(data.range.start);
|
|
1063
1064
|
conversionApi.mapper.bindElements(data.item, viewElement);
|
|
1064
1065
|
conversionApi.writer.insert(viewPosition, viewElement);
|
|
1065
1066
|
// Convert attributes before converting children.
|
|
@@ -1119,6 +1120,25 @@ export function insertUIElement(elementCreator) {
|
|
|
1119
1120
|
evt.stop();
|
|
1120
1121
|
};
|
|
1121
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Removes given view range content and unbinds removed elements.
|
|
1125
|
+
*/
|
|
1126
|
+
function removeRangeAndUnbind(viewRange, conversionApi) {
|
|
1127
|
+
const removed = conversionApi.writer.remove(viewRange);
|
|
1128
|
+
// After the range is removed, unbind all view elements from the model.
|
|
1129
|
+
// Range inside view document fragment is used to unbind deeply.
|
|
1130
|
+
for (const child of conversionApi.writer.createRangeIn(removed).getItems()) {
|
|
1131
|
+
conversionApi.mapper.unbindViewElement(child, { defer: true });
|
|
1132
|
+
}
|
|
1133
|
+
return viewRange.start;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Removes view element for given model element and unbinds removed view elements.
|
|
1137
|
+
*/
|
|
1138
|
+
function removeElementAndUnbind(modelElement, conversionApi) {
|
|
1139
|
+
const viewElement = conversionApi.mapper.toViewElement(modelElement);
|
|
1140
|
+
return viewElement && removeRangeAndUnbind(conversionApi.writer.createRangeOn(viewElement), conversionApi);
|
|
1141
|
+
}
|
|
1122
1142
|
/**
|
|
1123
1143
|
* Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~ViewUIElement UI element}
|
|
1124
1144
|
* based on marker remove change.
|
|
@@ -1941,10 +1961,14 @@ function createChangeReducer(model) {
|
|
|
1941
1961
|
// For attribute use node affected by the change.
|
|
1942
1962
|
// For insert or remove use parent element because we need to check if it's added/removed child.
|
|
1943
1963
|
const node = change.type == 'attribute' ? change.range.start.nodeAfter : change.position.parent;
|
|
1944
|
-
if (!node || !shouldReplace(node, change)) {
|
|
1964
|
+
if (!node || !shouldReplace(node, change) || change.type == 'reinsert') {
|
|
1945
1965
|
reducedChanges.push(change);
|
|
1946
1966
|
continue;
|
|
1947
1967
|
}
|
|
1968
|
+
// Force to not-reuse view elements renamed in model.
|
|
1969
|
+
if (change.type == 'insert' && change.action == 'rename') {
|
|
1970
|
+
data.refreshedItems.add(change.position.nodeAfter);
|
|
1971
|
+
}
|
|
1948
1972
|
// If it's already marked for reconversion, so skip this change, otherwise add the diff items.
|
|
1949
1973
|
if (!data.reconvertedElements.has(node)) {
|
|
1950
1974
|
data.reconvertedElements.add(node);
|
|
@@ -1963,11 +1987,6 @@ function createChangeReducer(model) {
|
|
|
1963
1987
|
changeIndex = i;
|
|
1964
1988
|
}
|
|
1965
1989
|
reducedChanges.splice(changeIndex, 0, {
|
|
1966
|
-
type: 'remove',
|
|
1967
|
-
name: node.name,
|
|
1968
|
-
position,
|
|
1969
|
-
length: 1
|
|
1970
|
-
}, {
|
|
1971
1990
|
type: 'reinsert',
|
|
1972
1991
|
name: node.name,
|
|
1973
1992
|
position,
|
|
@@ -2071,6 +2090,7 @@ function fillSlots(viewElement, slotsMap, conversionApi, options) {
|
|
|
2071
2090
|
// Fill slots with nested view nodes.
|
|
2072
2091
|
for ([currentSlot, currentSlotNodes] of slotsMap) {
|
|
2073
2092
|
reinsertOrConvertNodes(viewElement, currentSlotNodes, conversionApi, options);
|
|
2093
|
+
conversionApi.writer.setCustomProperty('$structureSlotParent', true, currentSlot.parent);
|
|
2074
2094
|
conversionApi.writer.move(conversionApi.writer.createRangeIn(currentSlot), conversionApi.writer.createPositionBefore(currentSlot));
|
|
2075
2095
|
conversionApi.writer.remove(currentSlot);
|
|
2076
2096
|
}
|
|
@@ -262,7 +262,10 @@ class Insertion {
|
|
|
262
262
|
*/
|
|
263
263
|
handleNodes(nodes) {
|
|
264
264
|
for (const node of Array.from(nodes)) {
|
|
265
|
-
|
|
265
|
+
// Ignore empty nodes, especially empty text nodes.
|
|
266
|
+
if (node.offsetSize > 0) {
|
|
267
|
+
this._handleNode(node);
|
|
268
|
+
}
|
|
266
269
|
}
|
|
267
270
|
// Insert nodes collected in temporary ModelDocumentFragment.
|
|
268
271
|
this._insertPartialFragment();
|
|
@@ -344,7 +347,7 @@ class Insertion {
|
|
|
344
347
|
return;
|
|
345
348
|
}
|
|
346
349
|
// Add node to the current temporary ModelDocumentFragment.
|
|
347
|
-
this._appendToFragment(node);
|
|
350
|
+
node = this._appendToFragment(node);
|
|
348
351
|
// Store the first and last nodes for easy access for merging with sibling nodes.
|
|
349
352
|
if (!this._firstNode) {
|
|
350
353
|
this._firstNode = node;
|
|
@@ -408,6 +411,11 @@ class Insertion {
|
|
|
408
411
|
}
|
|
409
412
|
this.writer.insert(node, this._documentFragmentPosition);
|
|
410
413
|
this._documentFragmentPosition = this._documentFragmentPosition.getShiftedBy(node.offsetSize);
|
|
414
|
+
// In case text node was merged with already inserted text node, we need to get the actual node that is in the document.
|
|
415
|
+
// This happens when there is a non-allowed object between text nodes.
|
|
416
|
+
if (!node.parent) {
|
|
417
|
+
node = this._documentFragmentPosition.nodeBefore;
|
|
418
|
+
}
|
|
411
419
|
// The last inserted object should be selected because we can't put a collapsed selection after it.
|
|
412
420
|
if (this.schema.isObject(node) && !this.schema.checkChild(this.position, '$text')) {
|
|
413
421
|
this._nodeToSelect = node;
|
|
@@ -416,6 +424,7 @@ class Insertion {
|
|
|
416
424
|
this._nodeToSelect = null;
|
|
417
425
|
}
|
|
418
426
|
this._filterAttributesOf.push(node);
|
|
427
|
+
return node;
|
|
419
428
|
}
|
|
420
429
|
/**
|
|
421
430
|
* Sets `_affectedStart` and `_affectedEnd` to the given `position`. Should be used before a change is done during insertion process to
|
|
@@ -675,12 +684,14 @@ class Insertion {
|
|
|
675
684
|
* @param childNode The node to check.
|
|
676
685
|
*/
|
|
677
686
|
_getAllowedIn(contextElement, childNode) {
|
|
687
|
+
const context = this.schema.createContext(contextElement);
|
|
678
688
|
// Check if a node can be inserted in the given context...
|
|
679
|
-
if (this.schema.checkChild(
|
|
689
|
+
if (this.schema.checkChild(context, childNode)) {
|
|
680
690
|
return contextElement;
|
|
681
691
|
}
|
|
682
692
|
// ...or it would be accepted if a paragraph would be inserted.
|
|
683
|
-
if (this.schema.checkChild(
|
|
693
|
+
if (this.schema.checkChild(context, 'paragraph') &&
|
|
694
|
+
this.schema.checkChild(context.push('paragraph'), childNode)) {
|
|
684
695
|
return contextElement;
|
|
685
696
|
}
|
|
686
697
|
// If the child wasn't allowed in the context element and the element is a limit there's no point in
|
|
@@ -66,10 +66,11 @@ export function insertObject(model, object, selectable, options = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
let elementToInsert = object;
|
|
68
68
|
const insertionPositionParent = insertionSelection.anchor.parent;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
model.schema.checkChild('paragraph'
|
|
69
|
+
const context = model.schema.createContext(insertionPositionParent);
|
|
70
|
+
// Auto-paragraphing of an inline objects.
|
|
71
|
+
if (!model.schema.checkChild(context, object) &&
|
|
72
|
+
model.schema.checkChild(context, 'paragraph') &&
|
|
73
|
+
model.schema.checkChild(context.push('paragraph'), object)) {
|
|
73
74
|
elementToInsert = writer.createElement('paragraph');
|
|
74
75
|
writer.insert(object, elementToInsert);
|
|
75
76
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { DomEventObserver } from './domeventobserver.js';
|
|
9
9
|
import { type ViewDocumentDomEventData } from './domeventdata.js';
|
|
10
|
+
import type { BubblingEvent } from './bubblingemittermixin.js';
|
|
10
11
|
import { type KeystrokeInfo } from '@ckeditor/ckeditor5-utils';
|
|
11
12
|
/**
|
|
12
13
|
* Observer for events connected with pressing keyboard keys.
|
|
@@ -36,10 +37,10 @@ export declare class KeyObserver extends DomEventObserver<'keydown' | 'keyup', K
|
|
|
36
37
|
* @see module:engine/view/observer/keyobserver~KeyObserver
|
|
37
38
|
* @eventName module:engine/view/document~ViewDocument#keydown
|
|
38
39
|
*/
|
|
39
|
-
export type ViewDocumentKeyDownEvent = {
|
|
40
|
+
export type ViewDocumentKeyDownEvent = BubblingEvent<{
|
|
40
41
|
name: 'keydown';
|
|
41
42
|
args: [data: ViewDocumentKeyEventData];
|
|
42
|
-
}
|
|
43
|
+
}>;
|
|
43
44
|
/**
|
|
44
45
|
* Fired when a key has been released.
|
|
45
46
|
*
|
|
@@ -51,10 +52,10 @@ export type ViewDocumentKeyDownEvent = {
|
|
|
51
52
|
* @see module:engine/view/observer/keyobserver~KeyObserver
|
|
52
53
|
* @eventName module:engine/view/document~ViewDocument#keyup
|
|
53
54
|
*/
|
|
54
|
-
export type ViewDocumentKeyUpEvent = {
|
|
55
|
+
export type ViewDocumentKeyUpEvent = BubblingEvent<{
|
|
55
56
|
name: 'keyup';
|
|
56
57
|
args: [data: ViewDocumentKeyEventData];
|
|
57
|
-
}
|
|
58
|
+
}>;
|
|
58
59
|
/**
|
|
59
60
|
* The value of both events - {@link ~ViewDocumentKeyDownEvent} and {@link ~ViewDocumentKeyUpEvent}.
|
|
60
61
|
*/
|
package/src/view/placeholder.js
CHANGED
|
@@ -197,6 +197,18 @@ function updateDocumentPlaceholders(placeholders, writer) {
|
|
|
197
197
|
continue;
|
|
198
198
|
}
|
|
199
199
|
const hostElement = getChildPlaceholderHostSubstitute(element);
|
|
200
|
+
// If host element changed, remove the placeholder from the previous one.
|
|
201
|
+
// This can happen when user replaces the first child element of the parent element
|
|
202
|
+
// with new one, but the previous one is still in the view tree.
|
|
203
|
+
// See:
|
|
204
|
+
// https://github.com/ckeditor/ckeditor5/issues/14354
|
|
205
|
+
// https://github.com/ckeditor/ckeditor5/issues/18149
|
|
206
|
+
if (hostElement !== config.hostElement && config.hostElement) {
|
|
207
|
+
writer.removeAttribute('data-placeholder', config.hostElement);
|
|
208
|
+
hideViewPlaceholder(writer, config.hostElement);
|
|
209
|
+
config.hostElement = null;
|
|
210
|
+
wasViewModified = true;
|
|
211
|
+
}
|
|
200
212
|
// When not a direct host, it could happen that there is no child element
|
|
201
213
|
// capable of displaying a placeholder.
|
|
202
214
|
if (!hostElement) {
|