@ckeditor/ckeditor5-engine 35.0.1 → 35.2.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 +4 -4
- package/package.json +30 -24
- 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 +100 -116
- package/src/dataprocessor/htmlwriter.js +1 -18
- package/src/dataprocessor/xmldataprocessor.js +116 -137
- 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 +176 -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 +980 -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 +757 -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 +1370 -1617
- 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 +199 -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 -1018
- 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
|
@@ -2,26 +2,21 @@
|
|
|
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
|
* Contains downcast (model-to-view) converters for {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher}.
|
|
8
7
|
*
|
|
9
8
|
* @module engine/conversion/downcasthelpers
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
10
|
import ModelRange from '../model/range';
|
|
13
11
|
import ModelSelection from '../model/selection';
|
|
12
|
+
import ModelDocumentSelection from '../model/documentselection';
|
|
14
13
|
import ModelElement from '../model/element';
|
|
15
14
|
import ModelPosition from '../model/position';
|
|
16
|
-
|
|
17
15
|
import ViewAttributeElement from '../view/attributeelement';
|
|
18
|
-
import DocumentSelection from '../model/documentselection';
|
|
19
16
|
import ConversionHelpers from './conversionhelpers';
|
|
20
|
-
|
|
21
17
|
import { cloneDeep } from 'lodash-es';
|
|
22
18
|
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
23
19
|
import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Downcast conversion helper functions.
|
|
27
22
|
*
|
|
@@ -30,700 +25,693 @@ import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
|
|
|
30
25
|
* @extends module:engine/conversion/conversionhelpers~ConversionHelpers
|
|
31
26
|
*/
|
|
32
27
|
export default class DowncastHelpers extends ConversionHelpers {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
720
|
-
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
721
|
-
*/
|
|
722
|
-
markerToData( config ) {
|
|
723
|
-
return this.add( downcastMarkerToData( config ) );
|
|
724
|
-
}
|
|
28
|
+
/**
|
|
29
|
+
* Model element to view element conversion helper.
|
|
30
|
+
*
|
|
31
|
+
* This conversion results in creating a view element. For example, model `<paragraph>Foo</paragraph>` becomes `<p>Foo</p>` in the view.
|
|
32
|
+
*
|
|
33
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
34
|
+
* model: 'paragraph',
|
|
35
|
+
* view: 'p'
|
|
36
|
+
* } );
|
|
37
|
+
*
|
|
38
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
39
|
+
* model: 'paragraph',
|
|
40
|
+
* view: 'div',
|
|
41
|
+
* converterPriority: 'high'
|
|
42
|
+
* } );
|
|
43
|
+
*
|
|
44
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
45
|
+
* model: 'fancyParagraph',
|
|
46
|
+
* view: {
|
|
47
|
+
* name: 'p',
|
|
48
|
+
* classes: 'fancy'
|
|
49
|
+
* }
|
|
50
|
+
* } );
|
|
51
|
+
*
|
|
52
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
53
|
+
* model: 'heading',
|
|
54
|
+
* view: ( modelElement, conversionApi ) => {
|
|
55
|
+
* const { writer } = conversionApi;
|
|
56
|
+
*
|
|
57
|
+
* return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
|
|
58
|
+
* }
|
|
59
|
+
* } );
|
|
60
|
+
*
|
|
61
|
+
* The element-to-element conversion supports the reconversion mechanism. It can be enabled by using either the `attributes` or
|
|
62
|
+
* the `children` props on a model description. You will find a couple examples below.
|
|
63
|
+
*
|
|
64
|
+
* In order to reconvert an element if any of its direct children have been added or removed, use the `children` property on a `model`
|
|
65
|
+
* description. For example, this model:
|
|
66
|
+
*
|
|
67
|
+
* <box>
|
|
68
|
+
* <paragraph>Some text.</paragraph>
|
|
69
|
+
* </box>
|
|
70
|
+
*
|
|
71
|
+
* will be converted into this structure in the view:
|
|
72
|
+
*
|
|
73
|
+
* <div class="box" data-type="single">
|
|
74
|
+
* <p>Some text.</p>
|
|
75
|
+
* </div>
|
|
76
|
+
*
|
|
77
|
+
* But if more items were inserted in the model:
|
|
78
|
+
*
|
|
79
|
+
* <box>
|
|
80
|
+
* <paragraph>Some text.</paragraph>
|
|
81
|
+
* <paragraph>Other item.</paragraph>
|
|
82
|
+
* </box>
|
|
83
|
+
*
|
|
84
|
+
* it will be converted into this structure in the view (note the element `data-type` change):
|
|
85
|
+
*
|
|
86
|
+
* <div class="box" data-type="multiple">
|
|
87
|
+
* <p>Some text.</p>
|
|
88
|
+
* <p>Other item.</p>
|
|
89
|
+
* </div>
|
|
90
|
+
*
|
|
91
|
+
* Such a converter would look like this (note that the `paragraph` elements are converted separately):
|
|
92
|
+
*
|
|
93
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
94
|
+
* model: {
|
|
95
|
+
* name: 'box',
|
|
96
|
+
* children: true
|
|
97
|
+
* },
|
|
98
|
+
* view: ( modelElement, conversionApi ) => {
|
|
99
|
+
* const { writer } = conversionApi;
|
|
100
|
+
*
|
|
101
|
+
* return writer.createContainerElement( 'div', {
|
|
102
|
+
* class: 'box',
|
|
103
|
+
* 'data-type': modelElement.childCount == 1 ? 'single' : 'multiple'
|
|
104
|
+
* } );
|
|
105
|
+
* }
|
|
106
|
+
* } );
|
|
107
|
+
*
|
|
108
|
+
* In order to reconvert element if any of its attributes have been updated, use the `attributes` property on a `model`
|
|
109
|
+
* description. For example, this model:
|
|
110
|
+
*
|
|
111
|
+
* <heading level="2">Some text.</heading>
|
|
112
|
+
*
|
|
113
|
+
* will be converted into this structure in the view:
|
|
114
|
+
*
|
|
115
|
+
* <h2>Some text.</h2>
|
|
116
|
+
*
|
|
117
|
+
* But if the `heading` element's `level` attribute has been updated to `3` for example, then
|
|
118
|
+
* it will be converted into this structure in the view:
|
|
119
|
+
*
|
|
120
|
+
* <h3>Some text.</h3>
|
|
121
|
+
*
|
|
122
|
+
* Such a converter would look as follows:
|
|
123
|
+
*
|
|
124
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
125
|
+
* model: {
|
|
126
|
+
* name: 'heading',
|
|
127
|
+
* attributes: 'level'
|
|
128
|
+
* },
|
|
129
|
+
* view: ( modelElement, conversionApi ) => {
|
|
130
|
+
* const { writer } = conversionApi;
|
|
131
|
+
*
|
|
132
|
+
* return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
|
|
133
|
+
* }
|
|
134
|
+
* } );
|
|
135
|
+
*
|
|
136
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
137
|
+
* to the conversion process.
|
|
138
|
+
*
|
|
139
|
+
* You can read more about the element-to-element conversion in the
|
|
140
|
+
* {@glink framework/guides/deep-dive/conversion/downcast downcast conversion} guide.
|
|
141
|
+
*
|
|
142
|
+
* @method #elementToElement
|
|
143
|
+
* @param {Object} config Conversion configuration.
|
|
144
|
+
* @param {String|Object} config.model The description or a name of the model element to convert.
|
|
145
|
+
* @param {String|Array.<String>} [config.model.attributes] The list of attribute names that should be consumed while creating
|
|
146
|
+
* the view element. Note that the view will be reconverted if any of the listed attributes changes.
|
|
147
|
+
* @param {Boolean} [config.model.children] Specifies whether the view element requires reconversion if the list
|
|
148
|
+
* of the model child nodes changed.
|
|
149
|
+
* @param {module:engine/view/elementdefinition~ElementDefinition|module:engine/conversion/downcasthelpers~ElementCreatorFunction}
|
|
150
|
+
* config.view A view element definition or a function that takes the model element and
|
|
151
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
152
|
+
* as parameters and returns a view container element.
|
|
153
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
154
|
+
*/
|
|
155
|
+
elementToElement(config) {
|
|
156
|
+
return this.add(downcastElementToElement(config));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The model element to view structure (several elements) conversion helper.
|
|
160
|
+
*
|
|
161
|
+
* This conversion results in creating a view structure with one or more slots defined for the child nodes.
|
|
162
|
+
* For example, a model `<table>` may become this structure in the view:
|
|
163
|
+
*
|
|
164
|
+
* <figure class="table">
|
|
165
|
+
* <table>
|
|
166
|
+
* <tbody>${ slot for table rows }</tbody>
|
|
167
|
+
* </table>
|
|
168
|
+
* </figure>
|
|
169
|
+
*
|
|
170
|
+
* The children of the model's `<table>` element will be inserted into the `<tbody>` element.
|
|
171
|
+
* If the `elementToElement()` helper was used, the children would be inserted into the `<figure>`.
|
|
172
|
+
*
|
|
173
|
+
* An example converter that converts the following model structure:
|
|
174
|
+
*
|
|
175
|
+
* <wrappedParagraph>Some text.</wrappedParagraph>
|
|
176
|
+
*
|
|
177
|
+
* into this structure in the view:
|
|
178
|
+
*
|
|
179
|
+
* <div class="wrapper">
|
|
180
|
+
* <p>Some text.</p>
|
|
181
|
+
* </div>
|
|
182
|
+
*
|
|
183
|
+
* would look like this:
|
|
184
|
+
*
|
|
185
|
+
* editor.conversion.for( 'downcast' ).elementToStructure( {
|
|
186
|
+
* model: 'wrappedParagraph',
|
|
187
|
+
* view: ( modelElement, conversionApi ) => {
|
|
188
|
+
* const { writer } = conversionApi;
|
|
189
|
+
*
|
|
190
|
+
* const wrapperViewElement = writer.createContainerElement( 'div', { class: 'wrapper' } );
|
|
191
|
+
* const paragraphViewElement = writer.createContainerElement( 'p' );
|
|
192
|
+
*
|
|
193
|
+
* writer.insert( writer.createPositionAt( wrapperViewElement, 0 ), paragraphViewElement );
|
|
194
|
+
* writer.insert( writer.createPositionAt( paragraphViewElement, 0 ), writer.createSlot() );
|
|
195
|
+
*
|
|
196
|
+
* return wrapperViewElement;
|
|
197
|
+
* }
|
|
198
|
+
* } );
|
|
199
|
+
*
|
|
200
|
+
* The `slorFor()` function can also take a callback that allows filtering which children of the model element
|
|
201
|
+
* should be converted into this slot.
|
|
202
|
+
*
|
|
203
|
+
* Imagine a table feature where for this model structure:
|
|
204
|
+
*
|
|
205
|
+
* <table headingRows="1">
|
|
206
|
+
* <tableRow> ... table cells 1 ... </tableRow>
|
|
207
|
+
* <tableRow> ... table cells 2 ... </tableRow>
|
|
208
|
+
* <tableRow> ... table cells 3 ... </tableRow>
|
|
209
|
+
* <caption>Caption text</caption>
|
|
210
|
+
* </table>
|
|
211
|
+
*
|
|
212
|
+
* we want to generate this view structure:
|
|
213
|
+
*
|
|
214
|
+
* <figure class="table">
|
|
215
|
+
* <table>
|
|
216
|
+
* <thead>
|
|
217
|
+
* <tr> ... table cells 1 ... </tr>
|
|
218
|
+
* </thead>
|
|
219
|
+
* <tbody>
|
|
220
|
+
* <tr> ... table cells 2 ... </tr>
|
|
221
|
+
* <tr> ... table cells 3 ... </tr>
|
|
222
|
+
* </tbody>
|
|
223
|
+
* </table>
|
|
224
|
+
* <figcaption>Caption text</figcaption>
|
|
225
|
+
* </figure>
|
|
226
|
+
*
|
|
227
|
+
* The converter has to take the `headingRows` attribute into consideration when allocating the `<tableRow>` elements
|
|
228
|
+
* into the `<tbody>` and `<thead>` elements. Hence, we need two slots and need to define proper filter callbacks for them.
|
|
229
|
+
*
|
|
230
|
+
* Additionally, all elements other than `<tableRow>` should be placed outside the `<table>` tag.
|
|
231
|
+
* In the example above, this will handle the table caption.
|
|
232
|
+
*
|
|
233
|
+
* Such a converter would look like this:
|
|
234
|
+
*
|
|
235
|
+
* editor.conversion.for( 'downcast' ).elementToStructure( {
|
|
236
|
+
* model: {
|
|
237
|
+
* name: 'table',
|
|
238
|
+
* attributes: [ 'headingRows' ]
|
|
239
|
+
* },
|
|
240
|
+
* view: ( modelElement, conversionApi ) => {
|
|
241
|
+
* const { writer } = conversionApi;
|
|
242
|
+
*
|
|
243
|
+
* const figureElement = writer.createContainerElement( 'figure', { class: 'table' } );
|
|
244
|
+
* const tableElement = writer.createContainerElement( 'table' );
|
|
245
|
+
*
|
|
246
|
+
* writer.insert( writer.createPositionAt( figureElement, 0 ), tableElement );
|
|
247
|
+
*
|
|
248
|
+
* const headingRows = modelElement.getAttribute( 'headingRows' ) || 0;
|
|
249
|
+
*
|
|
250
|
+
* if ( headingRows > 0 ) {
|
|
251
|
+
* const tableHead = writer.createContainerElement( 'thead' );
|
|
252
|
+
*
|
|
253
|
+
* const headSlot = writer.createSlot( node => node.is( 'element', 'tableRow' ) && node.index < headingRows );
|
|
254
|
+
*
|
|
255
|
+
* writer.insert( writer.createPositionAt( tableElement, 'end' ), tableHead );
|
|
256
|
+
* writer.insert( writer.createPositionAt( tableHead, 0 ), headSlot );
|
|
257
|
+
* }
|
|
258
|
+
*
|
|
259
|
+
* if ( headingRows < tableUtils.getRows( table ) ) {
|
|
260
|
+
* const tableBody = writer.createContainerElement( 'tbody' );
|
|
261
|
+
*
|
|
262
|
+
* const bodySlot = writer.createSlot( node => node.is( 'element', 'tableRow' ) && node.index >= headingRows );
|
|
263
|
+
*
|
|
264
|
+
* writer.insert( writer.createPositionAt( tableElement, 'end' ), tableBody );
|
|
265
|
+
* writer.insert( writer.createPositionAt( tableBody, 0 ), bodySlot );
|
|
266
|
+
* }
|
|
267
|
+
*
|
|
268
|
+
* const restSlot = writer.createSlot( node => !node.is( 'element', 'tableRow' ) );
|
|
269
|
+
*
|
|
270
|
+
* writer.insert( writer.createPositionAt( figureElement, 'end' ), restSlot );
|
|
271
|
+
*
|
|
272
|
+
* return figureElement;
|
|
273
|
+
* }
|
|
274
|
+
* } );
|
|
275
|
+
*
|
|
276
|
+
* Note: The children of a model element that's being converted must be allocated in the same order in the view
|
|
277
|
+
* in which they are placed in the model.
|
|
278
|
+
*
|
|
279
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
280
|
+
* to the conversion process.
|
|
281
|
+
*
|
|
282
|
+
* @method #elementToStructure
|
|
283
|
+
* @param {Object} config Conversion configuration.
|
|
284
|
+
* @param {String|Object} config.model The description or a name of the model element to convert.
|
|
285
|
+
* @param {String} [config.model.name] The name of the model element to convert.
|
|
286
|
+
* @param {String|Array.<String>} [config.model.attributes] The list of attribute names that should be consumed while creating
|
|
287
|
+
* the view structure. Note that the view will be reconverted if any of the listed attributes will change.
|
|
288
|
+
* @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view A function
|
|
289
|
+
* that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast
|
|
290
|
+
* conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into.
|
|
291
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
292
|
+
*/
|
|
293
|
+
elementToStructure(config) {
|
|
294
|
+
return this.add(downcastElementToStructure(config));
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Model attribute to view element conversion helper.
|
|
298
|
+
*
|
|
299
|
+
* This conversion results in wrapping view nodes with a view attribute element. For example, a model text node with
|
|
300
|
+
* `"Foo"` as data and the `bold` attribute becomes `<strong>Foo</strong>` in the view.
|
|
301
|
+
*
|
|
302
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
303
|
+
* model: 'bold',
|
|
304
|
+
* view: 'strong'
|
|
305
|
+
* } );
|
|
306
|
+
*
|
|
307
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
308
|
+
* model: 'bold',
|
|
309
|
+
* view: 'b',
|
|
310
|
+
* converterPriority: 'high'
|
|
311
|
+
* } );
|
|
312
|
+
*
|
|
313
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
314
|
+
* model: 'invert',
|
|
315
|
+
* view: {
|
|
316
|
+
* name: 'span',
|
|
317
|
+
* classes: [ 'font-light', 'bg-dark' ]
|
|
318
|
+
* }
|
|
319
|
+
* } );
|
|
320
|
+
*
|
|
321
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
322
|
+
* model: {
|
|
323
|
+
* key: 'fontSize',
|
|
324
|
+
* values: [ 'big', 'small' ]
|
|
325
|
+
* },
|
|
326
|
+
* view: {
|
|
327
|
+
* big: {
|
|
328
|
+
* name: 'span',
|
|
329
|
+
* styles: {
|
|
330
|
+
* 'font-size': '1.2em'
|
|
331
|
+
* }
|
|
332
|
+
* },
|
|
333
|
+
* small: {
|
|
334
|
+
* name: 'span',
|
|
335
|
+
* styles: {
|
|
336
|
+
* 'font-size': '0.8em'
|
|
337
|
+
* }
|
|
338
|
+
* }
|
|
339
|
+
* }
|
|
340
|
+
* } );
|
|
341
|
+
*
|
|
342
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
343
|
+
* model: 'bold',
|
|
344
|
+
* view: ( modelAttributeValue, conversionApi ) => {
|
|
345
|
+
* const { writer } = conversionApi;
|
|
346
|
+
*
|
|
347
|
+
* return writer.createAttributeElement( 'span', {
|
|
348
|
+
* style: 'font-weight:' + modelAttributeValue
|
|
349
|
+
* } );
|
|
350
|
+
* }
|
|
351
|
+
* } );
|
|
352
|
+
*
|
|
353
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
354
|
+
* model: {
|
|
355
|
+
* key: 'color',
|
|
356
|
+
* name: '$text'
|
|
357
|
+
* },
|
|
358
|
+
* view: ( modelAttributeValue, conversionApi ) => {
|
|
359
|
+
* const { writer } = conversionApi;
|
|
360
|
+
*
|
|
361
|
+
* return writer.createAttributeElement( 'span', {
|
|
362
|
+
* style: 'color:' + modelAttributeValue
|
|
363
|
+
* } );
|
|
364
|
+
* }
|
|
365
|
+
* } );
|
|
366
|
+
*
|
|
367
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
368
|
+
* to the conversion process.
|
|
369
|
+
*
|
|
370
|
+
* @method #attributeToElement
|
|
371
|
+
* @param {Object} config Conversion configuration.
|
|
372
|
+
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
|
|
373
|
+
* of `String`s with possible values if the model attribute is an enumerable.
|
|
374
|
+
* @param {module:engine/view/elementdefinition~ElementDefinition|Object|
|
|
375
|
+
* module:engine/conversion/downcasthelpers~AttributeElementCreatorFunction} config.view A view element definition or a function
|
|
376
|
+
* that takes the model attribute value and
|
|
377
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
|
|
378
|
+
* attribute element. If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values`
|
|
379
|
+
* to view element definitions or functions.
|
|
380
|
+
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
381
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
382
|
+
*/
|
|
383
|
+
attributeToElement(config) {
|
|
384
|
+
return this.add(downcastAttributeToElement(config));
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Model attribute to view attribute conversion helper.
|
|
388
|
+
*
|
|
389
|
+
* This conversion results in adding an attribute to a view node, basing on an attribute from a model node. For example,
|
|
390
|
+
* `<imageInline src='foo.jpg'></imageInline>` is converted to `<img src='foo.jpg'></img>`.
|
|
391
|
+
*
|
|
392
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
393
|
+
* model: 'source',
|
|
394
|
+
* view: 'src'
|
|
395
|
+
* } );
|
|
396
|
+
*
|
|
397
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
398
|
+
* model: 'source',
|
|
399
|
+
* view: 'href',
|
|
400
|
+
* converterPriority: 'high'
|
|
401
|
+
* } );
|
|
402
|
+
*
|
|
403
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
404
|
+
* model: {
|
|
405
|
+
* name: 'imageInline',
|
|
406
|
+
* key: 'source'
|
|
407
|
+
* },
|
|
408
|
+
* view: 'src'
|
|
409
|
+
* } );
|
|
410
|
+
*
|
|
411
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
412
|
+
* model: {
|
|
413
|
+
* name: 'styled',
|
|
414
|
+
* values: [ 'dark', 'light' ]
|
|
415
|
+
* },
|
|
416
|
+
* view: {
|
|
417
|
+
* dark: {
|
|
418
|
+
* key: 'class',
|
|
419
|
+
* value: [ 'styled', 'styled-dark' ]
|
|
420
|
+
* },
|
|
421
|
+
* light: {
|
|
422
|
+
* key: 'class',
|
|
423
|
+
* value: [ 'styled', 'styled-light' ]
|
|
424
|
+
* }
|
|
425
|
+
* }
|
|
426
|
+
* } );
|
|
427
|
+
*
|
|
428
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
429
|
+
* model: 'styled',
|
|
430
|
+
* view: modelAttributeValue => ( {
|
|
431
|
+
* key: 'class',
|
|
432
|
+
* value: 'styled-' + modelAttributeValue
|
|
433
|
+
* } )
|
|
434
|
+
* } );
|
|
435
|
+
*
|
|
436
|
+
* **Note**: Downcasting to a style property requires providing `value` as an object:
|
|
437
|
+
*
|
|
438
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
439
|
+
* model: 'lineHeight',
|
|
440
|
+
* view: modelAttributeValue => ( {
|
|
441
|
+
* key: 'style',
|
|
442
|
+
* value: {
|
|
443
|
+
* 'line-height': modelAttributeValue,
|
|
444
|
+
* 'border-bottom': '1px dotted #ba2'
|
|
445
|
+
* }
|
|
446
|
+
* } )
|
|
447
|
+
* } );
|
|
448
|
+
*
|
|
449
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
450
|
+
* to the conversion process.
|
|
451
|
+
*
|
|
452
|
+
* @method #attributeToAttribute
|
|
453
|
+
* @param {Object} config Conversion configuration.
|
|
454
|
+
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
|
|
455
|
+
* the attribute key, possible values and, optionally, an element name to convert from.
|
|
456
|
+
* @param {String|Object|module:engine/conversion/downcasthelpers~AttributeCreatorFunction} config.view A view attribute key,
|
|
457
|
+
* or a `{ key, value }` object or a function that takes the model attribute value and
|
|
458
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
459
|
+
* as parameters and returns a `{ key, value }` object. If the `key` is `'class'`, the `value` can be a `String` or an
|
|
460
|
+
* array of `String`s. If the `key` is `'style'`, the `value` is an object with key-value pairs. In other cases, `value` is a `String`.
|
|
461
|
+
* If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
|
|
462
|
+
* `{ key, value }` objects or a functions.
|
|
463
|
+
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
464
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
465
|
+
*/
|
|
466
|
+
attributeToAttribute(config) {
|
|
467
|
+
return this.add(downcastAttributeToAttribute(config));
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Model marker to view element conversion helper.
|
|
471
|
+
*
|
|
472
|
+
* **Note**: This method should be used mainly for editing the downcast and it is recommended
|
|
473
|
+
* to use the {@link #markerToData `#markerToData()`} helper instead.
|
|
474
|
+
*
|
|
475
|
+
* This helper may produce invalid HTML code (e.g. a span between table cells).
|
|
476
|
+
* It should only be used when you are sure that the produced HTML will be semantically correct.
|
|
477
|
+
*
|
|
478
|
+
* This conversion results in creating a view element on the boundaries of the converted marker. If the converted marker
|
|
479
|
+
* is collapsed, only one element is created. For example, a model marker set like this: `<paragraph>F[oo b]ar</paragraph>`
|
|
480
|
+
* becomes `<p>F<span data-marker="search"></span>oo b<span data-marker="search"></span>ar</p>` in the view.
|
|
481
|
+
*
|
|
482
|
+
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
|
|
483
|
+
* model: 'search',
|
|
484
|
+
* view: 'marker-search'
|
|
485
|
+
* } );
|
|
486
|
+
*
|
|
487
|
+
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
|
|
488
|
+
* model: 'search',
|
|
489
|
+
* view: 'search-result',
|
|
490
|
+
* converterPriority: 'high'
|
|
491
|
+
* } );
|
|
492
|
+
*
|
|
493
|
+
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
|
|
494
|
+
* model: 'search',
|
|
495
|
+
* view: {
|
|
496
|
+
* name: 'span',
|
|
497
|
+
* attributes: {
|
|
498
|
+
* 'data-marker': 'search'
|
|
499
|
+
* }
|
|
500
|
+
* }
|
|
501
|
+
* } );
|
|
502
|
+
*
|
|
503
|
+
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
|
|
504
|
+
* model: 'search',
|
|
505
|
+
* view: ( markerData, conversionApi ) => {
|
|
506
|
+
* const { writer } = conversionApi;
|
|
507
|
+
*
|
|
508
|
+
* return writer.createUIElement( 'span', {
|
|
509
|
+
* 'data-marker': 'search',
|
|
510
|
+
* 'data-start': markerData.isOpening
|
|
511
|
+
* } );
|
|
512
|
+
* }
|
|
513
|
+
* } );
|
|
514
|
+
*
|
|
515
|
+
* If a function is passed as the `config.view` parameter, it will be used to generate both boundary elements. The function
|
|
516
|
+
* receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
517
|
+
* as a parameters and should return an instance of the
|
|
518
|
+
* {@link module:engine/view/uielement~UIElement view UI element}. The `data` object and
|
|
519
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi `conversionApi`} are passed from
|
|
520
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
|
|
521
|
+
* the `data.isOpening` parameter is passed, which is set to `true` for the marker start boundary element, and `false` for
|
|
522
|
+
* the marker end boundary element.
|
|
523
|
+
*
|
|
524
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
525
|
+
* to the conversion process.
|
|
526
|
+
*
|
|
527
|
+
* @method #markerToElement
|
|
528
|
+
* @param {Object} config Conversion configuration.
|
|
529
|
+
* @param {String} config.model The name of the model marker (or model marker group) to convert.
|
|
530
|
+
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function that
|
|
531
|
+
* takes the model marker data and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
532
|
+
* as a parameters and returns a view UI element.
|
|
533
|
+
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
534
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
535
|
+
*/
|
|
536
|
+
markerToElement(config) {
|
|
537
|
+
return this.add(downcastMarkerToElement(config));
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Model marker to highlight conversion helper.
|
|
541
|
+
*
|
|
542
|
+
* This conversion results in creating a highlight on view nodes. For this kind of conversion,
|
|
543
|
+
* the {@link module:engine/conversion/downcasthelpers~HighlightDescriptor} should be provided.
|
|
544
|
+
*
|
|
545
|
+
* For text nodes, a `<span>` {@link module:engine/view/attributeelement~AttributeElement} is created and it wraps all text nodes
|
|
546
|
+
* in the converted marker range. For example, a model marker set like this: `<paragraph>F[oo b]ar</paragraph>` becomes
|
|
547
|
+
* `<p>F<span class="comment">oo b</span>ar</p>` in the view.
|
|
548
|
+
*
|
|
549
|
+
* {@link module:engine/view/containerelement~ContainerElement} may provide a custom way of handling highlight. Most often,
|
|
550
|
+
* the element itself is given classes and attributes described in the highlight descriptor (instead of being wrapped in `<span>`).
|
|
551
|
+
* For example, a model marker set like this:
|
|
552
|
+
* `[<imageInline src="foo.jpg"></imageInline>]` becomes `<img src="foo.jpg" class="comment"></img>` in the view.
|
|
553
|
+
*
|
|
554
|
+
* For container elements, the conversion is two-step. While the converter processes the highlight descriptor and passes it
|
|
555
|
+
* to a container element, it is the container element instance itself that applies values from the highlight descriptor.
|
|
556
|
+
* So, in a sense, the converter takes care of stating what should be applied on what, while the element decides how to apply that.
|
|
557
|
+
*
|
|
558
|
+
* editor.conversion.for( 'downcast' ).markerToHighlight( { model: 'comment', view: { classes: 'comment' } } );
|
|
559
|
+
*
|
|
560
|
+
* editor.conversion.for( 'downcast' ).markerToHighlight( {
|
|
561
|
+
* model: 'comment',
|
|
562
|
+
* view: { classes: 'comment' },
|
|
563
|
+
* converterPriority: 'high'
|
|
564
|
+
* } );
|
|
565
|
+
*
|
|
566
|
+
* editor.conversion.for( 'downcast' ).markerToHighlight( {
|
|
567
|
+
* model: 'comment',
|
|
568
|
+
* view: ( data, conversionApi ) => {
|
|
569
|
+
* // Assuming that the marker name is in a form of comment:commentType:commentId.
|
|
570
|
+
* const [ , commentType, commentId ] = data.markerName.split( ':' );
|
|
571
|
+
*
|
|
572
|
+
* return {
|
|
573
|
+
* classes: [ 'comment', 'comment-' + commentType ],
|
|
574
|
+
* attributes: { 'data-comment-id': commentId }
|
|
575
|
+
* };
|
|
576
|
+
* }
|
|
577
|
+
* } );
|
|
578
|
+
*
|
|
579
|
+
* If a function is passed as the `config.view` parameter, it will be used to generate the highlight descriptor. The function
|
|
580
|
+
* receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
581
|
+
* as the parameters and should return a
|
|
582
|
+
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor}.
|
|
583
|
+
* The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
|
|
584
|
+
*
|
|
585
|
+
* See {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} to learn how to add a converter
|
|
586
|
+
* to the conversion process.
|
|
587
|
+
*
|
|
588
|
+
* @method #markerToHighlight
|
|
589
|
+
* @param {Object} config Conversion configuration.
|
|
590
|
+
* @param {String} config.model The name of the model marker (or model marker group) to convert.
|
|
591
|
+
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor
|
|
592
|
+
* that will be used for highlighting or a function that takes the model marker data and
|
|
593
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
|
|
594
|
+
* and returns a highlight descriptor.
|
|
595
|
+
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
596
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
597
|
+
*/
|
|
598
|
+
markerToHighlight(config) {
|
|
599
|
+
return this.add(downcastMarkerToHighlight(config));
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Model marker converter for data downcast.
|
|
603
|
+
*
|
|
604
|
+
* This conversion creates a representation for model marker boundaries in the view:
|
|
605
|
+
*
|
|
606
|
+
* * If the marker boundary is before or after a model element, a view attribute is set on a corresponding view element.
|
|
607
|
+
* * In other cases, a view element with the specified tag name is inserted at the corresponding view position.
|
|
608
|
+
*
|
|
609
|
+
* Typically, the marker names use the `group:uniqueId:otherData` convention. For example: `comment:e34zfk9k2n459df53sjl34:zx32c`.
|
|
610
|
+
* The default configuration for this conversion is that the first part is the `group` part and the rest of
|
|
611
|
+
* the marker name becomes the `name` part.
|
|
612
|
+
*
|
|
613
|
+
* Tag and attribute names and values are generated from the marker name:
|
|
614
|
+
*
|
|
615
|
+
* * The templates for attributes are `data-[group]-start-before="[name]"`, `data-[group]-start-after="[name]"`,
|
|
616
|
+
* `data-[group]-end-before="[name]"` and `data-[group]-end-after="[name]"`.
|
|
617
|
+
* * The templates for view elements are `<[group]-start name="[name]">` and `<[group]-end name="[name]">`.
|
|
618
|
+
*
|
|
619
|
+
* Attributes mark whether the given marker's start or end boundary is before or after the given element.
|
|
620
|
+
* The `data-[group]-start-before` and `data-[group]-end-after` attributes are favored.
|
|
621
|
+
* The other two are used when the former two cannot be used.
|
|
622
|
+
*
|
|
623
|
+
* The conversion configuration can take a function that will generate different group and name parts.
|
|
624
|
+
* If such a function is set as the `config.view` parameter, it is passed a marker name and it is expected to return an object with two
|
|
625
|
+
* properties: `group` and `name`. If the function returns a falsy value, the conversion will not take place.
|
|
626
|
+
*
|
|
627
|
+
* Basic usage:
|
|
628
|
+
*
|
|
629
|
+
* // Using the default conversion.
|
|
630
|
+
* // In this case, all markers with names starting with 'comment:' will be converted.
|
|
631
|
+
* // The `group` parameter will be set to `comment`.
|
|
632
|
+
* // The `name` parameter will be the rest of the marker name (without the `:`).
|
|
633
|
+
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
634
|
+
* model: 'comment'
|
|
635
|
+
* } );
|
|
636
|
+
*
|
|
637
|
+
* An example of a view that may be generated by this conversion (assuming a marker with the name `comment:commentId:uid` marked
|
|
638
|
+
* by `[]`):
|
|
639
|
+
*
|
|
640
|
+
* // Model:
|
|
641
|
+
* <paragraph>Foo[bar</paragraph>
|
|
642
|
+
* <imageBlock src="abc.jpg"></imageBlock>]
|
|
643
|
+
*
|
|
644
|
+
* // View:
|
|
645
|
+
* <p>Foo<comment-start name="commentId:uid"></comment-start>bar</p>
|
|
646
|
+
* <figure data-comment-end-after="commentId:uid" class="image"><img src="abc.jpg" /></figure>
|
|
647
|
+
*
|
|
648
|
+
* In the example above, the comment starts before "bar" and ends after the image.
|
|
649
|
+
*
|
|
650
|
+
* If the `name` part is empty, the following view may be generated:
|
|
651
|
+
*
|
|
652
|
+
* <p>Foo <myMarker-start></myMarker-start>bar</p>
|
|
653
|
+
* <figure data-myMarker-end-after="" class="image"><img src="abc.jpg" /></figure>
|
|
654
|
+
*
|
|
655
|
+
* **Note:** A situation where some markers have the `name` part and some do not, is incorrect and should be avoided.
|
|
656
|
+
*
|
|
657
|
+
* Examples where `data-group-start-after` and `data-group-end-before` are used:
|
|
658
|
+
*
|
|
659
|
+
* // Model:
|
|
660
|
+
* <blockQuote>[]<paragraph>Foo</paragraph></blockQuote>
|
|
661
|
+
*
|
|
662
|
+
* // View:
|
|
663
|
+
* <blockquote><p data-group-end-before="name" data-group-start-before="name">Foo</p></blockquote>
|
|
664
|
+
*
|
|
665
|
+
* Similarly, when a marker is collapsed after the last element:
|
|
666
|
+
*
|
|
667
|
+
* // Model:
|
|
668
|
+
* <blockQuote><paragraph>Foo</paragraph>[]</blockQuote>
|
|
669
|
+
*
|
|
670
|
+
* // View:
|
|
671
|
+
* <blockquote><p data-group-end-after="name" data-group-start-after="name">Foo</p></blockquote>
|
|
672
|
+
*
|
|
673
|
+
* When there are multiple markers from the same group stored in the same attribute of the same element, their
|
|
674
|
+
* name parts are put together in the attribute value, for example: `data-group-start-before="name1,name2,name3"`.
|
|
675
|
+
*
|
|
676
|
+
* Other examples of usage:
|
|
677
|
+
*
|
|
678
|
+
* // Using a custom function which is the same as the default conversion:
|
|
679
|
+
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
680
|
+
* model: 'comment'
|
|
681
|
+
* view: markerName => ( {
|
|
682
|
+
* group: 'comment',
|
|
683
|
+
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
684
|
+
* } )
|
|
685
|
+
* } );
|
|
686
|
+
*
|
|
687
|
+
* // Using the converter priority:
|
|
688
|
+
* editor.conversion.for( 'dataDowncast' ).markerToData( {
|
|
689
|
+
* model: 'comment'
|
|
690
|
+
* view: markerName => ( {
|
|
691
|
+
* group: 'comment',
|
|
692
|
+
* name: markerName.substr( 8 ) // Removes 'comment:' part.
|
|
693
|
+
* } ),
|
|
694
|
+
* converterPriority: 'high'
|
|
695
|
+
* } );
|
|
696
|
+
*
|
|
697
|
+
* This kind of conversion is useful for saving data into the database, so it should be used in the data conversion pipeline.
|
|
698
|
+
*
|
|
699
|
+
* See the {@link module:engine/conversion/conversion~Conversion#for `conversion.for()`} API guide to learn how to
|
|
700
|
+
* add a converter to the conversion process.
|
|
701
|
+
*
|
|
702
|
+
* @method #markerToData
|
|
703
|
+
* @param {Object} config Conversion configuration.
|
|
704
|
+
* @param {String} config.model The name of the model marker (or the model marker group) to convert.
|
|
705
|
+
* @param {Function} [config.view] A function that takes the model marker name and
|
|
706
|
+
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as the parameters
|
|
707
|
+
* and returns an object with the `group` and `name` properties.
|
|
708
|
+
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
709
|
+
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
|
|
710
|
+
*/
|
|
711
|
+
markerToData(config) {
|
|
712
|
+
return this.add(downcastMarkerToData(config));
|
|
713
|
+
}
|
|
725
714
|
}
|
|
726
|
-
|
|
727
715
|
/**
|
|
728
716
|
* Function factory that creates a default downcast converter for text insertion changes.
|
|
729
717
|
*
|
|
@@ -735,36 +723,31 @@ export default class DowncastHelpers extends ConversionHelpers {
|
|
|
735
723
|
* @returns {Function} Insert text event converter.
|
|
736
724
|
*/
|
|
737
725
|
export function insertText() {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
viewWriter.insert( viewPosition, viewText );
|
|
748
|
-
};
|
|
726
|
+
return (evt, data, conversionApi) => {
|
|
727
|
+
if (!conversionApi.consumable.consume(data.item, evt.name)) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const viewWriter = conversionApi.writer;
|
|
731
|
+
const viewPosition = conversionApi.mapper.toViewPosition(data.range.start);
|
|
732
|
+
const viewText = viewWriter.createText(data.item.data);
|
|
733
|
+
viewWriter.insert(viewPosition, viewText);
|
|
734
|
+
};
|
|
749
735
|
}
|
|
750
|
-
|
|
751
736
|
/**
|
|
752
737
|
* Function factory that creates a default downcast converter for triggering attributes and children conversion.
|
|
753
738
|
*
|
|
754
739
|
* @returns {Function} The converter.
|
|
755
740
|
*/
|
|
756
741
|
export function insertAttributesAndChildren() {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
};
|
|
742
|
+
return (evt, data, conversionApi) => {
|
|
743
|
+
conversionApi.convertAttributes(data.item);
|
|
744
|
+
// Start converting children of the current item.
|
|
745
|
+
// In case of reconversion children were already re-inserted or converted separately.
|
|
746
|
+
if (!data.reconversion && data.item.is('element') && !data.item.isEmpty) {
|
|
747
|
+
conversionApi.convertChildren(data.item);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
766
750
|
}
|
|
767
|
-
|
|
768
751
|
/**
|
|
769
752
|
* Function factory that creates a default downcast converter for node remove changes.
|
|
770
753
|
*
|
|
@@ -773,26 +756,21 @@ export function insertAttributesAndChildren() {
|
|
|
773
756
|
* @returns {Function} Remove event converter.
|
|
774
757
|
*/
|
|
775
758
|
export function remove() {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
for ( const child of conversionApi.writer.createRangeIn( removed ).getItems() ) {
|
|
791
|
-
conversionApi.mapper.unbindViewElement( child, { defer: true } );
|
|
792
|
-
}
|
|
793
|
-
};
|
|
759
|
+
return (evt, data, conversionApi) => {
|
|
760
|
+
// Find the view range start position by mapping the model position at which the remove happened.
|
|
761
|
+
const viewStart = conversionApi.mapper.toViewPosition(data.position);
|
|
762
|
+
const modelEnd = data.position.getShiftedBy(data.length);
|
|
763
|
+
const viewEnd = conversionApi.mapper.toViewPosition(modelEnd, { isPhantom: true });
|
|
764
|
+
const viewRange = conversionApi.writer.createRange(viewStart, viewEnd);
|
|
765
|
+
// Trim the range to remove in case some UI elements are on the view range boundaries.
|
|
766
|
+
const removed = conversionApi.writer.remove(viewRange.getTrimmed());
|
|
767
|
+
// After the range is removed, unbind all view elements from the model.
|
|
768
|
+
// Range inside view document fragment is used to unbind deeply.
|
|
769
|
+
for (const child of conversionApi.writer.createRangeIn(removed).getItems()) {
|
|
770
|
+
conversionApi.mapper.unbindViewElement(child, { defer: true });
|
|
771
|
+
}
|
|
772
|
+
};
|
|
794
773
|
}
|
|
795
|
-
|
|
796
774
|
/**
|
|
797
775
|
* Creates a `<span>` {@link module:engine/view/attributeelement~AttributeElement view attribute element} from the information
|
|
798
776
|
* provided by the {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor} object. If the priority
|
|
@@ -802,22 +780,17 @@ export function remove() {
|
|
|
802
780
|
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor} descriptor
|
|
803
781
|
* @returns {module:engine/view/attributeelement~AttributeElement}
|
|
804
782
|
*/
|
|
805
|
-
export function createViewElementFromHighlightDescriptor(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
viewElement._id = descriptor.id;
|
|
817
|
-
|
|
818
|
-
return viewElement;
|
|
783
|
+
export function createViewElementFromHighlightDescriptor(writer, descriptor) {
|
|
784
|
+
const viewElement = writer.createAttributeElement('span', descriptor.attributes);
|
|
785
|
+
if (descriptor.classes) {
|
|
786
|
+
viewElement._addClass(descriptor.classes);
|
|
787
|
+
}
|
|
788
|
+
if (typeof descriptor.priority === 'number') {
|
|
789
|
+
viewElement._priority = descriptor.priority;
|
|
790
|
+
}
|
|
791
|
+
viewElement._id = descriptor.id;
|
|
792
|
+
return viewElement;
|
|
819
793
|
}
|
|
820
|
-
|
|
821
794
|
/**
|
|
822
795
|
* Function factory that creates a converter which converts a non-collapsed {@link module:engine/model/selection~Selection model selection}
|
|
823
796
|
* to a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
|
|
@@ -828,28 +801,21 @@ export function createViewElementFromHighlightDescriptor( writer, descriptor ) {
|
|
|
828
801
|
* @returns {Function} Selection converter.
|
|
829
802
|
*/
|
|
830
803
|
export function convertRangeSelection() {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const viewRange = conversionApi.mapper.toViewRange( range );
|
|
846
|
-
viewRanges.push( viewRange );
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
conversionApi.writer.setSelection( viewRanges, { backward: selection.isBackward } );
|
|
850
|
-
};
|
|
804
|
+
return (evt, data, conversionApi) => {
|
|
805
|
+
const selection = data.selection;
|
|
806
|
+
if (selection.isCollapsed) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (!conversionApi.consumable.consume(selection, 'selection')) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const viewRanges = [];
|
|
813
|
+
for (const range of selection.getRanges()) {
|
|
814
|
+
viewRanges.push(conversionApi.mapper.toViewRange(range));
|
|
815
|
+
}
|
|
816
|
+
conversionApi.writer.setSelection(viewRanges, { backward: selection.isBackward });
|
|
817
|
+
};
|
|
851
818
|
}
|
|
852
|
-
|
|
853
819
|
/**
|
|
854
820
|
* Function factory that creates a converter which converts a collapsed {@link module:engine/model/selection~Selection model selection} to
|
|
855
821
|
* a {@link module:engine/view/documentselection~DocumentSelection view selection}. The converter consumes appropriate
|
|
@@ -873,26 +839,21 @@ export function convertRangeSelection() {
|
|
|
873
839
|
* @returns {Function} Selection converter.
|
|
874
840
|
*/
|
|
875
841
|
export function convertCollapsedSelection() {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const brokenPosition = viewWriter.breakAttributes( viewPosition );
|
|
891
|
-
|
|
892
|
-
viewWriter.setSelection( brokenPosition );
|
|
893
|
-
};
|
|
842
|
+
return (evt, data, conversionApi) => {
|
|
843
|
+
const selection = data.selection;
|
|
844
|
+
if (!selection.isCollapsed) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (!conversionApi.consumable.consume(selection, 'selection')) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const viewWriter = conversionApi.writer;
|
|
851
|
+
const modelPosition = selection.getFirstPosition();
|
|
852
|
+
const viewPosition = conversionApi.mapper.toViewPosition(modelPosition);
|
|
853
|
+
const brokenPosition = viewWriter.breakAttributes(viewPosition);
|
|
854
|
+
viewWriter.setSelection(brokenPosition);
|
|
855
|
+
};
|
|
894
856
|
}
|
|
895
|
-
|
|
896
857
|
/**
|
|
897
858
|
* Function factory that creates a converter which clears artifacts after the previous
|
|
898
859
|
* {@link module:engine/model/selection~Selection model selection} conversion. It removes all empty
|
|
@@ -918,23 +879,21 @@ export function convertCollapsedSelection() {
|
|
|
918
879
|
* @returns {Function} Selection converter.
|
|
919
880
|
*/
|
|
920
881
|
export function clearAttributes() {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
};
|
|
882
|
+
return (evt, data, conversionApi) => {
|
|
883
|
+
const viewWriter = conversionApi.writer;
|
|
884
|
+
const viewSelection = viewWriter.document.selection;
|
|
885
|
+
for (const range of viewSelection.getRanges()) {
|
|
886
|
+
// Not collapsed selection should not have artifacts.
|
|
887
|
+
if (range.isCollapsed) {
|
|
888
|
+
// Position might be in the node removed by the view writer.
|
|
889
|
+
if (range.end.parent.isAttached()) {
|
|
890
|
+
conversionApi.writer.mergeAttributes(range.start);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
viewWriter.setSelection(null);
|
|
895
|
+
};
|
|
936
896
|
}
|
|
937
|
-
|
|
938
897
|
/**
|
|
939
898
|
* Function factory that creates a converter which converts the set/change/remove attribute changes from the model to the view.
|
|
940
899
|
* It can also be used to convert selection attributes. In that case, an empty attribute element will be created and the
|
|
@@ -966,47 +925,39 @@ export function clearAttributes() {
|
|
|
966
925
|
* @param {Function} elementCreator Function returning a view element that will be used for wrapping.
|
|
967
926
|
* @returns {Function} Set/change attribute converter.
|
|
968
927
|
*/
|
|
969
|
-
export function wrap(
|
|
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
|
-
if ( data.attributeNewValue !== null && newViewElement ) {
|
|
1004
|
-
viewWriter.wrap( viewRange, newViewElement );
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
928
|
+
export function wrap(elementCreator) {
|
|
929
|
+
return (evt, data, conversionApi) => {
|
|
930
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
|
|
934
|
+
// or the attribute was removed.
|
|
935
|
+
const oldViewElement = elementCreator(data.attributeOldValue, conversionApi, data);
|
|
936
|
+
// Create node to wrap with.
|
|
937
|
+
const newViewElement = elementCreator(data.attributeNewValue, conversionApi, data);
|
|
938
|
+
if (!oldViewElement && !newViewElement) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
942
|
+
const viewWriter = conversionApi.writer;
|
|
943
|
+
const viewSelection = viewWriter.document.selection;
|
|
944
|
+
if (data.item instanceof ModelSelection || data.item instanceof ModelDocumentSelection) {
|
|
945
|
+
// Selection attribute conversion.
|
|
946
|
+
viewWriter.wrap(viewSelection.getFirstRange(), newViewElement);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
// Node attribute conversion.
|
|
950
|
+
let viewRange = conversionApi.mapper.toViewRange(data.range);
|
|
951
|
+
// First, unwrap the range from current wrapper.
|
|
952
|
+
if (data.attributeOldValue !== null && oldViewElement) {
|
|
953
|
+
viewRange = viewWriter.unwrap(viewRange, oldViewElement);
|
|
954
|
+
}
|
|
955
|
+
if (data.attributeNewValue !== null && newViewElement) {
|
|
956
|
+
viewWriter.wrap(viewRange, newViewElement);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
};
|
|
1008
960
|
}
|
|
1009
|
-
|
|
1010
961
|
/**
|
|
1011
962
|
* Function factory that creates a converter which converts node insertion changes from the model to the view.
|
|
1012
963
|
* The function passed will be provided with all the parameters of the dispatcher's
|
|
@@ -1034,34 +985,26 @@ export function wrap( elementCreator ) {
|
|
|
1034
985
|
* By default this function just consume passed item insertion.
|
|
1035
986
|
* @returns {Function} Insert element event converter.
|
|
1036
987
|
*/
|
|
1037
|
-
export function insertElement(
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
// Convert attributes before converting children.
|
|
1058
|
-
conversionApi.convertAttributes( data.item );
|
|
1059
|
-
|
|
1060
|
-
// Convert children or reinsert previous view elements.
|
|
1061
|
-
reinsertOrConvertNodes( viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion } );
|
|
1062
|
-
};
|
|
988
|
+
export function insertElement(elementCreator, consumer = defaultConsumer) {
|
|
989
|
+
return (evt, data, conversionApi) => {
|
|
990
|
+
if (!consumer(data.item, conversionApi.consumable, { preflight: true })) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const viewElement = elementCreator(data.item, conversionApi, data);
|
|
994
|
+
if (!viewElement) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
|
|
998
|
+
consumer(data.item, conversionApi.consumable);
|
|
999
|
+
const viewPosition = conversionApi.mapper.toViewPosition(data.range.start);
|
|
1000
|
+
conversionApi.mapper.bindElements(data.item, viewElement);
|
|
1001
|
+
conversionApi.writer.insert(viewPosition, viewElement);
|
|
1002
|
+
// Convert attributes before converting children.
|
|
1003
|
+
conversionApi.convertAttributes(data.item);
|
|
1004
|
+
// Convert children or reinsert previous view elements.
|
|
1005
|
+
reinsertOrConvertNodes(viewElement, data.item.getChildren(), conversionApi, { reconversion: data.reconversion });
|
|
1006
|
+
};
|
|
1063
1007
|
}
|
|
1064
|
-
|
|
1065
1008
|
/**
|
|
1066
1009
|
* Function factory that creates a converter which converts a single model node insertion to a view structure.
|
|
1067
1010
|
*
|
|
@@ -1077,44 +1020,32 @@ export function insertElement( elementCreator, consumer = defaultConsumer ) {
|
|
|
1077
1020
|
* that were used by the element creator.
|
|
1078
1021
|
* @returns {Function} Insert element event converter.
|
|
1079
1022
|
*/
|
|
1080
|
-
export function insertStructure(
|
|
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
|
-
const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
|
|
1106
|
-
|
|
1107
|
-
conversionApi.mapper.bindElements( data.item, viewElement );
|
|
1108
|
-
conversionApi.writer.insert( viewPosition, viewElement );
|
|
1109
|
-
|
|
1110
|
-
// Convert attributes before converting children.
|
|
1111
|
-
conversionApi.convertAttributes( data.item );
|
|
1112
|
-
|
|
1113
|
-
// Fill view slots with previous view elements or create new ones.
|
|
1114
|
-
fillSlots( viewElement, slotsMap, conversionApi, { reconversion: data.reconversion } );
|
|
1115
|
-
};
|
|
1023
|
+
export function insertStructure(elementCreator, consumer) {
|
|
1024
|
+
return (evt, data, conversionApi) => {
|
|
1025
|
+
if (!consumer(data.item, conversionApi.consumable, { preflight: true })) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const slotsMap = new Map();
|
|
1029
|
+
conversionApi.writer._registerSlotFactory(createSlotFactory(data.item, slotsMap, conversionApi));
|
|
1030
|
+
// View creation.
|
|
1031
|
+
const viewElement = elementCreator(data.item, conversionApi, data);
|
|
1032
|
+
conversionApi.writer._clearSlotFactory();
|
|
1033
|
+
if (!viewElement) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Check if all children are covered by slots and there is no child that landed in multiple slots.
|
|
1037
|
+
validateSlotsChildren(data.item, slotsMap, conversionApi);
|
|
1038
|
+
// Consume an element insertion and all present attributes that are specified as a reconversion triggers.
|
|
1039
|
+
consumer(data.item, conversionApi.consumable);
|
|
1040
|
+
const viewPosition = conversionApi.mapper.toViewPosition(data.range.start);
|
|
1041
|
+
conversionApi.mapper.bindElements(data.item, viewElement);
|
|
1042
|
+
conversionApi.writer.insert(viewPosition, viewElement);
|
|
1043
|
+
// Convert attributes before converting children.
|
|
1044
|
+
conversionApi.convertAttributes(data.item);
|
|
1045
|
+
// Fill view slots with previous view elements or create new ones.
|
|
1046
|
+
fillSlots(viewElement, slotsMap, conversionApi, { reconversion: data.reconversion });
|
|
1047
|
+
};
|
|
1116
1048
|
}
|
|
1117
|
-
|
|
1118
1049
|
/**
|
|
1119
1050
|
* Function factory that creates a converter which converts marker adding change to the
|
|
1120
1051
|
* {@link module:engine/view/uielement~UIElement view UI element}.
|
|
@@ -1130,53 +1061,43 @@ export function insertStructure( elementCreator, consumer ) {
|
|
|
1130
1061
|
* that will be inserted.
|
|
1131
1062
|
* @returns {Function} Insert element event converter.
|
|
1132
1063
|
*/
|
|
1133
|
-
export function insertUIElement(
|
|
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
|
-
// Add "closing" element only if range is not collapsed.
|
|
1171
|
-
if ( !markerRange.isCollapsed ) {
|
|
1172
|
-
viewWriter.insert( mapper.toViewPosition( markerRange.end ), viewEndElement );
|
|
1173
|
-
conversionApi.mapper.bindElementToMarker( viewEndElement, data.markerName );
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
evt.stop();
|
|
1177
|
-
};
|
|
1064
|
+
export function insertUIElement(elementCreator) {
|
|
1065
|
+
return (evt, data, conversionApi) => {
|
|
1066
|
+
// Create two view elements. One will be inserted at the beginning of marker, one at the end.
|
|
1067
|
+
// If marker is collapsed, only "opening" element will be inserted.
|
|
1068
|
+
data.isOpening = true;
|
|
1069
|
+
const viewStartElement = elementCreator(data, conversionApi);
|
|
1070
|
+
data.isOpening = false;
|
|
1071
|
+
const viewEndElement = elementCreator(data, conversionApi);
|
|
1072
|
+
if (!viewStartElement || !viewEndElement) {
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const markerRange = data.markerRange;
|
|
1076
|
+
// Marker that is collapsed has consumable build differently that non-collapsed one.
|
|
1077
|
+
// For more information see `addMarker` event description.
|
|
1078
|
+
// If marker's range is collapsed - check if it can be consumed.
|
|
1079
|
+
if (markerRange.isCollapsed && !conversionApi.consumable.consume(markerRange, evt.name)) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
// If marker's range is not collapsed - consume all items inside.
|
|
1083
|
+
for (const value of markerRange) {
|
|
1084
|
+
if (!conversionApi.consumable.consume(value.item, evt.name)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const mapper = conversionApi.mapper;
|
|
1089
|
+
const viewWriter = conversionApi.writer;
|
|
1090
|
+
// Add "opening" element.
|
|
1091
|
+
viewWriter.insert(mapper.toViewPosition(markerRange.start), viewStartElement);
|
|
1092
|
+
conversionApi.mapper.bindElementToMarker(viewStartElement, data.markerName);
|
|
1093
|
+
// Add "closing" element only if range is not collapsed.
|
|
1094
|
+
if (!markerRange.isCollapsed) {
|
|
1095
|
+
viewWriter.insert(mapper.toViewPosition(markerRange.end), viewEndElement);
|
|
1096
|
+
conversionApi.mapper.bindElementToMarker(viewEndElement, data.markerName);
|
|
1097
|
+
}
|
|
1098
|
+
evt.stop();
|
|
1099
|
+
};
|
|
1178
1100
|
}
|
|
1179
|
-
|
|
1180
1101
|
// Function factory that returns a default downcast converter for removing a {@link module:engine/view/uielement~UIElement UI element}
|
|
1181
1102
|
// based on marker remove change.
|
|
1182
1103
|
//
|
|
@@ -1184,24 +1105,19 @@ export function insertUIElement( elementCreator ) {
|
|
|
1184
1105
|
//
|
|
1185
1106
|
// @returns {Function} Removed UI element converter.
|
|
1186
1107
|
function removeUIElement() {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
conversionApi.writer.clearClonedElementsGroup( data.markerName );
|
|
1200
|
-
|
|
1201
|
-
evt.stop();
|
|
1202
|
-
};
|
|
1108
|
+
return (evt, data, conversionApi) => {
|
|
1109
|
+
const elements = conversionApi.mapper.markerNameToElements(data.markerName);
|
|
1110
|
+
if (!elements) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
for (const element of elements) {
|
|
1114
|
+
conversionApi.mapper.unbindElementFromMarkerName(element, data.markerName);
|
|
1115
|
+
conversionApi.writer.clear(conversionApi.writer.createRangeOn(element), element);
|
|
1116
|
+
}
|
|
1117
|
+
conversionApi.writer.clearClonedElementsGroup(data.markerName);
|
|
1118
|
+
evt.stop();
|
|
1119
|
+
};
|
|
1203
1120
|
}
|
|
1204
|
-
|
|
1205
1121
|
// Function factory that creates a default converter for model markers.
|
|
1206
1122
|
//
|
|
1207
1123
|
// See {@link DowncastHelpers#markerToData} for more information what type of view is generated.
|
|
@@ -1210,140 +1126,112 @@ function removeUIElement() {
|
|
|
1210
1126
|
// using {@link module:engine/conversion/mapper~Mapper#bindElementToMarker}.
|
|
1211
1127
|
//
|
|
1212
1128
|
// @returns {Function} Add marker converter.
|
|
1213
|
-
function insertMarkerData(
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
handleMarkerBoundary( markerRange, false, conversionApi, data, viewMarkerData );
|
|
1229
|
-
handleMarkerBoundary( markerRange, true, conversionApi, data, viewMarkerData );
|
|
1230
|
-
|
|
1231
|
-
evt.stop();
|
|
1232
|
-
};
|
|
1129
|
+
function insertMarkerData(viewCreator) {
|
|
1130
|
+
return (evt, data, conversionApi) => {
|
|
1131
|
+
const viewMarkerData = viewCreator(data.markerName, conversionApi);
|
|
1132
|
+
if (!viewMarkerData) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const markerRange = data.markerRange;
|
|
1136
|
+
if (!conversionApi.consumable.consume(markerRange, evt.name)) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// Adding closing data first to keep the proper order in the view.
|
|
1140
|
+
handleMarkerBoundary(markerRange, false, conversionApi, data, viewMarkerData);
|
|
1141
|
+
handleMarkerBoundary(markerRange, true, conversionApi, data, viewMarkerData);
|
|
1142
|
+
evt.stop();
|
|
1143
|
+
};
|
|
1233
1144
|
}
|
|
1234
|
-
|
|
1235
1145
|
// Helper function for `insertMarkerData()` that marks a marker boundary at the beginning or end of given `range`.
|
|
1236
|
-
function handleMarkerBoundary(
|
|
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
|
-
|
|
1269
|
-
const viewPosition = conversionApi.mapper.toViewPosition( modelPosition );
|
|
1270
|
-
|
|
1271
|
-
insertMarkerAsElement( viewPosition, isStart, conversionApi, data, viewMarkerData );
|
|
1146
|
+
function handleMarkerBoundary(range, isStart, conversionApi, data, viewMarkerData) {
|
|
1147
|
+
const modelPosition = isStart ? range.start : range.end;
|
|
1148
|
+
const elementAfter = modelPosition.nodeAfter && modelPosition.nodeAfter.is('element') ? modelPosition.nodeAfter : null;
|
|
1149
|
+
const elementBefore = modelPosition.nodeBefore && modelPosition.nodeBefore.is('element') ? modelPosition.nodeBefore : null;
|
|
1150
|
+
if (elementAfter || elementBefore) {
|
|
1151
|
+
let modelElement;
|
|
1152
|
+
let isBefore;
|
|
1153
|
+
// If possible, we want to add `data-group-start-before` and `data-group-end-after` attributes.
|
|
1154
|
+
if (isStart && elementAfter || !isStart && !elementBefore) {
|
|
1155
|
+
// [<elementAfter>...</elementAfter> -> <elementAfter data-group-start-before="...">...</elementAfter>
|
|
1156
|
+
// <parent>]<elementAfter> -> <parent><elementAfter data-group-end-before="...">
|
|
1157
|
+
modelElement = elementAfter;
|
|
1158
|
+
isBefore = true;
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
// <elementBefore>...</elementBefore>] -> <elementBefore data-group-end-after="...">...</elementBefore>
|
|
1162
|
+
// </elementBefore>[</parent> -> </elementBefore data-group-start-after="..."></parent>
|
|
1163
|
+
modelElement = elementBefore;
|
|
1164
|
+
isBefore = false;
|
|
1165
|
+
}
|
|
1166
|
+
const viewElement = conversionApi.mapper.toViewElement(modelElement);
|
|
1167
|
+
// In rare circumstances, the model element may be not mapped to any view element and that would cause an error.
|
|
1168
|
+
// One of those situations is a soft break inside code block.
|
|
1169
|
+
if (viewElement) {
|
|
1170
|
+
insertMarkerAsAttribute(viewElement, isStart, isBefore, conversionApi, data, viewMarkerData);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
const viewPosition = conversionApi.mapper.toViewPosition(modelPosition);
|
|
1175
|
+
insertMarkerAsElement(viewPosition, isStart, conversionApi, data, viewMarkerData);
|
|
1272
1176
|
}
|
|
1273
|
-
|
|
1274
1177
|
// Helper function for `insertMarkerData()` that marks a marker boundary in the view as an attribute on a view element.
|
|
1275
|
-
function insertMarkerAsAttribute(
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
conversionApi.writer.setAttribute( attributeName, markerNames.join( ',' ), viewElement );
|
|
1284
|
-
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
|
|
1178
|
+
function insertMarkerAsAttribute(viewElement, isStart, isBefore, conversionApi, data, viewMarkerData) {
|
|
1179
|
+
const attributeName = `data-${viewMarkerData.group}-${isStart ? 'start' : 'end'}-${isBefore ? 'before' : 'after'}`;
|
|
1180
|
+
const markerNames = viewElement.hasAttribute(attributeName) ? viewElement.getAttribute(attributeName).split(',') : [];
|
|
1181
|
+
// Adding marker name at the beginning to have the same order in the attribute as there is with marker elements.
|
|
1182
|
+
markerNames.unshift(viewMarkerData.name);
|
|
1183
|
+
conversionApi.writer.setAttribute(attributeName, markerNames.join(','), viewElement);
|
|
1184
|
+
conversionApi.mapper.bindElementToMarker(viewElement, data.markerName);
|
|
1285
1185
|
}
|
|
1286
|
-
|
|
1287
1186
|
// Helper function for `insertMarkerData()` that marks a marker boundary in the view as a separate view ui element.
|
|
1288
|
-
function insertMarkerAsElement(
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
conversionApi.writer.insert( position, viewElement );
|
|
1295
|
-
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
|
|
1187
|
+
function insertMarkerAsElement(position, isStart, conversionApi, data, viewMarkerData) {
|
|
1188
|
+
const viewElementName = `${viewMarkerData.group}-${isStart ? 'start' : 'end'}`;
|
|
1189
|
+
const attrs = viewMarkerData.name ? { 'name': viewMarkerData.name } : null;
|
|
1190
|
+
const viewElement = conversionApi.writer.createUIElement(viewElementName, attrs);
|
|
1191
|
+
conversionApi.writer.insert(position, viewElement);
|
|
1192
|
+
conversionApi.mapper.bindElementToMarker(viewElement, data.markerName);
|
|
1296
1193
|
}
|
|
1297
|
-
|
|
1298
1194
|
// Function factory that creates a converter for removing a model marker data added by the {@link #insertMarkerData} converter.
|
|
1299
1195
|
//
|
|
1300
1196
|
// @returns {Function} Remove marker converter.
|
|
1301
|
-
function removeMarkerData(
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
conversionApi.writer.removeAttribute( attributeName, element );
|
|
1339
|
-
} else {
|
|
1340
|
-
conversionApi.writer.setAttribute( attributeName, Array.from( markerNames ).join( ',' ), element );
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
};
|
|
1197
|
+
function removeMarkerData(viewCreator) {
|
|
1198
|
+
return (evt, data, conversionApi) => {
|
|
1199
|
+
const viewData = viewCreator(data.markerName, conversionApi);
|
|
1200
|
+
if (!viewData) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
const elements = conversionApi.mapper.markerNameToElements(data.markerName);
|
|
1204
|
+
if (!elements) {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
for (const element of elements) {
|
|
1208
|
+
conversionApi.mapper.unbindElementFromMarkerName(element, data.markerName);
|
|
1209
|
+
if (element.is('containerElement')) {
|
|
1210
|
+
removeMarkerFromAttribute(`data-${viewData.group}-start-before`, element);
|
|
1211
|
+
removeMarkerFromAttribute(`data-${viewData.group}-start-after`, element);
|
|
1212
|
+
removeMarkerFromAttribute(`data-${viewData.group}-end-before`, element);
|
|
1213
|
+
removeMarkerFromAttribute(`data-${viewData.group}-end-after`, element);
|
|
1214
|
+
}
|
|
1215
|
+
else {
|
|
1216
|
+
conversionApi.writer.clear(conversionApi.writer.createRangeOn(element), element);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
conversionApi.writer.clearClonedElementsGroup(data.markerName);
|
|
1220
|
+
evt.stop();
|
|
1221
|
+
function removeMarkerFromAttribute(attributeName, element) {
|
|
1222
|
+
if (element.hasAttribute(attributeName)) {
|
|
1223
|
+
const markerNames = new Set(element.getAttribute(attributeName).split(','));
|
|
1224
|
+
markerNames.delete(viewData.name);
|
|
1225
|
+
if (markerNames.size == 0) {
|
|
1226
|
+
conversionApi.writer.removeAttribute(attributeName, element);
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
conversionApi.writer.setAttribute(attributeName, Array.from(markerNames).join(','), element);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1345
1234
|
}
|
|
1346
|
-
|
|
1347
1235
|
// Function factory that creates a converter which converts the set/change/remove attribute changes from the model to the view.
|
|
1348
1236
|
//
|
|
1349
1237
|
// Attributes from the model are converted to the view element attributes in the view. You may provide a custom function to generate
|
|
@@ -1373,104 +1261,96 @@ function removeMarkerData( viewCreator ) {
|
|
|
1373
1261
|
// represent the attribute key and attribute value to be set on a {@link module:engine/view/element~Element view element}.
|
|
1374
1262
|
// The function is passed the model attribute value as the first parameter and additional data about the change as the second parameter.
|
|
1375
1263
|
// @returns {Function} Set/change attribute converter.
|
|
1376
|
-
function changeAttribute(
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
viewWriter.setStyle( key, newAttribute.value[ key ], viewElement );
|
|
1466
|
-
}
|
|
1467
|
-
} else {
|
|
1468
|
-
viewWriter.setAttribute( newAttribute.key, newAttribute.value, viewElement );
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
};
|
|
1264
|
+
function changeAttribute(attributeCreator) {
|
|
1265
|
+
return (evt, data, conversionApi) => {
|
|
1266
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
const oldAttribute = attributeCreator(data.attributeOldValue, conversionApi, data);
|
|
1270
|
+
const newAttribute = attributeCreator(data.attributeNewValue, conversionApi, data);
|
|
1271
|
+
if (!oldAttribute && !newAttribute) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
1275
|
+
const viewElement = conversionApi.mapper.toViewElement(data.item);
|
|
1276
|
+
const viewWriter = conversionApi.writer;
|
|
1277
|
+
// If model item cannot be mapped to a view element, it means item is not an `Element` instance but a `TextProxy` node.
|
|
1278
|
+
// Only elements can have attributes in a view so do not proceed for anything else (#1587).
|
|
1279
|
+
if (!viewElement) {
|
|
1280
|
+
/**
|
|
1281
|
+
* This error occurs when a {@link module:engine/model/textproxy~TextProxy text node's} attribute is to be downcasted
|
|
1282
|
+
* by an {@link module:engine/conversion/conversion~Conversion#attributeToAttribute `Attribute to Attribute converter`}.
|
|
1283
|
+
* In most cases it is caused by converters misconfiguration when only "generic" converter is defined:
|
|
1284
|
+
*
|
|
1285
|
+
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
|
|
1286
|
+
* model: 'attribute-name',
|
|
1287
|
+
* view: 'attribute-name'
|
|
1288
|
+
* } ) );
|
|
1289
|
+
*
|
|
1290
|
+
* and given attribute is used on text node, for example:
|
|
1291
|
+
*
|
|
1292
|
+
* model.change( writer => {
|
|
1293
|
+
* writer.insertText( 'Foo', { 'attribute-name': 'bar' }, parent, 0 );
|
|
1294
|
+
* } );
|
|
1295
|
+
*
|
|
1296
|
+
* In such cases, to convert the same attribute for both {@link module:engine/model/element~Element}
|
|
1297
|
+
* and {@link module:engine/model/textproxy~TextProxy `Text`} nodes, text specific
|
|
1298
|
+
* {@link module:engine/conversion/conversion~Conversion#attributeToElement `Attribute to Element converter`}
|
|
1299
|
+
* with higher {@link module:utils/priorities~PriorityString priority} must also be defined:
|
|
1300
|
+
*
|
|
1301
|
+
* editor.conversion.for( 'downcast' ).attributeToElement( {
|
|
1302
|
+
* model: {
|
|
1303
|
+
* key: 'attribute-name',
|
|
1304
|
+
* name: '$text'
|
|
1305
|
+
* },
|
|
1306
|
+
* view: ( value, { writer } ) => {
|
|
1307
|
+
* return writer.createAttributeElement( 'span', { 'attribute-name': value } );
|
|
1308
|
+
* },
|
|
1309
|
+
* converterPriority: 'high'
|
|
1310
|
+
* } ) );
|
|
1311
|
+
*
|
|
1312
|
+
* @error conversion-attribute-to-attribute-on-text
|
|
1313
|
+
*/
|
|
1314
|
+
throw new CKEditorError('conversion-attribute-to-attribute-on-text', conversionApi.dispatcher, data);
|
|
1315
|
+
}
|
|
1316
|
+
// First remove the old attribute if there was one.
|
|
1317
|
+
if (data.attributeOldValue !== null && oldAttribute) {
|
|
1318
|
+
if (oldAttribute.key == 'class') {
|
|
1319
|
+
const classes = toArray(oldAttribute.value);
|
|
1320
|
+
for (const className of classes) {
|
|
1321
|
+
viewWriter.removeClass(className, viewElement);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
else if (oldAttribute.key == 'style') {
|
|
1325
|
+
const keys = Object.keys(oldAttribute.value);
|
|
1326
|
+
for (const key of keys) {
|
|
1327
|
+
viewWriter.removeStyle(key, viewElement);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
viewWriter.removeAttribute(oldAttribute.key, viewElement);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
// Then set the new attribute.
|
|
1335
|
+
if (data.attributeNewValue !== null && newAttribute) {
|
|
1336
|
+
if (newAttribute.key == 'class') {
|
|
1337
|
+
const classes = toArray(newAttribute.value);
|
|
1338
|
+
for (const className of classes) {
|
|
1339
|
+
viewWriter.addClass(className, viewElement);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
else if (newAttribute.key == 'style') {
|
|
1343
|
+
const keys = Object.keys(newAttribute.value);
|
|
1344
|
+
for (const key of keys) {
|
|
1345
|
+
viewWriter.setStyle(key, newAttribute.value[key], viewElement);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
viewWriter.setAttribute(newAttribute.key, newAttribute.value, viewElement);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1472
1353
|
}
|
|
1473
|
-
|
|
1474
1354
|
// Function factory that creates a converter which converts the text inside marker's range. The converter wraps the text with
|
|
1475
1355
|
// {@link module:engine/view/attributeelement~AttributeElement} created from the provided descriptor.
|
|
1476
1356
|
// See {link module:engine/conversion/downcasthelpers~createViewElementFromHighlightDescriptor}.
|
|
@@ -1487,49 +1367,41 @@ function changeAttribute( attributeCreator ) {
|
|
|
1487
1367
|
//
|
|
1488
1368
|
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
|
|
1489
1369
|
// @returns {Function}
|
|
1490
|
-
function highlightText(
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
// One attribute element is enough, because all of them are bound together by the view writer.
|
|
1525
|
-
// Mapper uses this binding to get all the elements no matter how many of them are registered in the mapper.
|
|
1526
|
-
break;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1370
|
+
function highlightText(highlightDescriptor) {
|
|
1371
|
+
return (evt, data, conversionApi) => {
|
|
1372
|
+
if (!data.item) {
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (!(data.item instanceof ModelSelection || data.item instanceof ModelDocumentSelection) && !data.item.is('$textProxy')) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const descriptor = prepareDescriptor(highlightDescriptor, data, conversionApi);
|
|
1379
|
+
if (!descriptor) {
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (!conversionApi.consumable.consume(data.item, evt.name)) {
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
const viewWriter = conversionApi.writer;
|
|
1386
|
+
const viewElement = createViewElementFromHighlightDescriptor(viewWriter, descriptor);
|
|
1387
|
+
const viewSelection = viewWriter.document.selection;
|
|
1388
|
+
if (data.item instanceof ModelSelection || data.item instanceof ModelDocumentSelection) {
|
|
1389
|
+
viewWriter.wrap(viewSelection.getFirstRange(), viewElement);
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
const viewRange = conversionApi.mapper.toViewRange(data.range);
|
|
1393
|
+
const rangeAfterWrap = viewWriter.wrap(viewRange, viewElement);
|
|
1394
|
+
for (const element of rangeAfterWrap.getItems()) {
|
|
1395
|
+
if (element.is('attributeElement') && element.isSimilar(viewElement)) {
|
|
1396
|
+
conversionApi.mapper.bindElementToMarker(element, data.markerName);
|
|
1397
|
+
// One attribute element is enough, because all of them are bound together by the view writer.
|
|
1398
|
+
// Mapper uses this binding to get all the elements no matter how many of them are registered in the mapper.
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1531
1404
|
}
|
|
1532
|
-
|
|
1533
1405
|
// Converter function factory. It creates a function which applies the marker's highlight to an element inside the marker's range.
|
|
1534
1406
|
//
|
|
1535
1407
|
// The converter checks if an element has the `addHighlight` function stored as a
|
|
@@ -1548,44 +1420,35 @@ function highlightText( highlightDescriptor ) {
|
|
|
1548
1420
|
//
|
|
1549
1421
|
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
|
|
1550
1422
|
// @returns {Function}
|
|
1551
|
-
function highlightElement(
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
conversionApi.consumable.consume( value.item, evt.name );
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
viewElement.getCustomProperty( 'addHighlight' )( viewElement, descriptor, conversionApi.writer );
|
|
1583
|
-
|
|
1584
|
-
conversionApi.mapper.bindElementToMarker( viewElement, data.markerName );
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1423
|
+
function highlightElement(highlightDescriptor) {
|
|
1424
|
+
return (evt, data, conversionApi) => {
|
|
1425
|
+
if (!data.item) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (!(data.item instanceof ModelElement)) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
const descriptor = prepareDescriptor(highlightDescriptor, data, conversionApi);
|
|
1432
|
+
if (!descriptor) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
const viewElement = conversionApi.mapper.toViewElement(data.item);
|
|
1439
|
+
if (viewElement && viewElement.getCustomProperty('addHighlight')) {
|
|
1440
|
+
// Consume element itself.
|
|
1441
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
1442
|
+
// Consume all children nodes.
|
|
1443
|
+
for (const value of ModelRange._createIn(data.item)) {
|
|
1444
|
+
conversionApi.consumable.consume(value.item, evt.name);
|
|
1445
|
+
}
|
|
1446
|
+
const addHighlightCallback = viewElement.getCustomProperty('addHighlight');
|
|
1447
|
+
addHighlightCallback(viewElement, descriptor, conversionApi.writer);
|
|
1448
|
+
conversionApi.mapper.bindElementToMarker(viewElement, data.markerName);
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1587
1451
|
}
|
|
1588
|
-
|
|
1589
1452
|
// Function factory that creates a converter which converts the removing model marker to the view.
|
|
1590
1453
|
//
|
|
1591
1454
|
// Both text nodes and elements are handled by this converter but they are handled a bit differently.
|
|
@@ -1608,46 +1471,38 @@ function highlightElement( highlightDescriptor ) {
|
|
|
1608
1471
|
//
|
|
1609
1472
|
// @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} highlightDescriptor
|
|
1610
1473
|
// @returns {Function}
|
|
1611
|
-
function removeHighlight(
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
conversionApi.writer.clearClonedElementsGroup( data.markerName );
|
|
1646
|
-
|
|
1647
|
-
evt.stop();
|
|
1648
|
-
};
|
|
1474
|
+
function removeHighlight(highlightDescriptor) {
|
|
1475
|
+
return (evt, data, conversionApi) => {
|
|
1476
|
+
// This conversion makes sense only for non-collapsed range.
|
|
1477
|
+
if (data.markerRange.isCollapsed) {
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
const descriptor = prepareDescriptor(highlightDescriptor, data, conversionApi);
|
|
1481
|
+
if (!descriptor) {
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
// View element that will be used to unwrap `AttributeElement`s.
|
|
1485
|
+
const viewHighlightElement = createViewElementFromHighlightDescriptor(conversionApi.writer, descriptor);
|
|
1486
|
+
// Get all elements bound with given marker name.
|
|
1487
|
+
const elements = conversionApi.mapper.markerNameToElements(data.markerName);
|
|
1488
|
+
if (!elements) {
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
for (const element of elements) {
|
|
1492
|
+
conversionApi.mapper.unbindElementFromMarkerName(element, data.markerName);
|
|
1493
|
+
if (element.is('attributeElement')) {
|
|
1494
|
+
conversionApi.writer.unwrap(conversionApi.writer.createRangeOn(element), viewHighlightElement);
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
// if element.is( 'containerElement' ).
|
|
1498
|
+
const removeHighlightCallback = element.getCustomProperty('removeHighlight');
|
|
1499
|
+
removeHighlightCallback(element, descriptor.id, conversionApi.writer);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
conversionApi.writer.clearClonedElementsGroup(data.markerName);
|
|
1503
|
+
evt.stop();
|
|
1504
|
+
};
|
|
1649
1505
|
}
|
|
1650
|
-
|
|
1651
1506
|
// Model element to view element conversion helper.
|
|
1652
1507
|
//
|
|
1653
1508
|
// See {@link ~DowncastHelpers#elementToElement `.elementToElement()` downcast helper} for examples and config params description.
|
|
@@ -1659,31 +1514,21 @@ function removeHighlight( highlightDescriptor ) {
|
|
|
1659
1514
|
// @param {module:engine/view/elementdefinition~ElementDefinition|module:engine/conversion/downcasthelpers~ElementCreatorFunction}
|
|
1660
1515
|
// config.view
|
|
1661
1516
|
// @returns {Function} Conversion helper.
|
|
1662
|
-
function downcastElementToElement(
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
'insert:' + config.model.name,
|
|
1677
|
-
insertElement( config.view, createConsumer( config.model ) ),
|
|
1678
|
-
{ priority: config.converterPriority || 'normal' }
|
|
1679
|
-
);
|
|
1680
|
-
|
|
1681
|
-
if ( config.model.children || config.model.attributes.length ) {
|
|
1682
|
-
dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } );
|
|
1683
|
-
}
|
|
1684
|
-
};
|
|
1517
|
+
function downcastElementToElement(config) {
|
|
1518
|
+
const model = normalizeModelElementConfig(config.model);
|
|
1519
|
+
const view = normalizeToElementConfig(config.view, 'container');
|
|
1520
|
+
// Trigger reconversion on children list change if element is a subject to any reconversion.
|
|
1521
|
+
// This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element.
|
|
1522
|
+
if (model.attributes.length) {
|
|
1523
|
+
model.children = true;
|
|
1524
|
+
}
|
|
1525
|
+
return (dispatcher) => {
|
|
1526
|
+
dispatcher.on(`insert:${model.name}`, insertElement(view, createConsumer(model)), { priority: config.converterPriority || 'normal' });
|
|
1527
|
+
if (model.children || model.attributes.length) {
|
|
1528
|
+
dispatcher.on('reduceChanges', createChangeReducer(model), { priority: 'low' });
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1685
1531
|
}
|
|
1686
|
-
|
|
1687
1532
|
// Model element to view structure conversion helper.
|
|
1688
1533
|
//
|
|
1689
1534
|
// See {@link ~DowncastHelpers#elementToStructure `.elementToStructure()` downcast helper} for examples and config params description.
|
|
@@ -1694,69 +1539,58 @@ function downcastElementToElement( config ) {
|
|
|
1694
1539
|
// @param {Array.<String>} [config.model.attributes]
|
|
1695
1540
|
// @param {module:engine/conversion/downcasthelpers~StructureCreatorFunction} config.view
|
|
1696
1541
|
// @returns {Function} Conversion helper.
|
|
1697
|
-
function downcastElementToStructure(
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
dispatcher.on(
|
|
1751
|
-
'insert:' + config.model.name,
|
|
1752
|
-
insertStructure( config.view, createConsumer( config.model ) ),
|
|
1753
|
-
{ priority: config.converterPriority || 'normal' }
|
|
1754
|
-
);
|
|
1755
|
-
|
|
1756
|
-
dispatcher.on( 'reduceChanges', createChangeReducer( config.model ), { priority: 'low' } );
|
|
1757
|
-
};
|
|
1542
|
+
function downcastElementToStructure(config) {
|
|
1543
|
+
const model = normalizeModelElementConfig(config.model);
|
|
1544
|
+
const view = normalizeToElementConfig(config.view, 'container');
|
|
1545
|
+
// Trigger reconversion on children list change because it always needs to use slots to put children in proper places.
|
|
1546
|
+
// This is required to be able to trigger Differ#refreshItem() on a direct child of the reconverted element.
|
|
1547
|
+
model.children = true;
|
|
1548
|
+
return (dispatcher) => {
|
|
1549
|
+
if (dispatcher._conversionApi.schema.checkChild(model.name, '$text')) {
|
|
1550
|
+
/**
|
|
1551
|
+
* This error occurs when a {@link module:engine/model/element~Element model element} is downcasted
|
|
1552
|
+
* via {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure} helper but the element was
|
|
1553
|
+
* allowed to host `$text` by the {@link module:engine/model/schema~Schema model schema}.
|
|
1554
|
+
*
|
|
1555
|
+
* For instance, this may be the result of `myElement` allowing the content of
|
|
1556
|
+
* {@glink framework/guides/deep-dive/schema#generic-items `$block`} in its schema definition:
|
|
1557
|
+
*
|
|
1558
|
+
* // Element definition in schema.
|
|
1559
|
+
* schema.register( 'myElement', {
|
|
1560
|
+
* allowContentOf: '$block',
|
|
1561
|
+
*
|
|
1562
|
+
* // ...
|
|
1563
|
+
* } );
|
|
1564
|
+
*
|
|
1565
|
+
* // ...
|
|
1566
|
+
*
|
|
1567
|
+
* // Conversion of myElement with the use of elementToStructure().
|
|
1568
|
+
* editor.conversion.for( 'downcast' ).elementToStructure( {
|
|
1569
|
+
* model: 'myElement',
|
|
1570
|
+
* view: ( modelElement, { writer } ) => {
|
|
1571
|
+
* // ...
|
|
1572
|
+
* }
|
|
1573
|
+
* } );
|
|
1574
|
+
*
|
|
1575
|
+
* In such case, {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()`} helper
|
|
1576
|
+
* can be used instead to get around this problem:
|
|
1577
|
+
*
|
|
1578
|
+
* editor.conversion.for( 'downcast' ).elementToElement( {
|
|
1579
|
+
* model: 'myElement',
|
|
1580
|
+
* view: ( modelElement, { writer } ) => {
|
|
1581
|
+
* // ...
|
|
1582
|
+
* }
|
|
1583
|
+
* } );
|
|
1584
|
+
*
|
|
1585
|
+
* @error conversion-element-to-structure-disallowed-text
|
|
1586
|
+
* @param {String} elementName The name of the element the structure is to be created for.
|
|
1587
|
+
*/
|
|
1588
|
+
throw new CKEditorError('conversion-element-to-structure-disallowed-text', dispatcher, { elementName: model.name });
|
|
1589
|
+
}
|
|
1590
|
+
dispatcher.on(`insert:${model.name}`, insertStructure(view, createConsumer(model)), { priority: config.converterPriority || 'normal' });
|
|
1591
|
+
dispatcher.on('reduceChanges', createChangeReducer(model), { priority: 'low' });
|
|
1592
|
+
};
|
|
1758
1593
|
}
|
|
1759
|
-
|
|
1760
1594
|
// Model attribute to view element conversion helper.
|
|
1761
1595
|
//
|
|
1762
1596
|
// See {@link ~DowncastHelpers#attributeToElement `.attributeToElement()` downcast helper} for examples.
|
|
@@ -1771,31 +1605,29 @@ function downcastElementToStructure( config ) {
|
|
|
1771
1605
|
// definitions or functions.
|
|
1772
1606
|
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
1773
1607
|
// @returns {Function} Conversion helper.
|
|
1774
|
-
function downcastAttributeToElement(
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
};
|
|
1608
|
+
function downcastAttributeToElement(config) {
|
|
1609
|
+
config = cloneDeep(config);
|
|
1610
|
+
let model = config.model;
|
|
1611
|
+
if (typeof model == 'string') {
|
|
1612
|
+
model = { key: model };
|
|
1613
|
+
}
|
|
1614
|
+
let eventName = `attribute:${model.key}`;
|
|
1615
|
+
if (model.name) {
|
|
1616
|
+
eventName += ':' + model.name;
|
|
1617
|
+
}
|
|
1618
|
+
if (model.values) {
|
|
1619
|
+
for (const modelValue of model.values) {
|
|
1620
|
+
config.view[modelValue] = normalizeToElementConfig(config.view[modelValue], 'attribute');
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
else {
|
|
1624
|
+
config.view = normalizeToElementConfig(config.view, 'attribute');
|
|
1625
|
+
}
|
|
1626
|
+
const elementCreator = getFromAttributeCreator(config);
|
|
1627
|
+
return (dispatcher) => {
|
|
1628
|
+
dispatcher.on(eventName, wrap(elementCreator), { priority: config.converterPriority || 'normal' });
|
|
1629
|
+
};
|
|
1797
1630
|
}
|
|
1798
|
-
|
|
1799
1631
|
// Model attribute to view attribute conversion helper.
|
|
1800
1632
|
//
|
|
1801
1633
|
// See {@link ~DowncastHelpers#attributeToAttribute `.attributeToAttribute()` downcast helper} for examples.
|
|
@@ -1811,31 +1643,29 @@ function downcastAttributeToElement( config ) {
|
|
|
1811
1643
|
// `{ key, value }` objects or a functions.
|
|
1812
1644
|
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
1813
1645
|
// @returns {Function} Conversion helper.
|
|
1814
|
-
function downcastAttributeToAttribute(
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
};
|
|
1646
|
+
function downcastAttributeToAttribute(config) {
|
|
1647
|
+
config = cloneDeep(config);
|
|
1648
|
+
let model = config.model;
|
|
1649
|
+
if (typeof model == 'string') {
|
|
1650
|
+
model = { key: model };
|
|
1651
|
+
}
|
|
1652
|
+
let eventName = `attribute:${model.key}`;
|
|
1653
|
+
if (model.name) {
|
|
1654
|
+
eventName += ':' + model.name;
|
|
1655
|
+
}
|
|
1656
|
+
if (model.values) {
|
|
1657
|
+
for (const modelValue of model.values) {
|
|
1658
|
+
config.view[modelValue] = normalizeToAttributeConfig(config.view[modelValue]);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
config.view = normalizeToAttributeConfig(config.view);
|
|
1663
|
+
}
|
|
1664
|
+
const elementCreator = getFromAttributeCreator(config);
|
|
1665
|
+
return (dispatcher) => {
|
|
1666
|
+
dispatcher.on(eventName, changeAttribute(elementCreator), { priority: config.converterPriority || 'normal' });
|
|
1667
|
+
};
|
|
1837
1668
|
}
|
|
1838
|
-
|
|
1839
1669
|
// Model marker to view element conversion helper.
|
|
1840
1670
|
//
|
|
1841
1671
|
// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.
|
|
@@ -1846,17 +1676,13 @@ function downcastAttributeToAttribute( config ) {
|
|
|
1846
1676
|
// that takes the model marker data as a parameter and returns a view UI element.
|
|
1847
1677
|
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
1848
1678
|
// @returns {Function} Conversion helper.
|
|
1849
|
-
function downcastMarkerToElement(
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
dispatcher.on( 'addMarker:' + config.model, insertUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
|
|
1856
|
-
dispatcher.on( 'removeMarker:' + config.model, removeUIElement( config.view ), { priority: config.converterPriority || 'normal' } );
|
|
1857
|
-
};
|
|
1679
|
+
function downcastMarkerToElement(config) {
|
|
1680
|
+
const view = normalizeToElementConfig(config.view, 'ui');
|
|
1681
|
+
return (dispatcher) => {
|
|
1682
|
+
dispatcher.on(`addMarker:${config.model}`, insertUIElement(view), { priority: config.converterPriority || 'normal' });
|
|
1683
|
+
dispatcher.on(`removeMarker:${config.model}`, removeUIElement(), { priority: config.converterPriority || 'normal' });
|
|
1684
|
+
};
|
|
1858
1685
|
}
|
|
1859
|
-
|
|
1860
1686
|
// Model marker to view data conversion helper.
|
|
1861
1687
|
//
|
|
1862
1688
|
// See {@link ~DowncastHelpers#markerToData `markerToData()` downcast helper} to learn more.
|
|
@@ -1866,25 +1692,22 @@ function downcastMarkerToElement( config ) {
|
|
|
1866
1692
|
// @param {Function} [config.view]
|
|
1867
1693
|
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal']
|
|
1868
1694
|
// @returns {Function} Conversion helper.
|
|
1869
|
-
function downcastMarkerToData(
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
dispatcher.on( 'removeMarker:' + group, removeMarkerData( config.view ), { priority: config.converterPriority || 'normal' } );
|
|
1885
|
-
};
|
|
1695
|
+
function downcastMarkerToData(config) {
|
|
1696
|
+
config = cloneDeep(config);
|
|
1697
|
+
const group = config.model;
|
|
1698
|
+
let view = config.view;
|
|
1699
|
+
// Default conversion.
|
|
1700
|
+
if (!view) {
|
|
1701
|
+
view = markerName => ({
|
|
1702
|
+
group,
|
|
1703
|
+
name: markerName.substr(config.model.length + 1)
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
return (dispatcher) => {
|
|
1707
|
+
dispatcher.on(`addMarker:${group}`, insertMarkerData(view), { priority: config.converterPriority || 'normal' });
|
|
1708
|
+
dispatcher.on(`removeMarker:${group}`, removeMarkerData(view), { priority: config.converterPriority || 'normal' });
|
|
1709
|
+
};
|
|
1886
1710
|
}
|
|
1887
|
-
|
|
1888
1711
|
// Model marker to highlight conversion helper.
|
|
1889
1712
|
//
|
|
1890
1713
|
// See {@link ~DowncastHelpers#markerToElement `.markerToElement()` downcast helper} for examples.
|
|
@@ -1895,14 +1718,13 @@ function downcastMarkerToData( config ) {
|
|
|
1895
1718
|
// that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.
|
|
1896
1719
|
// @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
|
|
1897
1720
|
// @returns {Function} Conversion helper.
|
|
1898
|
-
function downcastMarkerToHighlight(
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1721
|
+
function downcastMarkerToHighlight(config) {
|
|
1722
|
+
return (dispatcher) => {
|
|
1723
|
+
dispatcher.on(`addMarker:${config.model}`, highlightText(config.view), { priority: config.converterPriority || 'normal' });
|
|
1724
|
+
dispatcher.on(`addMarker:${config.model}`, highlightElement(config.view), { priority: config.converterPriority || 'normal' });
|
|
1725
|
+
dispatcher.on(`removeMarker:${config.model}`, removeHighlight(config.view), { priority: config.converterPriority || 'normal' });
|
|
1726
|
+
};
|
|
1904
1727
|
}
|
|
1905
|
-
|
|
1906
1728
|
// Takes `config.model`, and converts it to an object with normalized structure.
|
|
1907
1729
|
//
|
|
1908
1730
|
// @param {String|Object} model Model configuration or element name.
|
|
@@ -1910,153 +1732,136 @@ function downcastMarkerToHighlight( config ) {
|
|
|
1910
1732
|
// @param {Array.<String>} [model.attributes]
|
|
1911
1733
|
// @param {Boolean} [model.children]
|
|
1912
1734
|
// @returns {Object}
|
|
1913
|
-
function normalizeModelElementConfig(
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
return model;
|
|
1735
|
+
function normalizeModelElementConfig(model) {
|
|
1736
|
+
if (typeof model == 'string') {
|
|
1737
|
+
model = { name: model };
|
|
1738
|
+
}
|
|
1739
|
+
// List of attributes that should trigger reconversion.
|
|
1740
|
+
if (!model.attributes) {
|
|
1741
|
+
model.attributes = [];
|
|
1742
|
+
}
|
|
1743
|
+
else if (!Array.isArray(model.attributes)) {
|
|
1744
|
+
model.attributes = [model.attributes];
|
|
1745
|
+
}
|
|
1746
|
+
// Whether a children insertion/deletion should trigger reconversion.
|
|
1747
|
+
model.children = !!model.children;
|
|
1748
|
+
return model;
|
|
1929
1749
|
}
|
|
1930
|
-
|
|
1931
1750
|
// Takes `config.view`, and if it is an {@link module:engine/view/elementdefinition~ElementDefinition}, converts it
|
|
1932
1751
|
// to a function (because lower level converters accept only element creator functions).
|
|
1933
1752
|
//
|
|
1934
1753
|
// @param {module:engine/view/elementdefinition~ElementDefinition|Function} view View configuration.
|
|
1935
1754
|
// @param {'container'|'attribute'|'ui'} viewElementType View element type to create.
|
|
1936
1755
|
// @returns {Function} Element creator function to use in lower level converters.
|
|
1937
|
-
function normalizeToElementConfig(
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
return ( modelData, conversionApi ) => createViewElementFromDefinition( view, conversionApi, viewElementType );
|
|
1756
|
+
function normalizeToElementConfig(view, viewElementType) {
|
|
1757
|
+
if (typeof view == 'function') {
|
|
1758
|
+
// If `view` is already a function, don't do anything.
|
|
1759
|
+
return view;
|
|
1760
|
+
}
|
|
1761
|
+
return ((modelData, conversionApi) => createViewElementFromDefinition(view, conversionApi, viewElementType));
|
|
1944
1762
|
}
|
|
1945
|
-
|
|
1946
1763
|
// Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.
|
|
1947
1764
|
//
|
|
1948
1765
|
// @param {module:engine/view/elementdefinition~ElementDefinition} viewElementDefinition
|
|
1949
1766
|
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
|
|
1950
1767
|
// @param {'container'|'attribute'|'ui'} viewElementType
|
|
1951
1768
|
// @returns {module:engine/view/element~Element}
|
|
1952
|
-
function createViewElementFromDefinition(
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
return element;
|
|
1769
|
+
function createViewElementFromDefinition(viewElementDefinition, conversionApi, viewElementType) {
|
|
1770
|
+
if (typeof viewElementDefinition == 'string') {
|
|
1771
|
+
// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
|
|
1772
|
+
viewElementDefinition = { name: viewElementDefinition };
|
|
1773
|
+
}
|
|
1774
|
+
let element;
|
|
1775
|
+
const viewWriter = conversionApi.writer;
|
|
1776
|
+
const attributes = Object.assign({}, viewElementDefinition.attributes);
|
|
1777
|
+
if (viewElementType == 'container') {
|
|
1778
|
+
element = viewWriter.createContainerElement(viewElementDefinition.name, attributes);
|
|
1779
|
+
}
|
|
1780
|
+
else if (viewElementType == 'attribute') {
|
|
1781
|
+
const options = {
|
|
1782
|
+
priority: viewElementDefinition.priority || ViewAttributeElement.DEFAULT_PRIORITY
|
|
1783
|
+
};
|
|
1784
|
+
element = viewWriter.createAttributeElement(viewElementDefinition.name, attributes, options);
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
// 'ui'.
|
|
1788
|
+
element = viewWriter.createUIElement(viewElementDefinition.name, attributes);
|
|
1789
|
+
}
|
|
1790
|
+
if (viewElementDefinition.styles) {
|
|
1791
|
+
const keys = Object.keys(viewElementDefinition.styles);
|
|
1792
|
+
for (const key of keys) {
|
|
1793
|
+
viewWriter.setStyle(key, viewElementDefinition.styles[key], element);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
if (viewElementDefinition.classes) {
|
|
1797
|
+
const classes = viewElementDefinition.classes;
|
|
1798
|
+
if (typeof classes == 'string') {
|
|
1799
|
+
viewWriter.addClass(classes, element);
|
|
1800
|
+
}
|
|
1801
|
+
else {
|
|
1802
|
+
for (const className of classes) {
|
|
1803
|
+
viewWriter.addClass(className, element);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return element;
|
|
1996
1808
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
return config.view;
|
|
2011
|
-
}
|
|
1809
|
+
function getFromAttributeCreator(config) {
|
|
1810
|
+
if (config.model.values) {
|
|
1811
|
+
return ((modelAttributeValue, conversionApi, data) => {
|
|
1812
|
+
const view = config.view[modelAttributeValue];
|
|
1813
|
+
if (view) {
|
|
1814
|
+
return view(modelAttributeValue, conversionApi, data);
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
else {
|
|
1820
|
+
return config.view;
|
|
1821
|
+
}
|
|
2012
1822
|
}
|
|
2013
|
-
|
|
2014
1823
|
// Takes the configuration, adds default parameters if they do not exist and normalizes other parameters to be used in downcast converters
|
|
2015
1824
|
// for generating a view attribute.
|
|
2016
1825
|
//
|
|
2017
1826
|
// @param {Object} view View configuration.
|
|
2018
|
-
function normalizeToAttributeConfig(
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
1827
|
+
function normalizeToAttributeConfig(view) {
|
|
1828
|
+
if (typeof view == 'string') {
|
|
1829
|
+
return modelAttributeValue => ({ key: view, value: modelAttributeValue });
|
|
1830
|
+
}
|
|
1831
|
+
else if (typeof view == 'object') {
|
|
1832
|
+
// { key, value, ... }
|
|
1833
|
+
if (view.value) {
|
|
1834
|
+
return () => view;
|
|
1835
|
+
}
|
|
1836
|
+
// { key, ... }
|
|
1837
|
+
else {
|
|
1838
|
+
return modelAttributeValue => ({ key: view.key, value: modelAttributeValue });
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
// function.
|
|
1843
|
+
return view;
|
|
1844
|
+
}
|
|
2034
1845
|
}
|
|
2035
|
-
|
|
2036
1846
|
// Helper function for `highlight`. Prepares the actual descriptor object using value passed to the converter.
|
|
2037
|
-
function prepareDescriptor(
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
descriptor.id = data.markerName;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
return descriptor;
|
|
1847
|
+
function prepareDescriptor(highlightDescriptor, data, conversionApi) {
|
|
1848
|
+
// If passed descriptor is a creator function, call it. If not, just use passed value.
|
|
1849
|
+
const descriptor = typeof highlightDescriptor == 'function' ?
|
|
1850
|
+
highlightDescriptor(data, conversionApi) :
|
|
1851
|
+
highlightDescriptor;
|
|
1852
|
+
if (!descriptor) {
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
// Apply default descriptor priority.
|
|
1856
|
+
if (!descriptor.priority) {
|
|
1857
|
+
descriptor.priority = 10;
|
|
1858
|
+
}
|
|
1859
|
+
// Default descriptor id is marker name.
|
|
1860
|
+
if (!descriptor.id) {
|
|
1861
|
+
descriptor.id = data.markerName;
|
|
1862
|
+
}
|
|
1863
|
+
return descriptor;
|
|
2058
1864
|
}
|
|
2059
|
-
|
|
2060
1865
|
// Creates a function that checks a single differ diff item whether it should trigger reconversion.
|
|
2061
1866
|
//
|
|
2062
1867
|
// @param {Object} model A normalized `config.model` converter configuration.
|
|
@@ -2064,27 +1869,25 @@ function prepareDescriptor( highlightDescriptor, data, conversionApi ) {
|
|
|
2064
1869
|
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
|
|
2065
1870
|
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
|
|
2066
1871
|
// @returns {Function}
|
|
2067
|
-
function createChangeReducerCallback(
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
};
|
|
1872
|
+
function createChangeReducerCallback(model) {
|
|
1873
|
+
return (node, change) => {
|
|
1874
|
+
if (!node.is('element', model.name)) {
|
|
1875
|
+
return false;
|
|
1876
|
+
}
|
|
1877
|
+
if (change.type == 'attribute') {
|
|
1878
|
+
if (model.attributes.includes(change.attributeKey)) {
|
|
1879
|
+
return true;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
/* istanbul ignore else: This is always true because otherwise it would not register a reducer callback. */
|
|
1884
|
+
if (model.children) {
|
|
1885
|
+
return true;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return false;
|
|
1889
|
+
};
|
|
2086
1890
|
}
|
|
2087
|
-
|
|
2088
1891
|
// Creates a `reduceChanges` event handler for reconversion.
|
|
2089
1892
|
//
|
|
2090
1893
|
// @param {Object} model A normalized `config.model` converter configuration.
|
|
@@ -2092,51 +1895,41 @@ function createChangeReducerCallback( model ) {
|
|
|
2092
1895
|
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
|
|
2093
1896
|
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
|
|
2094
1897
|
// @returns {Function}
|
|
2095
|
-
function createChangeReducer(
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
name: node.name,
|
|
2130
|
-
position,
|
|
2131
|
-
length: 1
|
|
2132
|
-
} );
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
data.changes = reducedChanges;
|
|
2137
|
-
};
|
|
1898
|
+
function createChangeReducer(model) {
|
|
1899
|
+
const shouldReplace = createChangeReducerCallback(model);
|
|
1900
|
+
return (evt, data) => {
|
|
1901
|
+
const reducedChanges = [];
|
|
1902
|
+
if (!data.reconvertedElements) {
|
|
1903
|
+
data.reconvertedElements = new Set();
|
|
1904
|
+
}
|
|
1905
|
+
for (const change of data.changes) {
|
|
1906
|
+
// For attribute use node affected by the change.
|
|
1907
|
+
// For insert or remove use parent element because we need to check if it's added/removed child.
|
|
1908
|
+
const node = change.type == 'attribute' ? change.range.start.nodeAfter : change.position.parent;
|
|
1909
|
+
if (!node || !shouldReplace(node, change)) {
|
|
1910
|
+
reducedChanges.push(change);
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
// If it's already marked for reconversion, so skip this change, otherwise add the diff items.
|
|
1914
|
+
if (!data.reconvertedElements.has(node)) {
|
|
1915
|
+
data.reconvertedElements.add(node);
|
|
1916
|
+
const position = ModelPosition._createBefore(node);
|
|
1917
|
+
reducedChanges.push({
|
|
1918
|
+
type: 'remove',
|
|
1919
|
+
name: node.name,
|
|
1920
|
+
position,
|
|
1921
|
+
length: 1
|
|
1922
|
+
}, {
|
|
1923
|
+
type: 'reinsert',
|
|
1924
|
+
name: node.name,
|
|
1925
|
+
position,
|
|
1926
|
+
length: 1
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
data.changes = reducedChanges;
|
|
1931
|
+
};
|
|
2138
1932
|
}
|
|
2139
|
-
|
|
2140
1933
|
// Creates a function that checks if an element and its watched attributes can be consumed and consumes them.
|
|
2141
1934
|
//
|
|
2142
1935
|
// @param {Object} model A normalized `config.model` converter configuration.
|
|
@@ -2144,93 +1937,82 @@ function createChangeReducer( model ) {
|
|
|
2144
1937
|
// @param {Array.<String>} model.attributes The list of attribute names that should trigger reconversion.
|
|
2145
1938
|
// @param {Boolean} [model.children] Whether the child list change should trigger reconversion.
|
|
2146
1939
|
// @returns {module:engine/conversion/downcasthelpers~ConsumerFunction}
|
|
2147
|
-
function createConsumer(
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
return true;
|
|
2167
|
-
};
|
|
1940
|
+
function createConsumer(model) {
|
|
1941
|
+
return (node, consumable, options = {}) => {
|
|
1942
|
+
const events = ['insert'];
|
|
1943
|
+
// Collect all set attributes that are triggering conversion.
|
|
1944
|
+
for (const attributeName of model.attributes) {
|
|
1945
|
+
if (node.hasAttribute(attributeName)) {
|
|
1946
|
+
events.push(`attribute:${attributeName}`);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
if (!events.every(event => consumable.test(node, event))) {
|
|
1950
|
+
return false;
|
|
1951
|
+
}
|
|
1952
|
+
if (!options.preflight) {
|
|
1953
|
+
events.forEach(event => consumable.consume(node, event));
|
|
1954
|
+
}
|
|
1955
|
+
return true;
|
|
1956
|
+
};
|
|
2168
1957
|
}
|
|
2169
|
-
|
|
2170
1958
|
// Creates a function that create view slots.
|
|
2171
1959
|
//
|
|
2172
1960
|
// @param {module:engine/model/element~Element} element
|
|
2173
1961
|
// @param {Map.<module:engine/view/element~Element,Array.<module:engine/model/node~Node>>} slotsMap
|
|
2174
1962
|
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
2175
1963
|
// @returns {Function} Exposed by writer as createSlot().
|
|
2176
|
-
function createSlotFactory(
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
return slot;
|
|
2198
|
-
};
|
|
1964
|
+
function createSlotFactory(element, slotsMap, conversionApi) {
|
|
1965
|
+
return (writer, modeOrFilter = 'children') => {
|
|
1966
|
+
const slot = writer.createContainerElement('$slot');
|
|
1967
|
+
let children = null;
|
|
1968
|
+
if (modeOrFilter === 'children') {
|
|
1969
|
+
children = Array.from(element.getChildren());
|
|
1970
|
+
}
|
|
1971
|
+
else if (typeof modeOrFilter == 'function') {
|
|
1972
|
+
children = Array.from(element.getChildren()).filter(element => modeOrFilter(element));
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
/**
|
|
1976
|
+
* Unknown slot mode was provided to `writer.createSlot()` in downcast converter.
|
|
1977
|
+
*
|
|
1978
|
+
* @error conversion-slot-mode-unknown
|
|
1979
|
+
*/
|
|
1980
|
+
throw new CKEditorError('conversion-slot-mode-unknown', conversionApi.dispatcher, { modeOrFilter });
|
|
1981
|
+
}
|
|
1982
|
+
slotsMap.set(slot, children);
|
|
1983
|
+
return slot;
|
|
1984
|
+
};
|
|
2199
1985
|
}
|
|
2200
|
-
|
|
2201
1986
|
// Checks if all children are covered by slots and there is no child that landed in multiple slots.
|
|
2202
1987
|
//
|
|
2203
1988
|
// @param {module:engine/model/element~Element}
|
|
2204
1989
|
// @param {Map.<module:engine/view/element~Element,Array.<module:engine/model/node~Node>>} slotsMap
|
|
2205
1990
|
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
2206
|
-
function validateSlotsChildren(
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
throw new CKEditorError( 'conversion-slot-filter-incomplete', conversionApi.dispatcher, { element } );
|
|
2231
|
-
}
|
|
1991
|
+
function validateSlotsChildren(element, slotsMap, conversionApi) {
|
|
1992
|
+
const childrenInSlots = Array.from(slotsMap.values()).flat();
|
|
1993
|
+
const uniqueChildrenInSlots = new Set(childrenInSlots);
|
|
1994
|
+
if (uniqueChildrenInSlots.size != childrenInSlots.length) {
|
|
1995
|
+
/**
|
|
1996
|
+
* Filters provided to `writer.createSlot()` overlap (at least two filters accept the same child element).
|
|
1997
|
+
*
|
|
1998
|
+
* @error conversion-slot-filter-overlap
|
|
1999
|
+
* @param {module:engine/model/element~Element} element The element of which children would not be properly
|
|
2000
|
+
* allocated to multiple slots.
|
|
2001
|
+
*/
|
|
2002
|
+
throw new CKEditorError('conversion-slot-filter-overlap', conversionApi.dispatcher, { element });
|
|
2003
|
+
}
|
|
2004
|
+
if (uniqueChildrenInSlots.size != element.childCount) {
|
|
2005
|
+
/**
|
|
2006
|
+
* Filters provided to `writer.createSlot()` are incomplete and exclude at least one children element (one of
|
|
2007
|
+
* the children elements would not be assigned to any of the slots).
|
|
2008
|
+
*
|
|
2009
|
+
* @error conversion-slot-filter-incomplete
|
|
2010
|
+
* @param {module:engine/model/element~Element} element The element of which children would not be properly
|
|
2011
|
+
* allocated to multiple slots.
|
|
2012
|
+
*/
|
|
2013
|
+
throw new CKEditorError('conversion-slot-filter-incomplete', conversionApi.dispatcher, { element });
|
|
2014
|
+
}
|
|
2232
2015
|
}
|
|
2233
|
-
|
|
2234
2016
|
// Fill slots with appropriate view elements.
|
|
2235
2017
|
//
|
|
2236
2018
|
// @param {module:engine/view/element~Element} viewElement
|
|
@@ -2238,40 +2020,28 @@ function validateSlotsChildren( element, slotsMap, conversionApi ) {
|
|
|
2238
2020
|
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
2239
2021
|
// @param {Object} options
|
|
2240
2022
|
// @param {Boolean} [options.reconversion]
|
|
2241
|
-
function fillSlots(
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
const element = data.modelPosition.nodeAfter;
|
|
2263
|
-
|
|
2264
|
-
// Find the proper offset within the slot.
|
|
2265
|
-
const index = currentSlotNodes.indexOf( element );
|
|
2266
|
-
|
|
2267
|
-
if ( index < 0 ) {
|
|
2268
|
-
return;
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
data.viewPosition = data.mapper.findPositionIn( currentSlot, index );
|
|
2272
|
-
}
|
|
2023
|
+
function fillSlots(viewElement, slotsMap, conversionApi, options) {
|
|
2024
|
+
// Set temporary position mapping to redirect child view elements into a proper slots.
|
|
2025
|
+
conversionApi.mapper.on('modelToViewPosition', toViewPositionMapping, { priority: 'highest' });
|
|
2026
|
+
let currentSlot = null;
|
|
2027
|
+
let currentSlotNodes = null;
|
|
2028
|
+
// Fill slots with nested view nodes.
|
|
2029
|
+
for ([currentSlot, currentSlotNodes] of slotsMap) {
|
|
2030
|
+
reinsertOrConvertNodes(viewElement, currentSlotNodes, conversionApi, options);
|
|
2031
|
+
conversionApi.writer.move(conversionApi.writer.createRangeIn(currentSlot), conversionApi.writer.createPositionBefore(currentSlot));
|
|
2032
|
+
conversionApi.writer.remove(currentSlot);
|
|
2033
|
+
}
|
|
2034
|
+
conversionApi.mapper.off('modelToViewPosition', toViewPositionMapping);
|
|
2035
|
+
function toViewPositionMapping(evt, data) {
|
|
2036
|
+
const element = data.modelPosition.nodeAfter;
|
|
2037
|
+
// Find the proper offset within the slot.
|
|
2038
|
+
const index = currentSlotNodes.indexOf(element);
|
|
2039
|
+
if (index < 0) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
data.viewPosition = data.mapper.findPositionIn(currentSlot, index);
|
|
2043
|
+
}
|
|
2273
2044
|
}
|
|
2274
|
-
|
|
2275
2045
|
// Inserts view representation of `nodes` into the `viewElement` either by bringing back just removed view nodes
|
|
2276
2046
|
// or by triggering conversion for them.
|
|
2277
2047
|
//
|
|
@@ -2280,17 +2050,16 @@ function fillSlots( viewElement, slotsMap, conversionApi, options ) {
|
|
|
2280
2050
|
// @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
|
|
2281
2051
|
// @param {Object} options
|
|
2282
2052
|
// @param {Boolean} [options.reconversion]
|
|
2283
|
-
function reinsertOrConvertNodes(
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2053
|
+
function reinsertOrConvertNodes(viewElement, modelNodes, conversionApi, options) {
|
|
2054
|
+
// Fill with nested view nodes.
|
|
2055
|
+
for (const modelChildNode of modelNodes) {
|
|
2056
|
+
// Try reinserting the view node for the specified model node...
|
|
2057
|
+
if (!reinsertNode(viewElement.root, modelChildNode, conversionApi, options)) {
|
|
2058
|
+
// ...or else convert the model element to the view.
|
|
2059
|
+
conversionApi.convertItem(modelChildNode);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2292
2062
|
}
|
|
2293
|
-
|
|
2294
2063
|
// Checks if the view for the given model element could be reused and reinserts it to the view.
|
|
2295
2064
|
//
|
|
2296
2065
|
// @param {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment} viewRoot
|
|
@@ -2299,187 +2068,36 @@ function reinsertOrConvertNodes( viewElement, modelNodes, conversionApi, options
|
|
|
2299
2068
|
// @param {Object} options
|
|
2300
2069
|
// @param {Boolean} [options.reconversion]
|
|
2301
2070
|
// @returns {Boolean} `false` if view element can't be reused.
|
|
2302
|
-
function reinsertNode(
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
// Otherwise reinsert the view node.
|
|
2323
|
-
writer.move(
|
|
2324
|
-
writer.createRangeOn( viewChildNode ),
|
|
2325
|
-
mapper.toViewPosition( ModelPosition._createBefore( modelElement ) )
|
|
2326
|
-
);
|
|
2327
|
-
|
|
2328
|
-
return true;
|
|
2071
|
+
function reinsertNode(viewRoot, modelNode, conversionApi, options) {
|
|
2072
|
+
const { writer, mapper } = conversionApi;
|
|
2073
|
+
// Don't reinsert if this is not a reconversion...
|
|
2074
|
+
if (!options.reconversion) {
|
|
2075
|
+
return false;
|
|
2076
|
+
}
|
|
2077
|
+
const viewChildNode = mapper.toViewElement(modelNode);
|
|
2078
|
+
// ...or there is no view to reinsert or it was already inserted to the view structure...
|
|
2079
|
+
if (!viewChildNode || viewChildNode.root == viewRoot) {
|
|
2080
|
+
return false;
|
|
2081
|
+
}
|
|
2082
|
+
// ...or it was strictly marked as not to be reused.
|
|
2083
|
+
if (!conversionApi.canReuseView(viewChildNode)) {
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
// Otherwise reinsert the view node.
|
|
2087
|
+
writer.move(writer.createRangeOn(viewChildNode), mapper.toViewPosition(ModelPosition._createBefore(modelNode)));
|
|
2088
|
+
return true;
|
|
2329
2089
|
}
|
|
2330
|
-
|
|
2331
2090
|
// The default consumer for insert events.
|
|
2332
2091
|
// @param {module:engine/model/item~Item} item Model item.
|
|
2333
2092
|
// @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The model consumable.
|
|
2334
2093
|
// @param {Object} [options]
|
|
2335
2094
|
// @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed.
|
|
2336
2095
|
// @returns {Boolean}
|
|
2337
|
-
function defaultConsumer(
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2096
|
+
function defaultConsumer(item, consumable, { preflight } = {}) {
|
|
2097
|
+
if (preflight) {
|
|
2098
|
+
return consumable.test(item, 'insert');
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
return consumable.consume(item, 'insert');
|
|
2102
|
+
}
|
|
2343
2103
|
}
|
|
2344
|
-
|
|
2345
|
-
/**
|
|
2346
|
-
* An object describing how the marker highlight should be represented in the view.
|
|
2347
|
-
*
|
|
2348
|
-
* Each text node contained in a highlighted range will be wrapped in a `<span>`
|
|
2349
|
-
* {@link module:engine/view/attributeelement~AttributeElement view attribute element} with CSS class(es), attributes and a priority
|
|
2350
|
-
* described by this object.
|
|
2351
|
-
*
|
|
2352
|
-
* Additionally, each {@link module:engine/view/containerelement~ContainerElement container element} can handle displaying the highlight
|
|
2353
|
-
* separately by providing the `addHighlight` and `removeHighlight` custom properties. In this case:
|
|
2354
|
-
*
|
|
2355
|
-
* * The `HighlightDescriptor` object is passed to the `addHighlight` function upon conversion and should be used to apply the highlight to
|
|
2356
|
-
* the element.
|
|
2357
|
-
* * The descriptor `id` is passed to the `removeHighlight` function upon conversion and should be used to remove the highlight with the
|
|
2358
|
-
* given ID from the element.
|
|
2359
|
-
*
|
|
2360
|
-
* @typedef {Object} module:engine/conversion/downcasthelpers~HighlightDescriptor
|
|
2361
|
-
*
|
|
2362
|
-
* @property {String|Array.<String>} classes A CSS class or an array of classes to set. If the descriptor is used to
|
|
2363
|
-
* create an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these classes will be set
|
|
2364
|
-
* on that attribute element. If the descriptor is applied to an element, usually these classes will be set on that element, however,
|
|
2365
|
-
* this depends on how the element converts the descriptor.
|
|
2366
|
-
*
|
|
2367
|
-
* @property {String} [id] Descriptor identifier. If not provided, it defaults to the converted marker's name.
|
|
2368
|
-
*
|
|
2369
|
-
* @property {Number} [priority] Descriptor priority. If not provided, it defaults to `10`. If the descriptor is used to create
|
|
2370
|
-
* an {@link module:engine/view/attributeelement~AttributeElement attribute element}, it will be that element's
|
|
2371
|
-
* {@link module:engine/view/attributeelement~AttributeElement#priority priority}. If the descriptor is applied to an element,
|
|
2372
|
-
* the priority will be used to determine which descriptor is more important.
|
|
2373
|
-
*
|
|
2374
|
-
* @property {Object} [attributes] Attributes to set. If the descriptor is used to create
|
|
2375
|
-
* an {@link module:engine/view/attributeelement~AttributeElement attribute element} over text nodes, these attributes will be set on that
|
|
2376
|
-
* attribute element. If the descriptor is applied to an element, usually these attributes will be set on that element, however,
|
|
2377
|
-
* this depends on how the element converts the descriptor.
|
|
2378
|
-
*/
|
|
2379
|
-
|
|
2380
|
-
/**
|
|
2381
|
-
* A filtering function used to choose model child nodes to be downcasted into the specific view
|
|
2382
|
-
* {@link module:engine/view/downcastwriter~DowncastWriter#createSlot "slot"} while executing the
|
|
2383
|
-
* {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure `elementToStructure()`} converter.
|
|
2384
|
-
*
|
|
2385
|
-
* @callback module:engine/conversion/downcasthelpers~SlotFilter
|
|
2386
|
-
*
|
|
2387
|
-
* @param {module:engine/model/node~Node} node A model node.
|
|
2388
|
-
* @returns {Boolean} Whether the provided model node should be downcasted into this slot.
|
|
2389
|
-
*
|
|
2390
|
-
* @see module:engine/view/downcastwriter~DowncastWriter#createSlot
|
|
2391
|
-
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
|
|
2392
|
-
* @see module:engine/conversion/downcasthelpers~insertStructure
|
|
2393
|
-
*/
|
|
2394
|
-
|
|
2395
|
-
/**
|
|
2396
|
-
* A view element creator function that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi
|
|
2397
|
-
* downcast conversion API} as parameters and returns a view container element.
|
|
2398
|
-
*
|
|
2399
|
-
* @callback module:engine/conversion/downcasthelpers~ElementCreatorFunction
|
|
2400
|
-
* @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
|
|
2401
|
-
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
|
|
2402
|
-
* @param {Object} data Additional information about the change (same as for
|
|
2403
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`} event).
|
|
2404
|
-
* @param {module:engine/model/item~Item} data.item Inserted item.
|
|
2405
|
-
* @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
|
|
2406
|
-
* @returns {module:engine/view/element~Element} The view element.
|
|
2407
|
-
*
|
|
2408
|
-
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement
|
|
2409
|
-
* @see module:engine/conversion/downcasthelpers~insertElement
|
|
2410
|
-
*/
|
|
2411
|
-
|
|
2412
|
-
/**
|
|
2413
|
-
* A function that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast
|
|
2414
|
-
* conversion API} as parameters and returns a view container element with slots for model child nodes to be converted into.
|
|
2415
|
-
*
|
|
2416
|
-
* @callback module:engine/conversion/downcasthelpers~StructureCreatorFunction
|
|
2417
|
-
* @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
|
|
2418
|
-
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
|
|
2419
|
-
* @param {Object} data Additional information about the change (same as for
|
|
2420
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:insert `insert`} event).
|
|
2421
|
-
* @param {module:engine/model/item~Item} data.item Inserted item.
|
|
2422
|
-
* @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
|
|
2423
|
-
* @returns {module:engine/view/element~Element} The view structure with slots for model child nodes.
|
|
2424
|
-
*
|
|
2425
|
-
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#elementToStructure
|
|
2426
|
-
* @see module:engine/conversion/downcasthelpers~insertStructure
|
|
2427
|
-
*/
|
|
2428
|
-
|
|
2429
|
-
/**
|
|
2430
|
-
* A view element creator function that takes the model attribute value and
|
|
2431
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
|
|
2432
|
-
* attribute element.
|
|
2433
|
-
*
|
|
2434
|
-
* @callback module:engine/conversion/downcasthelpers~AttributeElementCreatorFunction
|
|
2435
|
-
* @param {*} attributeValue The model attribute value to be converted to the view attribute element.
|
|
2436
|
-
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
|
|
2437
|
-
* @param {Object} data Additional information about the change (same as for
|
|
2438
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} event).
|
|
2439
|
-
* @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} data.item Changed item
|
|
2440
|
-
* or converted selection.
|
|
2441
|
-
* @param {module:engine/model/range~Range} data.range Range spanning over changed item or selection range.
|
|
2442
|
-
* @param {String} data.attributeKey Attribute key.
|
|
2443
|
-
* @param {*} data.attributeOldValue Attribute value before the change. This is `null` when selection attribute is converted.
|
|
2444
|
-
* @param {*} data.attributeNewValue New attribute value.
|
|
2445
|
-
* @returns {module:engine/view/attributeelement~AttributeElement} The view attribute element.
|
|
2446
|
-
*
|
|
2447
|
-
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement
|
|
2448
|
-
* @see module:engine/conversion/downcasthelpers~wrap
|
|
2449
|
-
*/
|
|
2450
|
-
|
|
2451
|
-
/**
|
|
2452
|
-
* A function that takes the model attribute value and
|
|
2453
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
|
|
2454
|
-
* as parameters.
|
|
2455
|
-
*
|
|
2456
|
-
* @callback module:engine/conversion/downcasthelpers~AttributeCreatorFunction
|
|
2457
|
-
* @param {*} attributeValue The model attribute value to be converted to the view attribute element.
|
|
2458
|
-
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi The conversion interface.
|
|
2459
|
-
* @param {Object} data Additional information about the change (same as for
|
|
2460
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute `attribute`} event).
|
|
2461
|
-
* @param {module:engine/model/item~Item|module:engine/model/documentselection~DocumentSelection} data.item Changed item
|
|
2462
|
-
* or converted selection.
|
|
2463
|
-
* @param {module:engine/model/range~Range} data.range Range spanning over changed item or selection range.
|
|
2464
|
-
* @param {String} data.attributeKey Attribute key.
|
|
2465
|
-
* @param {*} data.attributeOldValue Attribute value before the change. This is `null` when selection attribute is converted.
|
|
2466
|
-
* @param {*} data.attributeNewValue New attribute value.
|
|
2467
|
-
* @returns {Object|null} A `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
|
|
2468
|
-
* array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
|
|
2469
|
-
*
|
|
2470
|
-
* @see module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToAttribute
|
|
2471
|
-
*/
|
|
2472
|
-
|
|
2473
|
-
/**
|
|
2474
|
-
* A function that is expected to consume all the consumables that were used by the element creator.
|
|
2475
|
-
*
|
|
2476
|
-
* @callback module:engine/conversion/downcasthelpers~ConsumerFunction
|
|
2477
|
-
* @param {module:engine/model/element~Element} element The model element to be converted to the view structure.
|
|
2478
|
-
* @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable The `ModelConsumable` same as in
|
|
2479
|
-
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#consumable `DowncastConversionApi.consumable`}.
|
|
2480
|
-
* @param {Object} [options]
|
|
2481
|
-
* @param {Boolean} [options.preflight=false] Whether should consume or just check if can be consumed.
|
|
2482
|
-
* @returns {Boolean} `true` if all consumable values were available and were consumed, `false` otherwise.
|
|
2483
|
-
*
|
|
2484
|
-
* @see module:engine/conversion/downcasthelpers~insertStructure
|
|
2485
|
-
*/
|