@ckeditor/ckeditor5-engine 47.6.1 → 48.0.0-alpha.0

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