transform.js 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  1. import InsertOperation from './insertoperation';
  2. import AttributeOperation from './attributeoperation';
  3. import RenameOperation from './renameoperation';
  4. import MarkerOperation from './markeroperation';
  5. import MoveOperation from './moveoperation';
  6. import RootAttributeOperation from './rootattributeoperation';
  7. import MergeOperation from './mergeoperation';
  8. import SplitOperation from './splitoperation';
  9. import WrapOperation from './wrapoperation';
  10. import UnwrapOperation from './unwrapoperation';
  11. import NoOperation from './nooperation';
  12. import Range from '../range';
  13. import Position from '../position';
  14. import compareArrays from '@ckeditor/ckeditor5-utils/src/comparearrays';
  15. import log from '@ckeditor/ckeditor5-utils/src/log';
  16. const transformations = new Map();
  17. function setTransformation( OperationA, OperationB, transformationFunction ) {
  18. let aGroup = transformations.get( OperationA );
  19. if ( !aGroup ) {
  20. aGroup = new Map();
  21. transformations.set( OperationA, aGroup );
  22. }
  23. aGroup.set( OperationB, transformationFunction );
  24. }
  25. function getTransformation( a, b ) {
  26. const aGroup = transformations.get( a.constructor );
  27. if ( aGroup && aGroup.has( b.constructor ) ) {
  28. return aGroup.get( b.constructor );
  29. }
  30. return noUpdateTransformation;
  31. }
  32. function noUpdateTransformation( a ) {
  33. return [ a.clone() ];
  34. }
  35. function updateBaseVersions( operations, baseVersion ) {
  36. for ( const operation of operations ) {
  37. operation.baseVersion = baseVersion++;
  38. }
  39. return operations;
  40. }
  41. export function transform( a, b, context = {} ) {
  42. const transformationFunction = getTransformation( a, b );
  43. try {
  44. a = a.clone();
  45. return transformationFunction( a, b, context );
  46. } catch ( e ) {
  47. log.error( 'Error during operation transformation!' );
  48. log.error( 'Transformed operation', a );
  49. log.error( 'Operation transformed by', b );
  50. log.error( 'context.aIsStrong', context.aIsStrong );
  51. log.error( 'context.aWasUndone', context.aWasUndone );
  52. log.error( 'context.bWasUndone', context.bWasUndone );
  53. log.error( 'context.abRelation', context.abRelation );
  54. log.error( 'context.baRelation', context.baRelation );
  55. throw e;
  56. }
  57. }
  58. export function transformSets( operationsA, operationsB, options ) {
  59. operationsA = operationsA.slice();
  60. operationsB = operationsB.slice();
  61. if ( operationsA.length == 0 || operationsB.length == 0 ) {
  62. return { operationsA, operationsB };
  63. }
  64. const toTransform = operationsA.map( op => ( { opA: op, transformFrom: 0 } ) );
  65. const data = {
  66. nextBaseVersionA: operationsA[ operationsA.length - 1 ].baseVersion + 1,
  67. nextBaseVersionB: operationsB[ operationsB.length - 1 ].baseVersion + 1,
  68. extraOpsA: 0,
  69. extraOpsB: 0
  70. };
  71. const context = initializeContext( operationsA, operationsB, options );
  72. for ( let i = 0; i < toTransform.length; i++ ) {
  73. const transformFrom = toTransform[ i ].transformFrom;
  74. for ( let j = transformFrom; j < operationsB.length; ) {
  75. const opA = toTransform[ i ].opA;
  76. const opB = operationsB[ j ];
  77. if ( options.useContext ) {
  78. updateRelations( context, opA, opB );
  79. }
  80. const contextAB = {
  81. aIsStrong: true,
  82. aWasUndone: context.wasUndone( opA ),
  83. bWasUndone: context.wasUndone( opB ),
  84. abRelation: context.getRelation( opA, opB ),
  85. baRelation: context.getRelation( opB, opA )
  86. };
  87. const contextBA = {
  88. aIsStrong: false,
  89. aWasUndone: context.wasUndone( opB ),
  90. bWasUndone: context.wasUndone( opA ),
  91. abRelation: context.getRelation( opB, opA ),
  92. baRelation: context.getRelation( opA, opB )
  93. };
  94. const newOpsA = transform( opA, opB, contextAB );
  95. const newOpsB = transform( opB, opA, contextBA );
  96. if ( options.useContext ) {
  97. updateOriginalOperation( context, opA, newOpsA );
  98. updateOriginalOperation( context, opB, newOpsB );
  99. }
  100. const newTransformFrom = j + newOpsB.length;
  101. const newToTransform = newOpsA.map( op => ( { opA: op, transformFrom: newTransformFrom } ) );
  102. toTransform.splice( i, 1, ...newToTransform );
  103. operationsB.splice( j, 1, ...newOpsB );
  104. j = j + newOpsB.length;
  105. data.extraOpsA += newOpsA.length - 1;
  106. data.extraOpsB += newOpsB.length - 1;
  107. }
  108. }
  109. operationsA = toTransform.map( item => item.opA );
  110. if ( options.padWithNoOps ) {
  111. padWithNoOps( operationsA, data.extraOpsB - data.extraOpsA );
  112. padWithNoOps( operationsB, data.extraOpsA - data.extraOpsB );
  113. }
  114. updateBaseVersions( operationsA, data.nextBaseVersionB );
  115. updateBaseVersions( operationsB, data.nextBaseVersionA );
  116. return { operationsA, operationsB };
  117. }
  118. function padWithNoOps( operations, howMany ) {
  119. for ( let i = 0; i < howMany; i++ ) {
  120. operations.push( new NoOperation( 0 ) );
  121. }
  122. }
  123. function initializeContext( opsA, opsB, options ) {
  124. const context = {};
  125. context.originalOperations = new Map();
  126. for ( const op of opsA.concat( opsB ) ) {
  127. context.originalOperations.set( op, op );
  128. }
  129. context.document = options.document;
  130. context.relations = new Map();
  131. context.wasUndone = function( op ) {
  132. if ( !options.useContext ) {
  133. return false;
  134. }
  135. const originalOp = this.originalOperations.get( op );
  136. return this.document.history.isUndoneOperation( originalOp );
  137. };
  138. context.getRelation = function( opA, opB ) {
  139. if ( !options.useContext ) {
  140. return null;
  141. }
  142. const origB = this.originalOperations.get( opB );
  143. const undoneB = this.document.history.getUndoneOperation( origB );
  144. if ( !undoneB ) {
  145. return null;
  146. }
  147. const origA = this.originalOperations.get( opA );
  148. const relationsA = this.relations.get( origA );
  149. if ( relationsA ) {
  150. return relationsA.get( undoneB ) || null;
  151. }
  152. return null;
  153. };
  154. return context;
  155. }
  156. function updateRelations( context, opA, opB ) {
  157. switch ( opA.constructor ) {
  158. case MoveOperation: {
  159. switch ( opB.constructor ) {
  160. case MergeOperation: {
  161. if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.targetPosition ) ) {
  162. setRelation( context, opA, opB, 'insertAtSource' );
  163. setRelation( context, opB, opA, 'splitBefore' );
  164. } else if ( opA.targetPosition.isEqual( opB.deletionPosition ) ) {
  165. setRelation( context, opA, opB, 'insertBetween' );
  166. }
  167. break;
  168. }
  169. case MoveOperation: {
  170. if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opA.targetPosition.isBefore( opB.sourcePosition ) ) {
  171. setRelation( context, opA, opB, 'insertBefore' );
  172. setRelation( context, opB, opA, 'insertAfter' );
  173. } else {
  174. setRelation( context, opA, opB, 'insertAfter' );
  175. setRelation( context, opB, opA, 'insertBefore' );
  176. }
  177. break;
  178. }
  179. case UnwrapOperation: {
  180. const isInside = opA.targetPosition.hasSameParentAs( opB.targetPosition );
  181. if ( isInside ) {
  182. setRelation( context, opA, opB, 'insertInside' );
  183. }
  184. break;
  185. }
  186. }
  187. break;
  188. }
  189. case SplitOperation: {
  190. switch ( opB.constructor ) {
  191. case MergeOperation: {
  192. if ( opA.position.isBefore( opB.sourcePosition ) ) {
  193. setRelation( context, opA, opB, 'splitBefore' );
  194. setRelation( context, opB, opA, 'splitAfter' );
  195. }
  196. break;
  197. }
  198. case MoveOperation: {
  199. if ( opA.position.isEqual( opB.sourcePosition ) || opA.position.isBefore( opB.sourcePosition ) ) {
  200. setRelation( context, opA, opB, 'splitBefore' );
  201. setRelation( context, opB, opA, 'insertAtSource' );
  202. }
  203. break;
  204. }
  205. case UnwrapOperation: {
  206. const isInside = opA.position.hasSameParentAs( opB.position );
  207. if ( isInside ) {
  208. setRelation( context, opA, opB, 'splitInside' );
  209. }
  210. break;
  211. }
  212. }
  213. break;
  214. }
  215. case InsertOperation: {
  216. switch ( opB.constructor ) {
  217. case MergeOperation: {
  218. if ( opA.position.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.position ) ) {
  219. setRelation( context, opA, opB, 'insertAtSource' );
  220. } else if ( opA.position.isEqual( opB.deletionPosition ) ) {
  221. setRelation( context, opA, opB, 'insertBetween' );
  222. }
  223. break;
  224. }
  225. case MoveOperation: {
  226. if ( opA.position.isEqual( opB.sourcePosition ) || opA.position.isBefore( opB.sourcePosition ) ) {
  227. setRelation( context, opA, opB, 'insertBefore' );
  228. }
  229. break;
  230. }
  231. case UnwrapOperation: {
  232. const isInside = opA.position.hasSameParentAs( opB.position );
  233. if ( isInside ) {
  234. setRelation( context, opA, opB, 'insertInside' );
  235. }
  236. break;
  237. }
  238. }
  239. break;
  240. }
  241. }
  242. }
  243. function setRelation( context, opA, opB, relation ) {
  244. const origA = context.originalOperations.get( opA );
  245. const origB = context.originalOperations.get( opB );
  246. let relationsA = context.relations.get( origA );
  247. if ( !relationsA ) {
  248. relationsA = new Map();
  249. context.relations.set( origA, relationsA );
  250. }
  251. relationsA.set( origB, relation );
  252. }
  253. function updateOriginalOperation( context, oldOp, newOps ) {
  254. const originalOp = context.originalOperations.get( oldOp );
  255. for ( const op of newOps ) {
  256. context.originalOperations.set( op, originalOp );
  257. }
  258. }
  259. function getNoOp() {
  260. return [ new NoOperation( 0 ) ];
  261. }
  262. // -----------------------
  263. setTransformation( AttributeOperation, AttributeOperation, ( a, b, context ) => {
  264. if ( a.key === b.key ) {
  265. // If operations attributes are in conflict, check if their ranges intersect and manage them properly.
  266. // First, we want to apply change to the part of a range that has not been changed by the other operation.
  267. const operations = a.range.getDifference( b.range ).map( range => {
  268. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, 0 );
  269. } );
  270. // Then we take care of the common part of ranges.
  271. const common = a.range.getIntersection( b.range );
  272. if ( common ) {
  273. // If this operation is more important, we also want to apply change to the part of the
  274. // original range that has already been changed by the other operation. Since that range
  275. // got changed we also have to update `oldValue`.
  276. if ( context.aIsStrong ) {
  277. operations.push( new AttributeOperation( common, b.key, b.newValue, a.newValue, 0 ) );
  278. }
  279. }
  280. if ( operations.length == 0 ) {
  281. return getNoOp();
  282. }
  283. return operations;
  284. } else {
  285. // If operations don't conflict, simply return an array containing just a clone of this operation.
  286. return [ a ];
  287. }
  288. } );
  289. setTransformation( AttributeOperation, InsertOperation, ( a, b ) => {
  290. // Case 1: The attribute operation range includes the position where nodes were inserted.
  291. // There are two possible scenarios: the inserted nodes were text and they should receive attributes or
  292. // the inserted nodes were elements and they should not receive attributes. In any case the attribute
  293. // operation range needs to be split. For text insertion, extra operation needs to be generated.
  294. //
  295. if ( a.range.start.hasSameParentAs( b.position ) && a.range.containsPosition( b.position ) ) {
  296. // This will return an array with a single, expanded range.
  297. const range = a.range._getTransformedByInsertion( b.position, b.howMany, !b.shouldReceiveAttributes );
  298. const result = range.map( r => {
  299. return new AttributeOperation( r, a.key, a.oldValue, a.newValue, a.baseVersion );
  300. } );
  301. // `AttributeOperation#range` includes some newly inserted text.
  302. // The operation should also change the attribute of that text.
  303. //
  304. // Bold should be applied on the following range:
  305. // <p>Fo[zb]ar</p>
  306. //
  307. // New text is typed:
  308. // <p>Fozxxbar</p>
  309. //
  310. // Bold should be applied also on the new text:
  311. // <p>Fo<$text bold=true>zxxb</$text>ar</p>
  312. //
  313. // There is a special case to consider here, which will dictate how the final solution will look like. The inserted
  314. // text might have already an attribute applied and the `oldValue` property of the attribute operation might be wrong:
  315. //
  316. // Attribute `highlight="yellow"` should be applied on the following range:
  317. // <p>Fo[zb]ar<p>
  318. //
  319. // New text with `highlight="red"` is typed:
  320. // <p>Fo[z<$text highlight="red">x</$text>a]r</p>
  321. //
  322. // In this case we cannot simply apply operation: `oldValue=null, newValue="yellow"` for the whole range
  323. // because that will led to an exception (`oldValue` is incorrect). We also cannot break the original range
  324. // as this would mess up following insert operations:
  325. //
  326. // Attribute operation is to apply bold on below `[]` range, but multiple characters are inserted at `^`.
  327. // Assume ranges breaking:
  328. //
  329. // <p>F[o^zba]r</p>
  330. // <p>F[o][x][zba]</p>
  331. // <p>F[o][x]x[zba]</p>
  332. // <p>F[o][x]xx[zba]</p>
  333. //
  334. // As you can see, characters after the first one are not included in any range when the original attribute range
  335. // is broken at first `x` insertion.
  336. //
  337. // So, the final solution is to just expand the original range, but "fix" all the nodes that have "incorrect" `oldValue`.
  338. // For example, in the highlight case above, we would need an operation to change `highlight="red"` to `null` so that
  339. // the original attribute operation `null` -> `yellow` would be correct.
  340. //
  341. if ( b.shouldReceiveAttributes ) {
  342. // Be sure to add those operations before the original operation.
  343. result.unshift( ..._getComplementaryAttributeOperations( b, a.key, a.oldValue ) );
  344. }
  345. // If nodes should not receive new attribute, just leave the spread ranges as they are.
  346. return result;
  347. }
  348. // If insert operation is not expanding the attribute operation range, simply transform the range.
  349. a.range = a.range._getTransformedByInsertion( b.position, b.howMany, false )[ 0 ];
  350. return [ a ];
  351. } );
  352. function _getComplementaryAttributeOperations( insertOperation, key, newValue ) {
  353. const nodes = insertOperation.nodes;
  354. const result = [];
  355. // At the beginning we store the attribute value from the first node.
  356. let val = nodes.getNode( 0 ).getAttribute( key );
  357. // This stores the last index of node list where the attribute value has changed.
  358. // We need it to create separate `AttributeOperation`s for nodes with different attribute values.
  359. let lastOffset = 0;
  360. // Sum of offsets of already processed nodes.
  361. let offsetSum = nodes.getNode( 0 ).offsetSize;
  362. for ( let i = 1; i < nodes.length; i++ ) {
  363. const node = nodes.getNode( i );
  364. const nodeAttrVal = node.getAttribute( key );
  365. // If previous node has different attribute value, we will create an operation to the point before current node.
  366. // So all nodes with the same attributes up to this point will be included in one `AttributeOperation`.
  367. if ( nodeAttrVal != val ) {
  368. // New operation is created only when it is needed. If given node already has proper value for this
  369. // attribute we simply skip it without adding a new operation.
  370. if ( val != newValue ) {
  371. addOperation();
  372. }
  373. val = nodeAttrVal;
  374. lastOffset = offsetSum;
  375. }
  376. offsetSum = offsetSum + node.offsetSize;
  377. }
  378. // At the end we have to add additional `AttributeOperation` for the last part of node list.
  379. // If all nodes on the node list had same attributes, this will be the only returned operation.
  380. addOperation();
  381. return result;
  382. function addOperation() {
  383. const range = new Range(
  384. insertOperation.position.getShiftedBy( lastOffset ),
  385. insertOperation.position.getShiftedBy( offsetSum )
  386. );
  387. result.push(
  388. new AttributeOperation( range, key, val, newValue, 0 )
  389. );
  390. }
  391. }
  392. setTransformation( AttributeOperation, MergeOperation, ( a, b ) => {
  393. const ranges = [];
  394. // Case 1: Attribute change on the merged element. In this case, the merged element was moved to graveyard.
  395. // An additional attribute operation that will change the (re)moved element needs to be generated.
  396. // Do it only, if there is more than one element in attribute range. If there is only one element,
  397. // it will be handled by the default algorithm.
  398. //
  399. if ( a.range.start.hasSameParentAs( b.deletionPosition ) ) {
  400. if ( a.range.containsPosition( b.deletionPosition ) || a.range.start.isEqual( b.deletionPosition ) ) {
  401. ranges.push( Range.createFromPositionAndShift( b.graveyardPosition, 1 ) );
  402. }
  403. }
  404. const range = a.range._getTransformedByMergeOperation( b );
  405. // Do not add empty (collapsed) ranges to the result. `range` may be collapsed if it contained only the merged element.
  406. if ( !range.isCollapsed ) {
  407. ranges.push( range );
  408. }
  409. // Create `AttributeOperation`s out of the ranges.
  410. return ranges.map( range => {
  411. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion );
  412. } );
  413. } );
  414. setTransformation( AttributeOperation, MoveOperation, ( a, b ) => {
  415. const ranges = breakRangeByMoveOperation( a.range, b, true );
  416. // Create `AttributeOperation`s out of the ranges.
  417. return ranges.map( range => new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion ) );
  418. } );
  419. function breakRangeByMoveOperation( range, moveOp, includeCommon ) {
  420. let common = null;
  421. let difference = [];
  422. const moveRange = Range.createFromPositionAndShift( moveOp.sourcePosition, moveOp.howMany );
  423. if ( moveRange.containsRange( range, true ) ) {
  424. common = range;
  425. } else if ( range.start.hasSameParentAs( moveRange.start ) ) {
  426. difference = range.getDifference( moveRange );
  427. common = includeCommon ? range.getIntersection( moveRange ) : null;
  428. } else {
  429. difference = [ range ];
  430. }
  431. const result = [];
  432. for ( let diff of difference ) {
  433. diff = diff._getTransformedByDeletion( moveOp.sourcePosition, moveOp.howMany );
  434. const targetPosition = moveOp.getMovedRangeStart();
  435. const spread = diff.start.hasSameParentAs( targetPosition );
  436. diff = diff._getTransformedByInsertion( targetPosition, moveOp.howMany, spread );
  437. result.push( ...diff );
  438. }
  439. if ( common ) {
  440. result.push(
  441. common._getTransformedByMove( moveOp.sourcePosition, moveOp.targetPosition, moveOp.howMany, false )[ 0 ]
  442. );
  443. }
  444. return result;
  445. }
  446. setTransformation( AttributeOperation, SplitOperation, ( a, b ) => {
  447. // Case 1: Split node is the last node in `AttributeOperation#range`.
  448. // `AttributeOperation#range` needs to be expanded to include the new (split) node.
  449. //
  450. // <listItem type="bulleted">foobar</listItem>
  451. //
  452. // After split:
  453. // <listItem type="bulleted">foo</listItem><listItem type="bulleted">bar</listItem>
  454. //
  455. // After attribute change:
  456. // <listItem type="numbered">foo</listItem><listItem type="numbered">foo</listItem>
  457. //
  458. if ( a.range.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
  459. a.range.end.offset++;
  460. return [ a ];
  461. }
  462. // Case 2: Split is inside `AttributeOperation#range` but the parent is not inside that range.
  463. // Transformed attribute operation should not include the element created by split.
  464. //
  465. // Content with range-to-change and split position:
  466. // <p>Fo[zb^a]r</p>
  467. //
  468. // After split:
  469. // <p>Fo[zb</p><p>a]r</p>
  470. //
  471. // Transformed range contains the new element. This is wrong. It should be like this:
  472. // <p>Fo[zb]</p><p>[a]r</p>
  473. //
  474. if ( a.range.start.hasSameParentAs( b.position ) && a.range.containsPosition( b.position ) ) {
  475. const secondPart = a.clone();
  476. secondPart.range = new Range(
  477. Position.createFromPosition( b.moveTargetPosition ),
  478. a.range.end._getCombined( b.position, b.moveTargetPosition )
  479. );
  480. a.range.end = Position.createFromPosition( b.position );
  481. a.range.end.stickiness = 'toPrevious';
  482. return [ a, secondPart ];
  483. }
  484. // The default case.
  485. //
  486. a.range = a.range._getTransformedBySplitOperation( b );
  487. return [ a ];
  488. } );
  489. setTransformation( AttributeOperation, WrapOperation, ( a, b ) => {
  490. // Case 1: `AttributeOperation#range` and range to wrap intersect. Two `AttributeOperation`s may be needed to handle this
  491. // situation as, after wrapping, the nodes to change may be in different parents.
  492. //
  493. // Both list items' type should be changed to numbered:
  494. // [<listItem type="bulleted">Foo</listItem><listItem type="bulleted">Bar</listItem>]
  495. //
  496. // Wrap one of the items inside block quote:
  497. // <blockQuote><listItem type="bulleted">Foo</listItem></blockQuote><listItem type="bulleted">Bar</listItem>
  498. //
  499. // Two operations are needed:
  500. // <blockQuote>[<listItem type="bulleted">Foo</listItem>]</blockQuote>[<listItem type="bulleted">Bar</listItem>]
  501. //
  502. // There might be three ranges needed, if the attribute operation range started before and ended after the wrap range.
  503. //
  504. if ( a.range.start.hasSameParentAs( b.position ) ) {
  505. const ranges = a.range.getDifference( b.wrappedRange );
  506. const common = a.range.getIntersection( b.wrappedRange );
  507. if ( common ) {
  508. ranges.push( common );
  509. }
  510. // Create `AttributeOperation`s out of the ranges.
  511. return ranges.map( range => {
  512. return new AttributeOperation( range._getTransformedByWrapOperation( b ), a.key, a.oldValue, a.newValue, 0 );
  513. } );
  514. }
  515. // The default case.
  516. //
  517. a.range = a.range._getTransformedByWrapOperation( b );
  518. return [ a ];
  519. } );
  520. setTransformation( AttributeOperation, UnwrapOperation, ( a, b ) => {
  521. // Case 1: `AttributeOperation#range` contains element to unwrap. Two or three `AttributeOperation`s are needed to handle this
  522. // situation. The unwrapped element was moved to graveyard and needs a separate operation. Then, if the unwrapped
  523. // nodes are inside attribute operation range, the range needs to be broken on two parts.
  524. //
  525. if ( a.range.start.hasSameParentAs( b.targetPosition ) ) {
  526. const insertionPosition = b.targetPosition.getShiftedBy( b.howMany );
  527. let ranges = a.range._getTransformedByInsertion( b.targetPosition, b.howMany );
  528. ranges = ranges.reduce( ( result, range ) => {
  529. return result.concat( range._getTransformedByMove( insertionPosition, b.graveyardPosition, 1 ) );
  530. }, [] );
  531. // Create `AttributeOperation`s out of the ranges.
  532. return ranges.map( range => {
  533. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, 0 );
  534. } );
  535. }
  536. a.range = a.range._getTransformedByUnwrapOperation( b );
  537. return [ a ];
  538. } );
  539. // -----------------------
  540. setTransformation( InsertOperation, AttributeOperation, ( a, b ) => {
  541. const result = [ a ];
  542. if ( a.shouldReceiveAttributes && a.position.hasSameParentAs( b.range.start ) && b.range.containsPosition( a.position ) ) {
  543. result.push( ..._getComplementaryAttributeOperations( a, b.key, b.newValue ) );
  544. }
  545. return result;
  546. } );
  547. setTransformation( InsertOperation, InsertOperation, ( a, b, context ) => {
  548. if ( a.position.isEqual( b.position ) && context.aIsStrong ) {
  549. return [ a ];
  550. }
  551. a.position = a.position._getTransformedByInsertOperation( b );
  552. return [ a ];
  553. } );
  554. setTransformation( InsertOperation, MoveOperation, ( a, b, context ) => {
  555. if ( a.position.isEqual( b.targetPosition ) && context.abRelation == 'insertBefore' ) {
  556. return [ a ];
  557. }
  558. a.position = a.position._getTransformedByMoveOperation( b );
  559. return [ a ];
  560. } );
  561. setTransformation( InsertOperation, SplitOperation, ( a, b, context ) => {
  562. if ( a.position.isEqual( b.position ) && context.abRelation == 'insertAtSource' ) {
  563. a.position = b.moveTargetPosition;
  564. return [ a ];
  565. }
  566. a.position = a.position._getTransformedBySplitOperation( b );
  567. return [ a ];
  568. } );
  569. setTransformation( InsertOperation, MergeOperation, ( a, b ) => {
  570. a.position = a.position._getTransformedByMergeOperation( b );
  571. return [ a ];
  572. } );
  573. setTransformation( InsertOperation, WrapOperation, ( a, b, context ) => {
  574. if ( a.position.isEqual( b.position ) && context.abRelation == 'insertInside' ) {
  575. a.position = b.targetPosition;
  576. return [ a ];
  577. }
  578. a.position = a.position._getTransformedByWrapOperation( b );
  579. return [ a ];
  580. } );
  581. setTransformation( InsertOperation, UnwrapOperation, ( a, b ) => {
  582. a.position = a.position._getTransformedByUnwrapOperation( b );
  583. return [ a ];
  584. } );
  585. // -----------------------
  586. setTransformation( MarkerOperation, InsertOperation, ( a, b ) => {
  587. if ( a.oldRange ) {
  588. a.oldRange = a.oldRange._getTransformedByInsertOperation( b )[ 0 ];
  589. }
  590. if ( a.newRange ) {
  591. a.newRange = a.newRange._getTransformedByInsertOperation( b )[ 0 ];
  592. }
  593. return [ a ];
  594. } );
  595. setTransformation( MarkerOperation, MarkerOperation, ( a, b, context ) => {
  596. if ( a.name == b.name ) {
  597. if ( context.aIsStrong ) {
  598. a.oldRange = Range.createFromRange( b.newRange );
  599. } else {
  600. return getNoOp();
  601. }
  602. }
  603. return [ a ];
  604. } );
  605. setTransformation( MarkerOperation, MergeOperation, ( a, b ) => {
  606. if ( a.oldRange ) {
  607. a.oldRange = a.oldRange._getTransformedByMergeOperation( b );
  608. }
  609. if ( a.newRange ) {
  610. a.newRange = a.newRange._getTransformedByMergeOperation( b );
  611. }
  612. return [ a ];
  613. } );
  614. setTransformation( MarkerOperation, MoveOperation, ( a, b ) => {
  615. if ( a.oldRange ) {
  616. a.oldRange = Range.createFromRanges( a.oldRange._getTransformedByMoveOperation( b ) );
  617. }
  618. if ( a.newRange ) {
  619. a.newRange = Range.createFromRanges( a.newRange._getTransformedByMoveOperation( b ) );
  620. }
  621. return [ a ];
  622. } );
  623. setTransformation( MarkerOperation, SplitOperation, ( a, b ) => {
  624. if ( a.oldRange ) {
  625. a.oldRange = a.oldRange._getTransformedBySplitOperation( b );
  626. }
  627. if ( a.newRange ) {
  628. a.newRange = a.newRange._getTransformedBySplitOperation( b );
  629. }
  630. return [ a ];
  631. } );
  632. setTransformation( MarkerOperation, WrapOperation, ( a, b ) => {
  633. if ( a.oldRange ) {
  634. a.oldRange = a.oldRange._getTransformedByWrapOperation( b );
  635. }
  636. if ( a.newRange ) {
  637. a.newRange = a.newRange._getTransformedByWrapOperation( b );
  638. }
  639. return [ a ];
  640. } );
  641. setTransformation( MarkerOperation, UnwrapOperation, ( a, b ) => {
  642. if ( a.oldRange ) {
  643. a.oldRange = a.oldRange._getTransformedByUnwrapOperation( b );
  644. }
  645. if ( a.newRange ) {
  646. a.newRange = a.newRange._getTransformedByUnwrapOperation( b );
  647. }
  648. return [ a ];
  649. } );
  650. // -----------------------
  651. setTransformation( MergeOperation, InsertOperation, ( a, b ) => {
  652. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  653. a.howMany += b.howMany;
  654. }
  655. a.sourcePosition = a.sourcePosition._getTransformedByInsertOperation( b );
  656. a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
  657. return [ a ];
  658. } );
  659. setTransformation( MergeOperation, MergeOperation, ( a, b, context ) => {
  660. // Case 1: Same merge operations. Both operations have same source and target positions. So the element already got merged.
  661. // In this case, keep the source operation in the merged element - in the graveyard and don't change target position.
  662. // This allows for a correct undo.
  663. //
  664. if ( a.sourcePosition.isEqual( b.sourcePosition ) && a.targetPosition.isEqual( b.targetPosition ) ) {
  665. const path = b.graveyardPosition.path.slice();
  666. path.push( 0 );
  667. a.sourcePosition = new Position( b.graveyardPosition.root, path );
  668. a.howMany = 0;
  669. return [ a ];
  670. }
  671. // Case 2: Same source position but different target positions. The same element got merged into different elements.
  672. //
  673. // The default case.
  674. //
  675. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  676. a.howMany += b.howMany;
  677. }
  678. a.sourcePosition = a.sourcePosition._getTransformedByMergeOperation( b );
  679. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  680. // Handle positions in graveyard.
  681. // If graveyard positions are same and `a` operation is strong - do not transform.
  682. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  683. a.graveyardPosition._getTransformedByInsertion( b.graveyardPosition, 1 );
  684. }
  685. return [ a ];
  686. } );
  687. setTransformation( MergeOperation, MoveOperation, ( a, b, context ) => {
  688. // Case 1: The element to merge got removed.
  689. // Merge operation does support merging elements which are not siblings. So it would not be a problem
  690. // from technical point of view. However, if the element was removed, the intention of the user
  691. // deleting it was to have it all deleted. From user experience point of view, moving back the
  692. // removed nodes might be unexpected. This means that in this scenario we will block the merging.
  693. // The exception of this rule would be if the remove operation was undone. Then, treat it as a normal move.
  694. //
  695. const removedRange = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  696. if ( b.type == 'remove' && !context.bWasUndone ) {
  697. if ( a.deletionPosition.hasSameParentAs( b.sourcePosition ) && removedRange.containsPosition( a.sourcePosition ) ) {
  698. return getNoOp();
  699. }
  700. }
  701. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  702. a.howMany += b.howMany;
  703. }
  704. if ( a.sourcePosition.hasSameParentAs( b.sourcePosition ) ) {
  705. a.howMany -= b.howMany;
  706. }
  707. a.sourcePosition = a.sourcePosition._getTransformedByMoveOperation( b );
  708. a.targetPosition = a.targetPosition._getTransformedByMoveOperation( b );
  709. if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
  710. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  711. }
  712. return [ a ];
  713. } );
  714. setTransformation( MergeOperation, SplitOperation, ( a, b ) => {
  715. if ( b.graveyardPosition ) {
  716. if ( a.deletionPosition.isEqual( b.graveyardPosition ) ) {
  717. a.howMany = b.howMany;
  718. }
  719. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  720. }
  721. // Case 1: Merge operation moves nodes to the place where split happens.
  722. //
  723. // Split is after `Foo`, while merge is from `Bar` to the end of `Foo`:
  724. // <p>Foo</p><p>Bar</p>
  725. //
  726. // After split:
  727. // <p>Foo</p><p></p><p>Bar</p>
  728. //
  729. // In this case, `Bar` should be merged to the new paragraph:
  730. // <p>Foo</p><p>Bar</p>
  731. //
  732. // This means that `targetPosition` needs to be transformed. This is the default case though.
  733. // For example, if the split would be after `F`, `targetPosition` should also be transformed.
  734. //
  735. // There are two exception, though, when we want to keep `targetPosition` as it was.
  736. //
  737. // First exception is when the merge target position is inside an element (not at the end, as usual). This
  738. // happens when the merge operation earlier was transformed by "the same" merge operation. If merge operation
  739. // targets inside the element we want to keep the original target position (and not transform it) because
  740. // we have additional context telling us that we want to merge to the original element. We can check if the
  741. // merge operation points inside element by checking what is `SplitOperation#howMany`. Since merge target position
  742. // is same as split position, if `howMany` is non-zero, it means that the merge target position is inside an element.
  743. //
  744. // Second exception is when the element to merge is in the graveyard and split operation uses it. In that case
  745. // if target position would be transformed, the merge operation would target at the source position:
  746. //
  747. // root: <p>Foo</p> graveyard: <p></p>
  748. //
  749. // SplitOperation: root [ 0, 3 ] using graveyard [ 0 ] (howMany = 0)
  750. // MergeOperation: graveyard [ 0, 0 ] -> root [ 0, 3 ] (howMany = 0)
  751. //
  752. // Since split operation moves the graveyard node back to the root, the merge operation source position changes.
  753. // We would like to merge from the empty <p> to the "Foo" <p>:
  754. //
  755. // root: <p>Foo</p><p></p> graveyard:
  756. //
  757. // MergeOperation#sourcePosition = root [ 1, 0 ]
  758. //
  759. // If `targetPosition` is transformed, it would become root [ 1, 0 ] as well. It has to be kept as it was.
  760. //
  761. if ( a.targetPosition.isEqual( b.position ) ) {
  762. if ( b.howMany != 0 || ( b.graveyardPosition && a.deletionPosition.isEqual( b.graveyardPosition ) ) ) {
  763. a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
  764. return [ a ];
  765. }
  766. }
  767. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  768. a.howMany = b.position.offset;
  769. }
  770. a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
  771. a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
  772. return [ a ];
  773. } );
  774. setTransformation( MergeOperation, WrapOperation, ( a, b ) => {
  775. if ( b.graveyardPosition ) {
  776. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  777. }
  778. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  779. a.howMany = a.howMany + 1 - b.howMany;
  780. }
  781. // Case 1: Wrap is with an element from graveyard, which also is merge operation target. This happens
  782. // in some undo scenarios. In this case, the target position needs to be properly transformed.
  783. //
  784. const path = a.targetPosition.path.slice( 0, -1 );
  785. const posBeforeTargetElement = new Position( a.targetPosition.root, path );
  786. if ( b.graveyardPosition && b.graveyardPosition.isEqual( posBeforeTargetElement ) ) {
  787. a.sourcePosition = a.sourcePosition._getTransformedByWrapOperation( b );
  788. a.targetPosition = b.targetPosition.getShiftedBy( b.howMany );
  789. return [ a ];
  790. }
  791. // Case 2: Merged element is wrapped and this is the last (only) element in the wrap.
  792. // Because of how this is resolved in `WrapOperation` x `MergeOperation`, we need to apply special handling here.
  793. // If the last element from wrapped range is "removed" from it, the wrap is effectively on empty range.
  794. // In that case, the wrapper element is moved to graveyard. This happens in `WrapOperation` x
  795. // `MergeOperation` and we need to mirror it here.
  796. //
  797. if ( b.position.isEqual( a.deletionPosition ) && b.howMany == 1 ) {
  798. // We need to change `MergeOperation#graveyardPosition` so the merged node is moved into the wrapper element.
  799. // Since `UnwrapOperation` created from reverse has graveyard position at [ 0 ], we can safely set the path here to [ 0, 0 ].
  800. a.graveyardPosition = new Position( a.graveyardPosition.root, [ 0, 0 ] );
  801. return [
  802. b.getReversed(),
  803. a
  804. ];
  805. }
  806. a.sourcePosition = a.sourcePosition._getTransformedByWrapOperation( b );
  807. a.targetPosition = a.targetPosition._getTransformedByWrapOperation( b );
  808. return [ a ];
  809. } );
  810. setTransformation( MergeOperation, UnwrapOperation, ( a, b, context ) => {
  811. // Case 1: The element to merge got unwrapped.
  812. // There are multiple possible solution to resolve this conflict:
  813. // * unwrap also merge target (all nodes are unwrapped),
  814. // * move the unwrapped nodes to the merge target (no nodes stayed unwrapped),
  815. // * leave merge position in the unwrapped node (some nodes are unwrapped and some are not).
  816. //
  817. // Third option is chosen in this algorithm. If the unwrap operation is undone before merge is applied,
  818. // the merge operation will work as expected. If the unwrap operation is not undone, then the merge
  819. // operation won't merge any nodes, so it will behave similarly to noop.
  820. //
  821. if ( a.sourcePosition.isEqual( b.position ) ) {
  822. const path = b.graveyardPosition.path.slice();
  823. path.push( 0 );
  824. a.sourcePosition = new Position( b.graveyardPosition.root, path );
  825. a.howMany = 0;
  826. return [ a ];
  827. }
  828. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  829. a.howMany = a.howMany - 1 + b.howMany;
  830. }
  831. a.sourcePosition = a.sourcePosition._getTransformedByUnwrapOperation( b );
  832. a.targetPosition = a.targetPosition._getTransformedByUnwrapOperation( b );
  833. // Handle positions in graveyard.
  834. // If graveyard positions are same and `a` operation is strong - do not transform.
  835. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  836. a.graveyardPosition._getTransformedByInsertion( b.graveyardPosition, 1 );
  837. }
  838. return [ a ];
  839. } );
  840. // -----------------------
  841. setTransformation( MoveOperation, InsertOperation, ( a, b, context ) => {
  842. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  843. const transformed = moveRange._getTransformedByInsertOperation( b, false )[ 0 ];
  844. a.sourcePosition = transformed.start;
  845. a.howMany = transformed.end.offset - transformed.start.offset;
  846. if ( !a.targetPosition.isEqual( b.position ) || context.abRelation == 'insertBefore' ) {
  847. a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
  848. }
  849. return [ a ];
  850. } );
  851. setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
  852. //
  853. // Setting and evaluating some variables that will be used in special cases and default algorithm.
  854. //
  855. // Create ranges from `MoveOperations` properties.
  856. const rangeA = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  857. const rangeB = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  858. // Assign `context.aIsStrong` to a different variable, because the value may change during execution of
  859. // this algorithm and we do not want to override original `context.aIsStrong` that will be used in later transformations.
  860. let aIsStrong = context.aIsStrong;
  861. let insertBefore = !context.aIsStrong;
  862. if ( context.abRelation == 'insertBefore' ) {
  863. insertBefore = true;
  864. } else if ( context.abRelation == 'insertAfter' ) {
  865. insertBefore = false;
  866. }
  867. // `a.targetPosition` could be affected by the `b` operation. We will transform it.
  868. let newTargetPosition;
  869. if ( a.targetPosition.isEqual( b.targetPosition ) && insertBefore ) {
  870. newTargetPosition = a.targetPosition._getTransformedByDeletion(
  871. b.sourcePosition,
  872. b.howMany
  873. );
  874. } else {
  875. newTargetPosition = a.targetPosition._getTransformedByMove(
  876. b.sourcePosition,
  877. b.targetPosition,
  878. b.howMany
  879. );
  880. }
  881. //
  882. // Special case #1 + mirror.
  883. //
  884. // Special case when both move operations' target positions are inside nodes that are
  885. // being moved by the other move operation. So in other words, we move ranges into inside of each other.
  886. // This case can't be solved reasonably (on the other hand, it should not happen often).
  887. if ( moveTargetIntoMovedRange( a, b ) && moveTargetIntoMovedRange( b, a ) ) {
  888. // Instead of transforming operation, we return a reverse of the operation that we transform by.
  889. // So when the results of this "transformation" will be applied, `b` MoveOperation will get reversed.
  890. return [ b.getReversed() ];
  891. }
  892. //
  893. // End of special case #1.
  894. //
  895. //
  896. // Special case #2.
  897. //
  898. // Check if `b` operation targets inside `rangeA`. Use stickiness if possible.
  899. const bTargetsToA = rangeA.containsPosition( b.targetPosition );
  900. // If `b` targets to `rangeA` and `rangeA` contains `rangeB`, `b` operation has no influence on `a` operation.
  901. // You might say that operation `b` is captured inside operation `a`.
  902. if ( bTargetsToA && rangeA.containsRange( rangeB, true ) ) {
  903. // There is a mini-special case here, where `rangeB` is on other level than `rangeA`. That's why
  904. // we need to transform `a` operation anyway.
  905. rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  906. rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  907. return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  908. }
  909. //
  910. // Special case #2 mirror.
  911. //
  912. const aTargetsToB = rangeB.containsPosition( a.targetPosition );
  913. if ( aTargetsToB && rangeB.containsRange( rangeA, true ) ) {
  914. // `a` operation is "moved together" with `b` operation.
  915. // Here, just move `rangeA` "inside" `rangeB`.
  916. rangeA.start = rangeA.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  917. rangeA.end = rangeA.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  918. return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  919. }
  920. //
  921. // End of special case #2.
  922. //
  923. //
  924. // Special case #3 + mirror.
  925. //
  926. // `rangeA` has a node which is an ancestor of `rangeB`. In other words, `rangeB` is inside `rangeA`
  927. // but not on the same tree level. In such case ranges have common part but we have to treat it
  928. // differently, because in such case those ranges are not really conflicting and should be treated like
  929. // two separate ranges. Also we have to discard two difference parts.
  930. const aCompB = compareArrays( a.sourcePosition.getParentPath(), b.sourcePosition.getParentPath() );
  931. if ( aCompB == 'prefix' || aCompB == 'extension' ) {
  932. // Transform `rangeA` by `b` operation and make operation out of it, and that's all.
  933. // Note that this is a simplified version of default case, but here we treat the common part (whole `rangeA`)
  934. // like a one difference part.
  935. rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  936. rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  937. return makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  938. }
  939. //
  940. // End of special case #3.
  941. //
  942. //
  943. // Default case - ranges are on the same level or are not connected with each other.
  944. //
  945. // Modifier for default case.
  946. // Modifies `aIsStrong` flag in certain conditions.
  947. //
  948. // If only one of operations is a remove operation, we force remove operation to be the "stronger" one
  949. // to provide more expected results.
  950. if ( a.type == 'remove' && b.type != 'remove' && !context.aWasUndone ) {
  951. aIsStrong = true;
  952. } else if ( a.type != 'remove' && b.type == 'remove' && !context.bWasUndone ) {
  953. aIsStrong = false;
  954. }
  955. // Handle operation's source ranges - check how `rangeA` is affected by `b` operation.
  956. // This will aggregate transformed ranges.
  957. const ranges = [];
  958. // Get the "difference part" of `a` operation source range.
  959. // This is an array with one or two ranges. Two ranges if `rangeB` is inside `rangeA`.
  960. const difference = rangeA.getDifference( rangeB );
  961. for ( const range of difference ) {
  962. // Transform those ranges by `b` operation. For example if `b` moved range from before those ranges, fix those ranges.
  963. range.start = range.start._getTransformedByDeletion( b.sourcePosition, b.howMany );
  964. range.end = range.end._getTransformedByDeletion( b.sourcePosition, b.howMany );
  965. // If `b` operation targets into `rangeA` on the same level, spread `rangeA` into two ranges.
  966. const shouldSpread = compareArrays( range.start.getParentPath(), b.getMovedRangeStart().getParentPath() ) == 'same';
  967. const newRanges = range._getTransformedByInsertion( b.getMovedRangeStart(), b.howMany, shouldSpread );
  968. ranges.push( ...newRanges );
  969. }
  970. // Then, we have to manage the "common part" of both move ranges.
  971. const common = rangeA.getIntersection( rangeB );
  972. if ( common !== null && aIsStrong && !bTargetsToA ) {
  973. // Calculate the new position of that part of original range.
  974. common.start = common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  975. common.end = common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  976. // Take care of proper range order.
  977. //
  978. // Put `common` at appropriate place. Keep in mind that we are interested in original order.
  979. // Basically there are only three cases: there is zero, one or two difference ranges.
  980. //
  981. // If there is zero difference ranges, just push `common` in the array.
  982. if ( ranges.length === 0 ) {
  983. ranges.push( common );
  984. }
  985. // If there is one difference range, we need to check whether common part was before it or after it.
  986. else if ( ranges.length == 1 ) {
  987. if ( rangeB.start.isBefore( rangeA.start ) || rangeB.start.isEqual( rangeA.start ) ) {
  988. ranges.unshift( common );
  989. } else {
  990. ranges.push( common );
  991. }
  992. }
  993. // If there are more ranges (which means two), put common part between them. This is the only scenario
  994. // where there could be two difference ranges so we don't have to make any comparisons.
  995. else {
  996. ranges.splice( 1, 0, common );
  997. }
  998. }
  999. if ( ranges.length === 0 ) {
  1000. // If there are no "source ranges", nothing should be changed.
  1001. // Note that this can happen only if `aIsStrong == false` and `rangeA.isEqual( rangeB )`.
  1002. return [ new NoOperation( a.baseVersion ) ];
  1003. }
  1004. return makeMoveOperationsFromRanges( ranges, newTargetPosition );
  1005. } );
  1006. setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
  1007. const newTargetPosition = a.targetPosition._getTransformedBySplitOperation( b );
  1008. // Case 1: Last element in the moved range got split.
  1009. // In this case the default range transformation will not work correctly as the element created by
  1010. // split operation would be outside the range. The range to move needs to be fixed manually.
  1011. //
  1012. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1013. if ( moveRange.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
  1014. a.howMany++;
  1015. a.targetPosition = newTargetPosition;
  1016. return [ a ];
  1017. }
  1018. // Case 2: Split happened between the moved nodes. In this case two ranges to move need to be generated.
  1019. //
  1020. // Characters `ozba` are moved to the end of paragraph `Xyz` but split happened.
  1021. // <p>F[oz|ba]r</p><p>Xyz</p>
  1022. //
  1023. // After split:
  1024. // <p>F[oz</p><p>ba]r</p><p>Xyz</p>
  1025. //
  1026. // Correct ranges:
  1027. // <p>F[oz]</p><p>[ba]r</p><p>Xyz</p>
  1028. //
  1029. // After move:
  1030. // <p>F</p><p>r</p><p>Xyzozba</p>
  1031. //
  1032. if ( moveRange.start.hasSameParentAs( b.position ) && moveRange.containsPosition( b.position ) ) {
  1033. let rightRange = new Range( b.position, moveRange.end );
  1034. rightRange = rightRange._getTransformedBySplitOperation( b );
  1035. const ranges = [
  1036. new Range( moveRange.start, b.position ),
  1037. rightRange
  1038. ];
  1039. return makeMoveOperationsFromRanges( ranges, newTargetPosition );
  1040. }
  1041. // The default case.
  1042. //
  1043. const transformed = moveRange._getTransformedBySplitOperation( b );
  1044. a.sourcePosition = transformed.start;
  1045. a.howMany = transformed.end.offset - transformed.start.offset;
  1046. if ( a.targetPosition.isEqual( b.position ) && context.abRelation == 'insertAtSource' ) {
  1047. a.targetPosition = b.moveTargetPosition;
  1048. } else if ( a.targetPosition.isEqual( b.insertionPosition ) && context.abRelation == 'insertBetween' ) {
  1049. a.targetPosition = a.targetPosition;
  1050. } else {
  1051. a.targetPosition = newTargetPosition;
  1052. }
  1053. return [ a ];
  1054. } );
  1055. setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
  1056. const movedRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1057. if ( b.deletionPosition.hasSameParentAs( a.sourcePosition ) && movedRange.containsPosition( b.sourcePosition ) ) {
  1058. if ( a.type == 'remove' ) {
  1059. // Case 1: The element to remove got merged.
  1060. // Merge operation does support merging elements which are not siblings. So it would not be a problem
  1061. // from technical point of view. However, if the element was removed, the intention of the user
  1062. // deleting it was to have it all deleted. From user experience point of view, moving back the
  1063. // removed nodes might be unexpected. This means that in this scenario we will reverse merging and remove the element.
  1064. //
  1065. if ( !context.aWasUndone ) {
  1066. return [ b.getReversed(), a ];
  1067. }
  1068. } else {
  1069. // Case 2: The element to move got merged and it was the only element to move.
  1070. // In this case just don't do anything, leave the node in the graveyard. Without special case
  1071. // it would be a move operation that moves 0 nodes, so maybe it is better just to return no-op.
  1072. //
  1073. if ( a.howMany == 1 ) {
  1074. if ( !context.bWasUndone ) {
  1075. return getNoOp();
  1076. } else {
  1077. a.sourcePosition = Position.createFromPosition( b.graveyardPosition );
  1078. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  1079. return [ a ];
  1080. }
  1081. }
  1082. }
  1083. }
  1084. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1085. const transformed = moveRange._getTransformedByMergeOperation( b );
  1086. a.sourcePosition = transformed.start;
  1087. a.howMany = transformed.end.offset - transformed.start.offset;
  1088. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  1089. return [ a ];
  1090. } );
  1091. setTransformation( MoveOperation, WrapOperation, ( a, b, context ) => {
  1092. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1093. const newTargetPosition = a.targetPosition._getTransformedByWrapOperation( b );
  1094. // Case 1: Some of the nodes to move got wrapped. In this case multiple ranges to move might need to be generated.
  1095. //
  1096. // First paragraph and the image should are wrapped, while the two images are moved after the last paragraph:
  1097. // [<paragraph>Foo</paragraph>{<image />]<image />}<paragraph>Bar</paragraph>
  1098. //
  1099. // After wrap:
  1100. // <blockQuote><paragraph>Foo</paragraph>[<image />]</blockQuote>[<image />]<paragraph>Bar</paragraph>
  1101. //
  1102. // After move:
  1103. // <blockQuote><paragraph>Foo</paragraph></blockQuote><paragraph>Bar</paragraph><image /><image />
  1104. //
  1105. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  1106. // If move range contains or is equal to the wrapped range, just move it all together.
  1107. // Change `howMany` to reflect that nodes got wrapped.
  1108. if ( moveRange.containsRange( b.wrappedRange, true ) ) {
  1109. a.howMany = a.howMany - b.howMany + 1;
  1110. return [ a ];
  1111. }
  1112. const result = [];
  1113. let difference = moveRange.getDifference( b.wrappedRange )[ 0 ];
  1114. let common = moveRange.getIntersection( b.wrappedRange );
  1115. if ( difference ) {
  1116. difference = difference._getTransformedByWrapOperation( b );
  1117. result.push( new MoveOperation( difference.start, difference.end.offset - difference.start.offset, newTargetPosition, 0 ) );
  1118. }
  1119. if ( common ) {
  1120. common = common._getTransformedByWrapOperation( b );
  1121. result.push( new MoveOperation( common.start, common.end.offset - common.start.offset, newTargetPosition, 0 ) );
  1122. }
  1123. return result;
  1124. }
  1125. // The default case.
  1126. //
  1127. const transformed = moveRange._getTransformedByWrapOperation( b );
  1128. a.sourcePosition = transformed.start;
  1129. a.howMany = transformed.end.offset - transformed.start.offset;
  1130. a.targetPosition = newTargetPosition;
  1131. if ( a.targetPosition.isEqual( b.position ) && context.abRelation == 'insertInside' ) {
  1132. a.targetPosition = b.targetPosition;
  1133. }
  1134. return [ a ];
  1135. } );
  1136. setTransformation( MoveOperation, UnwrapOperation, ( a, b ) => {
  1137. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1138. const transformed = moveRange._getTransformedByUnwrapOperation( b );
  1139. a.sourcePosition = transformed.start;
  1140. a.howMany = transformed.end.offset - transformed.start.offset;
  1141. a.targetPosition = a.targetPosition._getTransformedByUnwrapOperation( b );
  1142. return [ a ];
  1143. } );
  1144. // -----------------------
  1145. setTransformation( RenameOperation, InsertOperation, ( a, b ) => {
  1146. a.position = a.position._getTransformedByInsertOperation( b );
  1147. return [ a ];
  1148. } );
  1149. setTransformation( RenameOperation, MergeOperation, ( a, b ) => {
  1150. if ( a.position.isEqual( b.deletionPosition ) ) {
  1151. a.position = Position.createFromPosition( b.graveyardPosition );
  1152. a.position.stickiness = 'toNext';
  1153. return [ a ];
  1154. }
  1155. a.position = a.position._getTransformedByMergeOperation( b );
  1156. return [ a ];
  1157. } );
  1158. setTransformation( RenameOperation, MoveOperation, ( a, b ) => {
  1159. a.position = a.position._getTransformedByMoveOperation( b );
  1160. return [ a ];
  1161. } );
  1162. setTransformation( RenameOperation, RenameOperation, ( a, b, context ) => {
  1163. if ( a.position.isEqual( b.position ) ) {
  1164. if ( context.aIsStrong ) {
  1165. a.oldName = b.newName;
  1166. } else {
  1167. return getNoOp();
  1168. }
  1169. }
  1170. return [ a ];
  1171. } );
  1172. setTransformation( RenameOperation, SplitOperation, ( a, b ) => {
  1173. // Case 1: The element to rename has been split. In this case, the new element should be also renamed.
  1174. //
  1175. // This element should be renamed:
  1176. // <paragraph>Foobar</paragraph>
  1177. //
  1178. // After split:
  1179. // <paragraph>Foo</paragraph><paragraph>bar</paragraph>
  1180. //
  1181. // Rename both elements:
  1182. // <listItem>Foo</listItem><listItem>bar</listItem>
  1183. //
  1184. const renamePath = a.position.path;
  1185. const splitPath = b.position.getParentPath();
  1186. if ( compareArrays( renamePath, splitPath ) == 'same' && !b.graveyardPosition ) {
  1187. const extraRename = new RenameOperation( a.position.getShiftedBy( 1 ), a.oldName, a.newName, 0 );
  1188. return [ a, extraRename ];
  1189. }
  1190. // The default case.
  1191. //
  1192. a.position = a.position._getTransformedBySplitOperation( b );
  1193. return [ a ];
  1194. } );
  1195. setTransformation( RenameOperation, WrapOperation, ( a, b ) => {
  1196. a.position = a.position._getTransformedByWrapOperation( b );
  1197. return [ a ];
  1198. } );
  1199. setTransformation( RenameOperation, UnwrapOperation, ( a, b ) => {
  1200. a.position = a.position._getTransformedByUnwrapOperation( b );
  1201. return [ a ];
  1202. } );
  1203. // -----------------------
  1204. setTransformation( RootAttributeOperation, RootAttributeOperation, ( a, b, context ) => {
  1205. if ( a.root === b.root && a.key === b.key ) {
  1206. if ( !context.aIsStrong || a.newValue === b.newValue ) {
  1207. return [ new NoOperation( 0 ) ];
  1208. } else {
  1209. a.oldValue = b.newValue;
  1210. }
  1211. }
  1212. return [ a ];
  1213. } );
  1214. // -----------------------
  1215. setTransformation( SplitOperation, InsertOperation, ( a, b, context ) => {
  1216. if ( a.position.isEqual( b.position ) && context.baRelation == 'insertAtSource' ) {
  1217. a.howMany += b.howMany;
  1218. return [ a ];
  1219. }
  1220. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1221. a.howMany += b.howMany;
  1222. }
  1223. a.position = a.position._getTransformedByInsertOperation( b );
  1224. return [ a ];
  1225. } );
  1226. setTransformation( SplitOperation, MergeOperation, ( a, b ) => {
  1227. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  1228. a.howMany += b.howMany;
  1229. }
  1230. a.position = a.position._getTransformedByMergeOperation( b );
  1231. if ( a.graveyardPosition ) {
  1232. a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
  1233. }
  1234. return [ a ];
  1235. } );
  1236. setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
  1237. if ( a.graveyardPosition ) {
  1238. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  1239. }
  1240. // Case 1: If the split position is inside the moved range, we need to move the split position to a proper place.
  1241. // The position cannot be moved together with moved range because that would result in splitting an incorrect element.
  1242. //
  1243. // Characters `bc` should be moved to the second paragraph while split position is between them:
  1244. // <paragraph>A[b|c]d</paragraph><paragraph>Xyz</paragraph>
  1245. //
  1246. // After move, new split position is incorrect:
  1247. // <paragraph>Ad</paragraph><paragraph>Xb|cyz</paragraph>
  1248. //
  1249. // Correct split position:
  1250. // <paragraph>A|d</paragraph><paragraph>Xbcyz</paragraph>
  1251. //
  1252. // After split:
  1253. // <paragraph>A</paragraph><paragraph>d</paragraph><paragraph>Xbcyz</paragraph>
  1254. //
  1255. const rangeToMove = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  1256. if ( a.position.hasSameParentAs( b.sourcePosition ) && rangeToMove.containsPosition( a.position ) ) {
  1257. const howManyRemoved = b.howMany - ( a.position.offset - b.sourcePosition.offset );
  1258. a.howMany -= howManyRemoved;
  1259. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1260. a.howMany += b.howMany;
  1261. }
  1262. a.position = Position.createFromPosition( b.sourcePosition );
  1263. return [ a ];
  1264. }
  1265. if ( a.position.isEqual( b.targetPosition ) && context.abRelation == 'splitBefore' ) {
  1266. a.howMany += b.howMany;
  1267. a.position = a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1268. return [ a ];
  1269. }
  1270. // The default case.
  1271. //
  1272. if ( a.position.hasSameParentAs( b.sourcePosition ) && a.position.offset < b.sourcePosition.offset ) {
  1273. a.howMany -= b.howMany;
  1274. }
  1275. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1276. a.howMany += b.howMany;
  1277. }
  1278. a.position = a.position._getTransformedByMoveOperation( b );
  1279. return [ a ];
  1280. } );
  1281. setTransformation( SplitOperation, SplitOperation, ( a, b, context ) => {
  1282. if ( a.position.isEqual( b.position ) ) {
  1283. if ( !a.graveyardPosition && !b.graveyardPosition ) {
  1284. return getNoOp();
  1285. }
  1286. if ( a.graveyardPosition && b.graveyardPosition && a.graveyardPosition.isEqual( b.graveyardPosition ) ) {
  1287. return getNoOp();
  1288. }
  1289. a.howMany = 0;
  1290. } else if ( a.position.isEqual( b.insertionPosition ) && context.abRelation == 'splitBefore' ) {
  1291. return [ a ];
  1292. } else {
  1293. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1294. a.howMany -= b.howMany;
  1295. }
  1296. a.position = a.position._getTransformedBySplitOperation( b );
  1297. }
  1298. if ( a.graveyardPosition && b.graveyardPosition ) {
  1299. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1300. }
  1301. return [ a ];
  1302. } );
  1303. setTransformation( SplitOperation, WrapOperation, ( a, b, context ) => {
  1304. // Case 1: If split position has been wrapped, reverse the wrapping so that split can be applied as intended.
  1305. // This is an edge case scenario where it is difficult to find a correct solution.
  1306. // Since it will be a rare (or only theoretical) scenario, the algorithm will perform the easy solution.
  1307. //
  1308. if ( a.position.hasSameParentAs( b.position ) && b.wrappedRange.containsPosition( a.position ) ) {
  1309. const reversed = b.getReversed();
  1310. // Unwrap operation (reversed wrap) always puts a node into a graveyard. Not every wrap operation pulls a node
  1311. // from the graveyard, though. This means that after reversing a wrap operation, there might be a need to
  1312. // update a position in graveyard.
  1313. if ( !b.graveyardPosition && a.graveyardPosition ) {
  1314. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  1315. }
  1316. return [ reversed, a ];
  1317. }
  1318. if ( a.position.isEqual( b.position ) && context.abRelation == 'splitInside' ) {
  1319. a.position = b.targetPosition;
  1320. a.howMany = b.howMany;
  1321. } else {
  1322. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1323. a.howMany = a.howMany + 1 - b.howMany;
  1324. }
  1325. a.position = a.position._getTransformedByWrapOperation( b );
  1326. }
  1327. if ( a.graveyardPosition ) {
  1328. a.graveyardPosition = a.graveyardPosition._getTransformedByWrapOperation( b );
  1329. }
  1330. return [ a ];
  1331. } );
  1332. setTransformation( SplitOperation, UnwrapOperation, ( a, b, context ) => {
  1333. const splitInside = a.position.hasSameParentAs( b.position );
  1334. if ( splitInside && !context.bWasUndone ) {
  1335. const path = b.graveyardPosition.path.slice();
  1336. path.push( 0 );
  1337. a.position = new Position( b.graveyardPosition.root, path );
  1338. a.howMany = 0;
  1339. } else {
  1340. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1341. a.howMany = a.howMany - 1 + b.howMany;
  1342. }
  1343. a.position = a.position._getTransformedByUnwrapOperation( b );
  1344. }
  1345. if ( a.graveyardPosition ) {
  1346. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  1347. }
  1348. return [ a ];
  1349. } );
  1350. // -----------------------
  1351. setTransformation( WrapOperation, InsertOperation, ( a, b, context ) => {
  1352. if ( a.position.isEqual( b.position ) && context.baRelation == 'insertInside' ) {
  1353. a.howMany += b.howMany;
  1354. return [ a ];
  1355. }
  1356. const transformed = a.wrappedRange._getTransformedByInsertOperation( b, false )[ 0 ];
  1357. a.position = transformed.start;
  1358. a.howMany = transformed.end.offset - transformed.start.offset;
  1359. return [ a ];
  1360. } );
  1361. setTransformation( WrapOperation, MergeOperation, ( a, b ) => {
  1362. if ( a.graveyardPosition ) {
  1363. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( b.graveyardPosition, 1 );
  1364. }
  1365. // Case 1: The element to wrap got merged.
  1366. //
  1367. if ( a.position.isEqual( b.deletionPosition ) ) {
  1368. a.position = Position.createFromPosition( b.graveyardPosition );
  1369. a.position.stickiness = 'toNext';
  1370. return [ a ];
  1371. }
  1372. const transformed = a.wrappedRange._getTransformedByMergeOperation( b );
  1373. a.position = transformed.start;
  1374. a.howMany = transformed.end.offset - transformed.start.offset;
  1375. return [ a ];
  1376. } );
  1377. setTransformation( WrapOperation, MoveOperation, ( a, b, context ) => {
  1378. if ( a.graveyardPosition ) {
  1379. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  1380. }
  1381. if ( a.position.isEqual( b.targetPosition ) && context.baRelation == 'insertInside' ) {
  1382. a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1383. a.howMany += b.howMany;
  1384. return [ a ];
  1385. }
  1386. const moveRange = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  1387. const wrappedRange = a.wrappedRange;
  1388. if ( moveRange.containsRange( wrappedRange, true ) ) {
  1389. a.position = a.position._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  1390. } else {
  1391. let transformed = wrappedRange._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1392. transformed = transformed._getTransformedByInsertion( b.targetPosition, b.howMany, false )[ 0 ];
  1393. a.position = transformed.start;
  1394. a.howMany = transformed.end.offset - transformed.start.offset;
  1395. }
  1396. return [ a ];
  1397. } );
  1398. setTransformation( WrapOperation, SplitOperation, ( a, b, context ) => {
  1399. // Case 1: If range to wrap got split cancel the wrapping.
  1400. // Do that only if this is not undo mode. If `b` operation was earlier transformed by unwrap operation
  1401. // and the split position was inside the unwrapped range, then proceed without special case.
  1402. //
  1403. const isInside = a.position.hasSameParentAs( b.position ) && a.wrappedRange.containsPosition( b.position );
  1404. if ( isInside && context.baRelation !== 'splitInside' ) {
  1405. // We cannot just return no-op in this case, because in the mirror case scenario the wrap is reversed, which
  1406. // might introduce a new node in the graveyard (if the wrap didn't have `graveyardPosition`, then the wrap
  1407. // created a new element which was put to the graveyard when the wrap was reversed).
  1408. //
  1409. // Instead, a node in graveyard will be inserted.
  1410. //
  1411. if ( a.element ) {
  1412. const graveyard = a.position.root.document.graveyard;
  1413. const graveyardPosition = new Position( graveyard, [ 0 ] );
  1414. return [ new InsertOperation( graveyardPosition, a.element, 0 ) ];
  1415. } else {
  1416. return getNoOp();
  1417. }
  1418. }
  1419. if ( a.graveyardPosition && b.graveyardPosition ) {
  1420. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1421. }
  1422. // Case 2: If last element from range to wrap has been split, include the newly created element in the wrap range.
  1423. //
  1424. if ( b.insertionPosition.isEqual( a.wrappedRange.end ) ) {
  1425. a.howMany++;
  1426. return [ a ];
  1427. }
  1428. // The default case.
  1429. //
  1430. const transformed = a.wrappedRange._getTransformedBySplitOperation( b );
  1431. a.position = transformed.start;
  1432. a.howMany = transformed.end.offset - transformed.start.offset;
  1433. return [ a ];
  1434. } );
  1435. setTransformation( WrapOperation, WrapOperation, ( a, b, context ) => {
  1436. let newGraveyardPosition = a.graveyardPosition;
  1437. if ( a.graveyardPosition && b.graveyardPosition ) {
  1438. newGraveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1439. }
  1440. // Case 1: If ranges to wrap intersect on the same level then there is a conflict.
  1441. // Depending on `context.aIsStrong` the nodes in the intersecting part should be left as they were wrapped
  1442. // or moved to the new wrapping element.
  1443. //
  1444. // `Foo` and `Bar` are to be wrapped in `blockQuote`, while `Bar` and `Xyz` in `div`.
  1445. // [<paragraph>Foo</paragraph>{<paragraph>Bar</paragraph>]<paragraph>Xyz</paragraph>}
  1446. //
  1447. // After `blockQuote` wrap:
  1448. // <blockQuote>
  1449. // <paragraph>Foo</paragraph><paragraph>Bar</paragraph>
  1450. // </blockQuote>
  1451. // <paragraph>Xyz</paragraph>
  1452. //
  1453. // After `div` wrap:
  1454. // <blockQuote>
  1455. // <paragraph>Foo</paragraph><paragraph>Bar</paragraph>
  1456. // </blockQuote>
  1457. // <div>
  1458. // <paragraph>Xyz</paragraph>
  1459. // </div>
  1460. //
  1461. // Or, if `div` wrap is stronger:
  1462. // <blockQuote>
  1463. // <paragraph>Foo</paragraph>
  1464. // </blockQuote>
  1465. // <div>
  1466. // <paragraph>Bar</paragraph><paragraph>Xyz</paragraph>
  1467. // </div>
  1468. //
  1469. // The range from incoming operation may be also wholly included in the range from operation `b`.
  1470. // Then, cancel the wrapping. The same happens when the ranges are identical but in that case,
  1471. // `context.aIsStrong` decides which wrapping should be cancelled.
  1472. //
  1473. // Lastly, the range from operation `b` may be wholly included in the range from incoming operation.
  1474. // Then, unwrap the range from operation `b` and do a wrap on full range from operation `a`.
  1475. //
  1476. if ( a.position.hasSameParentAs( b.position ) ) {
  1477. const ranges = a.wrappedRange.getDifference( b.wrappedRange );
  1478. // Range from `a` is contained in range from `b` or ranges are equal.
  1479. if ( ranges.length == 0 ) {
  1480. if ( a.wrappedRange.isEqual( b.wrappedRange ) && context.aIsStrong ) {
  1481. // If ranges are equal and `a` is a stronger operation, reverse `b` operation and then apply `a` operation.
  1482. const reversed = b.getReversed();
  1483. // Unwrap operation (reversed wrap) always puts a node into a graveyard. Not every wrap operation pulls a node
  1484. // from the graveyard, though. This means that after reversing a wrap operation, there might be a need to
  1485. // update a position in graveyard.
  1486. if ( !b.graveyardPosition && a.graveyardPosition ) {
  1487. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  1488. }
  1489. return [ reversed, a ];
  1490. }
  1491. // If `a` is contained in `b` or they are same but `b` is stronger, operation `a` should do nothing.
  1492. // However, to keep the model state same on both clients, it is needed to create a wrapping element in the graveyard.
  1493. const graveyard = a.position.root.document.graveyard;
  1494. a.position = new Position( graveyard, [ 0 ] );
  1495. a.howMany = 0;
  1496. a.graveyardPosition = newGraveyardPosition;
  1497. return [ a ];
  1498. }
  1499. // Ranges intersect.
  1500. else {
  1501. // Range from `b` has some extra nodes other than nodes from `a`.
  1502. if ( !a.wrappedRange.containsRange( b.wrappedRange, true ) ) {
  1503. if ( context.aIsStrong ) {
  1504. // If the incoming wrap operation is strong, we need to reverse the previous wrap, then apply the incoming
  1505. // operation as is, then re-wrap the other nodes that were wrapped in the previous wrap.
  1506. //
  1507. // Content already wrapped into `blockQuote` but that wrap is not strong:
  1508. // <blockQuote><p>Foo</p><p>Bar</p></blockQuote><p>Xyz</p>
  1509. //
  1510. // Unwrap:
  1511. // <p>Foo</p><p>Bar</p><p>Xyz</p>
  1512. //
  1513. // Wrap with stronger wrap (`a`):
  1514. // <p>Foo</p><div><p>Bar</p><p>Xyz</p></div>
  1515. //
  1516. // Re-wrap:
  1517. // <blockQuote><p>Foo</p></blockQuote><div><p>Bar</p><p>Xyz</p></div>
  1518. //
  1519. const reversed = b.getReversed();
  1520. // Unwrap operation (reversed wrap) always puts a node into a graveyard. Not every wrap operation pulls a node
  1521. // from the graveyard, though. This means that after reversing a wrap operation, there might be a need to
  1522. // update a position in graveyard.
  1523. if ( !b.graveyardPosition && a.graveyardPosition ) {
  1524. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  1525. }
  1526. const bOnlyRange = b.wrappedRange.getDifference( a.wrappedRange )[ 0 ];
  1527. const rewrapRange = bOnlyRange._getTransformedByWrapOperation( a );
  1528. const rewrapHowMany = rewrapRange.end.offset - rewrapRange.start.offset;
  1529. const rewrap = new WrapOperation( rewrapRange.start, rewrapHowMany, reversed.graveyardPosition, 0 );
  1530. return [ reversed, a, rewrap ];
  1531. } else {
  1532. // If the incoming wrap operation is not strong, just wrap those nodes which were not wrapped already.
  1533. const range = ranges[ 0 ]._getTransformedByWrapOperation( b );
  1534. a.position = range.start;
  1535. a.howMany = range.end.offset - range.start.offset;
  1536. a.graveyardPosition = newGraveyardPosition;
  1537. return [ a ];
  1538. }
  1539. }
  1540. // Range from `b` is contained in range from `a`. Reverse operation `b` in addition to operation `a`.
  1541. else {
  1542. const reversed = b.getReversed();
  1543. // Unwrap operation (reversed wrap) always puts a node into a graveyard. Not every wrap operation pulls a node
  1544. // from the graveyard, though. This means that after reversing a wrap operation, there might be a need to
  1545. // update a position in graveyard.
  1546. if ( !b.graveyardPosition && a.graveyardPosition ) {
  1547. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  1548. }
  1549. return [ reversed, a ];
  1550. }
  1551. }
  1552. }
  1553. // The default case.
  1554. //
  1555. const transformed = a.wrappedRange._getTransformedByWrapOperation( b );
  1556. a.position = transformed.start;
  1557. a.howMany = transformed.end.offset - transformed.start.offset;
  1558. a.graveyardPosition = newGraveyardPosition;
  1559. return [ a ];
  1560. } );
  1561. setTransformation( WrapOperation, UnwrapOperation, ( a, b ) => {
  1562. const transformed = a.wrappedRange._getTransformedByUnwrapOperation( b );
  1563. a.position = transformed.start;
  1564. a.howMany = transformed.end.offset - transformed.start.offset;
  1565. if ( a.graveyardPosition ) {
  1566. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  1567. }
  1568. return [ a ];
  1569. } );
  1570. // -----------------------
  1571. setTransformation( UnwrapOperation, InsertOperation, ( a, b ) => {
  1572. // Case 1: Insert operation inserts nodes into the unwrapped element.
  1573. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  1574. //
  1575. if ( a.position.hasSameParentAs( b.position ) ) {
  1576. a.howMany += b.howMany;
  1577. }
  1578. a.position = a.position._getTransformedByInsertOperation( b );
  1579. return [ a ];
  1580. } );
  1581. setTransformation( UnwrapOperation, MergeOperation, ( a, b, context ) => {
  1582. // Case 1: The element to unwrap got merged.
  1583. // There are multiple possible solution to resolve this conflict:
  1584. // * unwrap the merge target element (all nodes are unwrapped),
  1585. // * cancel the unwrap (no nodes stayed unwrapped),
  1586. // * reverse the merge and apply the original unwrap (some nodes are unwrapped and some are not).
  1587. //
  1588. if ( a.position.isEqual( b.sourcePosition ) ) {
  1589. return [ b.getReversed(), a ];
  1590. }
  1591. // Case 2: The element to unwrap was merged-to and has new nodes.
  1592. //
  1593. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  1594. a.howMany += b.howMany;
  1595. }
  1596. if ( a.position.hasSameParentAs( b.graveyardPosition ) ) {
  1597. a.howMany++;
  1598. }
  1599. if ( a.position.hasSameParentAs( b.deletionPosition ) ) {
  1600. a.howMany--;
  1601. }
  1602. a.position = a.position._getTransformedByMergeOperation( b );
  1603. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  1604. a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
  1605. }
  1606. return [ a ];
  1607. } );
  1608. setTransformation( UnwrapOperation, MoveOperation, ( a, b ) => {
  1609. // Case 1: Move operation moves nodes from the unwrapped element.
  1610. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  1611. //
  1612. if ( a.position.hasSameParentAs( b.sourcePosition ) ) {
  1613. a.howMany -= b.howMany;
  1614. }
  1615. // Case 2: Move operation moves nodes into the unwrapped element.
  1616. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  1617. // Note, that case 1 and case 2 may happen together.
  1618. //
  1619. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  1620. a.howMany += b.howMany;
  1621. }
  1622. a.position = a.position._getTransformedByMoveOperation( b );
  1623. if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
  1624. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  1625. }
  1626. return [ a ];
  1627. } );
  1628. setTransformation( UnwrapOperation, SplitOperation, ( a, b ) => {
  1629. // Case 1: The element to unwrap got split, so now there are two elements to unwrap.
  1630. // This can be solved either by providing two unwrap operations or by reversing the split and applying the original unwrap.
  1631. //
  1632. if ( a.position.hasSameParentAs( b.position ) ) {
  1633. const reversed = b.getReversed();
  1634. // Merge operation (reversed split) always puts a node into a graveyard. Not every split operation pulls a node
  1635. // from the graveyard, though. This means that after reversing a split operation, there might be a need to
  1636. // update a position in graveyard.
  1637. if ( !b.graveyardPosition ) {
  1638. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  1639. }
  1640. return [ reversed, a ];
  1641. }
  1642. // Case 2: The split element is the last element in unwrapped element. In this case, we need to manually modify
  1643. // `howMany` property because it wouldn't be correctly calculated by `_getTransformedBySplitOperation`.
  1644. //
  1645. if ( a.position.hasSameParentAs( b.insertionPosition ) ) {
  1646. a.howMany++;
  1647. return [ a ];
  1648. }
  1649. a.position = a.position._getTransformedBySplitOperation( b );
  1650. if ( b.graveyardPosition && b.graveyardPosition.hasSameParentAs( a.position ) ) {
  1651. a.howMany--;
  1652. }
  1653. a.graveyardPosition = a.graveyardPosition._getTransformedBySplitOperation( b );
  1654. return [ a ];
  1655. } );
  1656. setTransformation( UnwrapOperation, WrapOperation, ( a, b ) => {
  1657. // Case 1: Wrapping took place inside the element to unwrap. `UnwrapOperation#howMany` needs to be updated.
  1658. //
  1659. if ( a.position.hasSameParentAs( b.position ) ) {
  1660. a.howMany = a.howMany - b.howMany + 1;
  1661. }
  1662. if ( b.graveyardPosition && compareArrays( a.position.getParentPath(), b.graveyardPosition.path ) == 'same' ) {
  1663. a.howMany = b.howMany;
  1664. }
  1665. // The default case.
  1666. //
  1667. a.position = a.position._getTransformedByWrapOperation( b );
  1668. a.graveyardPosition = a.graveyardPosition._getTransformedByWrapOperation( b );
  1669. return [ a ];
  1670. } );
  1671. setTransformation( UnwrapOperation, UnwrapOperation, ( a, b, context ) => {
  1672. // Case 1: Operations unwrap the same element.
  1673. //
  1674. if ( a.position.isEqual( b.position ) ) {
  1675. const path = b.graveyardPosition.path.slice();
  1676. path.push( 0 );
  1677. a.position = new Position( b.graveyardPosition.root, path );
  1678. a.howMany = 0;
  1679. a.graveyardPosition = Position.createFromPosition( b.graveyardPosition );
  1680. return [ a ];
  1681. }
  1682. a.position = a.position._getTransformedByUnwrapOperation( b );
  1683. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  1684. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  1685. }
  1686. return [ a ];
  1687. } );
  1688. // Checks whether MoveOperation targetPosition is inside a node from the moved range of the other MoveOperation.
  1689. function moveTargetIntoMovedRange( a, b ) {
  1690. return a.targetPosition._getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
  1691. }
  1692. // Helper function for `MoveOperation` x `MoveOperation` transformation.
  1693. // Convert given ranges and target position to move operations and return them.
  1694. // Ranges and target position will be transformed on-the-fly when generating operations.
  1695. // Given `ranges` should be in the order of how they were in the original transformed operation.
  1696. // Given `targetPosition` is the target position of the first range from `ranges`.
  1697. function makeMoveOperationsFromRanges( ranges, targetPosition ) {
  1698. // At this moment we have some ranges and a target position, to which those ranges should be moved.
  1699. // Order in `ranges` array is the go-to order of after transformation.
  1700. //
  1701. // We are almost done. We have `ranges` and `targetPosition` to make operations from.
  1702. // Unfortunately, those operations may affect each other. Precisely, first operation after move
  1703. // may affect source range and target position of second and third operation. Same with second
  1704. // operation affecting third.
  1705. //
  1706. // We need to fix those source ranges and target positions once again, before converting `ranges` to operations.
  1707. const operations = [];
  1708. // Keep in mind that nothing will be transformed if there is just one range in `ranges`.
  1709. for ( let i = 0; i < ranges.length; i++ ) {
  1710. // Create new operation out of a range and target position.
  1711. const op = makeMoveOperation( ranges[ i ], targetPosition );
  1712. operations.push( op );
  1713. // Transform other ranges by the generated operation.
  1714. for ( let j = i + 1; j < ranges.length; j++ ) {
  1715. // All ranges in `ranges` array should be:
  1716. // * non-intersecting (these are part of original operation source range), and
  1717. // * `targetPosition` does not target into them (opposite would mean that transformed operation targets "inside itself").
  1718. //
  1719. // This means that the transformation will be "clean" and always return one result.
  1720. ranges[ j ] = ranges[ j ]._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany )[ 0 ];
  1721. }
  1722. targetPosition = targetPosition._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany );
  1723. }
  1724. return operations;
  1725. }
  1726. function makeMoveOperation( range, targetPosition ) {
  1727. targetPosition.stickiness = 'toNone';
  1728. return new MoveOperation(
  1729. range.start,
  1730. range.end.offset - range.start.offset,
  1731. targetPosition,
  1732. 0
  1733. );
  1734. }