@ckeditor/ckeditor5-engine 32.0.0 → 33.0.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/package.json +22 -22
- package/src/controller/datacontroller.js +58 -66
- package/src/controller/editingcontroller.js +82 -5
- package/src/conversion/conversion.js +14 -13
- package/src/conversion/downcastdispatcher.js +297 -366
- package/src/conversion/downcasthelpers.js +770 -62
- package/src/conversion/mapper.js +104 -59
- package/src/conversion/modelconsumable.js +84 -34
- package/src/conversion/upcastdispatcher.js +2 -5
- package/src/conversion/upcasthelpers.js +3 -1
- package/src/dataprocessor/htmldataprocessor.js +1 -1
- package/src/dev-utils/model.js +13 -11
- package/src/index.js +1 -0
- package/src/model/batch.js +12 -12
- package/src/model/differ.js +86 -58
- package/src/model/document.js +12 -3
- package/src/model/markercollection.js +28 -4
- package/src/model/model.js +2 -1
- package/src/model/utils/modifyselection.js +14 -7
- package/src/model/writer.js +16 -26
- package/src/view/document.js +2 -1
- package/src/view/domconverter.js +31 -10
- package/src/view/downcastwriter.js +88 -3
- package/theme/placeholder.css +9 -0
package/src/dev-utils/model.js
CHANGED
|
@@ -31,6 +31,7 @@ import Mapper from '../conversion/mapper';
|
|
|
31
31
|
import {
|
|
32
32
|
convertCollapsedSelection,
|
|
33
33
|
convertRangeSelection,
|
|
34
|
+
insertAttributesAndChildren,
|
|
34
35
|
insertElement,
|
|
35
36
|
insertText,
|
|
36
37
|
insertUIElement,
|
|
@@ -233,6 +234,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
|
|
|
233
234
|
mapper.bindElements( node.root, viewRoot );
|
|
234
235
|
|
|
235
236
|
downcastDispatcher.on( 'insert:$text', insertText() );
|
|
237
|
+
downcastDispatcher.on( 'insert', insertAttributesAndChildren(), { priority: 'lowest' } );
|
|
236
238
|
downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
|
|
237
239
|
if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
|
|
238
240
|
const converter = wrap( ( modelAttributeValue, { writer } ) => {
|
|
@@ -260,28 +262,28 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
|
|
|
260
262
|
return writer.createUIElement( name );
|
|
261
263
|
} ) );
|
|
262
264
|
|
|
265
|
+
const markersMap = new Map();
|
|
266
|
+
|
|
267
|
+
if ( markers ) {
|
|
268
|
+
// To provide stable results, sort markers by name.
|
|
269
|
+
for ( const marker of Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 ) ) {
|
|
270
|
+
markersMap.set( marker.name, marker.getRange() );
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
263
274
|
// Convert model to view.
|
|
264
275
|
const writer = view._writer;
|
|
265
|
-
downcastDispatcher.
|
|
276
|
+
downcastDispatcher.convert( range, markersMap, writer );
|
|
266
277
|
|
|
267
278
|
// Convert model selection to view selection.
|
|
268
279
|
if ( selection ) {
|
|
269
280
|
downcastDispatcher.convertSelection( selection, markers || model.markers, writer );
|
|
270
281
|
}
|
|
271
282
|
|
|
272
|
-
if ( markers ) {
|
|
273
|
-
// To provide stable results, sort markers by name.
|
|
274
|
-
markers = Array.from( markers ).sort( ( a, b ) => a.name < b.name ? 1 : -1 );
|
|
275
|
-
|
|
276
|
-
for ( const marker of markers ) {
|
|
277
|
-
downcastDispatcher.convertMarkerAdd( marker.name, marker.getRange(), writer );
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
283
|
// Parse view to data string.
|
|
282
284
|
let data = viewStringify( viewRoot, viewDocument.selection, { sameSelectionCharacters: true } );
|
|
283
285
|
|
|
284
|
-
// Removing
|
|
286
|
+
// Removing unnecessary <div> and </div> added because `viewRoot` was also stringified alongside input data.
|
|
285
287
|
data = data.substr( 5, data.length - 11 );
|
|
286
288
|
|
|
287
289
|
view.destroy();
|
package/src/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export { default as LivePosition } from './model/liveposition';
|
|
|
28
28
|
export { default as Model } from './model/model';
|
|
29
29
|
export { default as TreeWalker } from './model/treewalker';
|
|
30
30
|
export { default as Element } from './model/element';
|
|
31
|
+
export { default as History } from './model/history';
|
|
31
32
|
|
|
32
33
|
export { default as DomConverter } from './view/domconverter';
|
|
33
34
|
export { default as Renderer } from './view/renderer';
|
package/src/model/batch.js
CHANGED
|
@@ -27,20 +27,20 @@ export default class Batch {
|
|
|
27
27
|
*
|
|
28
28
|
* @see module:engine/model/model~Model#enqueueChange
|
|
29
29
|
* @see module:engine/model/model~Model#change
|
|
30
|
-
* @param {Object} [type]
|
|
31
|
-
* encountering given `Batch` instance (for example, when a feature listens to applied operations).
|
|
32
|
-
* @param {Boolean} [type.isUndoable=true] Whether batch can be undone through undo feature.
|
|
33
|
-
* @param {Boolean} [type.isLocal=true] Whether batch includes operations created locally (`true`) or operations created on
|
|
30
|
+
* @param {Object} [type] A set of flags that specify the type of the batch. Batch type can alter how some of the features work
|
|
31
|
+
* when encountering a given `Batch` instance (for example, when a feature listens to applied operations).
|
|
32
|
+
* @param {Boolean} [type.isUndoable=true] Whether a batch can be undone through undo feature.
|
|
33
|
+
* @param {Boolean} [type.isLocal=true] Whether a batch includes operations created locally (`true`) or operations created on
|
|
34
34
|
* other, remote editors (`false`).
|
|
35
|
-
* @param {Boolean} [type.isUndo=false] Whether batch was created by the undo feature and undoes other operations.
|
|
36
|
-
* @param {Boolean} [type.isTyping=false] Whether batch includes operations connected with typing action.
|
|
35
|
+
* @param {Boolean} [type.isUndo=false] Whether a batch was created by the undo feature and undoes other operations.
|
|
36
|
+
* @param {Boolean} [type.isTyping=false] Whether a batch includes operations connected with a typing action.
|
|
37
37
|
*/
|
|
38
38
|
constructor( type = {} ) {
|
|
39
39
|
if ( typeof type === 'string' ) {
|
|
40
40
|
type = type === 'transparent' ? { isUndoable: false } : {};
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* The string value for `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
|
|
43
|
+
* The string value for a `type` property of the `Batch` constructor has been deprecated and will be removed in the near future.
|
|
44
44
|
* Please refer to the {@link module:engine/model/batch~Batch#constructor `Batch` constructor API documentation} for more
|
|
45
45
|
* information.
|
|
46
46
|
*
|
|
@@ -60,7 +60,7 @@ export default class Batch {
|
|
|
60
60
|
this.operations = [];
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Whether batch can be undone through the undo feature.
|
|
63
|
+
* Whether the batch can be undone through the undo feature.
|
|
64
64
|
*
|
|
65
65
|
* @readonly
|
|
66
66
|
* @type {Boolean}
|
|
@@ -68,7 +68,7 @@ export default class Batch {
|
|
|
68
68
|
this.isUndoable = isUndoable;
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Whether batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
|
|
71
|
+
* Whether the batch includes operations created locally (`true`) or operations created on other, remote editors (`false`).
|
|
72
72
|
*
|
|
73
73
|
* @readonly
|
|
74
74
|
* @type {Boolean}
|
|
@@ -76,7 +76,7 @@ export default class Batch {
|
|
|
76
76
|
this.isLocal = isLocal;
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Whether batch was created by the undo feature and undoes other operations.
|
|
79
|
+
* Whether the batch was created by the undo feature and undoes other operations.
|
|
80
80
|
*
|
|
81
81
|
* @readonly
|
|
82
82
|
* @type {Boolean}
|
|
@@ -84,7 +84,7 @@ export default class Batch {
|
|
|
84
84
|
this.isUndo = isUndo;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* Whether batch includes operations connected with typing.
|
|
87
|
+
* Whether the batch includes operations connected with typing.
|
|
88
88
|
*
|
|
89
89
|
* @readonly
|
|
90
90
|
* @type {Boolean}
|
|
@@ -95,7 +95,7 @@ export default class Batch {
|
|
|
95
95
|
/**
|
|
96
96
|
* The type of the batch.
|
|
97
97
|
*
|
|
98
|
-
* **This property has been deprecated and is always set to `'default'` value.**
|
|
98
|
+
* **This property has been deprecated and is always set to the `'default'` value.**
|
|
99
99
|
*
|
|
100
100
|
* It can be one of the following values:
|
|
101
101
|
* * `'default'` – All "normal" batches. This is the most commonly used type.
|
package/src/model/differ.js
CHANGED
|
@@ -58,11 +58,12 @@ export default class Differ {
|
|
|
58
58
|
* A map that stores all changed markers.
|
|
59
59
|
*
|
|
60
60
|
* The keys of the map are marker names.
|
|
61
|
-
* The values of the map are objects with the
|
|
62
|
-
*
|
|
61
|
+
* The values of the map are objects with the following properties:
|
|
62
|
+
* - `oldMarkerData`,
|
|
63
|
+
* - `newMarkerData`.
|
|
63
64
|
*
|
|
64
65
|
* @private
|
|
65
|
-
* @type {Map}
|
|
66
|
+
* @type {Map.<String, Object>}
|
|
66
67
|
*/
|
|
67
68
|
this._changedMarkers = new Map();
|
|
68
69
|
|
|
@@ -98,6 +99,14 @@ export default class Differ {
|
|
|
98
99
|
* @type {Array.<Object>|null}
|
|
99
100
|
*/
|
|
100
101
|
this._cachedChangesWithGraveyard = null;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Set of model items that were marked to get refreshed in {@link #_refreshItem}.
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @type {Set.<module:engine/model/item~Item>}
|
|
108
|
+
*/
|
|
109
|
+
this._refreshedItems = new Set();
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
/**
|
|
@@ -110,32 +119,6 @@ export default class Differ {
|
|
|
110
119
|
return this._changesInElement.size == 0 && this._changedMarkers.size == 0;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
/**
|
|
114
|
-
* Marks given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted in the differ changes
|
|
115
|
-
* set, so it will be effectively re-converted when differ changes will be handled by a dispatcher.
|
|
116
|
-
*
|
|
117
|
-
* @param {module:engine/model/item~Item} item Item to refresh.
|
|
118
|
-
*/
|
|
119
|
-
refreshItem( item ) {
|
|
120
|
-
if ( this._isInInsertedElement( item.parent ) ) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this._markRemove( item.parent, item.startOffset, item.offsetSize );
|
|
125
|
-
this._markInsert( item.parent, item.startOffset, item.offsetSize );
|
|
126
|
-
|
|
127
|
-
const range = Range._createOn( item );
|
|
128
|
-
|
|
129
|
-
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
130
|
-
const markerRange = marker.getRange();
|
|
131
|
-
|
|
132
|
-
this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Clear cache after each buffered operation as it is no longer valid.
|
|
136
|
-
this._cachedChanges = null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
122
|
/**
|
|
140
123
|
* Buffers the given operation. An operation has to be buffered before it is executed.
|
|
141
124
|
*
|
|
@@ -208,9 +191,9 @@ export default class Differ {
|
|
|
208
191
|
const range = Range._createFromPositionAndShift( operation.position, 1 );
|
|
209
192
|
|
|
210
193
|
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
211
|
-
const
|
|
194
|
+
const markerData = marker.getData();
|
|
212
195
|
|
|
213
|
-
this.bufferMarkerChange( marker.name,
|
|
196
|
+
this.bufferMarkerChange( marker.name, markerData, markerData );
|
|
214
197
|
}
|
|
215
198
|
|
|
216
199
|
break;
|
|
@@ -267,27 +250,23 @@ export default class Differ {
|
|
|
267
250
|
* Buffers a marker change.
|
|
268
251
|
*
|
|
269
252
|
* @param {String} markerName The name of the marker that changed.
|
|
270
|
-
* @param {module:engine/model/
|
|
271
|
-
*
|
|
272
|
-
* @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
|
|
273
|
-
* @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
|
|
253
|
+
* @param {module:engine/model/markercollection~MarkerData} oldMarkerData Marker data before the change.
|
|
254
|
+
* @param {module:engine/model/markercollection~MarkerData} newMarkerData Marker data after the change.
|
|
274
255
|
*/
|
|
275
|
-
bufferMarkerChange( markerName,
|
|
256
|
+
bufferMarkerChange( markerName, oldMarkerData, newMarkerData ) {
|
|
276
257
|
const buffered = this._changedMarkers.get( markerName );
|
|
277
258
|
|
|
278
259
|
if ( !buffered ) {
|
|
279
260
|
this._changedMarkers.set( markerName, {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
affectsData
|
|
261
|
+
newMarkerData,
|
|
262
|
+
oldMarkerData
|
|
283
263
|
} );
|
|
284
264
|
} else {
|
|
285
|
-
buffered.
|
|
286
|
-
buffered.affectsData = affectsData;
|
|
265
|
+
buffered.newMarkerData = newMarkerData;
|
|
287
266
|
|
|
288
|
-
if ( buffered.
|
|
289
|
-
// The marker is going to be removed (`
|
|
290
|
-
// (`buffered.
|
|
267
|
+
if ( buffered.oldMarkerData.range == null && newMarkerData.range == null ) {
|
|
268
|
+
// The marker is going to be removed (`newMarkerData.range == null`) but it did not exist before the first buffered change
|
|
269
|
+
// (`buffered.oldMarkerData.range == null`). In this case, do not keep the marker in buffer at all.
|
|
291
270
|
this._changedMarkers.delete( markerName );
|
|
292
271
|
}
|
|
293
272
|
}
|
|
@@ -302,8 +281,8 @@ export default class Differ {
|
|
|
302
281
|
const result = [];
|
|
303
282
|
|
|
304
283
|
for ( const [ name, change ] of this._changedMarkers ) {
|
|
305
|
-
if ( change.
|
|
306
|
-
result.push( { name, range: change.
|
|
284
|
+
if ( change.oldMarkerData.range != null ) {
|
|
285
|
+
result.push( { name, range: change.oldMarkerData.range } );
|
|
307
286
|
}
|
|
308
287
|
}
|
|
309
288
|
|
|
@@ -319,8 +298,8 @@ export default class Differ {
|
|
|
319
298
|
const result = [];
|
|
320
299
|
|
|
321
300
|
for ( const [ name, change ] of this._changedMarkers ) {
|
|
322
|
-
if ( change.
|
|
323
|
-
result.push( { name, range: change.
|
|
301
|
+
if ( change.newMarkerData.range != null ) {
|
|
302
|
+
result.push( { name, range: change.newMarkerData.range } );
|
|
324
303
|
}
|
|
325
304
|
}
|
|
326
305
|
|
|
@@ -333,12 +312,12 @@ export default class Differ {
|
|
|
333
312
|
* @returns {Array.<Object>}
|
|
334
313
|
*/
|
|
335
314
|
getChangedMarkers() {
|
|
336
|
-
return Array.from( this._changedMarkers ).map(
|
|
315
|
+
return Array.from( this._changedMarkers ).map( ( [ name, change ] ) => (
|
|
337
316
|
{
|
|
338
|
-
name
|
|
317
|
+
name,
|
|
339
318
|
data: {
|
|
340
|
-
oldRange:
|
|
341
|
-
newRange:
|
|
319
|
+
oldRange: change.oldMarkerData.range,
|
|
320
|
+
newRange: change.newMarkerData.range
|
|
342
321
|
}
|
|
343
322
|
}
|
|
344
323
|
) );
|
|
@@ -351,13 +330,23 @@ export default class Differ {
|
|
|
351
330
|
*
|
|
352
331
|
* * model structure changes,
|
|
353
332
|
* * attribute changes,
|
|
354
|
-
* * changes of markers which were defined as `
|
|
333
|
+
* * changes of markers which were defined as `affectsData`,
|
|
334
|
+
* * changes of markers' `affectsData` property.
|
|
355
335
|
*
|
|
356
336
|
* @returns {Boolean}
|
|
357
337
|
*/
|
|
358
338
|
hasDataChanges() {
|
|
359
|
-
for ( const
|
|
360
|
-
if (
|
|
339
|
+
for ( const { newMarkerData, oldMarkerData } of this._changedMarkers.values() ) {
|
|
340
|
+
if ( newMarkerData.affectsData !== oldMarkerData.affectsData ) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if ( newMarkerData.affectsData ) {
|
|
345
|
+
// Skip markers, which ranges have not changed.
|
|
346
|
+
if ( newMarkerData.range && oldMarkerData.range && newMarkerData.range.isEqual( oldMarkerData.range ) ) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
361
350
|
return true;
|
|
362
351
|
}
|
|
363
352
|
}
|
|
@@ -540,16 +529,25 @@ export default class Differ {
|
|
|
540
529
|
this._changeCount = 0;
|
|
541
530
|
|
|
542
531
|
// Cache changes.
|
|
543
|
-
this._cachedChangesWithGraveyard = diffSet
|
|
532
|
+
this._cachedChangesWithGraveyard = diffSet;
|
|
544
533
|
this._cachedChanges = diffSet.filter( _changesInGraveyardFilter );
|
|
545
534
|
|
|
546
535
|
if ( options.includeChangesInGraveyard ) {
|
|
547
|
-
return this._cachedChangesWithGraveyard;
|
|
536
|
+
return this._cachedChangesWithGraveyard.slice();
|
|
548
537
|
} else {
|
|
549
|
-
return this._cachedChanges;
|
|
538
|
+
return this._cachedChanges.slice();
|
|
550
539
|
}
|
|
551
540
|
}
|
|
552
541
|
|
|
542
|
+
/**
|
|
543
|
+
* Returns a set of model items that were marked to get refreshed.
|
|
544
|
+
*
|
|
545
|
+
* @return {Set.<module:engine/model/item~Item>}
|
|
546
|
+
*/
|
|
547
|
+
getRefreshedItems() {
|
|
548
|
+
return new Set( this._refreshedItems );
|
|
549
|
+
}
|
|
550
|
+
|
|
553
551
|
/**
|
|
554
552
|
* Resets `Differ`. Removes all buffered changes.
|
|
555
553
|
*/
|
|
@@ -557,6 +555,36 @@ export default class Differ {
|
|
|
557
555
|
this._changesInElement.clear();
|
|
558
556
|
this._elementSnapshots.clear();
|
|
559
557
|
this._changedMarkers.clear();
|
|
558
|
+
this._refreshedItems = new Set();
|
|
559
|
+
this._cachedChanges = null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted
|
|
564
|
+
* in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher.
|
|
565
|
+
*
|
|
566
|
+
* @protected
|
|
567
|
+
* @param {module:engine/model/item~Item} item Item to refresh.
|
|
568
|
+
*/
|
|
569
|
+
_refreshItem( item ) {
|
|
570
|
+
if ( this._isInInsertedElement( item.parent ) ) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
this._markRemove( item.parent, item.startOffset, item.offsetSize );
|
|
575
|
+
this._markInsert( item.parent, item.startOffset, item.offsetSize );
|
|
576
|
+
|
|
577
|
+
this._refreshedItems.add( item );
|
|
578
|
+
|
|
579
|
+
const range = Range._createOn( item );
|
|
580
|
+
|
|
581
|
+
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
|
|
582
|
+
const markerData = marker.getData();
|
|
583
|
+
|
|
584
|
+
this.bufferMarkerChange( marker.name, markerData, markerData );
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Clear cache after each buffered operation as it is no longer valid.
|
|
560
588
|
this._cachedChanges = null;
|
|
561
589
|
}
|
|
562
590
|
|
package/src/model/document.js
CHANGED
|
@@ -157,14 +157,23 @@ export default class Document {
|
|
|
157
157
|
// Buffer marker changes.
|
|
158
158
|
// This is not covered in buffering operations because markers may change outside of them (when they
|
|
159
159
|
// are modified using `model.markers` collection, not through `MarkerOperation`).
|
|
160
|
-
this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange ) => {
|
|
160
|
+
this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange, oldMarkerData ) => {
|
|
161
|
+
// Copy the `newRange` to the new marker data as during the marker removal the range is not updated.
|
|
162
|
+
const newMarkerData = { ...marker.getData(), range: newRange };
|
|
163
|
+
|
|
161
164
|
// Whenever marker is updated, buffer that change.
|
|
162
|
-
this.differ.bufferMarkerChange( marker.name,
|
|
165
|
+
this.differ.bufferMarkerChange( marker.name, oldMarkerData, newMarkerData );
|
|
163
166
|
|
|
164
167
|
if ( oldRange === null ) {
|
|
165
168
|
// If this is a new marker, add a listener that will buffer change whenever marker changes.
|
|
166
169
|
marker.on( 'change', ( evt, oldRange ) => {
|
|
167
|
-
|
|
170
|
+
const markerData = marker.getData();
|
|
171
|
+
|
|
172
|
+
this.differ.bufferMarkerChange(
|
|
173
|
+
marker.name,
|
|
174
|
+
{ ...markerData, range: oldRange },
|
|
175
|
+
markerData
|
|
176
|
+
);
|
|
168
177
|
} );
|
|
169
178
|
}
|
|
170
179
|
} );
|
|
@@ -106,6 +106,8 @@ export default class MarkerCollection {
|
|
|
106
106
|
const oldMarker = this._markers.get( markerName );
|
|
107
107
|
|
|
108
108
|
if ( oldMarker ) {
|
|
109
|
+
const oldMarkerData = oldMarker.getData();
|
|
110
|
+
|
|
109
111
|
const oldRange = oldMarker.getRange();
|
|
110
112
|
let hasChanged = false;
|
|
111
113
|
|
|
@@ -125,7 +127,7 @@ export default class MarkerCollection {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
if ( hasChanged ) {
|
|
128
|
-
this.fire( 'update:' + markerName, oldMarker, oldRange, range );
|
|
130
|
+
this.fire( 'update:' + markerName, oldMarker, oldRange, range, oldMarkerData );
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
return oldMarker;
|
|
@@ -135,7 +137,7 @@ export default class MarkerCollection {
|
|
|
135
137
|
const marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );
|
|
136
138
|
|
|
137
139
|
this._markers.set( markerName, marker );
|
|
138
|
-
this.fire( 'update:' + markerName, marker, null, range );
|
|
140
|
+
this.fire( 'update:' + markerName, marker, null, range, { ...marker.getData(), range: null } );
|
|
139
141
|
|
|
140
142
|
return marker;
|
|
141
143
|
}
|
|
@@ -154,7 +156,7 @@ export default class MarkerCollection {
|
|
|
154
156
|
|
|
155
157
|
if ( oldMarker ) {
|
|
156
158
|
this._markers.delete( markerName );
|
|
157
|
-
this.fire( 'update:' + markerName, oldMarker, oldMarker.getRange(), null );
|
|
159
|
+
this.fire( 'update:' + markerName, oldMarker, oldMarker.getRange(), null, oldMarker.getData() );
|
|
158
160
|
|
|
159
161
|
this._destroyMarker( oldMarker );
|
|
160
162
|
|
|
@@ -188,7 +190,7 @@ export default class MarkerCollection {
|
|
|
188
190
|
|
|
189
191
|
const range = marker.getRange();
|
|
190
192
|
|
|
191
|
-
this.fire( 'update:' + markerName, marker, range, range, marker.
|
|
193
|
+
this.fire( 'update:' + markerName, marker, range, range, marker.getData() );
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
/**
|
|
@@ -273,11 +275,20 @@ export default class MarkerCollection {
|
|
|
273
275
|
* means that marker is just added.
|
|
274
276
|
* @param {module:engine/model/range~Range|null} newRange Marker range after update. When is not defined it
|
|
275
277
|
* means that marker is just removed.
|
|
278
|
+
* @param {module:engine/model/markercollection~MarkerData} oldMarkerData Data of the marker before the change.
|
|
276
279
|
*/
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
mix( MarkerCollection, EmitterMixin );
|
|
280
283
|
|
|
284
|
+
/**
|
|
285
|
+
* @typedef {Object} module:engine/model/markercollection~MarkerData
|
|
286
|
+
*
|
|
287
|
+
* @property {module:engine/model/range~Range|null} range Marker range. `null` if the marker was removed.
|
|
288
|
+
* @property {Boolean} affectsData A property defining if the marker affects data.
|
|
289
|
+
* @property {Boolean} managedUsingOperations A property defining if the marker is managed using operations.
|
|
290
|
+
*/
|
|
291
|
+
|
|
281
292
|
/**
|
|
282
293
|
* `Marker` is a continuous parts of model (like a range), is named and represent some kind of information about marked
|
|
283
294
|
* part of model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
|
|
@@ -418,6 +429,19 @@ class Marker {
|
|
|
418
429
|
return this._affectsData;
|
|
419
430
|
}
|
|
420
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Returns the marker data (properties defining the marker).
|
|
434
|
+
*
|
|
435
|
+
* @returns {module:engine/model/markercollection~MarkerData}
|
|
436
|
+
*/
|
|
437
|
+
getData() {
|
|
438
|
+
return {
|
|
439
|
+
range: this.getRange(),
|
|
440
|
+
affectsData: this.affectsData,
|
|
441
|
+
managedUsingOperations: this.managedUsingOperations
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
421
445
|
/**
|
|
422
446
|
* Returns current marker start position.
|
|
423
447
|
*
|
package/src/model/model.js
CHANGED
|
@@ -215,7 +215,7 @@ export default class Model {
|
|
|
215
215
|
*
|
|
216
216
|
* Second, it lets you define the {@link module:engine/model/batch~Batch} into which you want to add your changes.
|
|
217
217
|
* By default, a new batch with the default {@link module:engine/model/batch~Batch#constructor batch type} is created.
|
|
218
|
-
* In the sample above, `change` and `enqueueChange` blocks will use a different batch (and a different
|
|
218
|
+
* In the sample above, the `change` and `enqueueChange` blocks will use a different batch (and a different
|
|
219
219
|
* {@link module:engine/model/writer~Writer} instance since each of them operates on a separate batch).
|
|
220
220
|
*
|
|
221
221
|
* model.enqueueChange( { isUndoable: false }, writer => {
|
|
@@ -516,6 +516,7 @@ export default class Model {
|
|
|
516
516
|
* @param {Object} [options]
|
|
517
517
|
* @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
|
|
518
518
|
* @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
|
|
519
|
+
* @param {Boolean} [options.treatEmojiAsSingleUnit=false] Whether multi-characer emoji sequences should be handled as single unit.
|
|
519
520
|
*/
|
|
520
521
|
modifySelection( selection, options ) {
|
|
521
522
|
modifySelection( this, selection, options );
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import Position from '../position';
|
|
11
11
|
import TreeWalker from '../treewalker';
|
|
12
12
|
import Range from '../range';
|
|
13
|
-
import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';
|
|
13
|
+
import { isInsideSurrogatePair, isInsideCombinedSymbol, isInsideEmojiSequence } from '@ckeditor/ckeditor5-utils/src/unicode';
|
|
14
14
|
import DocumentSelection from '../documentselection';
|
|
15
15
|
|
|
16
16
|
const wordBoundaryCharacters = ' ,.?!:;"-()';
|
|
@@ -49,11 +49,13 @@ const wordBoundaryCharacters = ' ,.?!:;"-()';
|
|
|
49
49
|
* @param {Object} [options]
|
|
50
50
|
* @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
|
|
51
51
|
* @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
|
|
52
|
+
* @param {Boolean} [options.treatEmojiAsSingleUnit=false] Whether multi-characer emoji sequences should be handled as single unit.
|
|
52
53
|
*/
|
|
53
54
|
export default function modifySelection( model, selection, options = {} ) {
|
|
54
55
|
const schema = model.schema;
|
|
55
56
|
const isForward = options.direction != 'backward';
|
|
56
57
|
const unit = options.unit ? options.unit : 'character';
|
|
58
|
+
const treatEmojiAsSingleUnit = !!options.treatEmojiAsSingleUnit;
|
|
57
59
|
|
|
58
60
|
const focus = selection.focus;
|
|
59
61
|
|
|
@@ -63,7 +65,7 @@ export default function modifySelection( model, selection, options = {} ) {
|
|
|
63
65
|
direction: isForward ? 'forward' : 'backward'
|
|
64
66
|
} );
|
|
65
67
|
|
|
66
|
-
const data = { walker, schema, isForward, unit };
|
|
68
|
+
const data = { walker, schema, isForward, unit, treatEmojiAsSingleUnit };
|
|
67
69
|
|
|
68
70
|
let next;
|
|
69
71
|
|
|
@@ -89,10 +91,10 @@ export default function modifySelection( model, selection, options = {} ) {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
// Checks whether the selection can be extended to the the walker's next value (next position).
|
|
92
|
-
// @param {{ walker, unit, isForward, schema }} data
|
|
94
|
+
// @param {{ walker, unit, isForward, schema, treatEmojiAsSingleUnit }} data
|
|
93
95
|
// @param {module:engine/view/treewalker~TreeWalkerValue} value
|
|
94
96
|
function tryExtendingTo( data, value ) {
|
|
95
|
-
const { isForward, walker, unit, schema } = data;
|
|
97
|
+
const { isForward, walker, unit, schema, treatEmojiAsSingleUnit } = data;
|
|
96
98
|
const { type, item, nextPosition } = value;
|
|
97
99
|
|
|
98
100
|
// If found text, we can certainly put the focus in it. Let's just find a correct position
|
|
@@ -102,7 +104,7 @@ function tryExtendingTo( data, value ) {
|
|
|
102
104
|
return getCorrectWordBreakPosition( walker, isForward );
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
return getCorrectPosition( walker, unit,
|
|
107
|
+
return getCorrectPosition( walker, unit, treatEmojiAsSingleUnit );
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
// Entering an element.
|
|
@@ -139,14 +141,19 @@ function tryExtendingTo( data, value ) {
|
|
|
139
141
|
//
|
|
140
142
|
// @param {module:engine/model/treewalker~TreeWalker} walker
|
|
141
143
|
// @param {String} unit The unit by which selection should be modified.
|
|
142
|
-
|
|
144
|
+
// @param {Boolean} treatEmojiAsSingleUnit
|
|
145
|
+
function getCorrectPosition( walker, unit, treatEmojiAsSingleUnit ) {
|
|
143
146
|
const textNode = walker.position.textNode;
|
|
144
147
|
|
|
145
148
|
if ( textNode ) {
|
|
146
149
|
const data = textNode.data;
|
|
147
150
|
let offset = walker.position.offset - textNode.startOffset;
|
|
148
151
|
|
|
149
|
-
while (
|
|
152
|
+
while (
|
|
153
|
+
isInsideSurrogatePair( data, offset ) ||
|
|
154
|
+
( unit == 'character' && isInsideCombinedSymbol( data, offset ) ) ||
|
|
155
|
+
( treatEmojiAsSingleUnit && isInsideEmojiSequence( data, offset ) )
|
|
156
|
+
) {
|
|
150
157
|
walker.next();
|
|
151
158
|
|
|
152
159
|
offset = walker.position.offset - textNode.startOffset;
|
package/src/model/writer.js
CHANGED
|
@@ -27,7 +27,7 @@ import DocumentSelection from './documentselection';
|
|
|
27
27
|
|
|
28
28
|
import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
|
|
29
29
|
|
|
30
|
-
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
30
|
+
import CKEditorError, { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* The model can only be modified by using the writer. It should be used whenever you want to create a node, modify
|
|
@@ -968,30 +968,8 @@ export default class Writer {
|
|
|
968
968
|
* As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique
|
|
969
969
|
* name is created and returned.
|
|
970
970
|
*
|
|
971
|
-
*
|
|
972
|
-
* the
|
|
973
|
-
* the marker {@link module:engine/view/element~Element view element} without changing any marker data.
|
|
974
|
-
*
|
|
975
|
-
* let isCommentActive = false;
|
|
976
|
-
*
|
|
977
|
-
* model.conversion.markerToHighlight( {
|
|
978
|
-
* model: 'comment',
|
|
979
|
-
* view: data => {
|
|
980
|
-
* const classes = [ 'comment-marker' ];
|
|
981
|
-
*
|
|
982
|
-
* if ( isCommentActive ) {
|
|
983
|
-
* classes.push( 'comment-marker--active' );
|
|
984
|
-
* }
|
|
985
|
-
*
|
|
986
|
-
* return { classes };
|
|
987
|
-
* }
|
|
988
|
-
* } );
|
|
989
|
-
*
|
|
990
|
-
* // Change the property that indicates if marker is displayed as active or not.
|
|
991
|
-
* isCommentActive = true;
|
|
992
|
-
*
|
|
993
|
-
* // And refresh the marker to convert it with additional class.
|
|
994
|
-
* model.change( writer => writer.updateMarker( 'comment' ) );
|
|
971
|
+
* **Note**: If you want to change the {@link module:engine/view/element~Element view element} of the marker while its data in the model
|
|
972
|
+
* remains the same, use the dedicated {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker} method.
|
|
995
973
|
*
|
|
996
974
|
* The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See
|
|
997
975
|
* {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
|
|
@@ -1037,7 +1015,7 @@ export default class Writer {
|
|
|
1037
1015
|
|
|
1038
1016
|
if ( !currentMarker ) {
|
|
1039
1017
|
/**
|
|
1040
|
-
* Marker with provided name does not
|
|
1018
|
+
* Marker with provided name does not exist and will not be updated.
|
|
1041
1019
|
*
|
|
1042
1020
|
* @error writer-updatemarker-marker-not-exists
|
|
1043
1021
|
*/
|
|
@@ -1045,6 +1023,18 @@ export default class Writer {
|
|
|
1045
1023
|
}
|
|
1046
1024
|
|
|
1047
1025
|
if ( !options ) {
|
|
1026
|
+
/**
|
|
1027
|
+
* The usage of `writer.updateMarker()` only to reconvert (refresh) a
|
|
1028
|
+
* {@link module:engine/model/markercollection~Marker model marker} was deprecated and may not work in the future.
|
|
1029
|
+
* Please update your code to use
|
|
1030
|
+
* {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker `editor.editing.reconvertMarker()`}
|
|
1031
|
+
* instead.
|
|
1032
|
+
*
|
|
1033
|
+
* @error writer-updatemarker-reconvert-using-editingcontroller
|
|
1034
|
+
* @param {String} markerName The name of the updated marker.
|
|
1035
|
+
*/
|
|
1036
|
+
logWarning( 'writer-updatemarker-reconvert-using-editingcontroller', { markerName } );
|
|
1037
|
+
|
|
1048
1038
|
this.model.markers._refresh( currentMarker );
|
|
1049
1039
|
|
|
1050
1040
|
return;
|
package/src/view/document.js
CHANGED
|
@@ -141,7 +141,8 @@ export default class Document {
|
|
|
141
141
|
*
|
|
142
142
|
* * adding or removing attribute from elements,
|
|
143
143
|
* * changes inside of {@link module:engine/view/uielement~UIElement UI elements},
|
|
144
|
-
* * {@link module:engine/
|
|
144
|
+
* * {@link module:engine/controller/editingcontroller~EditingController#reconvertItem marking some of the model elements to be
|
|
145
|
+
* re-converted}.
|
|
145
146
|
*
|
|
146
147
|
* Try to avoid changes which touch view structure:
|
|
147
148
|
*
|