@ckeditor/ckeditor5-engine 34.2.0 → 35.1.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/CHANGELOG.md +823 -0
- package/LICENSE.md +4 -0
- package/package.json +32 -25
- package/src/controller/datacontroller.js +467 -561
- package/src/controller/editingcontroller.js +168 -204
- package/src/conversion/conversion.js +541 -565
- package/src/conversion/conversionhelpers.js +24 -28
- package/src/conversion/downcastdispatcher.js +457 -686
- package/src/conversion/downcasthelpers.js +1583 -1965
- package/src/conversion/mapper.js +518 -707
- package/src/conversion/modelconsumable.js +240 -283
- package/src/conversion/upcastdispatcher.js +372 -718
- package/src/conversion/upcasthelpers.js +707 -818
- package/src/conversion/viewconsumable.js +524 -581
- package/src/dataprocessor/basichtmlwriter.js +12 -16
- package/src/dataprocessor/dataprocessor.js +5 -0
- package/src/dataprocessor/htmldataprocessor.js +101 -117
- package/src/dataprocessor/htmlwriter.js +1 -18
- package/src/dataprocessor/xmldataprocessor.js +117 -138
- package/src/dev-utils/model.js +260 -352
- package/src/dev-utils/operationreplayer.js +106 -126
- package/src/dev-utils/utils.js +34 -51
- package/src/dev-utils/view.js +632 -753
- package/src/index.js +0 -11
- package/src/model/batch.js +111 -127
- package/src/model/differ.js +988 -1233
- package/src/model/document.js +340 -449
- package/src/model/documentfragment.js +327 -364
- package/src/model/documentselection.js +996 -1189
- package/src/model/element.js +306 -410
- package/src/model/history.js +224 -262
- package/src/model/item.js +5 -0
- package/src/model/liveposition.js +84 -145
- package/src/model/liverange.js +108 -185
- package/src/model/markercollection.js +379 -480
- package/src/model/model.js +883 -1034
- package/src/model/node.js +419 -463
- package/src/model/nodelist.js +175 -201
- package/src/model/operation/attributeoperation.js +153 -182
- package/src/model/operation/detachoperation.js +64 -83
- package/src/model/operation/insertoperation.js +135 -166
- package/src/model/operation/markeroperation.js +114 -140
- package/src/model/operation/mergeoperation.js +163 -191
- package/src/model/operation/moveoperation.js +157 -187
- package/src/model/operation/nooperation.js +28 -38
- package/src/model/operation/operation.js +106 -125
- package/src/model/operation/operationfactory.js +30 -34
- package/src/model/operation/renameoperation.js +109 -135
- package/src/model/operation/rootattributeoperation.js +155 -188
- package/src/model/operation/splitoperation.js +196 -232
- package/src/model/operation/transform.js +1833 -2204
- package/src/model/operation/utils.js +140 -204
- package/src/model/position.js +899 -1053
- package/src/model/range.js +910 -1028
- package/src/model/rootelement.js +77 -97
- package/src/model/schema.js +1189 -1835
- package/src/model/selection.js +745 -862
- package/src/model/text.js +90 -114
- package/src/model/textproxy.js +204 -240
- package/src/model/treewalker.js +316 -397
- package/src/model/typecheckable.js +16 -0
- package/src/model/utils/autoparagraphing.js +32 -44
- package/src/model/utils/deletecontent.js +334 -418
- package/src/model/utils/findoptimalinsertionrange.js +25 -36
- package/src/model/utils/getselectedcontent.js +96 -118
- package/src/model/utils/insertcontent.js +654 -773
- package/src/model/utils/insertobject.js +96 -119
- package/src/model/utils/modifyselection.js +120 -158
- package/src/model/utils/selection-post-fixer.js +153 -201
- package/src/model/writer.js +1305 -1474
- package/src/view/attributeelement.js +189 -225
- package/src/view/containerelement.js +75 -85
- package/src/view/document.js +172 -215
- package/src/view/documentfragment.js +200 -249
- package/src/view/documentselection.js +338 -367
- package/src/view/domconverter.js +1371 -1613
- package/src/view/downcastwriter.js +1747 -2076
- package/src/view/editableelement.js +81 -97
- package/src/view/element.js +739 -890
- package/src/view/elementdefinition.js +5 -0
- package/src/view/emptyelement.js +82 -92
- package/src/view/filler.js +35 -50
- package/src/view/item.js +5 -0
- package/src/view/matcher.js +260 -559
- package/src/view/node.js +274 -360
- package/src/view/observer/arrowkeysobserver.js +19 -28
- package/src/view/observer/bubblingemittermixin.js +120 -263
- package/src/view/observer/bubblingeventinfo.js +47 -55
- package/src/view/observer/clickobserver.js +7 -13
- package/src/view/observer/compositionobserver.js +14 -24
- package/src/view/observer/domeventdata.js +57 -67
- package/src/view/observer/domeventobserver.js +40 -64
- package/src/view/observer/fakeselectionobserver.js +81 -96
- package/src/view/observer/focusobserver.js +45 -61
- package/src/view/observer/inputobserver.js +7 -13
- package/src/view/observer/keyobserver.js +17 -27
- package/src/view/observer/mouseobserver.js +7 -14
- package/src/view/observer/mutationobserver.js +220 -315
- package/src/view/observer/observer.js +81 -102
- package/src/view/observer/selectionobserver.js +191 -246
- package/src/view/observer/tabobserver.js +23 -36
- package/src/view/placeholder.js +128 -173
- package/src/view/position.js +350 -401
- package/src/view/range.js +453 -513
- package/src/view/rawelement.js +85 -112
- package/src/view/renderer.js +874 -1014
- package/src/view/rooteditableelement.js +80 -90
- package/src/view/selection.js +608 -689
- package/src/view/styles/background.js +43 -44
- package/src/view/styles/border.js +220 -276
- package/src/view/styles/margin.js +8 -17
- package/src/view/styles/padding.js +8 -16
- package/src/view/styles/utils.js +127 -160
- package/src/view/stylesmap.js +728 -905
- package/src/view/text.js +102 -126
- package/src/view/textproxy.js +144 -170
- package/src/view/treewalker.js +383 -479
- package/src/view/typecheckable.js +19 -0
- package/src/view/uielement.js +166 -187
- package/src/view/upcastwriter.js +395 -449
- package/src/view/view.js +569 -664
- package/src/dataprocessor/dataprocessor.jsdoc +0 -64
- package/src/model/item.jsdoc +0 -14
- package/src/view/elementdefinition.jsdoc +0 -59
- package/src/view/item.jsdoc +0 -14
package/src/model/writer.js
CHANGED
|
@@ -2,33 +2,27 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
* @module engine/model/writer
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
import AttributeOperation from './operation/attributeoperation';
|
|
11
9
|
import DetachOperation from './operation/detachoperation';
|
|
12
10
|
import InsertOperation from './operation/insertoperation';
|
|
13
11
|
import MarkerOperation from './operation/markeroperation';
|
|
12
|
+
import MergeOperation from './operation/mergeoperation';
|
|
14
13
|
import MoveOperation from './operation/moveoperation';
|
|
15
14
|
import RenameOperation from './operation/renameoperation';
|
|
16
15
|
import RootAttributeOperation from './operation/rootattributeoperation';
|
|
17
16
|
import SplitOperation from './operation/splitoperation';
|
|
18
|
-
import MergeOperation from './operation/mergeoperation';
|
|
19
|
-
|
|
20
17
|
import DocumentFragment from './documentfragment';
|
|
21
|
-
import
|
|
18
|
+
import DocumentSelection from './documentselection';
|
|
22
19
|
import Element from './element';
|
|
23
|
-
import RootElement from './rootelement';
|
|
24
20
|
import Position from './position';
|
|
25
|
-
import Range from './range
|
|
26
|
-
import
|
|
27
|
-
|
|
21
|
+
import Range from './range';
|
|
22
|
+
import RootElement from './rootelement';
|
|
23
|
+
import Text from './text';
|
|
28
24
|
import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
|
|
29
|
-
|
|
30
25
|
import CKEditorError, { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
31
|
-
|
|
32
26
|
/**
|
|
33
27
|
* The model can only be modified by using the writer. It should be used whenever you want to create a node, modify
|
|
34
28
|
* child nodes, attributes or text, set the selection's position and its attributes.
|
|
@@ -51,1358 +45,1221 @@ import CKEditorError, { logWarning } from '@ckeditor/ckeditor5-utils/src/ckedito
|
|
|
51
45
|
* @see module:engine/model/model~Model#enqueueChange
|
|
52
46
|
*/
|
|
53
47
|
export default class Writer {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
* @returns {String} The unique id which allows restoring the gravity.
|
|
1269
|
-
*/
|
|
1270
|
-
overrideSelectionGravity() {
|
|
1271
|
-
return this.model.document.selection._overrideGravity();
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* Restores {@link ~Writer#overrideSelectionGravity} gravity to default.
|
|
1276
|
-
*
|
|
1277
|
-
* Restoring the gravity is only possible using the unique identifier returned by
|
|
1278
|
-
* {@link ~Writer#overrideSelectionGravity}. Note that the gravity remains overridden as long as won't be restored
|
|
1279
|
-
* the same number of times it was overridden.
|
|
1280
|
-
*
|
|
1281
|
-
* @param {String} uid The unique id returned by {@link ~Writer#overrideSelectionGravity}.
|
|
1282
|
-
*/
|
|
1283
|
-
restoreSelectionGravity( uid ) {
|
|
1284
|
-
this.model.document.selection._restoreGravity( uid );
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
/**
|
|
1288
|
-
* @private
|
|
1289
|
-
* @param {String} key Key of the attribute to remove.
|
|
1290
|
-
* @param {*} value Attribute value.
|
|
1291
|
-
*/
|
|
1292
|
-
_setSelectionAttribute( key, value ) {
|
|
1293
|
-
const selection = this.model.document.selection;
|
|
1294
|
-
|
|
1295
|
-
// Store attribute in parent element if the selection is collapsed in an empty node.
|
|
1296
|
-
if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
|
|
1297
|
-
const storeKey = DocumentSelection._getStoreAttributeKey( key );
|
|
1298
|
-
|
|
1299
|
-
this.setAttribute( storeKey, value, selection.anchor.parent );
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
selection._setAttribute( key, value );
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* @private
|
|
1307
|
-
* @param {String} key Key of the attribute to remove.
|
|
1308
|
-
*/
|
|
1309
|
-
_removeSelectionAttribute( key ) {
|
|
1310
|
-
const selection = this.model.document.selection;
|
|
1311
|
-
|
|
1312
|
-
// Remove stored attribute from parent element if the selection is collapsed in an empty node.
|
|
1313
|
-
if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
|
|
1314
|
-
const storeKey = DocumentSelection._getStoreAttributeKey( key );
|
|
1315
|
-
|
|
1316
|
-
this.removeAttribute( storeKey, selection.anchor.parent );
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
selection._removeAttribute( key );
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/**
|
|
1323
|
-
* Throws `writer-detached-writer-tries-to-modify-model` error when the writer is used outside of the `change()` block.
|
|
1324
|
-
*
|
|
1325
|
-
* @private
|
|
1326
|
-
*/
|
|
1327
|
-
_assertWriterUsedCorrectly() {
|
|
1328
|
-
/**
|
|
1329
|
-
* Trying to use a writer outside a {@link module:engine/model/model~Model#change `change()`} or
|
|
1330
|
-
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`} blocks.
|
|
1331
|
-
*
|
|
1332
|
-
* The writer can only be used inside these blocks which ensures that the model
|
|
1333
|
-
* can only be changed during such "sessions".
|
|
1334
|
-
*
|
|
1335
|
-
* @error writer-incorrect-use
|
|
1336
|
-
*/
|
|
1337
|
-
if ( this.model._currentWriter !== this ) {
|
|
1338
|
-
throw new CKEditorError( 'writer-incorrect-use', this );
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
/**
|
|
1343
|
-
* For given action `type` and `positionOrRange` where the action happens, this function finds all affected markers
|
|
1344
|
-
* and applies a marker operation with the new marker range equal to the current range. Thanks to this, the marker range
|
|
1345
|
-
* can be later correctly processed during undo.
|
|
1346
|
-
*
|
|
1347
|
-
* @private
|
|
1348
|
-
* @param {'move'|'merge'} type Writer action type.
|
|
1349
|
-
* @param {module:engine/model/position~Position|module:engine/model/range~Range} positionOrRange Position or range
|
|
1350
|
-
* where the writer action happens.
|
|
1351
|
-
*/
|
|
1352
|
-
_addOperationForAffectedMarkers( type, positionOrRange ) {
|
|
1353
|
-
for ( const marker of this.model.markers ) {
|
|
1354
|
-
if ( !marker.managedUsingOperations ) {
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
const markerRange = marker.getRange();
|
|
1359
|
-
let isAffected = false;
|
|
1360
|
-
|
|
1361
|
-
if ( type === 'move' ) {
|
|
1362
|
-
isAffected =
|
|
1363
|
-
positionOrRange.containsPosition( markerRange.start ) ||
|
|
1364
|
-
positionOrRange.start.isEqual( markerRange.start ) ||
|
|
1365
|
-
positionOrRange.containsPosition( markerRange.end ) ||
|
|
1366
|
-
positionOrRange.end.isEqual( markerRange.end );
|
|
1367
|
-
} else {
|
|
1368
|
-
// if type === 'merge'.
|
|
1369
|
-
const elementBefore = positionOrRange.nodeBefore;
|
|
1370
|
-
const elementAfter = positionOrRange.nodeAfter;
|
|
1371
|
-
|
|
1372
|
-
// Start: <p>Foo[</p><p>Bar]</p>
|
|
1373
|
-
// After merge: <p>Foo[Bar]</p>
|
|
1374
|
-
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
|
|
1375
|
-
//
|
|
1376
|
-
const affectedInLeftElement = markerRange.start.parent == elementBefore && markerRange.start.isAtEnd;
|
|
1377
|
-
|
|
1378
|
-
// Start: <p>[Foo</p><p>]Bar</p>
|
|
1379
|
-
// After merge: <p>[Foo]Bar</p>
|
|
1380
|
-
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
|
|
1381
|
-
//
|
|
1382
|
-
const affectedInRightElement = markerRange.end.parent == elementAfter && markerRange.end.offset == 0;
|
|
1383
|
-
|
|
1384
|
-
// Start: <p>[Foo</p>]<p>Bar</p>
|
|
1385
|
-
// After merge: <p>[Foo]Bar</p>
|
|
1386
|
-
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
|
|
1387
|
-
//
|
|
1388
|
-
const affectedAfterLeftElement = markerRange.end.nodeAfter == elementAfter;
|
|
1389
|
-
|
|
1390
|
-
// Start: <p>Foo</p>[<p>Bar]</p>
|
|
1391
|
-
// After merge: <p>Foo[Bar]</p>
|
|
1392
|
-
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
|
|
1393
|
-
//
|
|
1394
|
-
const affectedBeforeRightElement = markerRange.start.nodeAfter == elementAfter;
|
|
1395
|
-
|
|
1396
|
-
isAffected = affectedInLeftElement || affectedInRightElement || affectedAfterLeftElement || affectedBeforeRightElement;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
if ( isAffected ) {
|
|
1400
|
-
this.updateMarker( marker.name, { range: markerRange } );
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates a writer instance.
|
|
50
|
+
*
|
|
51
|
+
* **Note:** It is not recommended to use it directly. Use {@link module:engine/model/model~Model#change `Model#change()`} or
|
|
52
|
+
* {@link module:engine/model/model~Model#enqueueChange `Model#enqueueChange()`} instead.
|
|
53
|
+
*
|
|
54
|
+
* @protected
|
|
55
|
+
* @param {module:engine/model/model~Model} model
|
|
56
|
+
* @param {module:engine/model/batch~Batch} batch
|
|
57
|
+
*/
|
|
58
|
+
constructor(model, batch) {
|
|
59
|
+
/**
|
|
60
|
+
* Instance of the model on which this writer operates.
|
|
61
|
+
*
|
|
62
|
+
* @readonly
|
|
63
|
+
* @type {module:engine/model/model~Model}
|
|
64
|
+
*/
|
|
65
|
+
this.model = model;
|
|
66
|
+
/**
|
|
67
|
+
* The batch to which this writer will add changes.
|
|
68
|
+
*
|
|
69
|
+
* @readonly
|
|
70
|
+
* @type {module:engine/model/batch~Batch}
|
|
71
|
+
*/
|
|
72
|
+
this.batch = batch;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a new {@link module:engine/model/text~Text text node}.
|
|
76
|
+
*
|
|
77
|
+
* writer.createText( 'foo' );
|
|
78
|
+
* writer.createText( 'foo', { bold: true } );
|
|
79
|
+
*
|
|
80
|
+
* @param {String} data Text data.
|
|
81
|
+
* @param {Object} [attributes] Text attributes.
|
|
82
|
+
* @returns {module:engine/model/text~Text} Created text node.
|
|
83
|
+
*/
|
|
84
|
+
createText(data, attributes) {
|
|
85
|
+
return new Text(data, attributes);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates a new {@link module:engine/model/element~Element element}.
|
|
89
|
+
*
|
|
90
|
+
* writer.createElement( 'paragraph' );
|
|
91
|
+
* writer.createElement( 'paragraph', { alignment: 'center' } );
|
|
92
|
+
*
|
|
93
|
+
* @param {String} name Name of the element.
|
|
94
|
+
* @param {Object} [attributes] Elements attributes.
|
|
95
|
+
* @returns {module:engine/model/element~Element} Created element.
|
|
96
|
+
*/
|
|
97
|
+
createElement(name, attributes) {
|
|
98
|
+
return new Element(name, attributes);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new {@link module:engine/model/documentfragment~DocumentFragment document fragment}.
|
|
102
|
+
*
|
|
103
|
+
* @returns {module:engine/model/documentfragment~DocumentFragment} Created document fragment.
|
|
104
|
+
*/
|
|
105
|
+
createDocumentFragment() {
|
|
106
|
+
return new DocumentFragment();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates a copy of the element and returns it. Created element has the same name and attributes as the original element.
|
|
110
|
+
* If clone is deep, the original element's children are also cloned. If not, then empty element is returned.
|
|
111
|
+
*
|
|
112
|
+
* @param {module:engine/model/element~Element} element The element to clone.
|
|
113
|
+
* @param {Boolean} [deep=true] If set to `true` clones element and all its children recursively. When set to `false`,
|
|
114
|
+
* element will be cloned without any child.
|
|
115
|
+
*/
|
|
116
|
+
cloneElement(element, deep = true) {
|
|
117
|
+
return element._clone(deep);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Inserts item on given position.
|
|
121
|
+
*
|
|
122
|
+
* const paragraph = writer.createElement( 'paragraph' );
|
|
123
|
+
* writer.insert( paragraph, position );
|
|
124
|
+
*
|
|
125
|
+
* Instead of using position you can use parent and offset:
|
|
126
|
+
*
|
|
127
|
+
* const text = writer.createText( 'foo' );
|
|
128
|
+
* writer.insert( text, paragraph, 5 );
|
|
129
|
+
*
|
|
130
|
+
* You can also use `end` instead of the offset to insert at the end:
|
|
131
|
+
*
|
|
132
|
+
* const text = writer.createText( 'foo' );
|
|
133
|
+
* writer.insert( text, paragraph, 'end' );
|
|
134
|
+
*
|
|
135
|
+
* Or insert before or after another element:
|
|
136
|
+
*
|
|
137
|
+
* const paragraph = writer.createElement( 'paragraph' );
|
|
138
|
+
* writer.insert( paragraph, anotherParagraph, 'after' );
|
|
139
|
+
*
|
|
140
|
+
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
|
|
141
|
+
*
|
|
142
|
+
* Note that if the item already has parent it will be removed from the previous parent.
|
|
143
|
+
*
|
|
144
|
+
* Note that you cannot re-insert a node from a document to a different document or a document fragment. In this case,
|
|
145
|
+
* `model-writer-insert-forbidden-move` is thrown.
|
|
146
|
+
*
|
|
147
|
+
* If you want to move {@link module:engine/model/range~Range range} instead of an
|
|
148
|
+
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
|
|
149
|
+
*
|
|
150
|
+
* **Note:** For a paste-like content insertion mechanism see
|
|
151
|
+
* {@link module:engine/model/model~Model#insertContent `model.insertContent()`}.
|
|
152
|
+
*
|
|
153
|
+
* @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment} item Item or document
|
|
154
|
+
* fragment to insert.
|
|
155
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
156
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
|
|
157
|
+
* second parameter is a {@link module:engine/model/item~Item model item}.
|
|
158
|
+
*/
|
|
159
|
+
insert(item, itemOrPosition, offset = 0) {
|
|
160
|
+
this._assertWriterUsedCorrectly();
|
|
161
|
+
if (item instanceof Text && item.data == '') {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const position = Position._createAt(itemOrPosition, offset);
|
|
165
|
+
// If item has a parent already.
|
|
166
|
+
if (item.parent) {
|
|
167
|
+
// We need to check if item is going to be inserted within the same document.
|
|
168
|
+
if (isSameTree(item.root, position.root)) {
|
|
169
|
+
// If it's we just need to move it.
|
|
170
|
+
this.move(Range._createOn(item), position);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// If it isn't the same root.
|
|
174
|
+
else {
|
|
175
|
+
if (item.root.document) {
|
|
176
|
+
/**
|
|
177
|
+
* Cannot move a node from a document to a different tree.
|
|
178
|
+
* It is forbidden to move a node that was already in a document outside of it.
|
|
179
|
+
*
|
|
180
|
+
* @error model-writer-insert-forbidden-move
|
|
181
|
+
*/
|
|
182
|
+
throw new CKEditorError('model-writer-insert-forbidden-move', this);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Move between two different document fragments or from document fragment to a document is possible.
|
|
186
|
+
// In that case, remove the item from it's original parent.
|
|
187
|
+
this.remove(item);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const version = position.root.document ? position.root.document.version : null;
|
|
192
|
+
const insert = new InsertOperation(position, item, version);
|
|
193
|
+
if (item instanceof Text) {
|
|
194
|
+
insert.shouldReceiveAttributes = true;
|
|
195
|
+
}
|
|
196
|
+
this.batch.addOperation(insert);
|
|
197
|
+
this.model.applyOperation(insert);
|
|
198
|
+
// When element is a DocumentFragment we need to move its markers to Document#markers.
|
|
199
|
+
if (item instanceof DocumentFragment) {
|
|
200
|
+
for (const [markerName, markerRange] of item.markers) {
|
|
201
|
+
// We need to migrate marker range from DocumentFragment to Document.
|
|
202
|
+
const rangeRootPosition = Position._createAt(markerRange.root, 0);
|
|
203
|
+
const range = new Range(markerRange.start._getCombined(rangeRootPosition, position), markerRange.end._getCombined(rangeRootPosition, position));
|
|
204
|
+
const options = { range, usingOperation: true, affectsData: true };
|
|
205
|
+
if (this.model.markers.has(markerName)) {
|
|
206
|
+
this.updateMarker(markerName, options);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
this.addMarker(markerName, options);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Creates and inserts text on given position. You can optionally set text attributes:
|
|
216
|
+
*
|
|
217
|
+
* writer.insertText( 'foo', position );
|
|
218
|
+
* writer.insertText( 'foo', { bold: true }, position );
|
|
219
|
+
*
|
|
220
|
+
* Instead of using position you can use parent and offset or define that text should be inserted at the end
|
|
221
|
+
* or before or after other node:
|
|
222
|
+
*
|
|
223
|
+
* // Inserts 'foo' in paragraph, at offset 5:
|
|
224
|
+
* writer.insertText( 'foo', paragraph, 5 );
|
|
225
|
+
* // Inserts 'foo' at the end of a paragraph:
|
|
226
|
+
* writer.insertText( 'foo', paragraph, 'end' );
|
|
227
|
+
* // Inserts 'foo' after an image:
|
|
228
|
+
* writer.insertText( 'foo', image, 'after' );
|
|
229
|
+
*
|
|
230
|
+
* These parameters work in the same way as {@link #createPositionAt `writer.createPositionAt()`}.
|
|
231
|
+
*
|
|
232
|
+
* @param {String} dattexta Text data.
|
|
233
|
+
* @param {Object} [attributes] Text attributes.
|
|
234
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
235
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
|
|
236
|
+
* third parameter is a {@link module:engine/model/item~Item model item}.
|
|
237
|
+
*/
|
|
238
|
+
insertText(text, attributes, // Too complicated when not using `any`.
|
|
239
|
+
itemOrPosition, // Too complicated when not using `any`.
|
|
240
|
+
offset // Too complicated when not using `any`.
|
|
241
|
+
) {
|
|
242
|
+
if (attributes instanceof DocumentFragment || attributes instanceof Element || attributes instanceof Position) {
|
|
243
|
+
this.insert(this.createText(text), attributes, itemOrPosition);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.insert(this.createText(text, attributes), itemOrPosition, offset);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Creates and inserts element on given position. You can optionally set attributes:
|
|
251
|
+
*
|
|
252
|
+
* writer.insertElement( 'paragraph', position );
|
|
253
|
+
* writer.insertElement( 'paragraph', { alignment: 'center' }, position );
|
|
254
|
+
*
|
|
255
|
+
* Instead of using position you can use parent and offset or define that text should be inserted at the end
|
|
256
|
+
* or before or after other node:
|
|
257
|
+
*
|
|
258
|
+
* // Inserts paragraph in the root at offset 5:
|
|
259
|
+
* writer.insertElement( 'paragraph', root, 5 );
|
|
260
|
+
* // Inserts paragraph at the end of a blockquote:
|
|
261
|
+
* writer.insertElement( 'paragraph', blockquote, 'end' );
|
|
262
|
+
* // Inserts after an image:
|
|
263
|
+
* writer.insertElement( 'paragraph', image, 'after' );
|
|
264
|
+
*
|
|
265
|
+
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
|
|
266
|
+
*
|
|
267
|
+
* @param {String} name Name of the element.
|
|
268
|
+
* @param {Object} [attributes] Elements attributes.
|
|
269
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
270
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
|
|
271
|
+
* third parameter is a {@link module:engine/model/item~Item model item}.
|
|
272
|
+
*/
|
|
273
|
+
insertElement(name, attributes, // Too complicated when not using `any`.
|
|
274
|
+
itemOrPositionOrOffset, // Too complicated when not using `any`.
|
|
275
|
+
offset // Too complicated when not using `any`.
|
|
276
|
+
) {
|
|
277
|
+
if (attributes instanceof DocumentFragment || attributes instanceof Element || attributes instanceof Position) {
|
|
278
|
+
this.insert(this.createElement(name), attributes, itemOrPositionOrOffset);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.insert(this.createElement(name, attributes), itemOrPositionOrOffset, offset);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Inserts item at the end of the given parent.
|
|
286
|
+
*
|
|
287
|
+
* const paragraph = writer.createElement( 'paragraph' );
|
|
288
|
+
* writer.append( paragraph, root );
|
|
289
|
+
*
|
|
290
|
+
* Note that if the item already has parent it will be removed from the previous parent.
|
|
291
|
+
*
|
|
292
|
+
* If you want to move {@link module:engine/model/range~Range range} instead of an
|
|
293
|
+
* {@link module:engine/model/item~Item item} use {@link module:engine/model/writer~Writer#move `Writer#move()`}.
|
|
294
|
+
*
|
|
295
|
+
* @param {module:engine/model/item~Item|module:engine/model/documentfragment~DocumentFragment}
|
|
296
|
+
* item Item or document fragment to insert.
|
|
297
|
+
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
|
|
298
|
+
*/
|
|
299
|
+
append(item, parent) {
|
|
300
|
+
this.insert(item, parent, 'end');
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Creates text node and inserts it at the end of the parent. You can optionally set text attributes:
|
|
304
|
+
*
|
|
305
|
+
* writer.appendText( 'foo', paragraph );
|
|
306
|
+
* writer.appendText( 'foo', { bold: true }, paragraph );
|
|
307
|
+
*
|
|
308
|
+
* @param {String} text Text data.
|
|
309
|
+
* @param {Object} [attributes] Text attributes.
|
|
310
|
+
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
|
|
311
|
+
*/
|
|
312
|
+
appendText(text, attributes, parent) {
|
|
313
|
+
if (attributes instanceof DocumentFragment || attributes instanceof Element) {
|
|
314
|
+
this.insert(this.createText(text), attributes, 'end');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
this.insert(this.createText(text, attributes), parent, 'end');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Creates element and inserts it at the end of the parent. You can optionally set attributes:
|
|
322
|
+
*
|
|
323
|
+
* writer.appendElement( 'paragraph', root );
|
|
324
|
+
* writer.appendElement( 'paragraph', { alignment: 'center' }, root );
|
|
325
|
+
*
|
|
326
|
+
* @param {String} name Name of the element.
|
|
327
|
+
* @param {Object} [attributes] Elements attributes.
|
|
328
|
+
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} parent
|
|
329
|
+
*/
|
|
330
|
+
appendElement(name, attributes, parent) {
|
|
331
|
+
if (attributes instanceof DocumentFragment || attributes instanceof Element) {
|
|
332
|
+
this.insert(this.createElement(name), attributes, 'end');
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.insert(this.createElement(name, attributes), parent, 'end');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Sets value of the attribute with given key on a {@link module:engine/model/item~Item model item}
|
|
340
|
+
* or on a {@link module:engine/model/range~Range range}.
|
|
341
|
+
*
|
|
342
|
+
* @param {String} key Attribute key.
|
|
343
|
+
* @param {*} value Attribute new value.
|
|
344
|
+
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
|
|
345
|
+
* Model item or range on which the attribute will be set.
|
|
346
|
+
*/
|
|
347
|
+
setAttribute(key, value, itemOrRange) {
|
|
348
|
+
this._assertWriterUsedCorrectly();
|
|
349
|
+
if (itemOrRange instanceof Range) {
|
|
350
|
+
const ranges = itemOrRange.getMinimalFlatRanges();
|
|
351
|
+
for (const range of ranges) {
|
|
352
|
+
setAttributeOnRange(this, key, value, range);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
setAttributeOnItem(this, key, value, itemOrRange);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Sets values of attributes on a {@link module:engine/model/item~Item model item}
|
|
361
|
+
* or on a {@link module:engine/model/range~Range range}.
|
|
362
|
+
*
|
|
363
|
+
* writer.setAttributes( {
|
|
364
|
+
* bold: true,
|
|
365
|
+
* italic: true
|
|
366
|
+
* }, range );
|
|
367
|
+
*
|
|
368
|
+
* @param {Object} attributes Attributes keys and values.
|
|
369
|
+
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
|
|
370
|
+
* Model item or range on which the attributes will be set.
|
|
371
|
+
*/
|
|
372
|
+
setAttributes(attributes, itemOrRange) {
|
|
373
|
+
for (const [key, val] of toMap(attributes)) {
|
|
374
|
+
this.setAttribute(key, val, itemOrRange);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Removes an attribute with given key from a {@link module:engine/model/item~Item model item}
|
|
379
|
+
* or from a {@link module:engine/model/range~Range range}.
|
|
380
|
+
*
|
|
381
|
+
* @param {String} key Attribute key.
|
|
382
|
+
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
|
|
383
|
+
* Model item or range from which the attribute will be removed.
|
|
384
|
+
*/
|
|
385
|
+
removeAttribute(key, itemOrRange) {
|
|
386
|
+
this._assertWriterUsedCorrectly();
|
|
387
|
+
if (itemOrRange instanceof Range) {
|
|
388
|
+
const ranges = itemOrRange.getMinimalFlatRanges();
|
|
389
|
+
for (const range of ranges) {
|
|
390
|
+
setAttributeOnRange(this, key, null, range);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
setAttributeOnItem(this, key, null, itemOrRange);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Removes all attributes from all elements in the range or from the given item.
|
|
399
|
+
*
|
|
400
|
+
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange
|
|
401
|
+
* Model item or range from which all attributes will be removed.
|
|
402
|
+
*/
|
|
403
|
+
clearAttributes(itemOrRange) {
|
|
404
|
+
this._assertWriterUsedCorrectly();
|
|
405
|
+
const removeAttributesFromItem = (item) => {
|
|
406
|
+
for (const attribute of item.getAttributeKeys()) {
|
|
407
|
+
this.removeAttribute(attribute, item);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
if (!(itemOrRange instanceof Range)) {
|
|
411
|
+
removeAttributesFromItem(itemOrRange);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
for (const item of itemOrRange.getItems()) {
|
|
415
|
+
removeAttributesFromItem(item);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Moves all items in the source range to the target position.
|
|
421
|
+
*
|
|
422
|
+
* writer.move( sourceRange, targetPosition );
|
|
423
|
+
*
|
|
424
|
+
* Instead of the target position you can use parent and offset or define that range should be moved to the end
|
|
425
|
+
* or before or after chosen item:
|
|
426
|
+
*
|
|
427
|
+
* // Moves all items in the range to the paragraph at offset 5:
|
|
428
|
+
* writer.move( sourceRange, paragraph, 5 );
|
|
429
|
+
* // Moves all items in the range to the end of a blockquote:
|
|
430
|
+
* writer.move( sourceRange, blockquote, 'end' );
|
|
431
|
+
* // Moves all items in the range to a position after an image:
|
|
432
|
+
* writer.move( sourceRange, image, 'after' );
|
|
433
|
+
*
|
|
434
|
+
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}.
|
|
435
|
+
*
|
|
436
|
+
* Note that items can be moved only within the same tree. It means that you can move items within the same root
|
|
437
|
+
* (element or document fragment) or between {@link module:engine/model/document~Document#roots documents roots},
|
|
438
|
+
* but you can not move items from document fragment to the document or from one detached element to another. Use
|
|
439
|
+
* {@link module:engine/model/writer~Writer#insert} in such cases.
|
|
440
|
+
*
|
|
441
|
+
* @param {module:engine/model/range~Range} range Source range.
|
|
442
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
443
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
|
|
444
|
+
* second parameter is a {@link module:engine/model/item~Item model item}.
|
|
445
|
+
*/
|
|
446
|
+
move(range, itemOrPosition, offset) {
|
|
447
|
+
this._assertWriterUsedCorrectly();
|
|
448
|
+
if (!(range instanceof Range)) {
|
|
449
|
+
/**
|
|
450
|
+
* Invalid range to move.
|
|
451
|
+
*
|
|
452
|
+
* @error writer-move-invalid-range
|
|
453
|
+
*/
|
|
454
|
+
throw new CKEditorError('writer-move-invalid-range', this);
|
|
455
|
+
}
|
|
456
|
+
if (!range.isFlat) {
|
|
457
|
+
/**
|
|
458
|
+
* Range to move is not flat.
|
|
459
|
+
*
|
|
460
|
+
* @error writer-move-range-not-flat
|
|
461
|
+
*/
|
|
462
|
+
throw new CKEditorError('writer-move-range-not-flat', this);
|
|
463
|
+
}
|
|
464
|
+
const position = Position._createAt(itemOrPosition, offset);
|
|
465
|
+
// Do not move anything if the move target is same as moved range start.
|
|
466
|
+
if (position.isEqual(range.start)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
// If part of the marker is removed, create additional marker operation for undo purposes.
|
|
470
|
+
this._addOperationForAffectedMarkers('move', range);
|
|
471
|
+
if (!isSameTree(range.root, position.root)) {
|
|
472
|
+
/**
|
|
473
|
+
* Range is going to be moved within not the same document. Please use
|
|
474
|
+
* {@link module:engine/model/writer~Writer#insert insert} instead.
|
|
475
|
+
*
|
|
476
|
+
* @error writer-move-different-document
|
|
477
|
+
*/
|
|
478
|
+
throw new CKEditorError('writer-move-different-document', this);
|
|
479
|
+
}
|
|
480
|
+
const version = range.root.document ? range.root.document.version : null;
|
|
481
|
+
const operation = new MoveOperation(range.start, range.end.offset - range.start.offset, position, version);
|
|
482
|
+
this.batch.addOperation(operation);
|
|
483
|
+
this.model.applyOperation(operation);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Removes given model {@link module:engine/model/item~Item item} or {@link module:engine/model/range~Range range}.
|
|
487
|
+
*
|
|
488
|
+
* @param {module:engine/model/item~Item|module:engine/model/range~Range} itemOrRange Model item or range to remove.
|
|
489
|
+
*/
|
|
490
|
+
remove(itemOrRange) {
|
|
491
|
+
this._assertWriterUsedCorrectly();
|
|
492
|
+
const rangeToRemove = itemOrRange instanceof Range ? itemOrRange : Range._createOn(itemOrRange);
|
|
493
|
+
const ranges = rangeToRemove.getMinimalFlatRanges().reverse();
|
|
494
|
+
for (const flat of ranges) {
|
|
495
|
+
// If part of the marker is removed, create additional marker operation for undo purposes.
|
|
496
|
+
this._addOperationForAffectedMarkers('move', flat);
|
|
497
|
+
applyRemoveOperation(flat.start, flat.end.offset - flat.start.offset, this.batch, this.model);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Merges two siblings at the given position.
|
|
502
|
+
*
|
|
503
|
+
* Node before and after the position have to be an element. Otherwise `writer-merge-no-element-before` or
|
|
504
|
+
* `writer-merge-no-element-after` error will be thrown.
|
|
505
|
+
*
|
|
506
|
+
* @param {module:engine/model/position~Position} position Position between merged elements.
|
|
507
|
+
*/
|
|
508
|
+
merge(position) {
|
|
509
|
+
this._assertWriterUsedCorrectly();
|
|
510
|
+
const nodeBefore = position.nodeBefore;
|
|
511
|
+
const nodeAfter = position.nodeAfter;
|
|
512
|
+
// If part of the marker is removed, create additional marker operation for undo purposes.
|
|
513
|
+
this._addOperationForAffectedMarkers('merge', position);
|
|
514
|
+
if (!(nodeBefore instanceof Element)) {
|
|
515
|
+
/**
|
|
516
|
+
* Node before merge position must be an element.
|
|
517
|
+
*
|
|
518
|
+
* @error writer-merge-no-element-before
|
|
519
|
+
*/
|
|
520
|
+
throw new CKEditorError('writer-merge-no-element-before', this);
|
|
521
|
+
}
|
|
522
|
+
if (!(nodeAfter instanceof Element)) {
|
|
523
|
+
/**
|
|
524
|
+
* Node after merge position must be an element.
|
|
525
|
+
*
|
|
526
|
+
* @error writer-merge-no-element-after
|
|
527
|
+
*/
|
|
528
|
+
throw new CKEditorError('writer-merge-no-element-after', this);
|
|
529
|
+
}
|
|
530
|
+
if (!position.root.document) {
|
|
531
|
+
this._mergeDetached(position);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
this._merge(position);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Shortcut for {@link module:engine/model/model~Model#createPositionFromPath `Model#createPositionFromPath()`}.
|
|
539
|
+
*
|
|
540
|
+
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} root Root of the position.
|
|
541
|
+
* @param {Array.<Number>} path Position path. See {@link module:engine/model/position~Position#path}.
|
|
542
|
+
* @param {module:engine/model/position~PositionStickiness} [stickiness='toNone'] Position stickiness.
|
|
543
|
+
* See {@link module:engine/model/position~PositionStickiness}.
|
|
544
|
+
* @returns {module:engine/model/position~Position}
|
|
545
|
+
*/
|
|
546
|
+
createPositionFromPath(root, path, stickiness) {
|
|
547
|
+
return this.model.createPositionFromPath(root, path, stickiness);
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Shortcut for {@link module:engine/model/model~Model#createPositionAt `Model#createPositionAt()`}.
|
|
551
|
+
*
|
|
552
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
553
|
+
* @param {Number|'end'|'before'|'after'} [offset] Offset or one of the flags. Used only when
|
|
554
|
+
* first parameter is a {@link module:engine/model/item~Item model item}.
|
|
555
|
+
* @returns {module:engine/model/position~Position}
|
|
556
|
+
*/
|
|
557
|
+
createPositionAt(itemOrPosition, offset) {
|
|
558
|
+
return this.model.createPositionAt(itemOrPosition, offset);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Shortcut for {@link module:engine/model/model~Model#createPositionAfter `Model#createPositionAfter()`}.
|
|
562
|
+
*
|
|
563
|
+
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
|
|
564
|
+
* @returns {module:engine/model/position~Position}
|
|
565
|
+
*/
|
|
566
|
+
createPositionAfter(item) {
|
|
567
|
+
return this.model.createPositionAfter(item);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Shortcut for {@link module:engine/model/model~Model#createPositionBefore `Model#createPositionBefore()`}.
|
|
571
|
+
*
|
|
572
|
+
* @param {module:engine/model/item~Item} item Item after which the position should be placed.
|
|
573
|
+
* @returns {module:engine/model/position~Position}
|
|
574
|
+
*/
|
|
575
|
+
createPositionBefore(item) {
|
|
576
|
+
return this.model.createPositionBefore(item);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Shortcut for {@link module:engine/model/model~Model#createRange `Model#createRange()`}.
|
|
580
|
+
*
|
|
581
|
+
* @param {module:engine/model/position~Position} start Start position.
|
|
582
|
+
* @param {module:engine/model/position~Position} [end] End position. If not set, range will be collapsed at `start` position.
|
|
583
|
+
* @returns {module:engine/model/range~Range}
|
|
584
|
+
*/
|
|
585
|
+
createRange(start, end) {
|
|
586
|
+
return this.model.createRange(start, end);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Shortcut for {@link module:engine/model/model~Model#createRangeIn `Model#createRangeIn()`}.
|
|
590
|
+
*
|
|
591
|
+
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
|
|
592
|
+
* @returns {module:engine/model/range~Range}
|
|
593
|
+
*/
|
|
594
|
+
createRangeIn(element) {
|
|
595
|
+
return this.model.createRangeIn(element);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Shortcut for {@link module:engine/model/model~Model#createRangeOn `Model#createRangeOn()`}.
|
|
599
|
+
*
|
|
600
|
+
* @param {module:engine/model/element~Element} element Element which is a parent for the range.
|
|
601
|
+
* @returns {module:engine/model/range~Range}
|
|
602
|
+
*/
|
|
603
|
+
createRangeOn(element) {
|
|
604
|
+
return this.model.createRangeOn(element);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Shortcut for {@link module:engine/model/model~Model#createSelection `Model#createSelection()`}.
|
|
608
|
+
*
|
|
609
|
+
* @param {module:engine/model/selection~Selectable} selectable
|
|
610
|
+
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
|
|
611
|
+
* @param {Object} [options]
|
|
612
|
+
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
|
|
613
|
+
* @returns {module:engine/model/selection~Selection}
|
|
614
|
+
*/
|
|
615
|
+
createSelection(...args) {
|
|
616
|
+
return this.model.createSelection(...args);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Performs merge action in a detached tree.
|
|
620
|
+
*
|
|
621
|
+
* @private
|
|
622
|
+
* @param {module:engine/model/position~Position} position Position between merged elements.
|
|
623
|
+
*/
|
|
624
|
+
_mergeDetached(position) {
|
|
625
|
+
const nodeBefore = position.nodeBefore;
|
|
626
|
+
const nodeAfter = position.nodeAfter;
|
|
627
|
+
this.move(Range._createIn(nodeAfter), Position._createAt(nodeBefore, 'end'));
|
|
628
|
+
this.remove(nodeAfter);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Performs merge action in a non-detached tree.
|
|
632
|
+
*
|
|
633
|
+
* @private
|
|
634
|
+
* @param {module:engine/model/position~Position} position Position between merged elements.
|
|
635
|
+
*/
|
|
636
|
+
_merge(position) {
|
|
637
|
+
const targetPosition = Position._createAt(position.nodeBefore, 'end');
|
|
638
|
+
const sourcePosition = Position._createAt(position.nodeAfter, 0);
|
|
639
|
+
const graveyard = position.root.document.graveyard;
|
|
640
|
+
const graveyardPosition = new Position(graveyard, [0]);
|
|
641
|
+
const version = position.root.document.version;
|
|
642
|
+
const merge = new MergeOperation(sourcePosition, position.nodeAfter.maxOffset, targetPosition, graveyardPosition, version);
|
|
643
|
+
this.batch.addOperation(merge);
|
|
644
|
+
this.model.applyOperation(merge);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Renames the given element.
|
|
648
|
+
*
|
|
649
|
+
* @param {module:engine/model/element~Element} element The element to rename.
|
|
650
|
+
* @param {String} newName New element name.
|
|
651
|
+
*/
|
|
652
|
+
rename(element, newName) {
|
|
653
|
+
this._assertWriterUsedCorrectly();
|
|
654
|
+
if (!(element instanceof Element)) {
|
|
655
|
+
/**
|
|
656
|
+
* Trying to rename an object which is not an instance of Element.
|
|
657
|
+
*
|
|
658
|
+
* @error writer-rename-not-element-instance
|
|
659
|
+
*/
|
|
660
|
+
throw new CKEditorError('writer-rename-not-element-instance', this);
|
|
661
|
+
}
|
|
662
|
+
const version = element.root.document ? element.root.document.version : null;
|
|
663
|
+
const renameOperation = new RenameOperation(Position._createBefore(element), element.name, newName, version);
|
|
664
|
+
this.batch.addOperation(renameOperation);
|
|
665
|
+
this.model.applyOperation(renameOperation);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Splits elements starting from the given position and going to the top of the model tree as long as given
|
|
669
|
+
* `limitElement` is reached. When `limitElement` is not defined then only the parent of the given position will be split.
|
|
670
|
+
*
|
|
671
|
+
* The element needs to have a parent. It cannot be a root element nor a document fragment.
|
|
672
|
+
* The `writer-split-element-no-parent` error will be thrown if you try to split an element with no parent.
|
|
673
|
+
*
|
|
674
|
+
* @param {module:engine/model/position~Position} position Position of split.
|
|
675
|
+
* @param {module:engine/model/node~Node} [limitElement] Stop splitting when this element will be reached.
|
|
676
|
+
* @returns {Object} result Split result.
|
|
677
|
+
* @returns {module:engine/model/position~Position} result.position Position between split elements.
|
|
678
|
+
* @returns {module:engine/model/range~Range} result.range Range that stars from the end of the first split element and ends
|
|
679
|
+
* at the beginning of the first copy element.
|
|
680
|
+
*/
|
|
681
|
+
split(position, limitElement) {
|
|
682
|
+
this._assertWriterUsedCorrectly();
|
|
683
|
+
let splitElement = position.parent;
|
|
684
|
+
if (!splitElement.parent) {
|
|
685
|
+
/**
|
|
686
|
+
* Element with no parent can not be split.
|
|
687
|
+
*
|
|
688
|
+
* @error writer-split-element-no-parent
|
|
689
|
+
*/
|
|
690
|
+
throw new CKEditorError('writer-split-element-no-parent', this);
|
|
691
|
+
}
|
|
692
|
+
// When limit element is not defined lets set splitElement parent as limit.
|
|
693
|
+
if (!limitElement) {
|
|
694
|
+
limitElement = splitElement.parent;
|
|
695
|
+
}
|
|
696
|
+
if (!position.parent.getAncestors({ includeSelf: true }).includes(limitElement)) {
|
|
697
|
+
/**
|
|
698
|
+
* Limit element is not a position ancestor.
|
|
699
|
+
*
|
|
700
|
+
* @error writer-split-invalid-limit-element
|
|
701
|
+
*/
|
|
702
|
+
throw new CKEditorError('writer-split-invalid-limit-element', this);
|
|
703
|
+
}
|
|
704
|
+
// We need to cache elements that will be created as a result of the first split because
|
|
705
|
+
// we need to create a range from the end of the first split element to the beginning of the
|
|
706
|
+
// first copy element. This should be handled by LiveRange but it doesn't work on detached nodes.
|
|
707
|
+
let firstSplitElement;
|
|
708
|
+
let firstCopyElement;
|
|
709
|
+
do {
|
|
710
|
+
const version = splitElement.root.document ? splitElement.root.document.version : null;
|
|
711
|
+
const howMany = splitElement.maxOffset - position.offset;
|
|
712
|
+
const insertionPosition = SplitOperation.getInsertionPosition(position);
|
|
713
|
+
const split = new SplitOperation(position, howMany, insertionPosition, null, version);
|
|
714
|
+
this.batch.addOperation(split);
|
|
715
|
+
this.model.applyOperation(split);
|
|
716
|
+
// Cache result of the first split.
|
|
717
|
+
if (!firstSplitElement && !firstCopyElement) {
|
|
718
|
+
firstSplitElement = splitElement;
|
|
719
|
+
firstCopyElement = position.parent.nextSibling;
|
|
720
|
+
}
|
|
721
|
+
position = this.createPositionAfter(position.parent);
|
|
722
|
+
splitElement = position.parent;
|
|
723
|
+
} while (splitElement !== limitElement);
|
|
724
|
+
return {
|
|
725
|
+
position,
|
|
726
|
+
range: new Range(Position._createAt(firstSplitElement, 'end'), Position._createAt(firstCopyElement, 0))
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Wraps the given range with the given element or with a new element (if a string was passed).
|
|
731
|
+
*
|
|
732
|
+
* **Note:** range to wrap should be a "flat range" (see {@link module:engine/model/range~Range#isFlat `Range#isFlat`}).
|
|
733
|
+
* If not, an error will be thrown.
|
|
734
|
+
*
|
|
735
|
+
* @param {module:engine/model/range~Range} range Range to wrap.
|
|
736
|
+
* @param {module:engine/model/element~Element|String} elementOrString Element or name of element to wrap the range with.
|
|
737
|
+
*/
|
|
738
|
+
wrap(range, elementOrString) {
|
|
739
|
+
this._assertWriterUsedCorrectly();
|
|
740
|
+
if (!range.isFlat) {
|
|
741
|
+
/**
|
|
742
|
+
* Range to wrap is not flat.
|
|
743
|
+
*
|
|
744
|
+
* @error writer-wrap-range-not-flat
|
|
745
|
+
*/
|
|
746
|
+
throw new CKEditorError('writer-wrap-range-not-flat', this);
|
|
747
|
+
}
|
|
748
|
+
const element = elementOrString instanceof Element ? elementOrString : new Element(elementOrString);
|
|
749
|
+
if (element.childCount > 0) {
|
|
750
|
+
/**
|
|
751
|
+
* Element to wrap with is not empty.
|
|
752
|
+
*
|
|
753
|
+
* @error writer-wrap-element-not-empty
|
|
754
|
+
*/
|
|
755
|
+
throw new CKEditorError('writer-wrap-element-not-empty', this);
|
|
756
|
+
}
|
|
757
|
+
if (element.parent !== null) {
|
|
758
|
+
/**
|
|
759
|
+
* Element to wrap with is already attached to a tree model.
|
|
760
|
+
*
|
|
761
|
+
* @error writer-wrap-element-attached
|
|
762
|
+
*/
|
|
763
|
+
throw new CKEditorError('writer-wrap-element-attached', this);
|
|
764
|
+
}
|
|
765
|
+
this.insert(element, range.start);
|
|
766
|
+
// Shift the range-to-wrap because we just inserted an element before that range.
|
|
767
|
+
const shiftedRange = new Range(range.start.getShiftedBy(1), range.end.getShiftedBy(1));
|
|
768
|
+
this.move(shiftedRange, Position._createAt(element, 0));
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Unwraps children of the given element – all its children are moved before it and then the element is removed.
|
|
772
|
+
* Throws error if you try to unwrap an element which does not have a parent.
|
|
773
|
+
*
|
|
774
|
+
* @param {module:engine/model/element~Element} element Element to unwrap.
|
|
775
|
+
*/
|
|
776
|
+
unwrap(element) {
|
|
777
|
+
this._assertWriterUsedCorrectly();
|
|
778
|
+
if (element.parent === null) {
|
|
779
|
+
/**
|
|
780
|
+
* Trying to unwrap an element which has no parent.
|
|
781
|
+
*
|
|
782
|
+
* @error writer-unwrap-element-no-parent
|
|
783
|
+
*/
|
|
784
|
+
throw new CKEditorError('writer-unwrap-element-no-parent', this);
|
|
785
|
+
}
|
|
786
|
+
this.move(Range._createIn(element), this.createPositionAfter(element));
|
|
787
|
+
this.remove(element);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Adds a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
|
|
791
|
+
* changes in the document and updates its range automatically, when model tree changes.
|
|
792
|
+
*
|
|
793
|
+
* As the first parameter you can set marker name.
|
|
794
|
+
*
|
|
795
|
+
* The required `options.usingOperation` parameter lets you decide if the marker should be managed by operations or not. See
|
|
796
|
+
* {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
|
|
797
|
+
* markers managed by operations and not-managed by operations.
|
|
798
|
+
*
|
|
799
|
+
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
|
|
800
|
+
* `true` when the marker change changes the data returned by the
|
|
801
|
+
* {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
|
|
802
|
+
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
|
|
803
|
+
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
|
|
804
|
+
*
|
|
805
|
+
* Create marker directly base on marker's name:
|
|
806
|
+
*
|
|
807
|
+
* addMarker( markerName, { range, usingOperation: false } );
|
|
808
|
+
*
|
|
809
|
+
* Create marker using operation:
|
|
810
|
+
*
|
|
811
|
+
* addMarker( markerName, { range, usingOperation: true } );
|
|
812
|
+
*
|
|
813
|
+
* Create marker that affects the editor data:
|
|
814
|
+
*
|
|
815
|
+
* addMarker( markerName, { range, usingOperation: false, affectsData: true } );
|
|
816
|
+
*
|
|
817
|
+
* Note: For efficiency reasons, it's best to create and keep as little markers as possible.
|
|
818
|
+
*
|
|
819
|
+
* @see module:engine/model/markercollection~Marker
|
|
820
|
+
* @param {String} name Name of a marker to create - must be unique.
|
|
821
|
+
* @param {Object} options
|
|
822
|
+
* @param {Boolean} options.usingOperation Flag indicating that the marker should be added by MarkerOperation.
|
|
823
|
+
* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
|
|
824
|
+
* @param {module:engine/model/range~Range} options.range Marker range.
|
|
825
|
+
* @param {Boolean} [options.affectsData=false] Flag indicating that the marker changes the editor data.
|
|
826
|
+
* @returns {module:engine/model/markercollection~Marker} Marker that was set.
|
|
827
|
+
*/
|
|
828
|
+
addMarker(name, options) {
|
|
829
|
+
this._assertWriterUsedCorrectly();
|
|
830
|
+
if (!options || typeof options.usingOperation != 'boolean') {
|
|
831
|
+
/**
|
|
832
|
+
* The `options.usingOperation` parameter is required when adding a new marker.
|
|
833
|
+
*
|
|
834
|
+
* @error writer-addmarker-no-usingoperation
|
|
835
|
+
*/
|
|
836
|
+
throw new CKEditorError('writer-addmarker-no-usingoperation', this);
|
|
837
|
+
}
|
|
838
|
+
const usingOperation = options.usingOperation;
|
|
839
|
+
const range = options.range;
|
|
840
|
+
const affectsData = options.affectsData === undefined ? false : options.affectsData;
|
|
841
|
+
if (this.model.markers.has(name)) {
|
|
842
|
+
/**
|
|
843
|
+
* Marker with provided name already exists.
|
|
844
|
+
*
|
|
845
|
+
* @error writer-addmarker-marker-exists
|
|
846
|
+
*/
|
|
847
|
+
throw new CKEditorError('writer-addmarker-marker-exists', this);
|
|
848
|
+
}
|
|
849
|
+
if (!range) {
|
|
850
|
+
/**
|
|
851
|
+
* Range parameter is required when adding a new marker.
|
|
852
|
+
*
|
|
853
|
+
* @error writer-addmarker-no-range
|
|
854
|
+
*/
|
|
855
|
+
throw new CKEditorError('writer-addmarker-no-range', this);
|
|
856
|
+
}
|
|
857
|
+
if (!usingOperation) {
|
|
858
|
+
return this.model.markers._set(name, range, usingOperation, affectsData);
|
|
859
|
+
}
|
|
860
|
+
applyMarkerOperation(this, name, null, range, affectsData);
|
|
861
|
+
return this.model.markers.get(name);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Adds, updates or refreshes a {@link module:engine/model/markercollection~Marker marker}. Marker is a named range, which tracks
|
|
865
|
+
* changes in the document and updates its range automatically, when model tree changes. Still, it is possible to change the
|
|
866
|
+
* marker's range directly using this method.
|
|
867
|
+
*
|
|
868
|
+
* As the first parameter you can set marker name or instance. If none of them is provided, new marker, with a unique
|
|
869
|
+
* name is created and returned.
|
|
870
|
+
*
|
|
871
|
+
* **Note**: If you want to change the {@link module:engine/view/element~Element view element} of the marker while its data in the model
|
|
872
|
+
* remains the same, use the dedicated {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker} method.
|
|
873
|
+
*
|
|
874
|
+
* The `options.usingOperation` parameter lets you change if the marker should be managed by operations or not. See
|
|
875
|
+
* {@link module:engine/model/markercollection~Marker marker class description} to learn about the difference between
|
|
876
|
+
* markers managed by operations and not-managed by operations. It is possible to change this option for an existing marker.
|
|
877
|
+
*
|
|
878
|
+
* The `options.affectsData` parameter, which defaults to `false`, allows you to define if a marker affects the data. It should be
|
|
879
|
+
* `true` when the marker change changes the data returned by
|
|
880
|
+
* the {@link module:core/editor/utils/dataapimixin~DataApi#getData `editor.getData()`} method.
|
|
881
|
+
* When set to `true` it fires the {@link module:engine/model/document~Document#event:change:data `change:data`} event.
|
|
882
|
+
* When set to `false` it fires the {@link module:engine/model/document~Document#event:change `change`} event.
|
|
883
|
+
*
|
|
884
|
+
* Update marker directly base on marker's name:
|
|
885
|
+
*
|
|
886
|
+
* updateMarker( markerName, { range } );
|
|
887
|
+
*
|
|
888
|
+
* Update marker using operation:
|
|
889
|
+
*
|
|
890
|
+
* updateMarker( marker, { range, usingOperation: true } );
|
|
891
|
+
* updateMarker( markerName, { range, usingOperation: true } );
|
|
892
|
+
*
|
|
893
|
+
* Change marker's option (start using operations to manage it):
|
|
894
|
+
*
|
|
895
|
+
* updateMarker( marker, { usingOperation: true } );
|
|
896
|
+
*
|
|
897
|
+
* Change marker's option (inform the engine, that the marker does not affect the data anymore):
|
|
898
|
+
*
|
|
899
|
+
* updateMarker( markerName, { affectsData: false } );
|
|
900
|
+
*
|
|
901
|
+
* @see module:engine/model/markercollection~Marker
|
|
902
|
+
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of a marker to update, or a marker instance.
|
|
903
|
+
* @param {Object} [options] If options object is not defined then marker will be refreshed by triggering
|
|
904
|
+
* downcast conversion for this marker with the same data.
|
|
905
|
+
* @param {module:engine/model/range~Range} [options.range] Marker range to update.
|
|
906
|
+
* @param {Boolean} [options.usingOperation] Flag indicated whether the marker should be added by MarkerOperation.
|
|
907
|
+
* See {@link module:engine/model/markercollection~Marker#managedUsingOperations}.
|
|
908
|
+
* @param {Boolean} [options.affectsData] Flag indicating that the marker changes the editor data.
|
|
909
|
+
*/
|
|
910
|
+
updateMarker(markerOrName, options) {
|
|
911
|
+
this._assertWriterUsedCorrectly();
|
|
912
|
+
const markerName = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
|
|
913
|
+
const currentMarker = this.model.markers.get(markerName);
|
|
914
|
+
if (!currentMarker) {
|
|
915
|
+
/**
|
|
916
|
+
* Marker with provided name does not exist and will not be updated.
|
|
917
|
+
*
|
|
918
|
+
* @error writer-updatemarker-marker-not-exists
|
|
919
|
+
*/
|
|
920
|
+
throw new CKEditorError('writer-updatemarker-marker-not-exists', this);
|
|
921
|
+
}
|
|
922
|
+
if (!options) {
|
|
923
|
+
/**
|
|
924
|
+
* The usage of `writer.updateMarker()` only to reconvert (refresh) a
|
|
925
|
+
* {@link module:engine/model/markercollection~Marker model marker} was deprecated and may not work in the future.
|
|
926
|
+
* Please update your code to use
|
|
927
|
+
* {@link module:engine/controller/editingcontroller~EditingController#reconvertMarker `editor.editing.reconvertMarker()`}
|
|
928
|
+
* instead.
|
|
929
|
+
*
|
|
930
|
+
* @error writer-updatemarker-reconvert-using-editingcontroller
|
|
931
|
+
* @param {String} markerName The name of the updated marker.
|
|
932
|
+
*/
|
|
933
|
+
logWarning('writer-updatemarker-reconvert-using-editingcontroller', { markerName });
|
|
934
|
+
this.model.markers._refresh(currentMarker);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const hasUsingOperationDefined = typeof options.usingOperation == 'boolean';
|
|
938
|
+
const affectsDataDefined = typeof options.affectsData == 'boolean';
|
|
939
|
+
// Use previously defined marker's affectsData if the property is not provided.
|
|
940
|
+
const affectsData = affectsDataDefined ? options.affectsData : currentMarker.affectsData;
|
|
941
|
+
if (!hasUsingOperationDefined && !options.range && !affectsDataDefined) {
|
|
942
|
+
/**
|
|
943
|
+
* One of the options is required - provide range, usingOperations or affectsData.
|
|
944
|
+
*
|
|
945
|
+
* @error writer-updatemarker-wrong-options
|
|
946
|
+
*/
|
|
947
|
+
throw new CKEditorError('writer-updatemarker-wrong-options', this);
|
|
948
|
+
}
|
|
949
|
+
const currentRange = currentMarker.getRange();
|
|
950
|
+
const updatedRange = options.range ? options.range : currentRange;
|
|
951
|
+
if (hasUsingOperationDefined && options.usingOperation !== currentMarker.managedUsingOperations) {
|
|
952
|
+
// The marker type is changed so it's necessary to create proper operations.
|
|
953
|
+
if (options.usingOperation) {
|
|
954
|
+
// If marker changes to a managed one treat this as synchronizing existing marker.
|
|
955
|
+
// Create `MarkerOperation` with `oldRange` set to `null`, so reverse operation will remove the marker.
|
|
956
|
+
applyMarkerOperation(this, markerName, null, updatedRange, affectsData);
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
// If marker changes to a marker that do not use operations then we need to create additional operation
|
|
960
|
+
// that removes that marker first.
|
|
961
|
+
applyMarkerOperation(this, markerName, currentRange, null, affectsData);
|
|
962
|
+
// Although not managed the marker itself should stay in model and its range should be preserver or changed to passed range.
|
|
963
|
+
this.model.markers._set(markerName, updatedRange, undefined, affectsData);
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
// Marker's type doesn't change so update it accordingly.
|
|
968
|
+
if (currentMarker.managedUsingOperations) {
|
|
969
|
+
applyMarkerOperation(this, markerName, currentRange, updatedRange, affectsData);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
this.model.markers._set(markerName, updatedRange, undefined, affectsData);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Removes given {@link module:engine/model/markercollection~Marker marker} or marker with given name.
|
|
977
|
+
* The marker is removed accordingly to how it has been created, so if the marker was created using operation,
|
|
978
|
+
* it will be destroyed using operation.
|
|
979
|
+
*
|
|
980
|
+
* @param {module:engine/model/markercollection~Marker|String} markerOrName Marker or marker name to remove.
|
|
981
|
+
*/
|
|
982
|
+
removeMarker(markerOrName) {
|
|
983
|
+
this._assertWriterUsedCorrectly();
|
|
984
|
+
const name = typeof markerOrName == 'string' ? markerOrName : markerOrName.name;
|
|
985
|
+
if (!this.model.markers.has(name)) {
|
|
986
|
+
/**
|
|
987
|
+
* Trying to remove marker which does not exist.
|
|
988
|
+
*
|
|
989
|
+
* @error writer-removemarker-no-marker
|
|
990
|
+
*/
|
|
991
|
+
throw new CKEditorError('writer-removemarker-no-marker', this);
|
|
992
|
+
}
|
|
993
|
+
const marker = this.model.markers.get(name);
|
|
994
|
+
if (!marker.managedUsingOperations) {
|
|
995
|
+
this.model.markers._remove(name);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const oldRange = marker.getRange();
|
|
999
|
+
applyMarkerOperation(this, name, oldRange, null, marker.affectsData);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Sets the document's selection (ranges and direction) to the specified location based on the given
|
|
1003
|
+
* {@link module:engine/model/selection~Selectable selectable} or creates an empty selection if no arguments were passed.
|
|
1004
|
+
*
|
|
1005
|
+
* // Sets selection to the given range.
|
|
1006
|
+
* const range = writer.createRange( start, end );
|
|
1007
|
+
* writer.setSelection( range );
|
|
1008
|
+
*
|
|
1009
|
+
* // Sets selection to given ranges.
|
|
1010
|
+
* const ranges = [ writer.createRange( start1, end2 ), writer.createRange( star2, end2 ) ];
|
|
1011
|
+
* writer.setSelection( ranges );
|
|
1012
|
+
*
|
|
1013
|
+
* // Sets selection to other selection.
|
|
1014
|
+
* const otherSelection = writer.createSelection();
|
|
1015
|
+
* writer.setSelection( otherSelection );
|
|
1016
|
+
*
|
|
1017
|
+
* // Sets selection to the given document selection.
|
|
1018
|
+
* const documentSelection = model.document.selection;
|
|
1019
|
+
* writer.setSelection( documentSelection );
|
|
1020
|
+
*
|
|
1021
|
+
* // Sets collapsed selection at the given position.
|
|
1022
|
+
* const position = writer.createPosition( root, path );
|
|
1023
|
+
* writer.setSelection( position );
|
|
1024
|
+
*
|
|
1025
|
+
* // Sets collapsed selection at the position of the given node and an offset.
|
|
1026
|
+
* writer.setSelection( paragraph, offset );
|
|
1027
|
+
*
|
|
1028
|
+
* Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
|
|
1029
|
+
* that element and ends after the last child of that element.
|
|
1030
|
+
*
|
|
1031
|
+
* writer.setSelection( paragraph, 'in' );
|
|
1032
|
+
*
|
|
1033
|
+
* Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends just after the item.
|
|
1034
|
+
*
|
|
1035
|
+
* writer.setSelection( paragraph, 'on' );
|
|
1036
|
+
*
|
|
1037
|
+
* // Removes all selection's ranges.
|
|
1038
|
+
* writer.setSelection( null );
|
|
1039
|
+
*
|
|
1040
|
+
* `Writer#setSelection()` allow passing additional options (`backward`) as the last argument.
|
|
1041
|
+
*
|
|
1042
|
+
* // Sets selection as backward.
|
|
1043
|
+
* writer.setSelection( range, { backward: true } );
|
|
1044
|
+
*
|
|
1045
|
+
* Throws `writer-incorrect-use` error when the writer is used outside the `change()` block.
|
|
1046
|
+
*
|
|
1047
|
+
* @param {module:engine/model/selection~Selectable} selectable
|
|
1048
|
+
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
|
|
1049
|
+
* @param {Object} [options]
|
|
1050
|
+
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
|
|
1051
|
+
*/
|
|
1052
|
+
setSelection(...args) {
|
|
1053
|
+
this._assertWriterUsedCorrectly();
|
|
1054
|
+
this.model.document.selection._setTo(...args);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Moves {@link module:engine/model/documentselection~DocumentSelection#focus} to the specified location.
|
|
1058
|
+
*
|
|
1059
|
+
* The location can be specified in the same form as
|
|
1060
|
+
* {@link #createPositionAt `writer.createPositionAt()`} parameters.
|
|
1061
|
+
*
|
|
1062
|
+
* @param {module:engine/model/item~Item|module:engine/model/position~Position} itemOrPosition
|
|
1063
|
+
* @param {Number|'end'|'before'|'after'} [offset=0] Offset or one of the flags. Used only when
|
|
1064
|
+
* first parameter is a {@link module:engine/model/item~Item model item}.
|
|
1065
|
+
*/
|
|
1066
|
+
setSelectionFocus(itemOrPosition, offset) {
|
|
1067
|
+
this._assertWriterUsedCorrectly();
|
|
1068
|
+
this.model.document.selection._setFocus(itemOrPosition, offset);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Sets attribute(s) on the selection. If attribute with the same key already is set, it's value is overwritten.
|
|
1072
|
+
*
|
|
1073
|
+
* Using key and value pair:
|
|
1074
|
+
*
|
|
1075
|
+
* writer.setSelectionAttribute( 'italic', true );
|
|
1076
|
+
*
|
|
1077
|
+
* Using key-value object:
|
|
1078
|
+
*
|
|
1079
|
+
* writer.setSelectionAttribute( { italic: true, bold: false } );
|
|
1080
|
+
*
|
|
1081
|
+
* Using iterable object:
|
|
1082
|
+
*
|
|
1083
|
+
* writer.setSelectionAttribute( new Map( [ [ 'italic', true ] ] ) );
|
|
1084
|
+
*
|
|
1085
|
+
* @param {String|Object|Iterable.<*>} keyOrObjectOrIterable Key of the attribute to set
|
|
1086
|
+
* or object / iterable of key => value attribute pairs.
|
|
1087
|
+
* @param {*} [value] Attribute value.
|
|
1088
|
+
*/
|
|
1089
|
+
setSelectionAttribute(keyOrObjectOrIterable, value) {
|
|
1090
|
+
this._assertWriterUsedCorrectly();
|
|
1091
|
+
if (typeof keyOrObjectOrIterable === 'string') {
|
|
1092
|
+
this._setSelectionAttribute(keyOrObjectOrIterable, value);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
for (const [key, value] of toMap(keyOrObjectOrIterable)) {
|
|
1096
|
+
this._setSelectionAttribute(key, value);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Removes attribute(s) with given key(s) from the selection.
|
|
1102
|
+
*
|
|
1103
|
+
* Remove one attribute:
|
|
1104
|
+
*
|
|
1105
|
+
* writer.removeSelectionAttribute( 'italic' );
|
|
1106
|
+
*
|
|
1107
|
+
* Remove multiple attributes:
|
|
1108
|
+
*
|
|
1109
|
+
* writer.removeSelectionAttribute( [ 'italic', 'bold' ] );
|
|
1110
|
+
*
|
|
1111
|
+
* @param {String|Iterable.<String>} keyOrIterableOfKeys Key of the attribute to remove or an iterable of attribute keys to remove.
|
|
1112
|
+
*/
|
|
1113
|
+
removeSelectionAttribute(keyOrIterableOfKeys) {
|
|
1114
|
+
this._assertWriterUsedCorrectly();
|
|
1115
|
+
if (typeof keyOrIterableOfKeys === 'string') {
|
|
1116
|
+
this._removeSelectionAttribute(keyOrIterableOfKeys);
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
for (const key of keyOrIterableOfKeys) {
|
|
1120
|
+
this._removeSelectionAttribute(key);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Temporarily changes the {@link module:engine/model/documentselection~DocumentSelection#isGravityOverridden gravity}
|
|
1126
|
+
* of the selection from left to right.
|
|
1127
|
+
*
|
|
1128
|
+
* The gravity defines from which direction the selection inherits its attributes. If it's the default left gravity,
|
|
1129
|
+
* then the selection (after being moved by the user) inherits attributes from its left-hand side.
|
|
1130
|
+
* This method allows to temporarily override this behavior by forcing the gravity to the right.
|
|
1131
|
+
*
|
|
1132
|
+
* For the following model fragment:
|
|
1133
|
+
*
|
|
1134
|
+
* <$text bold="true" linkHref="url">bar[]</$text><$text bold="true">biz</$text>
|
|
1135
|
+
*
|
|
1136
|
+
* * Default gravity: selection will have the `bold` and `linkHref` attributes.
|
|
1137
|
+
* * Overridden gravity: selection will have `bold` attribute.
|
|
1138
|
+
*
|
|
1139
|
+
* **Note**: It returns an unique identifier which is required to restore the gravity. It guarantees the symmetry
|
|
1140
|
+
* of the process.
|
|
1141
|
+
*
|
|
1142
|
+
* @returns {String} The unique id which allows restoring the gravity.
|
|
1143
|
+
*/
|
|
1144
|
+
overrideSelectionGravity() {
|
|
1145
|
+
return this.model.document.selection._overrideGravity();
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Restores {@link ~Writer#overrideSelectionGravity} gravity to default.
|
|
1149
|
+
*
|
|
1150
|
+
* Restoring the gravity is only possible using the unique identifier returned by
|
|
1151
|
+
* {@link ~Writer#overrideSelectionGravity}. Note that the gravity remains overridden as long as won't be restored
|
|
1152
|
+
* the same number of times it was overridden.
|
|
1153
|
+
*
|
|
1154
|
+
* @param {String} uid The unique id returned by {@link ~Writer#overrideSelectionGravity}.
|
|
1155
|
+
*/
|
|
1156
|
+
restoreSelectionGravity(uid) {
|
|
1157
|
+
this.model.document.selection._restoreGravity(uid);
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* @private
|
|
1161
|
+
* @param {String} key Key of the attribute to remove.
|
|
1162
|
+
* @param {*} value Attribute value.
|
|
1163
|
+
*/
|
|
1164
|
+
_setSelectionAttribute(key, value) {
|
|
1165
|
+
const selection = this.model.document.selection;
|
|
1166
|
+
// Store attribute in parent element if the selection is collapsed in an empty node.
|
|
1167
|
+
if (selection.isCollapsed && selection.anchor.parent.isEmpty) {
|
|
1168
|
+
const storeKey = DocumentSelection._getStoreAttributeKey(key);
|
|
1169
|
+
this.setAttribute(storeKey, value, selection.anchor.parent);
|
|
1170
|
+
}
|
|
1171
|
+
selection._setAttribute(key, value);
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* @private
|
|
1175
|
+
* @param {String} key Key of the attribute to remove.
|
|
1176
|
+
*/
|
|
1177
|
+
_removeSelectionAttribute(key) {
|
|
1178
|
+
const selection = this.model.document.selection;
|
|
1179
|
+
// Remove stored attribute from parent element if the selection is collapsed in an empty node.
|
|
1180
|
+
if (selection.isCollapsed && selection.anchor.parent.isEmpty) {
|
|
1181
|
+
const storeKey = DocumentSelection._getStoreAttributeKey(key);
|
|
1182
|
+
this.removeAttribute(storeKey, selection.anchor.parent);
|
|
1183
|
+
}
|
|
1184
|
+
selection._removeAttribute(key);
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Throws `writer-detached-writer-tries-to-modify-model` error when the writer is used outside of the `change()` block.
|
|
1188
|
+
*
|
|
1189
|
+
* @private
|
|
1190
|
+
*/
|
|
1191
|
+
_assertWriterUsedCorrectly() {
|
|
1192
|
+
/**
|
|
1193
|
+
* Trying to use a writer outside a {@link module:engine/model/model~Model#change `change()`} or
|
|
1194
|
+
* {@link module:engine/model/model~Model#enqueueChange `enqueueChange()`} blocks.
|
|
1195
|
+
*
|
|
1196
|
+
* The writer can only be used inside these blocks which ensures that the model
|
|
1197
|
+
* can only be changed during such "sessions".
|
|
1198
|
+
*
|
|
1199
|
+
* @error writer-incorrect-use
|
|
1200
|
+
*/
|
|
1201
|
+
if (this.model._currentWriter !== this) {
|
|
1202
|
+
throw new CKEditorError('writer-incorrect-use', this);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* For given action `type` and `positionOrRange` where the action happens, this function finds all affected markers
|
|
1207
|
+
* and applies a marker operation with the new marker range equal to the current range. Thanks to this, the marker range
|
|
1208
|
+
* can be later correctly processed during undo.
|
|
1209
|
+
*
|
|
1210
|
+
* @private
|
|
1211
|
+
* @param {'move'|'merge'} type Writer action type.
|
|
1212
|
+
* @param {module:engine/model/position~Position|module:engine/model/range~Range} positionOrRange Position or range
|
|
1213
|
+
* where the writer action happens.
|
|
1214
|
+
*/
|
|
1215
|
+
_addOperationForAffectedMarkers(type, positionOrRange) {
|
|
1216
|
+
for (const marker of this.model.markers) {
|
|
1217
|
+
if (!marker.managedUsingOperations) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const markerRange = marker.getRange();
|
|
1221
|
+
let isAffected = false;
|
|
1222
|
+
if (type === 'move') {
|
|
1223
|
+
const range = positionOrRange;
|
|
1224
|
+
isAffected =
|
|
1225
|
+
range.containsPosition(markerRange.start) ||
|
|
1226
|
+
range.start.isEqual(markerRange.start) ||
|
|
1227
|
+
range.containsPosition(markerRange.end) ||
|
|
1228
|
+
range.end.isEqual(markerRange.end);
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
// if type === 'merge'.
|
|
1232
|
+
const position = positionOrRange;
|
|
1233
|
+
const elementBefore = position.nodeBefore;
|
|
1234
|
+
const elementAfter = position.nodeAfter;
|
|
1235
|
+
// Start: <p>Foo[</p><p>Bar]</p>
|
|
1236
|
+
// After merge: <p>Foo[Bar]</p>
|
|
1237
|
+
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
|
|
1238
|
+
//
|
|
1239
|
+
const affectedInLeftElement = markerRange.start.parent == elementBefore && markerRange.start.isAtEnd;
|
|
1240
|
+
// Start: <p>[Foo</p><p>]Bar</p>
|
|
1241
|
+
// After merge: <p>[Foo]Bar</p>
|
|
1242
|
+
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
|
|
1243
|
+
//
|
|
1244
|
+
const affectedInRightElement = markerRange.end.parent == elementAfter && markerRange.end.offset == 0;
|
|
1245
|
+
// Start: <p>[Foo</p>]<p>Bar</p>
|
|
1246
|
+
// After merge: <p>[Foo]Bar</p>
|
|
1247
|
+
// After undoing split: <p>[Foo]</p><p>Bar</p> <-- incorrect, needs remembering for undo.
|
|
1248
|
+
//
|
|
1249
|
+
const affectedAfterLeftElement = markerRange.end.nodeAfter == elementAfter;
|
|
1250
|
+
// Start: <p>Foo</p>[<p>Bar]</p>
|
|
1251
|
+
// After merge: <p>Foo[Bar]</p>
|
|
1252
|
+
// After undoing split: <p>Foo</p><p>[Bar]</p> <-- incorrect, needs remembering for undo.
|
|
1253
|
+
//
|
|
1254
|
+
const affectedBeforeRightElement = markerRange.start.nodeAfter == elementAfter;
|
|
1255
|
+
isAffected = affectedInLeftElement || affectedInRightElement || affectedAfterLeftElement || affectedBeforeRightElement;
|
|
1256
|
+
}
|
|
1257
|
+
if (isAffected) {
|
|
1258
|
+
this.updateMarker(marker.name, { range: markerRange });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1404
1262
|
}
|
|
1405
|
-
|
|
1406
1263
|
// Sets given attribute to each node in given range. When attribute value is null then attribute will be removed.
|
|
1407
1264
|
//
|
|
1408
1265
|
// Because attribute operation needs to have the same attribute value on the whole range, this function splits
|
|
@@ -1415,57 +1272,45 @@ export default class Writer {
|
|
|
1415
1272
|
// @param {String} key Attribute key.
|
|
1416
1273
|
// @param {*} value Attribute new value.
|
|
1417
1274
|
// @param {module:engine/model/range~Range} range Model range on which the attribute will be set.
|
|
1418
|
-
function setAttributeOnRange(
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
addOperation();
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
function addOperation() {
|
|
1460
|
-
const range = new Range( lastSplitPosition, position );
|
|
1461
|
-
const version = range.root.document ? doc.version : null;
|
|
1462
|
-
const operation = new AttributeOperation( range, key, valueBefore, value, version );
|
|
1463
|
-
|
|
1464
|
-
writer.batch.addOperation( operation );
|
|
1465
|
-
model.applyOperation( operation );
|
|
1466
|
-
}
|
|
1275
|
+
function setAttributeOnRange(writer, key, value, range) {
|
|
1276
|
+
const model = writer.model;
|
|
1277
|
+
const doc = model.document;
|
|
1278
|
+
// Position of the last split, the beginning of the new range.
|
|
1279
|
+
let lastSplitPosition = range.start;
|
|
1280
|
+
// Currently position in the scanning range. Because we need value after the position, it is not a current
|
|
1281
|
+
// position of the iterator but the previous one (we need to iterate one more time to get the value after).
|
|
1282
|
+
let position;
|
|
1283
|
+
// Value before the currently position.
|
|
1284
|
+
let valueBefore;
|
|
1285
|
+
// Value after the currently position.
|
|
1286
|
+
let valueAfter;
|
|
1287
|
+
for (const val of range.getWalker({ shallow: true })) {
|
|
1288
|
+
valueAfter = val.item.getAttribute(key);
|
|
1289
|
+
// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but
|
|
1290
|
+
// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).
|
|
1291
|
+
if (position && valueBefore != valueAfter) {
|
|
1292
|
+
// if valueBefore == value there is nothing to change, so we add operation only if these values are different.
|
|
1293
|
+
if (valueBefore != value) {
|
|
1294
|
+
addOperation();
|
|
1295
|
+
}
|
|
1296
|
+
lastSplitPosition = position;
|
|
1297
|
+
}
|
|
1298
|
+
position = val.nextPosition;
|
|
1299
|
+
valueBefore = valueAfter;
|
|
1300
|
+
}
|
|
1301
|
+
// Because position in the loop is not the iterator position (see let position comment), the last position in
|
|
1302
|
+
// the while loop will be last but one position in the range. We need to check the last position manually.
|
|
1303
|
+
if (position instanceof Position && position != lastSplitPosition && valueBefore != value) {
|
|
1304
|
+
addOperation();
|
|
1305
|
+
}
|
|
1306
|
+
function addOperation() {
|
|
1307
|
+
const range = new Range(lastSplitPosition, position);
|
|
1308
|
+
const version = range.root.document ? doc.version : null;
|
|
1309
|
+
const operation = new AttributeOperation(range, key, valueBefore, value, version);
|
|
1310
|
+
writer.batch.addOperation(operation);
|
|
1311
|
+
model.applyOperation(operation);
|
|
1312
|
+
}
|
|
1467
1313
|
}
|
|
1468
|
-
|
|
1469
1314
|
// Sets given attribute to the given node. When attribute value is null then attribute will be removed.
|
|
1470
1315
|
//
|
|
1471
1316
|
// @private
|
|
@@ -1473,33 +1318,27 @@ function setAttributeOnRange( writer, key, value, range ) {
|
|
|
1473
1318
|
// @param {String} key Attribute key.
|
|
1474
1319
|
// @param {*} value Attribute new value.
|
|
1475
1320
|
// @param {module:engine/model/item~Item} item Model item on which the attribute will be set.
|
|
1476
|
-
function setAttributeOnItem(
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
writer.batch.addOperation( operation );
|
|
1499
|
-
model.applyOperation( operation );
|
|
1500
|
-
}
|
|
1321
|
+
function setAttributeOnItem(writer, key, value, item) {
|
|
1322
|
+
const model = writer.model;
|
|
1323
|
+
const doc = model.document;
|
|
1324
|
+
const previousValue = item.getAttribute(key);
|
|
1325
|
+
let range, operation;
|
|
1326
|
+
if (previousValue != value) {
|
|
1327
|
+
const isRootChanged = item.root === item;
|
|
1328
|
+
if (isRootChanged) {
|
|
1329
|
+
// If we change attributes of root element, we have to use `RootAttributeOperation`.
|
|
1330
|
+
const version = item.document ? doc.version : null;
|
|
1331
|
+
operation = new RootAttributeOperation(item, key, previousValue, value, version);
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
range = new Range(Position._createBefore(item), writer.createPositionAfter(item));
|
|
1335
|
+
const version = range.root.document ? doc.version : null;
|
|
1336
|
+
operation = new AttributeOperation(range, key, previousValue, value, version);
|
|
1337
|
+
}
|
|
1338
|
+
writer.batch.addOperation(operation);
|
|
1339
|
+
model.applyOperation(operation);
|
|
1340
|
+
}
|
|
1501
1341
|
}
|
|
1502
|
-
|
|
1503
1342
|
// Creates and applies marker operation to {@link module:engine/model/operation/operation~Operation operation}.
|
|
1504
1343
|
//
|
|
1505
1344
|
// @private
|
|
@@ -1508,16 +1347,13 @@ function setAttributeOnItem( writer, key, value, item ) {
|
|
|
1508
1347
|
// @param {module:engine/model/range~Range} oldRange Marker range before the change.
|
|
1509
1348
|
// @param {module:engine/model/range~Range} newRange Marker range after the change.
|
|
1510
1349
|
// @param {Boolean} affectsData
|
|
1511
|
-
function applyMarkerOperation(
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
writer.batch.addOperation( operation );
|
|
1518
|
-
model.applyOperation( operation );
|
|
1350
|
+
function applyMarkerOperation(writer, name, oldRange, newRange, affectsData) {
|
|
1351
|
+
const model = writer.model;
|
|
1352
|
+
const doc = model.document;
|
|
1353
|
+
const operation = new MarkerOperation(name, oldRange, newRange, model.markers, !!affectsData, doc.version);
|
|
1354
|
+
writer.batch.addOperation(operation);
|
|
1355
|
+
model.applyOperation(operation);
|
|
1519
1356
|
}
|
|
1520
|
-
|
|
1521
1357
|
// Creates `MoveOperation` or `DetachOperation` that removes `howMany` nodes starting from `position`.
|
|
1522
1358
|
// The operation will be applied on given model instance and added to given operation instance.
|
|
1523
1359
|
//
|
|
@@ -1526,22 +1362,19 @@ function applyMarkerOperation( writer, name, oldRange, newRange, affectsData ) {
|
|
|
1526
1362
|
// @param {Number} howMany Number of nodes to remove.
|
|
1527
1363
|
// @param {Batch} batch Batch to which the operation will be added.
|
|
1528
1364
|
// @param {module:engine/model/model~Model} model Model instance on which operation will be applied.
|
|
1529
|
-
function applyRemoveOperation(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
batch.addOperation( operation );
|
|
1542
|
-
model.applyOperation( operation );
|
|
1365
|
+
function applyRemoveOperation(position, howMany, batch, model) {
|
|
1366
|
+
let operation;
|
|
1367
|
+
if (position.root.document) {
|
|
1368
|
+
const doc = model.document;
|
|
1369
|
+
const graveyardPosition = new Position(doc.graveyard, [0]);
|
|
1370
|
+
operation = new MoveOperation(position, howMany, graveyardPosition, doc.version);
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
operation = new DetachOperation(position, howMany);
|
|
1374
|
+
}
|
|
1375
|
+
batch.addOperation(operation);
|
|
1376
|
+
model.applyOperation(operation);
|
|
1543
1377
|
}
|
|
1544
|
-
|
|
1545
1378
|
// Returns `true` if both root elements are the same element or both are documents root elements.
|
|
1546
1379
|
//
|
|
1547
1380
|
// Elements in the same tree can be moved (for instance you can move element form one documents root to another, or
|
|
@@ -1549,16 +1382,14 @@ function applyRemoveOperation( position, howMany, batch, model ) {
|
|
|
1549
1382
|
// to another document it should be removed and inserted to avoid problems with OT. This is because features like undo or
|
|
1550
1383
|
// collaboration may track changes on the document but ignore changes on detached fragments and should not get
|
|
1551
1384
|
// unexpected `move` operation.
|
|
1552
|
-
function isSameTree(
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
return false;
|
|
1385
|
+
function isSameTree(rootA, rootB) {
|
|
1386
|
+
// If it is the same root this is the same tree.
|
|
1387
|
+
if (rootA === rootB) {
|
|
1388
|
+
return true;
|
|
1389
|
+
}
|
|
1390
|
+
// If both roots are documents root it is operation within the document what we still treat as the same tree.
|
|
1391
|
+
if (rootA instanceof RootElement && rootB instanceof RootElement) {
|
|
1392
|
+
return true;
|
|
1393
|
+
}
|
|
1394
|
+
return false;
|
|
1564
1395
|
}
|