@@ -866,27 +866,6 @@ export default class DowncastWriter {
866
866
867
867
// Break attributes at range start and end.
868
868
const { start : breakStart , end : breakEnd } = this . _breakAttributesRange ( range , true ) ;
869
-
870
- // Range around one element - check if AttributeElement can be unwrapped partially when it's not similar.
871
- // For example:
872
- // <b class="foo bar" title="baz"></b> unwrap with: <b class="foo"></p> result: <b class"bar" title="baz"></b>
873
- if ( breakEnd . isEqual ( breakStart . getShiftedBy ( 1 ) ) ) {
874
- const node = breakStart . nodeAfter ;
875
-
876
- // Unwrap single attribute element.
877
- if ( ! attribute . isSimilar ( node ) && node instanceof AttributeElement && this . _unwrapAttributeElement ( attribute , node ) ) {
878
- const start = this . mergeAttributes ( breakStart ) ;
879
-
880
- if ( ! start . isEqual ( breakStart ) ) {
881
- breakEnd . offset -- ;
882
- }
883
-
884
- const end = this . mergeAttributes ( breakEnd ) ;
885
-
886
- return new Range ( start , end ) ;
887
- }
888
- }
889
-
890
869
const parentContainer = breakStart . parent ;
891
870
892
871
// Unwrap children located between break points.
@@ -1085,16 +1064,16 @@ export default class DowncastWriter {
1085
1064
}
1086
1065
1087
1066
/**
1088
- * Wraps children with provided `attribute `. Only children contained in `parent` element between
1067
+ * Wraps children with provided `wrapElement `. Only children contained in `parent` element between
1089
1068
* `startOffset` and `endOffset` will be wrapped.
1090
1069
*
1091
1070
* @private
1092
1071
* @param {module:engine/view/element~Element } parent
1093
1072
* @param {Number } startOffset
1094
1073
* @param {Number } endOffset
1095
- * @param {module:engine/view/element~Element } attribute
1074
+ * @param {module:engine/view/element~Element } wrapElement
1096
1075
*/
1097
- _wrapChildren ( parent , startOffset , endOffset , attribute ) {
1076
+ _wrapChildren ( parent , startOffset , endOffset , wrapElement ) {
1098
1077
let i = startOffset ;
1099
1078
const wrapPositions = [ ] ;
1100
1079
@@ -1105,10 +1084,27 @@ export default class DowncastWriter {
1105
1084
const isEmpty = child . is ( 'emptyElement' ) ;
1106
1085
const isUI = child . is ( 'uiElement' ) ;
1107
1086
1108
- // Wrap text, empty elements, ui elements or attributes with higher or equal priority.
1109
- if ( isText || isEmpty || isUI || ( isAttribute && shouldABeOutsideB ( attribute , child ) ) ) {
1087
+ //
1088
+ // (In all examples, assume that `wrapElement` is `<span class="foo">` element.)
1089
+ //
1090
+ // Check if `wrapElement` can be joined with the wrapped element. One of requirements is having same name.
1091
+ // If possible, join elements.
1092
+ //
1093
+ // <p><span class="bar">abc</span></p> --> <p><span class="foo bar">abc</span></p>
1094
+ //
1095
+ if ( isAttribute && this . _wrapAttributeElement ( wrapElement , child ) ) {
1096
+ wrapPositions . push ( new Position ( parent , i ) ) ;
1097
+ }
1098
+ //
1099
+ // Wrap the child if it is not an attribute element or if it is an attribute element that should be inside
1100
+ // `wrapElement` (due to priority).
1101
+ //
1102
+ // <p>abc</p> --> <p><span class="foo">abc</span></p>
1103
+ // <p><strong>abc</strong></p> --> <p><span class="foo"><strong>abc</strong></span></p>
1104
+ //
1105
+ else if ( isText || isEmpty || isUI || ( isAttribute && shouldABeOutsideB ( wrapElement , child ) ) ) {
1110
1106
// Clone attribute.
1111
- const newAttribute = attribute . _clone ( ) ;
1107
+ const newAttribute = wrapElement . _clone ( ) ;
1112
1108
1113
1109
// Wrap current node with new attribute.
1114
1110
child . _remove ( ) ;
@@ -1119,9 +1115,13 @@ export default class DowncastWriter {
1119
1115
1120
1116
wrapPositions . push ( new Position ( parent , i ) ) ;
1121
1117
}
1122
- // If other nested attribute is found start wrapping there.
1118
+ //
1119
+ // If other nested attribute is found and it wasn't wrapped (see above), continue wrapping inside it.
1120
+ //
1121
+ // <p><a href="foo.html">abc</a></p> --> <p><a href="foo.html"><span class="foo">abc</span></a></p>
1122
+ //
1123
1123
else if ( isAttribute ) {
1124
- this . _wrapChildren ( child , 0 , child . childCount , attribute ) ;
1124
+ this . _wrapChildren ( child , 0 , child . childCount , wrapElement ) ;
1125
1125
}
1126
1126
1127
1127
i ++ ;
@@ -1151,25 +1151,40 @@ export default class DowncastWriter {
1151
1151
}
1152
1152
1153
1153
/**
1154
- * Unwraps children from provided `attribute `. Only children contained in `parent` element between
1154
+ * Unwraps children from provided `unwrapElement `. Only children contained in `parent` element between
1155
1155
* `startOffset` and `endOffset` will be unwrapped.
1156
1156
*
1157
1157
* @private
1158
1158
* @param {module:engine/view/element~Element } parent
1159
1159
* @param {Number } startOffset
1160
1160
* @param {Number } endOffset
1161
- * @param {module:engine/view/element~Element } attribute
1161
+ * @param {module:engine/view/element~Element } unwrapElement
1162
1162
*/
1163
- _unwrapChildren ( parent , startOffset , endOffset , attribute ) {
1163
+ _unwrapChildren ( parent , startOffset , endOffset , unwrapElement ) {
1164
1164
let i = startOffset ;
1165
1165
const unwrapPositions = [ ] ;
1166
1166
1167
1167
// Iterate over each element between provided offsets inside parent.
1168
+ // We don't use tree walker or range iterator because we will be removing and merging potentially multiple nodes,
1169
+ // so it could get messy. It is safer to it manually in this case.
1168
1170
while ( i < endOffset ) {
1169
1171
const child = parent . getChild ( i ) ;
1170
1172
1171
- // If attributes are the similar, then unwrap.
1172
- if ( child . isSimilar ( attribute ) ) {
1173
+ // Skip all text nodes. There should be no container element's here either.
1174
+ if ( ! child . is ( 'attributeElement' ) ) {
1175
+ i ++ ;
1176
+
1177
+ continue ;
1178
+ }
1179
+
1180
+ //
1181
+ // (In all examples, assume that `unwrapElement` is `<span class="foo">` element.)
1182
+ //
1183
+ // If the child is similar to the given attribute element, unwrap it - it will be completely removed.
1184
+ //
1185
+ // <p><span class="foo">abc</span>xyz</p> --> <p>abcxyz</p>
1186
+ //
1187
+ if ( child . isSimilar ( unwrapElement ) ) {
1173
1188
const unwrapped = child . getChildren ( ) ;
1174
1189
const count = child . childCount ;
1175
1190
@@ -1185,18 +1200,39 @@ export default class DowncastWriter {
1185
1200
new Position ( parent , i + count )
1186
1201
) ;
1187
1202
1188
- // Skip elements that were unwrapped. Assuming that there won't be another element to unwrap in child
1189
- // elements.
1203
+ // Skip elements that were unwrapped. Assuming there won't be another element to unwrap in child elements.
1190
1204
i += count ;
1191
1205
endOffset += count - 1 ;
1192
- } else {
1193
- // If other nested attribute is found start unwrapping there.
1194
- if ( child . is ( 'attributeElement' ) ) {
1195
- this . _unwrapChildren ( child , 0 , child . childCount , attribute ) ;
1196
- }
1206
+
1207
+ continue ;
1208
+ }
1209
+
1210
+ //
1211
+ // If the child is not similar but is an attribute element, try partial unwrapping - remove the same attributes/styles/classes.
1212
+ // Partial unwrapping will happen only if the elements have the same name.
1213
+ //
1214
+ // <p><span class="foo bar">abc</span>xyz</p> --> <p><span class="bar">abc</span>xyz</p>
1215
+ // <p><i class="foo">abc</i>xyz</p> --> <p><i class="foo">abc</i>xyz</p>
1216
+ //
1217
+ if ( this . _unwrapAttributeElement ( unwrapElement , child ) ) {
1218
+ unwrapPositions . push (
1219
+ new Position ( parent , i ) ,
1220
+ new Position ( parent , i + 1 )
1221
+ ) ;
1197
1222
1198
1223
i ++ ;
1224
+
1225
+ continue ;
1199
1226
}
1227
+
1228
+ //
1229
+ // If other nested attribute is found, look through it's children for elements to unwrap.
1230
+ //
1231
+ // <p><i><span class="foo">abc</span></i><p> --> <p><i>abc</i><p>
1232
+ //
1233
+ this . _unwrapChildren ( child , 0 , child . childCount , unwrapElement ) ;
1234
+
1235
+ i ++ ;
1200
1236
}
1201
1237
1202
1238
// Merge at each unwrap.
@@ -1235,43 +1271,12 @@ export default class DowncastWriter {
1235
1271
* @returns {module:engine/view/range~Range } New range after wrapping, spanning over wrapping attribute element.
1236
1272
*/
1237
1273
_wrapRange ( range , attribute ) {
1238
- // Range is inside single attribute and spans on all children.
1239
- if ( rangeSpansOnAllChildren ( range ) && this . _wrapAttributeElement ( attribute , range . start . parent ) ) {
1240
- const parent = range . start . parent ;
1241
-
1242
- const end = this . mergeAttributes ( Position . _createAfter ( parent ) ) ;
1243
- const start = this . mergeAttributes ( Position . _createBefore ( parent ) ) ;
1244
-
1245
- return new Range ( start , end ) ;
1246
- }
1247
-
1248
1274
// Break attributes at range start and end.
1249
1275
const { start : breakStart , end : breakEnd } = this . _breakAttributesRange ( range , true ) ;
1250
-
1251
- // Range around one element.
1252
- if ( breakEnd . isEqual ( breakStart . getShiftedBy ( 1 ) ) ) {
1253
- const node = breakStart . nodeAfter ;
1254
-
1255
- if ( node instanceof AttributeElement && this . _wrapAttributeElement ( attribute , node ) ) {
1256
- const start = this . mergeAttributes ( breakStart ) ;
1257
-
1258
- if ( ! start . isEqual ( breakStart ) ) {
1259
- breakEnd . offset -- ;
1260
- }
1261
-
1262
- const end = this . mergeAttributes ( breakEnd ) ;
1263
-
1264
- return new Range ( start , end ) ;
1265
- }
1266
- }
1267
-
1268
1276
const parentContainer = breakStart . parent ;
1269
1277
1270
- // Unwrap children located between break points.
1271
- const unwrappedRange = this . _unwrapChildren ( parentContainer , breakStart . offset , breakEnd . offset , attribute ) ;
1272
-
1273
1278
// Wrap all children with attribute.
1274
- const newRange = this . _wrapChildren ( parentContainer , unwrappedRange . start . offset , unwrappedRange . end . offset , attribute ) ;
1279
+ const newRange = this . _wrapChildren ( parentContainer , breakStart . offset , breakEnd . offset , attribute ) ;
1275
1280
1276
1281
// Merge attributes at the both ends and return a new range.
1277
1282
const start = this . mergeAttributes ( newRange . start ) ;
@@ -1805,17 +1810,6 @@ function mergeTextNodes( t1, t2 ) {
1805
1810
return new Position ( t1 , nodeBeforeLength ) ;
1806
1811
}
1807
1812
1808
- // Returns `true` if range is located in same {@link module:engine/view/attributeelement~AttributeElement AttributeElement}
1809
- // (`start` and `end` positions are located inside same {@link module:engine/view/attributeelement~AttributeElement AttributeElement}),
1810
- // starts on 0 offset and ends after last child node.
1811
- //
1812
- // @param {module:engine/view/range~Range } Range
1813
- // @returns {Boolean }
1814
- function rangeSpansOnAllChildren ( range ) {
1815
- return range . start . parent == range . end . parent && range . start . parent . is ( 'attributeElement' ) &&
1816
- range . start . offset === 0 && range . end . offset === range . start . parent . childCount ;
1817
- }
1818
-
1819
1813
// Checks if provided nodes are valid to insert. Checks if each node is an instance of
1820
1814
// {@link module:engine/view/text~Text Text } or {@link module:engine/view/attributeelement~AttributeElement AttributeElement },
1821
1815
// {@link module:engine/view/containerelement~ContainerElement ContainerElement },
0 commit comments