8
0

transform.js 107 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868
  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. /**
  18. * @module engine/model/operation/transform
  19. */
  20. /**
  21. * Sets a transformation function to be be used to transform instances of class `OperationA` by instances of class `OperationB`.
  22. *
  23. * The `transformationFunction` is passed three parameters:
  24. *
  25. * * `a` - operation to be transformed, an instance of `OperationA`,
  26. * * `b` - operation to be transformed by, an instance of `OperationB`,
  27. * * {@link module:engine/model/operation/transform~TransformationContext `context`} - object with additional information about
  28. * transformation context.
  29. *
  30. * The `transformationFunction` should return transformation result, which is an array with one or multiple
  31. * {@link module:engine/model/operation/operation~Operation operation} instances.
  32. *
  33. * @protected
  34. * @param {Function} OperationA
  35. * @param {Function} OperationB
  36. * @param {Function} transformationFunction Function to use for transforming.
  37. */
  38. function setTransformation( OperationA, OperationB, transformationFunction ) {
  39. let aGroup = transformations.get( OperationA );
  40. if ( !aGroup ) {
  41. aGroup = new Map();
  42. transformations.set( OperationA, aGroup );
  43. }
  44. aGroup.set( OperationB, transformationFunction );
  45. }
  46. /**
  47. * Returns a previously set transformation function for transforming an instance of `OperationA` by an instance of `OperationB`.
  48. *
  49. * If no transformation was set for given pair of operations, {@link module:engine/model/operation/transform~noUpdateTransformation}
  50. * is returned. This means that if no transformation was set, the `OperationA` instance will not change when transformed
  51. * by the `OperationB` instance.
  52. *
  53. * @private
  54. * @param {Function} OperationA
  55. * @param {Function} OperationB
  56. * @returns {Function} Function set to transform an instance of `OperationA` by an instance of `OperationB`.
  57. */
  58. function getTransformation( OperationA, OperationB ) {
  59. const aGroup = transformations.get( OperationA );
  60. if ( aGroup && aGroup.has( OperationB ) ) {
  61. return aGroup.get( OperationB );
  62. }
  63. return noUpdateTransformation;
  64. }
  65. /**
  66. * A transformation function that only clones operation to transform, without changing it.
  67. *
  68. * @private
  69. * @param {module:engine/model/operation/operation~Operation} a Operation to transform.
  70. * @returns {Array.<module:engine/model/operation/operation~Operation>}
  71. */
  72. function noUpdateTransformation( a ) {
  73. return [ a ];
  74. }
  75. /**
  76. * Transforms operation `a` by operation `b`.
  77. *
  78. * @param {module:engine/model/operation/operation~Operation} a Operation to be transformed.
  79. * @param {module:engine/model/operation/operation~Operation} b Operation to transform by.
  80. * @param {module:engine/model/operation/transform~TransformationContext} context Transformation context for this transformation.
  81. * @returns {Array.<module:engine/model/operation/operation~Operation>} Transformation result.
  82. */
  83. export function transform( a, b, context = {} ) {
  84. const transformationFunction = getTransformation( a.constructor, b.constructor );
  85. try {
  86. a = a.clone();
  87. return transformationFunction( a, b, context );
  88. } catch ( e ) {
  89. log.error( 'Error during operation transformation!', e.message );
  90. log.error( 'Transformed operation', a );
  91. log.error( 'Operation transformed by', b );
  92. log.error( 'context.aIsStrong', context.aIsStrong );
  93. log.error( 'context.aWasUndone', context.aWasUndone );
  94. log.error( 'context.bWasUndone', context.bWasUndone );
  95. log.error( 'context.abRelation', context.abRelation );
  96. log.error( 'context.baRelation', context.baRelation );
  97. throw e;
  98. }
  99. }
  100. /**
  101. * Performs a transformation of two sets of operations - `operationsA` and `operationsB`. The transformation is two-way -
  102. * both transformed `operationsA` and transformed `operationsB` are returned.
  103. *
  104. * Note, that the first operation in each set should base on the same document state (
  105. * {@link module:engine/model/document~Document#version document version}).
  106. *
  107. * It is assumed that `operationsA` are "more important" during conflict resolution between two operations.
  108. *
  109. * New copies of both passed arrays and operations inside them are returned. Passed arguments are not altered.
  110. *
  111. * Base versions of the transformed operations sets are updated accordingly. For example, assume that base versions are `4`
  112. * and there are `3` operations in `operationsA` and `5` operations in `operationsB`. Then:
  113. *
  114. * * transformed `operationsA` will start from base version `9` (`4` base version + `5` operations B),
  115. * * transformed `operationsB` will start from base version `7` (`4` base version + `3` operations A).
  116. *
  117. * If no operation was broken into two during transformation, then both sets will end up with an operation that bases on version `11`:
  118. *
  119. * * transformed `operationsA` start from `9` and there are `3` of them, so the last will have `baseVersion` equal to `11`,
  120. * * transformed `operationsB` start from `7` and there are `5` of them, so the last will have `baseVersion` equal to `11`.
  121. *
  122. * @param {Array.<module:engine/model/operation/operation~Operation>} operationsA
  123. * @param {Array.<module:engine/model/operation/operation~Operation>} operationsB
  124. * @param {Object} options Additional transformation options.
  125. * @param {module:engine/model/document~Document|null} options.document Document which the operations change.
  126. * @param {Boolean} [options.useRelations=false] Whether during transformation relations should be used (used during undo for
  127. * better conflict resolution).
  128. * @param {Boolean} [options.padWithNoOps=false] Whether additional {@link module:engine/model/operation/nooperation~NoOperation}s
  129. * should be added to the transformation results to force the same last base version for both transformed sets (in case
  130. * if some operations got broken into multiple operations during transformation).
  131. * @returns {Object} Transformation result.
  132. * @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsA Transformed `operationsA`.
  133. * @returns {Array.<module:engine/model/operation/operation~Operation>} return.operationsB Transformed `operationsB`.
  134. */
  135. export function transformSets( operationsA, operationsB, options ) {
  136. // Create new arrays so the originally passed arguments are not changed.
  137. // No need to clone operations, they are cloned as they are transformed.
  138. operationsA = operationsA.slice();
  139. operationsB = operationsB.slice();
  140. // If one of sets is empty there is simply nothing to transform, so return sets as they are.
  141. if ( operationsA.length == 0 || operationsB.length == 0 ) {
  142. return { operationsA, operationsB };
  143. }
  144. //
  145. // Following is a description of transformation process:
  146. //
  147. // There are `operationsA` and `operationsB` to be transformed, both by both.
  148. //
  149. // So, suppose we have sets of two operations each: `operationsA` = `[ a1, a2 ]`, `operationsB` = `[ b1, b2 ]`.
  150. //
  151. // Remember, that we can only transform operations that base on the same context. We assert that `a1` and `b1` base on
  152. // the same context and we transform them. Then, we get `a1'` and `b1'`. `a2` bases on a context with `a1` -- `a2`
  153. // is an operation that followed `a1`. Similarly, `b2` bases on a context with `b1`.
  154. //
  155. // However, since `a1'` is a result of transformation by `b1`, `a1'` now also has a context with `b1`. This means that
  156. // we can safely transform `a1'` by `b2`. As we finish transforming `a1`, we also transformed all `operationsB`.
  157. // All `operationsB` also have context including `a1`. Now, we can properly transform `a2` by those operations.
  158. //
  159. // The transformation process can be visualized on a transformation diagram ("diamond diagram"):
  160. //
  161. // [the initial state]
  162. // [common for a1 and b1]
  163. //
  164. // *
  165. // / \
  166. // / \
  167. // b1 a1
  168. // / \
  169. // / \
  170. // * *
  171. // / \ / \
  172. // / \ / \
  173. // b2 a1' b1' a2
  174. // / \ / \
  175. // / \ / \
  176. // * * *
  177. // \ / \ /
  178. // \ / \ /
  179. // a1'' b2' a2' b1''
  180. // \ / \ /
  181. // \ / \ /
  182. // * *
  183. // \ /
  184. // \ /
  185. // a2'' b2''
  186. // \ /
  187. // \ /
  188. // *
  189. //
  190. // [the final state]
  191. //
  192. // The final state can be reached from the initial state by applying `a1`, `a2`, `b1''` and `b2''`, as well as by
  193. // applying `b1`, `b2`, `a1''`, `a2''`. Note how the operations get to a proper common state before each pair is
  194. // transformed.
  195. //
  196. // Another thing to consider is that an operation during transformation can be broken into multiple operations.
  197. // Suppose that `a1` * `b1` = `[ a11', a12' ]` (instead of `a1'` that we considered previously).
  198. //
  199. // In that case, we leave `a12'` for later and we continue transforming `a11'` until it is transformed by all `operationsB`
  200. // (in our case it is just `b2`). At this point, `b1` is transformed by "whole" `a1`, while `b2` is only transformed
  201. // by `a11'`. Similarly, `a12'` is only transformed by `b1`. This leads to a conclusion that we need to start transforming `a12'`
  202. // from the moment just after it was broken. So, `a12'` is transformed by `b2`. Now, "the whole" `a1` is transformed
  203. // by `operationsB`, while all `operationsB` are transformed by "the whole" `a1`. This means that we can continue with
  204. // following `operationsA` (in our case it is just `a2`).
  205. //
  206. // Of course, also `operationsB` can be broken. However, since we focus on transforming operation `a` to the end,
  207. // the only thing to do is to store both pieces of operation `b`, so that the next transformed operation `a` will
  208. // be transformed by both of them.
  209. //
  210. // *
  211. // / \
  212. // / \
  213. // / \
  214. // b1 a1
  215. // / \
  216. // / \
  217. // / \
  218. // * *
  219. // / \ / \
  220. // / a11' / \
  221. // / \ / \
  222. // b2 * b1' a2
  223. // / / \ / \
  224. // / / a12' / \
  225. // / / \ / \
  226. // * b2' * *
  227. // \ / / \ /
  228. // a11'' / b21'' \ /
  229. // \ / / \ /
  230. // * * a2' b1''
  231. // \ / \ \ /
  232. // a12'' b22''\ \ /
  233. // \ / \ \ /
  234. // * a2'' *
  235. // \ \ /
  236. // \ \ b21'''
  237. // \ \ /
  238. // a2''' *
  239. // \ /
  240. // \ b22'''
  241. // \ /
  242. // *
  243. //
  244. // Note, how `a1` is broken and transformed into `a11'` and `a12'`, while `b2'` got broken and transformed into `b21''` and `b22''`.
  245. //
  246. // Having all that on mind, here is an outline for the transformation process algorithm:
  247. //
  248. // 1. We have `operationsA` and `operationsB` array, which we dynamically update as the transformation process goes.
  249. //
  250. // 2. We take next (or first) operation from `operationsA` and check from which operation `b` we need to start transforming it.
  251. // All original `operationsA` are set to be transformed starting from the first operation `b`.
  252. //
  253. // 3. We take operations from `operationsB`, one by one, starting from the correct one, and transform operation `a`
  254. // by operation `b` (and vice versa). We update `operationsA` and `operationsB` by replacing the original operations
  255. // with the transformation results.
  256. //
  257. // 4. If operation is broken into multiple operations, we save all the new operations in the place of the
  258. // original operation.
  259. //
  260. // 5. Additionally, if operation `a` was broken, for the "new" operation, we remember from which operation `b` it should
  261. // be transformed by.
  262. //
  263. // 6. We continue transforming "current" operation `a` until it is transformed by all `operationsB`. Then, go to 2.
  264. // unless the last operation `a` was transformed.
  265. //
  266. // The actual implementation of the above algorithm is slightly different, as only one loop (while) is used.
  267. // The difference is that we have "current" `a` operation to transform and we store the index of the next `b` operation
  268. // to transform by. Each loop operates on two indexes then: index pointing to currently processed `a` operation and
  269. // index pointing to next `b` operation. Each loop is just one `a * b` + `b * a` transformation. After each loop
  270. // operation `b` index is updated. If all `b` operations were visited for the current `a` operation, we change
  271. // current `a` operation index to the next one.
  272. //
  273. // For each operation `a`, keeps information what is the index in `operationsB` from which the transformation should start.
  274. const nextTransformIndex = new WeakMap();
  275. // For all the original `operationsA`, set that they should be transformed starting from the first of `operationsB`.
  276. for ( const op of operationsA ) {
  277. nextTransformIndex.set( op, 0 );
  278. }
  279. // Additional data that is used for some postprocessing after the main transformation process is done.
  280. const data = {
  281. nextBaseVersionA: operationsA[ operationsA.length - 1 ].baseVersion + 1,
  282. nextBaseVersionB: operationsB[ operationsB.length - 1 ].baseVersion + 1,
  283. originalOperationsACount: operationsA.length,
  284. originalOperationsBCount: operationsB.length
  285. };
  286. const contextFactory = new ContextFactory( options.document, options.useRelations );
  287. contextFactory.setOriginalOperations( operationsA );
  288. contextFactory.setOriginalOperations( operationsB );
  289. // Index of currently transformed operation `a`.
  290. let i = 0;
  291. // While not all `operationsA` are transformed...
  292. while ( i < operationsA.length ) {
  293. // Get "current" operation `a`.
  294. const opA = operationsA[ i ];
  295. // For the "current" operation `a`, get the index of the next operation `b` to transform by.
  296. const indexB = nextTransformIndex.get( opA );
  297. // If operation `a` was already transformed by every operation `b`, change "current" operation `a` to the next one.
  298. if ( indexB == operationsB.length ) {
  299. i++;
  300. continue;
  301. }
  302. const opB = operationsB[ indexB ];
  303. // Transform `a` by `b` and `b` by `a`.
  304. const newOpsA = transform( opA, opB, contextFactory.getContext( opA, opB, true ) );
  305. const newOpsB = transform( opB, opA, contextFactory.getContext( opB, opA, false ) );
  306. // As a result we get one or more `newOpsA` and one or more `newOpsB` operations.
  307. // Update contextual information about operations.
  308. contextFactory.updateRelation( opA, opB );
  309. contextFactory.setOriginalOperations( newOpsA, opA );
  310. contextFactory.setOriginalOperations( newOpsB, opB );
  311. // For new `a` operations, update their index of the next operation `b` to transform them by.
  312. //
  313. // This is needed even if there was only one result (`a` was not broken) because that information is used
  314. // at the beginning of this loop every time.
  315. for ( const newOpA of newOpsA ) {
  316. // Acknowledge, that operation `b` also might be broken into multiple operations.
  317. //
  318. // This is why we raise `indexB` not just by 1. If `newOpsB` are multiple operations, they will be
  319. // spliced in the place of `opB`. So we need to change `transformBy` accordingly, so that an operation won't
  320. // be transformed by the same operation (part of it) again.
  321. nextTransformIndex.set( newOpA, indexB + newOpsB.length );
  322. }
  323. // Update `operationsA` and `operationsB` with the transformed versions.
  324. operationsA.splice( i, 1, ...newOpsA );
  325. operationsB.splice( indexB, 1, ...newOpsB );
  326. }
  327. if ( options.padWithNoOps ) {
  328. // If no-operations padding is enabled, count how many extra `a` and `b` operations were generated.
  329. const brokenOperationsACount = operationsA.length - data.originalOperationsACount;
  330. const brokenOperationsBCount = operationsB.length - data.originalOperationsBCount;
  331. // Then, if that number is not the same, pad `operationsA` or `operationsB` with correct number of no-ops so
  332. // that the base versions are equalled.
  333. //
  334. // Note that only one array will be updated, as only one of those subtractions can be greater than zero.
  335. padWithNoOps( operationsA, brokenOperationsBCount - brokenOperationsACount );
  336. padWithNoOps( operationsB, brokenOperationsACount - brokenOperationsBCount );
  337. }
  338. // Finally, update base versions of transformed operations.
  339. updateBaseVersions( operationsA, data.nextBaseVersionB );
  340. updateBaseVersions( operationsB, data.nextBaseVersionA );
  341. return { operationsA, operationsB };
  342. }
  343. // Gathers additional data about operations processed during transformation. Can be used to obtain contextual information
  344. // about two operations that are about to be transformed. This contextual information can be used for better conflict resolution.
  345. class ContextFactory {
  346. // Creates `ContextFactory` instance.
  347. //
  348. // @param {module:engine/model/document~Document} document Document which the operations change.
  349. // @param {Boolean} useRelations Whether during transformation relations should be used (used during undo for
  350. // better conflict resolution).
  351. constructor( document, useRelations ) {
  352. // `model.History` instance which information about undone operations will be taken from.
  353. this._history = document.history;
  354. // Whether additional context should be used.
  355. this._useRelations = useRelations;
  356. // For each operation that is created during transformation process, we keep a reference to the original operation
  357. // which it comes from. The original operation works as a kind of "identifier". Every contextual information
  358. // gathered during transformation that we want to save for given operation, is actually saved for the original operation.
  359. // This way no matter if operation `a` is cloned, then transformed, even breaks, we still have access to the previously
  360. // gathered data through original operation reference.
  361. this._originalOperations = new Map();
  362. // Relations is a double-map structure (maps in map) where for two operations we store how those operations were related
  363. // to each other. Those relations are evaluated during transformation process. For every transformated pair of operations
  364. // we keep relations between them.
  365. this._relations = new Map();
  366. }
  367. // Sets "original operation" for given operations.
  368. //
  369. // During transformation process, operations are cloned, then changed, then processed again, sometimes broken into two
  370. // or multiple operations. When gathering additional data it is important that all operations can be somehow linked
  371. // so a cloned and transformed "version" still kept track of the data assigned earlier to it.
  372. //
  373. // The original operation object will be used as such an universal linking id. Throughout the transformation process
  374. // all cloned operations will refer to "the original operation" when storing and reading additional data.
  375. //
  376. // If `takeFrom` is not set, each operation from `operations` array will be assigned itself as "the original operation".
  377. // This should be used as an initialization step.
  378. //
  379. // If `takeFrom` is set, each operation from `operations` will be assigned the same original operation as assigned
  380. // for `takeFrom` operation. This should be used to update original operations. It should be used in a way that
  381. // `operations` are the result of `takeFrom` transformation to ensure proper "original operation propagation".
  382. //
  383. // @param {Array.<module:engine/model/operation/operation~Operation>} operations
  384. // @param {module:engine/model/operation/operation~Operation|null} [takeFrom=null]
  385. setOriginalOperations( operations, takeFrom = null ) {
  386. const originalOperation = takeFrom ? this._originalOperations.get( takeFrom ) : null;
  387. for ( const operation of operations ) {
  388. this._originalOperations.set( operation, originalOperation || operation );
  389. }
  390. }
  391. // Saves a relation between operations `opA` and `opB`.
  392. //
  393. // Relations are then later used to help solve conflicts when operations are transformed.
  394. //
  395. // @param {module:engine/model/operation/operation~Operation} opA
  396. // @param {module:engine/model/operation/operation~Operation} opB
  397. updateRelation( opA, opB ) {
  398. // The use of relations is described in a bigger detail in transformation functions.
  399. //
  400. // In brief, this function, for specified pairs of operation types, checks how positions defined in those operations relate.
  401. // Then those relations are saved. For example, for two move operations, it is saved if one of those operations target
  402. // position is before the other operation source position. This kind of information gives contextual information when
  403. // transformation is used during undo. Similar checks are done for other pairs of operations.
  404. //
  405. switch ( opA.constructor ) {
  406. case MoveOperation: {
  407. switch ( opB.constructor ) {
  408. case MergeOperation: {
  409. if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opB.movedRange.containsPosition( opA.targetPosition ) ) {
  410. this._setRelation( opA, opB, 'insertAtSource' );
  411. } else if ( opA.targetPosition.isEqual( opB.deletionPosition ) ) {
  412. this._setRelation( opA, opB, 'insertBetween' );
  413. }
  414. break;
  415. }
  416. case MoveOperation: {
  417. if ( opA.targetPosition.isEqual( opB.sourcePosition ) || opA.targetPosition.isBefore( opB.sourcePosition ) ) {
  418. this._setRelation( opA, opB, 'insertBefore' );
  419. } else {
  420. this._setRelation( opA, opB, 'insertAfter' );
  421. }
  422. break;
  423. }
  424. case UnwrapOperation: {
  425. const isInside = opA.targetPosition.hasSameParentAs( opB.position );
  426. if ( isInside ) {
  427. this._setRelation( opA, opB, 'insertInside' );
  428. }
  429. break;
  430. }
  431. }
  432. break;
  433. }
  434. case SplitOperation: {
  435. switch ( opB.constructor ) {
  436. case MergeOperation: {
  437. if ( opA.position.isBefore( opB.sourcePosition ) ) {
  438. this._setRelation( opA, opB, 'splitBefore' );
  439. }
  440. break;
  441. }
  442. case MoveOperation: {
  443. if ( opA.position.isEqual( opB.sourcePosition ) || opA.position.isBefore( opB.sourcePosition ) ) {
  444. this._setRelation( opA, opB, 'splitBefore' );
  445. }
  446. break;
  447. }
  448. }
  449. break;
  450. }
  451. }
  452. }
  453. // Evaluates and returns contextual information about two given operations `opA` and `opB` which are about to be transformed.
  454. //
  455. // @param {module:engine/model/operation/operation~Operation} opA
  456. // @param {module:engine/model/operation/operation~Operation} opB
  457. // @returns {module:engine/model/operation/transform~TransformationContext}
  458. getContext( opA, opB, aIsStrong ) {
  459. return {
  460. aIsStrong,
  461. aWasUndone: this._wasUndone( opA ),
  462. bWasUndone: this._wasUndone( opB ),
  463. abRelation: this._useRelations ? this._getRelation( opA, opB ) : null,
  464. baRelation: this._useRelations ? this._getRelation( opB, opA ) : null
  465. };
  466. }
  467. // Returns whether given operation `op` has already been undone.
  468. //
  469. // Information whether an operation was undone gives more context when making a decision when two operations are in conflict.
  470. //
  471. // @param {module:engine/model/operation/operation~Operation} op
  472. // @returns {Boolean}
  473. _wasUndone( op ) {
  474. // For `op`, get its original operation. After all, if `op` is a clone (or even transformed clone) of another
  475. // operation, literally `op` couldn't be undone. It was just generated. If anything, it was the operation it origins
  476. // from which was undone. So get that original operation.
  477. const originalOp = this._originalOperations.get( op );
  478. // And check with the document if the original operation was undone.
  479. return originalOp.wasUndone || this._history.isUndoneOperation( originalOp );
  480. }
  481. // Returns a relation between `opA` and an operation which is undone by `opB`. This can be `String` value if a relation
  482. // was set earlier or `null` if there was no relation between those operations.
  483. //
  484. // This is a little tricky to understand, so let's compare it to `ContextFactory#_wasUndone`.
  485. //
  486. // When `wasUndone( opB )` is used, we check if the `opB` has already been undone. It is obvious, that the
  487. // undoing operation must happen after the undone operation. So, essentially, we have `opB`, we take document history,
  488. // we look forward in the future and ask if in that future `opB` was undone.
  489. //
  490. // Relations is a backward process to `wasUndone()`.
  491. //
  492. // Long story short - using relations is asking what happened in the past. Looking back. This time we have an undoing
  493. // operation `opB` which has undone some other operation. When there is a transformation `opA` x `opB` and there is
  494. // a conflict to solve and `opB` is an undoing operation, we can look back in the history and see what was a relation
  495. // between `opA` and the operation which `opB` undone. Basing on that relation from the past, we can now make
  496. // a better decision when resolving a conflict between two operations, because we know more about the context of
  497. // those two operations.
  498. //
  499. // This is why this function does not return a relation directly between `opA` and `opB` because we need to look
  500. // back to search for a meaningful contextual information.
  501. //
  502. // @param {module:engine/model/operation/operation~Operation} opA
  503. // @param {module:engine/model/operation/operation~Operation} opB
  504. // @returns {String|null}
  505. _getRelation( opA, opB ) {
  506. // Get the original operation. Similarly as in `wasUndone()` it is used as an universal identifier for stored data.
  507. const origB = this._originalOperations.get( opB );
  508. const undoneB = this._history.getUndoneOperation( origB );
  509. // If `opB` is not undoing any operation, there is no relation.
  510. if ( !undoneB ) {
  511. return null;
  512. }
  513. const origA = this._originalOperations.get( opA );
  514. const relationsA = this._relations.get( origA );
  515. // Get all relations for `opA`, and check if there is a relation with `opB`-undone-counterpart. If so, return it.
  516. if ( relationsA ) {
  517. return relationsA.get( undoneB ) || null;
  518. }
  519. return null;
  520. }
  521. // Helper function for `ContextFactory#updateRelations`.
  522. //
  523. // @private
  524. // @param {module:engine/model/operation/operation~Operation} opA
  525. // @param {module:engine/model/operation/operation~Operation} opB
  526. // @param {String} relation
  527. _setRelation( opA, opB, relation ) {
  528. // As always, setting is for original operations, not the clones/transformed operations.
  529. const origA = this._originalOperations.get( opA );
  530. const origB = this._originalOperations.get( opB );
  531. let relationsA = this._relations.get( origA );
  532. if ( !relationsA ) {
  533. relationsA = new Map();
  534. this._relations.set( origA, relationsA );
  535. }
  536. relationsA.set( origB, relation );
  537. }
  538. }
  539. /**
  540. * Holds additional contextual information about a transformed pair of operations (`a` and `b`). Those information
  541. * can be used for better conflict resolving.
  542. *
  543. * @typedef {Object} module:engine/model/operation/transform~TransformationContext
  544. *
  545. * @property {Boolean} aIsStrong Whether `a` is strong operation in this transformation, or weak.
  546. * @property {Boolean} aWasUndone Whether `a` operation was undone.
  547. * @property {Boolean} bWasUndone Whether `b` operation was undone.
  548. * @property {String|null} abRelation The relation between `a` operation and an operation undone by `b` operation.
  549. * @property {String|null} baRelation The relation between `b` operation and an operation undone by `a` operation.
  550. */
  551. /**
  552. * An utility function that updates {@link module:engine/model/operation/operation~Operation#baseVersion base versions}
  553. * of passed operations.
  554. *
  555. * The function simply sets `baseVersion` as a base version of the first passed operation and then increments it for
  556. * each following operation in `operations`.
  557. *
  558. * @private
  559. * @param {Array.<module:engine/model/operation/operation~Operation>} operations Operations to update.
  560. * @param {Number} baseVersion Base version to set for the first operation in `operations`.
  561. */
  562. function updateBaseVersions( operations, baseVersion ) {
  563. for ( const operation of operations ) {
  564. operation.baseVersion = baseVersion++;
  565. }
  566. }
  567. /**
  568. * Adds `howMany` instances of {@link module:engine/model/operation/nooperation~NoOperation} to `operations` set.
  569. *
  570. * @private
  571. * @param {Array.<module:engine/model/operation/operation~Operation>} operations
  572. * @param {Number} howMany
  573. */
  574. function padWithNoOps( operations, howMany ) {
  575. for ( let i = 0; i < howMany; i++ ) {
  576. operations.push( new NoOperation( 0 ) );
  577. }
  578. }
  579. // -----------------------
  580. setTransformation( AttributeOperation, AttributeOperation, ( a, b, context ) => {
  581. if ( a.key === b.key ) {
  582. // If operations attributes are in conflict, check if their ranges intersect and manage them properly.
  583. // First, we want to apply change to the part of a range that has not been changed by the other operation.
  584. const operations = a.range.getDifference( b.range ).map( range => {
  585. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, 0 );
  586. } );
  587. // Then we take care of the common part of ranges.
  588. const common = a.range.getIntersection( b.range );
  589. if ( common ) {
  590. // If this operation is more important, we also want to apply change to the part of the
  591. // original range that has already been changed by the other operation. Since that range
  592. // got changed we also have to update `oldValue`.
  593. if ( context.aIsStrong ) {
  594. operations.push( new AttributeOperation( common, b.key, b.newValue, a.newValue, 0 ) );
  595. }
  596. }
  597. if ( operations.length == 0 ) {
  598. return [ new NoOperation( 0 ) ];
  599. }
  600. return operations;
  601. } else {
  602. // If operations don't conflict, simply return an array containing just a clone of this operation.
  603. return [ a ];
  604. }
  605. } );
  606. setTransformation( AttributeOperation, InsertOperation, ( a, b ) => {
  607. // Case 1:
  608. //
  609. // The attribute operation range includes the position where nodes were inserted.
  610. // There are two possible scenarios: the inserted nodes were text and they should receive attributes or
  611. // the inserted nodes were elements and they should not receive attributes.
  612. //
  613. if ( a.range.start.hasSameParentAs( b.position ) && a.range.containsPosition( b.position ) ) {
  614. // If new nodes should not receive attributes, two separated ranges will be returned.
  615. // Otherwise, one expanded range will be returned.
  616. const range = a.range._getTransformedByInsertion( b.position, b.howMany, !b.shouldReceiveAttributes );
  617. const result = range.map( r => {
  618. return new AttributeOperation( r, a.key, a.oldValue, a.newValue, a.baseVersion );
  619. } );
  620. if ( b.shouldReceiveAttributes ) {
  621. // `AttributeOperation#range` includes some newly inserted text.
  622. // The operation should also change the attribute of that text. An example:
  623. //
  624. // Bold should be applied on the following range:
  625. // <p>Fo[zb]ar</p>
  626. //
  627. // In meantime, new text is typed:
  628. // <p>Fozxxbar</p>
  629. //
  630. // Bold should be applied also on the new text:
  631. // <p>Fo[zxxb]ar</p>
  632. // <p>Fo<$text bold="true">zxxb</$text>ar</p>
  633. //
  634. // There is a special case to consider here to consider.
  635. //
  636. // Consider setting an attribute with multiple possible values, for example `highlight`. The inserted text might
  637. // have already an attribute value applied and the `oldValue` property of the attribute operation might be wrong:
  638. //
  639. // Attribute `highlight="yellow"` should be applied on the following range:
  640. // <p>Fo[zb]ar<p>
  641. //
  642. // In meantime, character `x` with `highlight="red"` is typed:
  643. // <p>Fo[z<$text highlight="red">x</$text>b]ar</p>
  644. //
  645. // In this case we cannot simply apply operation changing the attribute value from `null` to `"yellow"` for the whole range
  646. // because that would lead to an exception (`oldValue` is incorrect for `x`).
  647. //
  648. // We also cannot break the original range as this would mess up a scenario when there are multiple following
  649. // insert operations, because then only the first inserted character is included in those ranges:
  650. // <p>Fo[z][x][b]ar</p> --> <p>Fo[z][x]x[b]ar</p> --> <p>Fo[z][x]xx[b]ar</p>
  651. //
  652. // So, the attribute range needs be expanded, no matter what attributes are set on the inserted nodes:
  653. //
  654. // <p>Fo[z<$text highlight="red">x</$text>b]ar</p> <--- Change from `null` to `yellow`, throwing an exception.
  655. //
  656. // But before that operation would be applied, we will add an additional attribute operation that will change
  657. // attributes on the inserted nodes in a way which would make the original operation correct:
  658. //
  659. // <p>Fo[z{<$text highlight="red">}x</$text>b]ar</p> <--- Change range `{}` from `red` to `null`.
  660. // <p>Fo[zxb]ar</p> <--- Now change from `null` to `yellow` is completely fine.
  661. //
  662. // Generate complementary attribute operation. Be sure to add it before the original operation.
  663. const op = _getComplementaryAttributeOperations( b, a.key, a.oldValue );
  664. if ( op ) {
  665. result.unshift( op );
  666. }
  667. }
  668. // If nodes should not receive new attribute, we are done here.
  669. return result;
  670. }
  671. // If insert operation is not expanding the attribute operation range, simply transform the range.
  672. a.range = a.range._getTransformedByInsertion( b.position, b.howMany, false )[ 0 ];
  673. return [ a ];
  674. } );
  675. /**
  676. * Helper function for `AttributeOperation` x `InsertOperation` (and reverse) transformation.
  677. *
  678. * For given `insertOperation` it checks the inserted node if it has an attribute `key` set to a value different
  679. * than `newValue`. If so, it generates an `AttributeOperation` which changes the value of `key` attribute to `newValue`.
  680. *
  681. * @private
  682. * @param {module:engine/model/operation/insertoperation~InsertOperation} insertOperation
  683. * @param {String} key
  684. * @param {*} newValue
  685. * @returns {module:engine/model/operation/attributeoperation~AttributeOperation|null}
  686. */
  687. function _getComplementaryAttributeOperations( insertOperation, key, newValue ) {
  688. const nodes = insertOperation.nodes;
  689. // At the beginning we store the attribute value from the first node.
  690. const insertValue = nodes.getNode( 0 ).getAttribute( key );
  691. if ( insertValue == newValue ) {
  692. return null;
  693. }
  694. const range = new Range( insertOperation.position, insertOperation.position.getShiftedBy( insertOperation.howMany ) );
  695. return new AttributeOperation( range, key, insertValue, newValue, 0 );
  696. }
  697. setTransformation( AttributeOperation, MergeOperation, ( a, b ) => {
  698. const ranges = [];
  699. // Case 1:
  700. //
  701. // Attribute change on the merged element. In this case, the merged element was moved to the graveyard.
  702. // An additional attribute operation that will change the (re)moved element needs to be generated.
  703. //
  704. if ( a.range.start.hasSameParentAs( b.deletionPosition ) ) {
  705. if ( a.range.containsPosition( b.deletionPosition ) || a.range.start.isEqual( b.deletionPosition ) ) {
  706. ranges.push( Range.createFromPositionAndShift( b.graveyardPosition, 1 ) );
  707. }
  708. }
  709. const range = a.range._getTransformedByMergeOperation( b );
  710. // Do not add empty (collapsed) ranges to the result. `range` may be collapsed if it contained only the merged element.
  711. if ( !range.isCollapsed ) {
  712. ranges.push( range );
  713. }
  714. // Create `AttributeOperation`s out of the ranges.
  715. return ranges.map( range => {
  716. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion );
  717. } );
  718. } );
  719. setTransformation( AttributeOperation, MoveOperation, ( a, b ) => {
  720. const ranges = _breakRangeByMoveOperation( a.range, b );
  721. // Create `AttributeOperation`s out of the ranges.
  722. return ranges.map( range => new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion ) );
  723. } );
  724. // Helper function for `AttributeOperation` x `MoveOperation` transformation.
  725. //
  726. // Takes the passed `range` and transforms it by move operation `moveOp` in a specific way. Only top-level nodes of `range`
  727. // are considered to be in the range. If move operation moves nodes deep from inside of the range, those nodes won't
  728. // be included in the result. In other words, top-level nodes of the ranges from the result are exactly the same as
  729. // top-level nodes of the original `range`.
  730. //
  731. // This is important for `AttributeOperation` because, for its range, it changes only the top-level nodes. So we need to
  732. // track only how those nodes have been affected by `MoveOperation`.
  733. //
  734. // @private
  735. // @param {module:engine/model/range~Range} range
  736. // @param {module:engine/model/operation/moveoperation~MoveOperation} moveOp
  737. // @returns {Array.<module:engine/model/range~Range>}
  738. function _breakRangeByMoveOperation( range, moveOp ) {
  739. const moveRange = Range.createFromPositionAndShift( moveOp.sourcePosition, moveOp.howMany );
  740. // We are transforming `range` (original range) by `moveRange` (range moved by move operation). As usual when it comes to
  741. // transforming a ranges, we may have a common part of the ranges and we may have a difference part (zero to two ranges).
  742. let common = null;
  743. let difference = [];
  744. // Let's compare the ranges.
  745. if ( moveRange.containsRange( range, true ) ) {
  746. // If the whole original range is moved, treat it whole as a common part. There's also no difference part.
  747. common = range;
  748. } else if ( range.start.hasSameParentAs( moveRange.start ) ) {
  749. // If the ranges are "on the same level" (in the same parent) then move operation may move exactly those nodes
  750. // that are changed by the attribute operation. In this case we get common part and difference part in the usual way.
  751. difference = range.getDifference( moveRange );
  752. common = range.getIntersection( moveRange );
  753. } else {
  754. // In any other situation we assume that original range is different than move range, that is that move operation
  755. // moves other nodes that attribute operation change. Even if the moved range is deep inside in the original range.
  756. //
  757. // Note that this is different than in `.getIntersection` (we would get a common part in that case) and different
  758. // than `.getDifference` (we would get two ranges).
  759. difference = [ range ];
  760. }
  761. const result = [];
  762. // The default behaviour of `_getTransformedByMove` might get wrong results for difference part, though, so
  763. // we do it by hand.
  764. for ( let diff of difference ) {
  765. // First, transform the range by removing moved nodes. Since this is a difference, this is safe, `null` won't be returned
  766. // as the range is different than the moved range.
  767. diff = diff._getTransformedByDeletion( moveOp.sourcePosition, moveOp.howMany );
  768. // Transform also `targetPosition`.
  769. const targetPosition = moveOp.getMovedRangeStart();
  770. // Spread the range only if moved nodes are inserted only between the top-level nodes of the `diff` range.
  771. const spread = diff.start.hasSameParentAs( targetPosition );
  772. // Transform by insertion of moved nodes.
  773. diff = diff._getTransformedByInsertion( targetPosition, moveOp.howMany, spread );
  774. result.push( ...diff );
  775. }
  776. // Common part can be simply transformed by the move operation. This is because move operation will not target to
  777. // that common part (the operation would have to target inside its own moved range).
  778. if ( common ) {
  779. result.push(
  780. common._getTransformedByMove( moveOp.sourcePosition, moveOp.targetPosition, moveOp.howMany, false )[ 0 ]
  781. );
  782. }
  783. return result;
  784. }
  785. setTransformation( AttributeOperation, SplitOperation, ( a, b ) => {
  786. // Case 1:
  787. //
  788. // Split node is the last node in `AttributeOperation#range`.
  789. // `AttributeOperation#range` needs to be expanded to include the new (split) node.
  790. //
  791. // Attribute `type` to be changed to `numbered` but the `listItem` is split.
  792. // <listItem type="bulleted">foobar</listItem>
  793. //
  794. // After split:
  795. // <listItem type="bulleted">foo</listItem><listItem type="bulleted">bar</listItem>
  796. //
  797. // After attribute change:
  798. // <listItem type="numbered">foo</listItem><listItem type="numbered">foo</listItem>
  799. //
  800. if ( a.range.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
  801. a.range.end.offset++;
  802. return [ a ];
  803. }
  804. // Case 2:
  805. //
  806. // Split position is inside `AttributeOperation#range`, at the same level, so the nodes to change are
  807. // not going to make a flat range.
  808. //
  809. // Content with range-to-change and split position:
  810. // <p>Fo[zb^a]r</p>
  811. //
  812. // After split:
  813. // <p>Fozb</p><p>ar</p>
  814. //
  815. // Make two separate ranges containing all nodes to change:
  816. // <p>Fo[zb]</p><p>[a]r</p>
  817. //
  818. if ( a.range.start.hasSameParentAs( b.position ) && a.range.containsPosition( b.position ) ) {
  819. const secondPart = a.clone();
  820. secondPart.range = new Range(
  821. Position.createFromPosition( b.moveTargetPosition ),
  822. a.range.end._getCombined( b.position, b.moveTargetPosition )
  823. );
  824. a.range.end = Position.createFromPosition( b.position );
  825. a.range.end.stickiness = 'toPrevious';
  826. return [ a, secondPart ];
  827. }
  828. // The default case.
  829. //
  830. a.range = a.range._getTransformedBySplitOperation( b );
  831. return [ a ];
  832. } );
  833. setTransformation( AttributeOperation, WrapOperation, ( a, b ) => {
  834. // Case 1:
  835. //
  836. // `AttributeOperation#range` and range to wrap intersect. Multiple `AttributeOperation`s may be needed
  837. // to handle this situation as, after wrapping, the nodes to change may be in different parents.
  838. //
  839. // Both list items' type should be changed to numbered:
  840. // [<listItem type="bulleted">Foo</listItem><listItem type="bulleted">Bar</listItem>]
  841. //
  842. // Wrap one of the items inside block quote:
  843. // <blockQuote><listItem type="bulleted">Foo</listItem></blockQuote><listItem type="bulleted">Bar</listItem>
  844. //
  845. // Two operations are needed:
  846. // <blockQuote>[<listItem type="bulleted">Foo</listItem>]</blockQuote>[<listItem type="bulleted">Bar</listItem>]
  847. //
  848. // Up to there operations/ranges might be needed in other cases.
  849. //
  850. if ( a.range.start.hasSameParentAs( b.position ) ) {
  851. const ranges = a.range.getDifference( b.wrappedRange );
  852. const common = a.range.getIntersection( b.wrappedRange );
  853. if ( common ) {
  854. ranges.push( common );
  855. }
  856. // Create `AttributeOperation`s out of the ranges.
  857. return ranges.map( range => {
  858. return new AttributeOperation( range._getTransformedByWrapOperation( b ), a.key, a.oldValue, a.newValue, 0 );
  859. } );
  860. }
  861. // The default case.
  862. //
  863. a.range = a.range._getTransformedByWrapOperation( b );
  864. return [ a ];
  865. } );
  866. setTransformation( AttributeOperation, UnwrapOperation, ( a, b ) => {
  867. // Case 1:
  868. //
  869. // `AttributeOperation#range` contains element to unwrap. Two or three `AttributeOperation`s are needed to handle this
  870. // situation. The unwrapped element was moved to graveyard and needs a separate operation. Then, if the unwrapped
  871. // nodes end up inside the attribute operation range, the range needs to be broken on two parts.
  872. //
  873. if ( a.range.start.hasSameParentAs( b.targetPosition ) ) {
  874. const insertionPosition = b.targetPosition.getShiftedBy( b.howMany );
  875. let ranges = a.range._getTransformedByInsertion( b.targetPosition, b.howMany );
  876. ranges = ranges.reduce( ( result, range ) => {
  877. return result.concat( range._getTransformedByMove( insertionPosition, b.graveyardPosition, 1 ) );
  878. }, [] );
  879. // Create `AttributeOperation`s out of the ranges.
  880. return ranges.map( range => {
  881. return new AttributeOperation( range, a.key, a.oldValue, a.newValue, 0 );
  882. } );
  883. }
  884. // The default case.
  885. //
  886. a.range = a.range._getTransformedByUnwrapOperation( b );
  887. return [ a ];
  888. } );
  889. // -----------------------
  890. setTransformation( InsertOperation, AttributeOperation, ( a, b ) => {
  891. const result = [ a ];
  892. // Case 1:
  893. //
  894. // The attribute operation range includes the position where nodes were inserted.
  895. // There are two possible scenarios: the inserted nodes were text and they should receive attributes or
  896. // the inserted nodes were elements and they should not receive attributes.
  897. //
  898. // This is a mirror scenario to the one described in `AttributeOperation` x `InsertOperation` transformation,
  899. // although this case is a little less complicated. In this case we simply need to change attributes of the
  900. // inserted nodes and that's it.
  901. //
  902. if ( a.shouldReceiveAttributes && a.position.hasSameParentAs( b.range.start ) && b.range.containsPosition( a.position ) ) {
  903. const op = _getComplementaryAttributeOperations( a, b.key, b.newValue );
  904. if ( op ) {
  905. result.push( op );
  906. }
  907. }
  908. // The default case is: do nothing.
  909. // `AttributeOperation` does not change the model tree structure so `InsertOperation` does not need to be changed.
  910. //
  911. return result;
  912. } );
  913. setTransformation( InsertOperation, InsertOperation, ( a, b, context ) => {
  914. // Case 1:
  915. //
  916. // Two insert operations insert nodes at the same position. Since they are the same, it needs to be decided
  917. // what will be the order of inserted nodes. However, there is no additional information to help in that
  918. // decision. Also, when `b` will be transformed by `a`, the same order must be maintained.
  919. //
  920. // To achieve that, we will check if the operation is strong.
  921. // If it is, it won't get transformed. If it is not, it will be moved.
  922. //
  923. if ( a.position.isEqual( b.position ) && context.aIsStrong ) {
  924. return [ a ];
  925. }
  926. // The default case.
  927. //
  928. a.position = a.position._getTransformedByInsertOperation( b );
  929. return [ a ];
  930. } );
  931. setTransformation( InsertOperation, MoveOperation, ( a, b ) => {
  932. // The default case.
  933. //
  934. a.position = a.position._getTransformedByMoveOperation( b );
  935. return [ a ];
  936. } );
  937. setTransformation( InsertOperation, SplitOperation, ( a, b ) => {
  938. // The default case.
  939. //
  940. a.position = a.position._getTransformedBySplitOperation( b );
  941. return [ a ];
  942. } );
  943. setTransformation( InsertOperation, MergeOperation, ( a, b ) => {
  944. a.position = a.position._getTransformedByMergeOperation( b );
  945. return [ a ];
  946. } );
  947. setTransformation( InsertOperation, WrapOperation, ( a, b ) => {
  948. // The default case.
  949. //
  950. a.position = a.position._getTransformedByWrapOperation( b );
  951. return [ a ];
  952. } );
  953. setTransformation( InsertOperation, UnwrapOperation, ( a, b ) => {
  954. a.position = a.position._getTransformedByUnwrapOperation( b );
  955. return [ a ];
  956. } );
  957. // -----------------------
  958. setTransformation( MarkerOperation, InsertOperation, ( a, b ) => {
  959. if ( a.oldRange ) {
  960. a.oldRange = a.oldRange._getTransformedByInsertOperation( b )[ 0 ];
  961. }
  962. if ( a.newRange ) {
  963. a.newRange = a.newRange._getTransformedByInsertOperation( b )[ 0 ];
  964. }
  965. return [ a ];
  966. } );
  967. setTransformation( MarkerOperation, MarkerOperation, ( a, b, context ) => {
  968. if ( a.name == b.name ) {
  969. if ( context.aIsStrong ) {
  970. a.oldRange = b.newRange ? Range.createFromRange( b.newRange ) : null;
  971. } else {
  972. return [ new NoOperation( 0 ) ];
  973. }
  974. }
  975. return [ a ];
  976. } );
  977. setTransformation( MarkerOperation, MergeOperation, ( a, b ) => {
  978. if ( a.oldRange ) {
  979. a.oldRange = a.oldRange._getTransformedByMergeOperation( b );
  980. }
  981. if ( a.newRange ) {
  982. a.newRange = a.newRange._getTransformedByMergeOperation( b );
  983. }
  984. return [ a ];
  985. } );
  986. setTransformation( MarkerOperation, MoveOperation, ( a, b ) => {
  987. if ( a.oldRange ) {
  988. a.oldRange = Range.createFromRanges( a.oldRange._getTransformedByMoveOperation( b ) );
  989. }
  990. if ( a.newRange ) {
  991. a.newRange = Range.createFromRanges( a.newRange._getTransformedByMoveOperation( b ) );
  992. }
  993. return [ a ];
  994. } );
  995. setTransformation( MarkerOperation, SplitOperation, ( a, b ) => {
  996. if ( a.oldRange ) {
  997. a.oldRange = a.oldRange._getTransformedBySplitOperation( b );
  998. }
  999. if ( a.newRange ) {
  1000. a.newRange = a.newRange._getTransformedBySplitOperation( b );
  1001. }
  1002. return [ a ];
  1003. } );
  1004. setTransformation( MarkerOperation, WrapOperation, ( a, b ) => {
  1005. if ( a.oldRange ) {
  1006. a.oldRange = a.oldRange._getTransformedByWrapOperation( b );
  1007. }
  1008. if ( a.newRange ) {
  1009. a.newRange = a.newRange._getTransformedByWrapOperation( b );
  1010. }
  1011. return [ a ];
  1012. } );
  1013. setTransformation( MarkerOperation, UnwrapOperation, ( a, b ) => {
  1014. if ( a.oldRange ) {
  1015. a.oldRange = a.oldRange._getTransformedByUnwrapOperation( b );
  1016. }
  1017. if ( a.newRange ) {
  1018. a.newRange = a.newRange._getTransformedByUnwrapOperation( b );
  1019. }
  1020. return [ a ];
  1021. } );
  1022. // -----------------------
  1023. setTransformation( MergeOperation, InsertOperation, ( a, b ) => {
  1024. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  1025. a.howMany += b.howMany;
  1026. }
  1027. a.sourcePosition = a.sourcePosition._getTransformedByInsertOperation( b );
  1028. a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
  1029. return [ a ];
  1030. } );
  1031. setTransformation( MergeOperation, MergeOperation, ( a, b, context ) => {
  1032. // Case 1:
  1033. //
  1034. // Same merge operations.
  1035. //
  1036. // Both operations have same source and target positions. So the element already got merged and there is
  1037. // theoretically nothing to do.
  1038. //
  1039. // In this case, keep the source operation in the merged element - in the graveyard - and don't change target position.
  1040. // Doing this instead of returning `NoOperation` allows for a correct undo later.
  1041. //
  1042. if ( a.sourcePosition.isEqual( b.sourcePosition ) && a.targetPosition.isEqual( b.targetPosition ) ) {
  1043. const path = b.graveyardPosition.path.slice();
  1044. path.push( 0 );
  1045. a.sourcePosition = new Position( b.graveyardPosition.root, path );
  1046. a.howMany = 0;
  1047. return [ a ];
  1048. }
  1049. // The default case.
  1050. //
  1051. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  1052. a.howMany += b.howMany;
  1053. }
  1054. a.sourcePosition = a.sourcePosition._getTransformedByMergeOperation( b );
  1055. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  1056. // Handle positions in graveyard.
  1057. // If graveyard positions are same and `a` operation is strong - do not transform.
  1058. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  1059. a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
  1060. }
  1061. return [ a ];
  1062. } );
  1063. setTransformation( MergeOperation, MoveOperation, ( a, b, context ) => {
  1064. // Case 1:
  1065. //
  1066. // The element to merge got removed.
  1067. //
  1068. // Merge operation does support merging elements which are not siblings. So it would not be a problem
  1069. // from technical point of view. However, if the element was removed, the intention of the user deleting it
  1070. // was to have it all deleted, together with its children. From user experience point of view, moving back the
  1071. // removed nodes might be unexpected. This means that in this scenario we will block the merging.
  1072. //
  1073. // The exception of this rule would be if the remove operation was later undone.
  1074. //
  1075. const removedRange = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  1076. if ( b.type == 'remove' && !context.bWasUndone ) {
  1077. if ( a.deletionPosition.hasSameParentAs( b.sourcePosition ) && removedRange.containsPosition( a.sourcePosition ) ) {
  1078. return [ new NoOperation( 0 ) ];
  1079. }
  1080. }
  1081. // The default case.
  1082. //
  1083. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  1084. a.howMany += b.howMany;
  1085. }
  1086. if ( a.sourcePosition.hasSameParentAs( b.sourcePosition ) ) {
  1087. a.howMany -= b.howMany;
  1088. }
  1089. a.sourcePosition = a.sourcePosition._getTransformedByMoveOperation( b );
  1090. a.targetPosition = a.targetPosition._getTransformedByMoveOperation( b );
  1091. // `MergeOperation` graveyard position is like `MoveOperation` target position. It is a position where element(s) will
  1092. // be moved. Like in other similar cases, we need to consider the scenario when those positions are same.
  1093. // Here, we will treat `MergeOperation` like it is always strong (see `InsertOperation` x `InsertOperation` for comparison).
  1094. // This means that we won't transform graveyard position if it is equal to move operation target position.
  1095. if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
  1096. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  1097. }
  1098. return [ a ];
  1099. } );
  1100. setTransformation( MergeOperation, SplitOperation, ( a, b ) => {
  1101. if ( b.graveyardPosition ) {
  1102. // If `b` operation defines graveyard position, a node from graveyard will be moved. This means that we need to
  1103. // transform `a.graveyardPosition` accordingly.
  1104. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1105. // This is a scenario foreseen in `MergeOperation` x `MergeOperation`, with two identical merge operations.
  1106. //
  1107. // So, there was `MergeOperation` x `MergeOperation` transformation earlier. Now, `a` is a merge operation which
  1108. // source position is in graveyard. Interestingly, split operation wants to use the node to be merged by `a`. This
  1109. // means that `b` is undoing that merge operation from earlier, which caused `a` to be in graveyard.
  1110. //
  1111. // If that's the case, at this point, we will only "fix" `a.howMany`. It was earlier set to `0` in
  1112. // `MergeOperation` x `MergeOperation` transformation. Later transformations in this function will change other
  1113. // properties.
  1114. //
  1115. if ( a.deletionPosition.isEqual( b.graveyardPosition ) ) {
  1116. a.howMany = b.howMany;
  1117. }
  1118. }
  1119. // Case 1:
  1120. //
  1121. // Merge operation moves nodes to the place where split happens.
  1122. // This is a classic situation when there are two paragraphs, and there is a split (enter) after the first
  1123. // paragraph and there is a merge (delete) at the beginning of the second paragraph:
  1124. //
  1125. // <p>Foo{}</p><p>[]Bar</p>.
  1126. //
  1127. // Split is after `Foo`, while merge is from `Bar` to the end of `Foo`.
  1128. //
  1129. // State after split:
  1130. // <p>Foo</p><p></p><p>Bar</p>
  1131. //
  1132. // Now, `Bar` should be merged to the new paragraph:
  1133. // <p>Foo</p><p>Bar</p>
  1134. //
  1135. // Instead of merging it to the original paragraph:
  1136. // <p>FooBar</p><p></p>
  1137. //
  1138. // This means that `targetPosition` needs to be transformed. This is the default case though.
  1139. // For example, if the split would be after `F`, `targetPosition` should also be transformed.
  1140. //
  1141. // There are two exception, though, when we want to keep `targetPosition` as it was.
  1142. //
  1143. // First exception is when the merge target position is inside an element (not at the end, as usual). This
  1144. // happens when the merge operation earlier was transformed by "the same" merge operation. If merge operation
  1145. // targets inside the element we want to keep the original target position (and not transform it) because
  1146. // we have additional context telling us that we want to merge to the original element. We can check if the
  1147. // merge operation points inside element by checking what is `SplitOperation#howMany`. Since merge target position
  1148. // is same as split position, if `howMany` is non-zero, it means that the merge target position is inside an element.
  1149. //
  1150. // Second exception is when the element to merge is in the graveyard and split operation uses it. In that case
  1151. // if target position would be transformed, the merge operation would target at the source position:
  1152. //
  1153. // root: <p>Foo</p> graveyard: <p></p>
  1154. //
  1155. // SplitOperation: root [ 0, 3 ] using graveyard [ 0 ] (howMany = 0)
  1156. // MergeOperation: graveyard [ 0, 0 ] -> root [ 0, 3 ] (howMany = 0)
  1157. //
  1158. // Since split operation moves the graveyard node back to the root, the merge operation source position changes.
  1159. // We would like to merge from the empty <p> to the "Foo" <p>:
  1160. //
  1161. // root: <p>Foo</p><p></p> graveyard:
  1162. //
  1163. // MergeOperation#sourcePosition = root [ 1, 0 ]
  1164. //
  1165. // If `targetPosition` is transformed, it would become root [ 1, 0 ] as well. It has to be kept as it was.
  1166. //
  1167. if ( a.targetPosition.isEqual( b.position ) ) {
  1168. if ( b.howMany != 0 || ( b.graveyardPosition && a.deletionPosition.isEqual( b.graveyardPosition ) ) ) {
  1169. a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
  1170. return [ a ];
  1171. }
  1172. }
  1173. // The default case.
  1174. //
  1175. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  1176. a.howMany = b.position.offset;
  1177. }
  1178. a.sourcePosition = a.sourcePosition._getTransformedBySplitOperation( b );
  1179. a.targetPosition = a.targetPosition._getTransformedBySplitOperation( b );
  1180. return [ a ];
  1181. } );
  1182. setTransformation( MergeOperation, WrapOperation, ( a, b ) => {
  1183. if ( b.graveyardPosition ) {
  1184. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1185. }
  1186. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  1187. a.howMany = a.howMany + 1 - b.howMany;
  1188. }
  1189. // Case 2:
  1190. //
  1191. // Merged element is wrapped and this is the last (only) element in the wrap.
  1192. // Because of how this is resolved in `WrapOperation` x `MergeOperation`, we need to apply special handling here.
  1193. // If the last element from wrapped range is "removed" from it, the wrap is effectively on empty range.
  1194. // In that case, the wrapper element is moved to graveyard. This happens in `WrapOperation` x
  1195. // `MergeOperation` and we need to mirror it here.
  1196. //
  1197. if ( b.position.isEqual( a.deletionPosition ) && b.howMany == 1 ) {
  1198. // We need to change `MergeOperation#graveyardPosition` so the merged node is moved into the wrapper element.
  1199. // Since `UnwrapOperation` created from reverse has graveyard position at [ 0 ], we can safely set the path here to [ 0, 0 ].
  1200. a.graveyardPosition = new Position( a.graveyardPosition.root, [ 0, 0 ] );
  1201. return [
  1202. b.getReversed(),
  1203. a
  1204. ];
  1205. }
  1206. // The default case.
  1207. //
  1208. a.sourcePosition = a.sourcePosition._getTransformedByWrapOperation( b );
  1209. a.targetPosition = a.targetPosition._getTransformedByWrapOperation( b );
  1210. return [ a ];
  1211. } );
  1212. setTransformation( MergeOperation, UnwrapOperation, ( a, b, context ) => {
  1213. // Case 1:
  1214. //
  1215. // The element to merge got unwrapped.
  1216. //
  1217. // There are multiple possible solutions to resolve this conflict:
  1218. //
  1219. // * unwrap also merge target (all nodes are unwrapped),
  1220. // * move the unwrapped nodes to the merge target (no nodes stayed unwrapped),
  1221. // * leave merge position in the unwrapped node (some nodes are unwrapped and some are not).
  1222. //
  1223. // Third option is chosen in this algorithm. If the unwrap operation is undone before merge is applied,
  1224. // the merge operation will work as expected. If the unwrap operation is not undone, then the merge
  1225. // operation won't merge any nodes, so it will behave similarly to noop.
  1226. //
  1227. if ( a.sourcePosition.isEqual( b.position ) ) {
  1228. const path = b.graveyardPosition.path.slice();
  1229. path.push( 0 );
  1230. a.sourcePosition = new Position( b.graveyardPosition.root, path );
  1231. a.howMany = 0;
  1232. return [ a ];
  1233. }
  1234. // Case 2: The element to merge into got unwrapped.
  1235. //
  1236. // In this case there is nothing to merge into. We can imagine, that the merge operation was a little earlier
  1237. // than unwrap operation, so first elements got merged, and then both were unwrapped. To simulate this,
  1238. // we will change this merge into unwrap.
  1239. //
  1240. // We have this:
  1241. // <blockQuote><p>Foo</p></blockQuote><blockQuote><p>Bar</p></blockQuote>
  1242. //
  1243. // Unwrap already happened:
  1244. // <p>Foo</p><blockQuote><p>Bar</p></blockQuote>
  1245. //
  1246. // Let's act like merge happened first and lead to this state:
  1247. // <p>Foo</p><p>Bar</p>
  1248. //
  1249. if ( a.targetPosition.hasSameParentAs( b.position ) ) {
  1250. const graveyard = a.sourcePosition.root.document.graveyard;
  1251. const graveyardPosition = new Position( graveyard, [ 0 ] );
  1252. return [ new UnwrapOperation( a.sourcePosition, a.howMany, graveyardPosition, 0 ) ];
  1253. }
  1254. // The default case.
  1255. //
  1256. if ( a.sourcePosition.hasSameParentAs( b.targetPosition ) ) {
  1257. a.howMany = a.howMany - 1 + b.howMany;
  1258. }
  1259. a.sourcePosition = a.sourcePosition._getTransformedByUnwrapOperation( b );
  1260. a.targetPosition = a.targetPosition._getTransformedByUnwrapOperation( b );
  1261. // Handle positions in graveyard.
  1262. // If graveyard positions are same and `a` operation is strong - do not transform.
  1263. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  1264. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  1265. }
  1266. return [ a ];
  1267. } );
  1268. // -----------------------
  1269. setTransformation( MoveOperation, InsertOperation, ( a, b ) => {
  1270. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1271. const transformed = moveRange._getTransformedByInsertOperation( b, false )[ 0 ];
  1272. a.sourcePosition = transformed.start;
  1273. a.howMany = transformed.end.offset - transformed.start.offset;
  1274. // See `InsertOperation` x `MoveOperation` transformation for details on this case.
  1275. //
  1276. // In summary, both operations point to the same place, so the order of nodes needs to be decided.
  1277. // `MoveOperation` is considered weaker, so it is always transformed, unless there was a certain relation
  1278. // between operations.
  1279. //
  1280. if ( !a.targetPosition.isEqual( b.position ) ) {
  1281. a.targetPosition = a.targetPosition._getTransformedByInsertOperation( b );
  1282. }
  1283. return [ a ];
  1284. } );
  1285. setTransformation( MoveOperation, MoveOperation, ( a, b, context ) => {
  1286. //
  1287. // Setting and evaluating some variables that will be used in special cases and default algorithm.
  1288. //
  1289. // Create ranges from `MoveOperations` properties.
  1290. const rangeA = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1291. const rangeB = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  1292. // Assign `context.aIsStrong` to a different variable, because the value may change during execution of
  1293. // this algorithm and we do not want to override original `context.aIsStrong` that will be used in later transformations.
  1294. let aIsStrong = context.aIsStrong;
  1295. // This will be used to decide the order of nodes if both operations target at the same position.
  1296. // By default, use strong/weak operation mechanism.
  1297. let insertBefore = !context.aIsStrong;
  1298. // If the relation is set, then use it to decide nodes order.
  1299. if ( context.abRelation == 'insertBefore' ) {
  1300. insertBefore = true;
  1301. } else if ( context.abRelation == 'insertAfter' ) {
  1302. insertBefore = false;
  1303. }
  1304. // `a.targetPosition` could be affected by the `b` operation. We will transform it.
  1305. let newTargetPosition;
  1306. if ( a.targetPosition.isEqual( b.targetPosition ) && insertBefore ) {
  1307. newTargetPosition = a.targetPosition._getTransformedByDeletion(
  1308. b.sourcePosition,
  1309. b.howMany
  1310. );
  1311. } else {
  1312. newTargetPosition = a.targetPosition._getTransformedByMove(
  1313. b.sourcePosition,
  1314. b.targetPosition,
  1315. b.howMany
  1316. );
  1317. }
  1318. //
  1319. // Special case #1 + mirror.
  1320. //
  1321. // Special case when both move operations' target positions are inside nodes that are
  1322. // being moved by the other move operation. So in other words, we move ranges into inside of each other.
  1323. // This case can't be solved reasonably (on the other hand, it should not happen often).
  1324. if ( _moveTargetIntoMovedRange( a, b ) && _moveTargetIntoMovedRange( b, a ) ) {
  1325. // Instead of transforming operation, we return a reverse of the operation that we transform by.
  1326. // So when the results of this "transformation" will be applied, `b` MoveOperation will get reversed.
  1327. return [ b.getReversed() ];
  1328. }
  1329. //
  1330. // End of special case #1.
  1331. //
  1332. //
  1333. // Special case #2.
  1334. //
  1335. // Check if `b` operation targets inside `rangeA`. Use stickiness if possible.
  1336. const bTargetsToA = rangeA.containsPosition( b.targetPosition );
  1337. // If `b` targets to `rangeA` and `rangeA` contains `rangeB`, `b` operation has no influence on `a` operation.
  1338. // You might say that operation `b` is captured inside operation `a`.
  1339. if ( bTargetsToA && rangeA.containsRange( rangeB, true ) ) {
  1340. // There is a mini-special case here, where `rangeB` is on other level than `rangeA`. That's why
  1341. // we need to transform `a` operation anyway.
  1342. rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  1343. rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  1344. return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  1345. }
  1346. //
  1347. // Special case #2 mirror.
  1348. //
  1349. const aTargetsToB = rangeB.containsPosition( a.targetPosition );
  1350. if ( aTargetsToB && rangeB.containsRange( rangeA, true ) ) {
  1351. // `a` operation is "moved together" with `b` operation.
  1352. // Here, just move `rangeA` "inside" `rangeB`.
  1353. rangeA.start = rangeA.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  1354. rangeA.end = rangeA.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  1355. return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  1356. }
  1357. //
  1358. // End of special case #2.
  1359. //
  1360. //
  1361. // Special case #3 + mirror.
  1362. //
  1363. // `rangeA` has a node which is an ancestor of `rangeB`. In other words, `rangeB` is inside `rangeA`
  1364. // but not on the same tree level. In such case ranges have common part but we have to treat it
  1365. // differently, because in such case those ranges are not really conflicting and should be treated like
  1366. // two separate ranges. Also we have to discard two difference parts.
  1367. const aCompB = compareArrays( a.sourcePosition.getParentPath(), b.sourcePosition.getParentPath() );
  1368. if ( aCompB == 'prefix' || aCompB == 'extension' ) {
  1369. // Transform `rangeA` by `b` operation and make operation out of it, and that's all.
  1370. // Note that this is a simplified version of default case, but here we treat the common part (whole `rangeA`)
  1371. // like a one difference part.
  1372. rangeA.start = rangeA.start._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  1373. rangeA.end = rangeA.end._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany );
  1374. return _makeMoveOperationsFromRanges( [ rangeA ], newTargetPosition );
  1375. }
  1376. //
  1377. // End of special case #3.
  1378. //
  1379. //
  1380. // Default case - ranges are on the same level or are not connected with each other.
  1381. //
  1382. // Modifier for default case.
  1383. // Modifies `aIsStrong` flag in certain conditions.
  1384. //
  1385. // If only one of operations is a remove operation, we force remove operation to be the "stronger" one
  1386. // to provide more expected results.
  1387. if ( a.type == 'remove' && b.type != 'remove' && !context.aWasUndone ) {
  1388. aIsStrong = true;
  1389. } else if ( a.type != 'remove' && b.type == 'remove' && !context.bWasUndone ) {
  1390. aIsStrong = false;
  1391. }
  1392. // Handle operation's source ranges - check how `rangeA` is affected by `b` operation.
  1393. // This will aggregate transformed ranges.
  1394. const ranges = [];
  1395. // Get the "difference part" of `a` operation source range.
  1396. // This is an array with one or two ranges. Two ranges if `rangeB` is inside `rangeA`.
  1397. const difference = rangeA.getDifference( rangeB );
  1398. for ( const range of difference ) {
  1399. // Transform those ranges by `b` operation. For example if `b` moved range from before those ranges, fix those ranges.
  1400. range.start = range.start._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1401. range.end = range.end._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1402. // If `b` operation targets into `rangeA` on the same level, spread `rangeA` into two ranges.
  1403. const shouldSpread = compareArrays( range.start.getParentPath(), b.getMovedRangeStart().getParentPath() ) == 'same';
  1404. const newRanges = range._getTransformedByInsertion( b.getMovedRangeStart(), b.howMany, shouldSpread );
  1405. ranges.push( ...newRanges );
  1406. }
  1407. // Then, we have to manage the "common part" of both move ranges.
  1408. const common = rangeA.getIntersection( rangeB );
  1409. if ( common !== null && aIsStrong && !bTargetsToA ) {
  1410. // Calculate the new position of that part of original range.
  1411. common.start = common.start._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  1412. common.end = common.end._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  1413. // Take care of proper range order.
  1414. //
  1415. // Put `common` at appropriate place. Keep in mind that we are interested in original order.
  1416. // Basically there are only three cases: there is zero, one or two difference ranges.
  1417. //
  1418. // If there is zero difference ranges, just push `common` in the array.
  1419. if ( ranges.length === 0 ) {
  1420. ranges.push( common );
  1421. }
  1422. // If there is one difference range, we need to check whether common part was before it or after it.
  1423. else if ( ranges.length == 1 ) {
  1424. if ( rangeB.start.isBefore( rangeA.start ) || rangeB.start.isEqual( rangeA.start ) ) {
  1425. ranges.unshift( common );
  1426. } else {
  1427. ranges.push( common );
  1428. }
  1429. }
  1430. // If there are more ranges (which means two), put common part between them. This is the only scenario
  1431. // where there could be two difference ranges so we don't have to make any comparisons.
  1432. else {
  1433. ranges.splice( 1, 0, common );
  1434. }
  1435. }
  1436. if ( ranges.length === 0 ) {
  1437. // If there are no "source ranges", nothing should be changed.
  1438. // Note that this can happen only if `aIsStrong == false` and `rangeA.isEqual( rangeB )`.
  1439. return [ new NoOperation( a.baseVersion ) ];
  1440. }
  1441. return _makeMoveOperationsFromRanges( ranges, newTargetPosition );
  1442. } );
  1443. setTransformation( MoveOperation, SplitOperation, ( a, b, context ) => {
  1444. let newTargetPosition = a.targetPosition._getTransformedBySplitOperation( b );
  1445. // Case 1:
  1446. //
  1447. // Last element in the moved range got split.
  1448. //
  1449. // In this case the default range transformation will not work correctly as the element created by
  1450. // split operation would be outside the range. The range to move needs to be fixed manually.
  1451. // Do it only if this is a "natural" split, not a one that comes from undo.
  1452. // TODO: this should be done on relations
  1453. //
  1454. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1455. if ( moveRange.end.isEqual( b.insertionPosition ) && !b.graveyardPosition ) {
  1456. a.howMany++;
  1457. a.targetPosition = newTargetPosition;
  1458. return [ a ];
  1459. }
  1460. // Case 2:
  1461. //
  1462. // Split happened between the moved nodes. In this case two ranges to move need to be generated.
  1463. //
  1464. // Characters `ozba` are moved to the end of paragraph `Xyz` but split happened.
  1465. // <p>F[oz|ba]r</p><p>Xyz</p>
  1466. //
  1467. // After split:
  1468. // <p>F[oz</p><p>ba]r</p><p>Xyz</p>
  1469. //
  1470. // Correct ranges:
  1471. // <p>F[oz]</p><p>[ba]r</p><p>Xyz</p>
  1472. //
  1473. // After move:
  1474. // <p>F</p><p>r</p><p>Xyzozba</p>
  1475. //
  1476. if ( moveRange.start.hasSameParentAs( b.position ) && moveRange.containsPosition( b.position ) ) {
  1477. let rightRange = new Range( b.position, moveRange.end );
  1478. rightRange = rightRange._getTransformedBySplitOperation( b );
  1479. const ranges = [
  1480. new Range( moveRange.start, b.position ),
  1481. rightRange
  1482. ];
  1483. return _makeMoveOperationsFromRanges( ranges, newTargetPosition );
  1484. }
  1485. // Case 3:
  1486. //
  1487. // Move operation targets at the split position. We need to decide if the nodes should be inserted
  1488. // at the end of the split element or at the beginning of the new element.
  1489. //
  1490. if ( a.targetPosition.isEqual( b.position ) && context.abRelation == 'insertAtSource' ) {
  1491. newTargetPosition = b.moveTargetPosition;
  1492. }
  1493. // Case 4:
  1494. //
  1495. // Move operation targets just after the split element. We need to decide if the nodes should be inserted
  1496. // between two parts of split element, or after the new element.
  1497. //
  1498. // Split at `|`, while move operation moves `<p>Xyz</p>` and targets at `^`:
  1499. // <p>Foo|bar</p>^<p>baz</p>
  1500. // <p>Foo</p>^<p>bar</p><p>baz</p> or <p>Foo</p><p>bar</p>^<p>baz</p>?
  1501. //
  1502. // If there is no contextual information between operations (for example, they come from collaborative
  1503. // editing), we don't want to put some unrelated content (move) between parts of related content (split parts).
  1504. // However, if the split is from undo, in the past, the moved content might be targeting between the
  1505. // split parts, meaning that was exactly user's intention:
  1506. //
  1507. // <p>Foo</p>^<p>bar</p> <--- original situation, in "past".
  1508. // <p>Foobar</p>^ <--- after merge target position is transformed.
  1509. // <p>Foo|bar</p>^ <--- then the merge is undone, and split happens, which leads us to current situation.
  1510. //
  1511. // In this case it is pretty clear that the intention was to put new paragraph between those nodes,
  1512. // so we need to transform accordingly. We can detect this scenario thanks to relations.
  1513. //
  1514. if ( a.targetPosition.isEqual( b.insertionPosition ) && context.abRelation == 'insertBetween' ) {
  1515. newTargetPosition = a.targetPosition;
  1516. }
  1517. // The default case.
  1518. //
  1519. const transformed = moveRange._getTransformedBySplitOperation( b );
  1520. a.sourcePosition = transformed.start;
  1521. a.howMany = transformed.end.offset - transformed.start.offset;
  1522. a.targetPosition = newTargetPosition;
  1523. return [ a ];
  1524. } );
  1525. setTransformation( MoveOperation, MergeOperation, ( a, b, context ) => {
  1526. const movedRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1527. if ( b.deletionPosition.hasSameParentAs( a.sourcePosition ) && movedRange.containsPosition( b.sourcePosition ) ) {
  1528. if ( a.type == 'remove' ) {
  1529. // Case 1:
  1530. //
  1531. // The element to remove got merged.
  1532. //
  1533. // Merge operation does support merging elements which are not siblings. So it would not be a problem
  1534. // from technical point of view. However, if the element was removed, the intention of the user
  1535. // deleting it was to have it all deleted. From user experience point of view, moving back the
  1536. // removed nodes might be unexpected. This means that in this scenario we will reverse merging and remove the element.
  1537. //
  1538. if ( !context.aWasUndone ) {
  1539. return [ b.getReversed(), a ];
  1540. }
  1541. } else {
  1542. // Case 2:
  1543. //
  1544. // The element to move got merged and it was the only element to move.
  1545. // In this case just don't do anything, leave the node in the graveyard. Without special case
  1546. // it would be a move operation that moves 0 nodes, so maybe it is better just to return no-op.
  1547. //
  1548. if ( a.howMany == 1 ) {
  1549. if ( !context.bWasUndone ) {
  1550. return [ new NoOperation( 0 ) ];
  1551. } else {
  1552. a.sourcePosition = Position.createFromPosition( b.graveyardPosition );
  1553. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  1554. return [ a ];
  1555. }
  1556. }
  1557. }
  1558. }
  1559. // The default case.
  1560. //
  1561. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1562. const transformed = moveRange._getTransformedByMergeOperation( b );
  1563. a.sourcePosition = transformed.start;
  1564. a.howMany = transformed.end.offset - transformed.start.offset;
  1565. a.targetPosition = a.targetPosition._getTransformedByMergeOperation( b );
  1566. return [ a ];
  1567. } );
  1568. setTransformation( MoveOperation, WrapOperation, ( a, b, context ) => {
  1569. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1570. let newTargetPosition = a.targetPosition._getTransformedByWrapOperation( b );
  1571. // Case 1:
  1572. //
  1573. // Move operation targets to the beginning of the wrapped range. We need to decide if the moved nodes
  1574. // should also be wrapped.
  1575. //
  1576. if ( a.targetPosition.isEqual( b.position ) && context.abRelation == 'insertInside' ) {
  1577. newTargetPosition = b.targetPosition;
  1578. }
  1579. // Case 2:
  1580. //
  1581. // Some of the nodes to move got wrapped. In this case multiple ranges to move might need to be generated.
  1582. //
  1583. // First paragraph and the image should are wrapped, while the two images are moved after the last paragraph:
  1584. // [<paragraph>Foo</paragraph>{<image />]<image />}<paragraph>Bar</paragraph>
  1585. //
  1586. // After wrap:
  1587. // <blockQuote><paragraph>Foo</paragraph>[<image />]</blockQuote>[<image />]<paragraph>Bar</paragraph>
  1588. //
  1589. // After move:
  1590. // <blockQuote><paragraph>Foo</paragraph></blockQuote><paragraph>Bar</paragraph><image /><image />
  1591. //
  1592. if ( a.sourcePosition.hasSameParentAs( b.position ) ) {
  1593. // If move range contains or is equal to the wrapped range, just move it all together.
  1594. // Change `howMany` to reflect that nodes got wrapped.
  1595. if ( moveRange.containsRange( b.wrappedRange, true ) ) {
  1596. a.howMany = a.howMany - b.howMany + 1;
  1597. return [ a ];
  1598. }
  1599. const result = [];
  1600. let difference = moveRange.getDifference( b.wrappedRange )[ 0 ];
  1601. let common = moveRange.getIntersection( b.wrappedRange );
  1602. if ( difference ) {
  1603. difference = difference._getTransformedByWrapOperation( b );
  1604. result.push( new MoveOperation( difference.start, difference.end.offset - difference.start.offset, newTargetPosition, 0 ) );
  1605. }
  1606. if ( common ) {
  1607. common = common._getTransformedByWrapOperation( b );
  1608. result.push( new MoveOperation( common.start, common.end.offset - common.start.offset, newTargetPosition, 0 ) );
  1609. }
  1610. return result;
  1611. }
  1612. // The default case.
  1613. //
  1614. const transformed = moveRange._getTransformedByWrapOperation( b );
  1615. a.sourcePosition = transformed.start;
  1616. a.howMany = transformed.end.offset - transformed.start.offset;
  1617. a.targetPosition = newTargetPosition;
  1618. return [ a ];
  1619. } );
  1620. setTransformation( MoveOperation, UnwrapOperation, ( a, b ) => {
  1621. const moveRange = Range.createFromPositionAndShift( a.sourcePosition, a.howMany );
  1622. const transformed = moveRange._getTransformedByUnwrapOperation( b );
  1623. a.sourcePosition = transformed.start;
  1624. a.howMany = transformed.end.offset - transformed.start.offset;
  1625. a.targetPosition = a.targetPosition._getTransformedByUnwrapOperation( b );
  1626. return [ a ];
  1627. } );
  1628. // -----------------------
  1629. setTransformation( RenameOperation, InsertOperation, ( a, b ) => {
  1630. a.position = a.position._getTransformedByInsertOperation( b );
  1631. return [ a ];
  1632. } );
  1633. setTransformation( RenameOperation, MergeOperation, ( a, b ) => {
  1634. // Case 1:
  1635. //
  1636. // Element to rename got merged, so it was moved to `b.graveyardPosition`.
  1637. //
  1638. if ( a.position.isEqual( b.deletionPosition ) ) {
  1639. a.position = Position.createFromPosition( b.graveyardPosition );
  1640. a.position.stickiness = 'toNext';
  1641. return [ a ];
  1642. }
  1643. a.position = a.position._getTransformedByMergeOperation( b );
  1644. return [ a ];
  1645. } );
  1646. setTransformation( RenameOperation, MoveOperation, ( a, b ) => {
  1647. a.position = a.position._getTransformedByMoveOperation( b );
  1648. return [ a ];
  1649. } );
  1650. setTransformation( RenameOperation, RenameOperation, ( a, b, context ) => {
  1651. if ( a.position.isEqual( b.position ) ) {
  1652. if ( context.aIsStrong ) {
  1653. a.oldName = b.newName;
  1654. } else {
  1655. return [ new NoOperation( 0 ) ];
  1656. }
  1657. }
  1658. return [ a ];
  1659. } );
  1660. setTransformation( RenameOperation, SplitOperation, ( a, b ) => {
  1661. // Case 1:
  1662. //
  1663. // The element to rename has been split. In this case, the new element should be also renamed.
  1664. //
  1665. // User decides to change the paragraph to a list item:
  1666. // <paragraph>Foobar</paragraph>
  1667. //
  1668. // However, in meantime, split happens:
  1669. // <paragraph>Foo</paragraph><paragraph>bar</paragraph>
  1670. //
  1671. // As a result, rename both elements:
  1672. // <listItem>Foo</listItem><listItem>bar</listItem>
  1673. //
  1674. const renamePath = a.position.path;
  1675. const splitPath = b.position.getParentPath();
  1676. if ( compareArrays( renamePath, splitPath ) == 'same' && !b.graveyardPosition ) {
  1677. const extraRename = new RenameOperation( a.position.getShiftedBy( 1 ), a.oldName, a.newName, 0 );
  1678. return [ a, extraRename ];
  1679. }
  1680. // The default case.
  1681. //
  1682. a.position = a.position._getTransformedBySplitOperation( b );
  1683. return [ a ];
  1684. } );
  1685. setTransformation( RenameOperation, WrapOperation, ( a, b ) => {
  1686. a.position = a.position._getTransformedByWrapOperation( b );
  1687. return [ a ];
  1688. } );
  1689. setTransformation( RenameOperation, UnwrapOperation, ( a, b ) => {
  1690. // Case 1:
  1691. //
  1692. // Element to rename got unwrapped, so it was moved to `b.graveyardPosition`.
  1693. //
  1694. if ( a.position.isEqual( b.targetPosition ) ) {
  1695. a.position = Position.createFromPosition( b.graveyardPosition );
  1696. return [ a ];
  1697. }
  1698. a.position = a.position._getTransformedByUnwrapOperation( b );
  1699. return [ a ];
  1700. } );
  1701. // -----------------------
  1702. setTransformation( RootAttributeOperation, RootAttributeOperation, ( a, b, context ) => {
  1703. if ( a.root === b.root && a.key === b.key ) {
  1704. if ( !context.aIsStrong || a.newValue === b.newValue ) {
  1705. return [ new NoOperation( 0 ) ];
  1706. } else {
  1707. a.oldValue = b.newValue;
  1708. }
  1709. }
  1710. return [ a ];
  1711. } );
  1712. // -----------------------
  1713. setTransformation( SplitOperation, InsertOperation, ( a, b ) => {
  1714. // The default case.
  1715. //
  1716. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1717. a.howMany += b.howMany;
  1718. }
  1719. a.position = a.position._getTransformedByInsertOperation( b );
  1720. return [ a ];
  1721. } );
  1722. setTransformation( SplitOperation, MergeOperation, ( a, b, context ) => {
  1723. // Case 1:
  1724. //
  1725. // Split element got merged. If two different elements were merged, clients will have different content.
  1726. //
  1727. // Example. Merge at `{}`, split at `[]`:
  1728. // <heading>Foo</heading>{}<paragraph>B[]ar</paragraph>
  1729. //
  1730. // On merge side it will look like this:
  1731. // <heading>FooB[]ar</heading>
  1732. // <heading>FooB</heading><heading>ar</heading>
  1733. //
  1734. // On split side it will look like this:
  1735. // <heading>Foo</heading>{}<paragraph>B</paragraph><paragraph>ar</paragraph>
  1736. // <heading>FooB</heading><paragraph>ar</paragraph>
  1737. //
  1738. // Clearly, the second element is different for both clients.
  1739. //
  1740. // We could use the removed merge element from graveyard as a split element but then clients would have a different
  1741. // model state (in graveyard), because the split side client would still have an element in graveyard (removed by merge).
  1742. //
  1743. // To overcome this, in `SplitOperation` x `MergeOperation` transformation we will add additional `SplitOperation`
  1744. // in the graveyard, which will actually clone the merged-and-deleted element. Then, that cloned element will be
  1745. // used for splitting. Example below.
  1746. //
  1747. // Original state:
  1748. // <heading>Foo</heading>{}<paragraph>B[]ar</paragraph>
  1749. //
  1750. // Merge side client:
  1751. //
  1752. // After merge:
  1753. // <heading>FooB[]ar</heading> graveyard: <paragraph></paragraph>
  1754. //
  1755. // Extra split:
  1756. // <heading>FooB[]ar</heading> graveyard: <paragraph></paragraph><paragraph></paragraph>
  1757. //
  1758. // Use the "cloned" element from graveyard:
  1759. // <heading>FooB</heading><paragraph>ar</paragraph> graveyard: <paragraph></paragraph>
  1760. //
  1761. // Split side client:
  1762. //
  1763. // After split:
  1764. // <heading>Foo</heading>{}<paragraph>B</paragraph><paragraph>ar</paragraph>
  1765. //
  1766. // After merge:
  1767. // <heading>FooB</heading><paragraph>ar</paragraph> graveyard: <paragraph></paragraph>
  1768. //
  1769. // This special case scenario only applies if the original split operation clones the split element.
  1770. // If the original split operation has `graveyardPosition` set, it all doesn't have sense because split operation
  1771. // knows exactly which element it should use. So there would be no original problem with different contents.
  1772. //
  1773. // Additionally, the special case applies only if the merge wasn't already undone.
  1774. //
  1775. if ( !a.graveyardPosition && !context.bWasUndone && a.position.hasSameParentAs( b.sourcePosition ) ) {
  1776. const splitPath = b.graveyardPosition.path.slice();
  1777. splitPath.push( 0 );
  1778. const additionalSplit = new SplitOperation( new Position( b.graveyardPosition.root, splitPath ), 0, null, 0 );
  1779. a.position = a.position._getTransformedByMergeOperation( b );
  1780. a.graveyardPosition = Position.createFromPosition( additionalSplit.insertionPosition );
  1781. a.graveyardPosition.stickiness = 'toNext';
  1782. return [ additionalSplit, a ];
  1783. }
  1784. // The default case.
  1785. //
  1786. if ( a.position.hasSameParentAs( b.deletionPosition ) && !a.position.isAfter( b.deletionPosition ) ) {
  1787. a.howMany--;
  1788. }
  1789. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  1790. a.howMany += b.howMany;
  1791. }
  1792. a.position = a.position._getTransformedByMergeOperation( b );
  1793. if ( a.graveyardPosition ) {
  1794. a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
  1795. }
  1796. return [ a ];
  1797. } );
  1798. setTransformation( SplitOperation, MoveOperation, ( a, b, context ) => {
  1799. if ( a.graveyardPosition ) {
  1800. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  1801. }
  1802. // Case 1:
  1803. //
  1804. // If the split position is inside the moved range, we need to shift the split position to a proper place.
  1805. // The position cannot be moved together with moved range because that would result in splitting of an incorrect element.
  1806. //
  1807. // Characters `bc` should be moved to the second paragraph while split position is between them:
  1808. // <paragraph>A[b|c]d</paragraph><paragraph>Xyz</paragraph>
  1809. //
  1810. // After move, new split position is incorrect:
  1811. // <paragraph>Ad</paragraph><paragraph>Xb|cyz</paragraph>
  1812. //
  1813. // Correct split position:
  1814. // <paragraph>A|d</paragraph><paragraph>Xbcyz</paragraph>
  1815. //
  1816. // After split:
  1817. // <paragraph>A</paragraph><paragraph>d</paragraph><paragraph>Xbcyz</paragraph>
  1818. //
  1819. const rangeToMove = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  1820. if ( a.position.hasSameParentAs( b.sourcePosition ) && rangeToMove.containsPosition( a.position ) ) {
  1821. const howManyRemoved = b.howMany - ( a.position.offset - b.sourcePosition.offset );
  1822. a.howMany -= howManyRemoved;
  1823. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1824. a.howMany += b.howMany;
  1825. }
  1826. a.position = Position.createFromPosition( b.sourcePosition );
  1827. return [ a ];
  1828. }
  1829. // Case 2:
  1830. //
  1831. // Split is at a position where nodes were moved.
  1832. //
  1833. // This is a scenario described in `MoveOperation` x `SplitOperation` transformation but from the
  1834. // "split operation point of view".
  1835. //
  1836. if ( a.position.isEqual( b.targetPosition ) && ( context.baRelation == 'insertAtSource' || context.abRelation == 'splitBefore' ) ) {
  1837. a.howMany += b.howMany;
  1838. a.position = a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
  1839. return [ a ];
  1840. }
  1841. // The default case.
  1842. //
  1843. if ( a.position.hasSameParentAs( b.sourcePosition ) && a.position.offset <= b.sourcePosition.offset ) {
  1844. a.howMany -= b.howMany;
  1845. }
  1846. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1847. a.howMany += b.howMany;
  1848. }
  1849. // Change position stickiness to force a correct transformation.
  1850. a.position.stickiness = 'toNone';
  1851. a.position = a.position._getTransformedByMoveOperation( b );
  1852. a.position.stickiness = 'toNext';
  1853. return [ a ];
  1854. } );
  1855. setTransformation( SplitOperation, SplitOperation, ( a, b, context ) => {
  1856. // Case 1:
  1857. //
  1858. // Split at the same position.
  1859. //
  1860. // If there already was a split at the same position as in `a` operation, it means that the intention
  1861. // conveyed by `a` operation has already been fulfilled and `a` should not do anything (to avoid double split).
  1862. //
  1863. // However, there is a difference if these are new splits or splits created by undo. These have different
  1864. // intentions. Also splits moving back different elements from graveyard have different intentions. They
  1865. // are just different operations.
  1866. //
  1867. // So we cancel split operation only if it was really identical.
  1868. //
  1869. if ( a.position.isEqual( b.position ) ) {
  1870. if ( !a.graveyardPosition && !b.graveyardPosition ) {
  1871. return [ new NoOperation( 0 ) ];
  1872. }
  1873. if ( a.graveyardPosition && b.graveyardPosition && a.graveyardPosition.isEqual( b.graveyardPosition ) ) {
  1874. return [ new NoOperation( 0 ) ];
  1875. }
  1876. }
  1877. if ( a.graveyardPosition && b.graveyardPosition ) {
  1878. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  1879. }
  1880. // Case 2:
  1881. //
  1882. // Position where operation `b` inserted a new node after split is the same as the operation `a` split position.
  1883. // As in similar cases, there is ambiguity if the split should be before the new node (created by `b`) or after.
  1884. //
  1885. if ( a.position.isEqual( b.insertionPosition ) && context.abRelation == 'splitBefore' ) {
  1886. a.howMany++;
  1887. return [ a ];
  1888. }
  1889. // Case 3:
  1890. //
  1891. // This is a mirror to the case 2. above.
  1892. //
  1893. if ( b.position.isEqual( a.insertionPosition ) && context.baRelation == 'splitBefore' ) {
  1894. const newPositionPath = b.insertionPosition.path.slice();
  1895. newPositionPath.push( 0 );
  1896. const newPosition = new Position( b.insertionPosition.root, newPositionPath );
  1897. const moveOp = new MoveOperation( a.insertionPosition, 1, newPosition, 0 );
  1898. return [ a, moveOp ];
  1899. }
  1900. // The default case.
  1901. //
  1902. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1903. a.howMany -= b.howMany;
  1904. }
  1905. a.position = a.position._getTransformedBySplitOperation( b );
  1906. return [ a ];
  1907. } );
  1908. setTransformation( SplitOperation, WrapOperation, ( a, b ) => {
  1909. if ( a.graveyardPosition ) {
  1910. a.graveyardPosition = a.graveyardPosition._getTransformedByWrapOperation( b );
  1911. }
  1912. // Case 1:
  1913. //
  1914. // If split position has been wrapped, reverse the wrapping so that split can be applied as intended.
  1915. // This is an edge case scenario where it is difficult to find a correct solution.
  1916. // Since it will be a rare (or only theoretical) scenario, the algorithm will perform the easy solution.
  1917. //
  1918. if ( a.position.hasSameParentAs( b.position ) && b.wrappedRange.containsPosition( a.position ) ) {
  1919. const reversed = b.getReversed();
  1920. return [ reversed, a ];
  1921. }
  1922. // Case 2:
  1923. //
  1924. // Split position is in a wrapper element in graveyard. This is a case that happens in undo. Earlier, `SplitOperation`
  1925. // was transformed by `UnwrapOperation` and it was left in the unwrapped node. Now the unwrapped node will be re-used
  1926. // and we need to fix `howMany` property in the `SplitOperation`.
  1927. //
  1928. if ( b.graveyardPosition && compareArrays( b.graveyardPosition.path, a.position.getParentPath() ) == 'same' ) {
  1929. a.position = a.position._getCombined( b.graveyardPosition, b.position );
  1930. a.howMany = a.howMany + b.howMany;
  1931. return [ a ];
  1932. }
  1933. // The default case.
  1934. //
  1935. if ( a.position.hasSameParentAs( b.position ) && a.position.offset < b.position.offset ) {
  1936. a.howMany = a.howMany + 1 - b.howMany;
  1937. }
  1938. a.position = a.position._getTransformedByWrapOperation( b );
  1939. return [ a ];
  1940. } );
  1941. setTransformation( SplitOperation, UnwrapOperation, ( a, b, context ) => {
  1942. if ( a.graveyardPosition ) {
  1943. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  1944. }
  1945. // Case 1:
  1946. //
  1947. // The element to split got unwrapped.
  1948. //
  1949. // At this moment, there is nothing to split. We probably shouldn't split the "new parent" because that
  1950. // might be very different from the user's intention. Instead, we will leave split position inside the
  1951. // unwrapped element, which is now in graveyard.
  1952. //
  1953. // There is an exception to that - if the unwrap operation got undone. In this case we will follow normal
  1954. // transformation (move split position together with unwrapped nodes). Because later it will be in the wrapper
  1955. // element back again.
  1956. //
  1957. const splitInside = a.position.hasSameParentAs( b.position );
  1958. if ( splitInside ) {
  1959. const path = b.graveyardPosition.path.slice();
  1960. path.push( 0 );
  1961. a.position = new Position( b.graveyardPosition.root, path );
  1962. if ( !context.bWasUndone ) {
  1963. a.howMany = 0;
  1964. } else {
  1965. // New `howMany` will be 0 or below. Will be fixed in SplitOperation x WrapOperation.
  1966. a.howMany = a.howMany - b.howMany;
  1967. }
  1968. return [ a ];
  1969. }
  1970. // The default case.
  1971. //
  1972. if ( a.position.hasSameParentAs( b.targetPosition ) && a.position.offset < b.targetPosition.offset ) {
  1973. a.howMany = a.howMany - 1 + b.howMany;
  1974. }
  1975. a.position = a.position._getTransformedByUnwrapOperation( b );
  1976. return [ a ];
  1977. } );
  1978. // -----------------------
  1979. setTransformation( WrapOperation, InsertOperation, ( a, b ) => {
  1980. // The default case.
  1981. //
  1982. const transformed = a.wrappedRange._getTransformedByInsertOperation( b, false )[ 0 ];
  1983. a.position = transformed.start;
  1984. a.howMany = transformed.end.offset - transformed.start.offset;
  1985. return [ a ];
  1986. } );
  1987. setTransformation( WrapOperation, MergeOperation, ( a, b ) => {
  1988. if ( a.graveyardPosition ) {
  1989. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( b.graveyardPosition, 1 );
  1990. }
  1991. // Case 1:
  1992. //
  1993. // The element to wrap got merged and it was the only element to wrap. Let's wrap it in the graveyard.
  1994. //
  1995. if ( a.position.isEqual( b.deletionPosition ) && a.howMany == 1 ) {
  1996. a.position = Position.createFromPosition( b.graveyardPosition );
  1997. a.position.stickiness = 'toNext';
  1998. return [ a ];
  1999. }
  2000. // The default case.
  2001. //
  2002. const transformed = a.wrappedRange._getTransformedByMergeOperation( b );
  2003. a.position = transformed.start;
  2004. a.howMany = transformed.end.offset - transformed.start.offset;
  2005. return [ a ];
  2006. } );
  2007. setTransformation( WrapOperation, MoveOperation, ( a, b, context ) => {
  2008. if ( a.graveyardPosition ) {
  2009. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  2010. }
  2011. // Case 1:
  2012. //
  2013. // Move operation targets to the beginning of the wrapped range.
  2014. // We need to decide if the moved node should also be wrapped.
  2015. //
  2016. if ( a.position.isEqual( b.targetPosition ) && context.baRelation == 'insertInside' ) {
  2017. a.position._getTransformedByDeletion( b.sourcePosition, b.howMany );
  2018. a.howMany += b.howMany;
  2019. return [ a ];
  2020. }
  2021. const moveRange = Range.createFromPositionAndShift( b.sourcePosition, b.howMany );
  2022. const wrappedRange = a.wrappedRange;
  2023. // Case 2:
  2024. //
  2025. // Range to wrap is equal to moved range or is wholly inside the moved range. In other words, all nodes
  2026. // to wrap has been moved. In this case, wrap all of those nodes in their new place.
  2027. //
  2028. // Note that this is different than the default case. In the default case, if only some nodes are moved
  2029. // those moved nodes are not wrapped at their new location. Only those nodes which didn't move are wrapped
  2030. // at their old location.
  2031. //
  2032. if ( moveRange.containsRange( wrappedRange, true ) ) {
  2033. a.position = a.position._getCombined( b.sourcePosition, b.getMovedRangeStart() );
  2034. return [ a ];
  2035. }
  2036. // The default case.
  2037. //
  2038. let transformed = wrappedRange._getTransformedByDeletion( b.sourcePosition, b.howMany );
  2039. transformed = transformed._getTransformedByInsertion( b.targetPosition, b.howMany, false )[ 0 ];
  2040. a.position = transformed.start;
  2041. a.howMany = transformed.end.offset - transformed.start.offset;
  2042. return [ a ];
  2043. } );
  2044. setTransformation( WrapOperation, SplitOperation, ( a, b ) => {
  2045. // Case 1:
  2046. //
  2047. // If range to wrap got split cancel the wrapping. See `SplitOperation` x `WrapOperation`.
  2048. //
  2049. const isInside = a.position.hasSameParentAs( b.position ) && a.wrappedRange.containsPosition( b.position );
  2050. if ( isInside ) {
  2051. // We cannot just return no-op in this case, because in the mirror case scenario the wrap is reversed, which
  2052. // might introduce a new node in the graveyard (if the wrap didn't have `graveyardPosition`, then the wrap
  2053. // created a new element which was put to the graveyard when the wrap was reversed).
  2054. //
  2055. // Instead, a node in graveyard will be inserted.
  2056. //
  2057. if ( a.element ) {
  2058. const graveyard = a.position.root.document.graveyard;
  2059. const graveyardPosition = new Position( graveyard, [ 0 ] );
  2060. return [ new InsertOperation( graveyardPosition, a.element, 0 ) ];
  2061. } else {
  2062. // If `WrapOperation` comes from undo then the, in `SplitOperation` x `WrapOperation`, after it is reversed
  2063. // that element will be moved to graveyard, at offset `0`. We should reflect it here to avoid different
  2064. // graveyard root states.
  2065. //
  2066. const gyPos = new Position( a.graveyardPosition.root, [ 0 ] );
  2067. return [ new MoveOperation( a.graveyardPosition, 1, gyPos, 0 ) ];
  2068. }
  2069. }
  2070. if ( a.graveyardPosition && b.graveyardPosition ) {
  2071. a.graveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  2072. }
  2073. // Case 2:
  2074. //
  2075. // If last element from range to wrap has been split, include the newly created element in the wrap range.
  2076. //
  2077. if ( b.insertionPosition.isEqual( a.wrappedRange.end ) ) {
  2078. a.howMany++;
  2079. return [ a ];
  2080. }
  2081. // The default case.
  2082. //
  2083. const transformed = a.wrappedRange._getTransformedBySplitOperation( b );
  2084. a.position = transformed.start;
  2085. a.howMany = transformed.end.offset - transformed.start.offset;
  2086. return [ a ];
  2087. } );
  2088. setTransformation( WrapOperation, WrapOperation, ( a, b, context ) => {
  2089. let newGraveyardPosition = a.graveyardPosition;
  2090. if ( a.graveyardPosition && b.graveyardPosition ) {
  2091. newGraveyardPosition = a.graveyardPosition._getTransformedByDeletion( b.graveyardPosition, 1 );
  2092. }
  2093. // Case 1:
  2094. //
  2095. // If ranges to wrap intersect on the same level then there is a conflict.
  2096. // Depending on `context.aIsStrong` the nodes in the intersecting part should be left as they were wrapped
  2097. // or moved to the new wrapping element.
  2098. //
  2099. // `Foo` and `Bar` are to be wrapped in `blockQuote`, while `Bar` and `Xyz` in `div`.
  2100. // [<paragraph>Foo</paragraph>{<paragraph>Bar</paragraph>]<paragraph>Xyz</paragraph>}
  2101. //
  2102. // After `blockQuote` wrap:
  2103. // <blockQuote>
  2104. // <paragraph>Foo</paragraph><paragraph>Bar</paragraph>
  2105. // </blockQuote>
  2106. // <paragraph>Xyz</paragraph>
  2107. //
  2108. // After `div` wrap:
  2109. // <blockQuote>
  2110. // <paragraph>Foo</paragraph><paragraph>Bar</paragraph>
  2111. // </blockQuote>
  2112. // <div>
  2113. // <paragraph>Xyz</paragraph>
  2114. // </div>
  2115. //
  2116. // Or, if `div` wrap is stronger:
  2117. // <blockQuote>
  2118. // <paragraph>Foo</paragraph>
  2119. // </blockQuote>
  2120. // <div>
  2121. // <paragraph>Bar</paragraph><paragraph>Xyz</paragraph>
  2122. // </div>
  2123. //
  2124. // The range from incoming operation may be also wholly included in the range from operation `b`.
  2125. // Then, cancel the wrapping. The same happens when the ranges are identical but in that case,
  2126. // `context.aIsStrong` decides which wrapping should be cancelled.
  2127. //
  2128. // Lastly, the range from operation `b` may be wholly included in the range from incoming operation.
  2129. // Then, unwrap the range from operation `b` and do a wrap on full range from operation `a`.
  2130. //
  2131. if ( a.position.hasSameParentAs( b.position ) ) {
  2132. const ranges = a.wrappedRange.getDifference( b.wrappedRange );
  2133. // Range from `a` is contained in range from `b` or ranges are equal.
  2134. if ( ranges.length == 0 ) {
  2135. if ( a.wrappedRange.isEqual( b.wrappedRange ) && context.aIsStrong ) {
  2136. // If ranges are equal and `a` is a stronger operation, reverse `b` operation and then apply `a` operation.
  2137. const reversed = b.getReversed();
  2138. if ( a.graveyardPosition ) {
  2139. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( reversed );
  2140. }
  2141. return [ reversed, a ];
  2142. }
  2143. // If `a` is contained in `b` or they are same but `b` is stronger, operation `a` should do nothing.
  2144. // However, to keep the model state same on both clients, it is needed to create a wrapping element in the graveyard.
  2145. const graveyard = a.position.root.document.graveyard;
  2146. a.position = new Position( graveyard, [ 0 ] );
  2147. a.howMany = 0;
  2148. a.graveyardPosition = newGraveyardPosition;
  2149. return [ a ];
  2150. }
  2151. // Ranges intersect.
  2152. else {
  2153. // Range from `b` has some extra nodes other than nodes from `a`.
  2154. if ( !a.wrappedRange.containsRange( b.wrappedRange, true ) ) {
  2155. if ( context.aIsStrong ) {
  2156. // If the incoming wrap operation is strong, we need to reverse the previous wrap, then apply the incoming
  2157. // operation as is, then re-wrap the other nodes that were wrapped in the previous wrap.
  2158. //
  2159. // Content already wrapped into `blockQuote` but that wrap is not strong:
  2160. // <blockQuote><p>Foo</p><p>Bar</p></blockQuote><p>Xyz</p>
  2161. //
  2162. // Unwrap:
  2163. // <p>Foo</p><p>Bar</p><p>Xyz</p>
  2164. //
  2165. // Wrap with stronger wrap:
  2166. // <p>Foo</p><div><p>Bar</p><p>Xyz</p></div>
  2167. //
  2168. // Re-wrap:
  2169. // <blockQuote><p>Foo</p></blockQuote><div><p>Bar</p><p>Xyz</p></div>
  2170. //
  2171. const reversed = b.getReversed();
  2172. if ( a.graveyardPosition ) {
  2173. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( reversed );
  2174. }
  2175. const bOnlyRange = b.wrappedRange.getDifference( a.wrappedRange )[ 0 ];
  2176. const rewrapRange = bOnlyRange._getTransformedByWrapOperation( a );
  2177. const rewrapHowMany = rewrapRange.end.offset - rewrapRange.start.offset;
  2178. const rewrap = new WrapOperation( rewrapRange.start, rewrapHowMany, reversed.graveyardPosition, 0 );
  2179. return [ reversed, a, rewrap ];
  2180. } else {
  2181. // If the incoming wrap operation is not strong, just wrap those nodes which were not wrapped already.
  2182. const range = ranges[ 0 ]._getTransformedByWrapOperation( b );
  2183. a.position = range.start;
  2184. a.howMany = range.end.offset - range.start.offset;
  2185. a.graveyardPosition = newGraveyardPosition;
  2186. return [ a ];
  2187. }
  2188. }
  2189. // Range from `b` is contained in range from `a`. Reverse operation `b` in addition to operation `a`.
  2190. else {
  2191. const reversed = b.getReversed();
  2192. if ( a.graveyardPosition ) {
  2193. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( reversed );
  2194. }
  2195. return [ reversed, a ];
  2196. }
  2197. }
  2198. }
  2199. // The default case.
  2200. //
  2201. const transformed = a.wrappedRange._getTransformedByWrapOperation( b );
  2202. a.position = transformed.start;
  2203. a.howMany = transformed.end.offset - transformed.start.offset;
  2204. a.graveyardPosition = newGraveyardPosition;
  2205. return [ a ];
  2206. } );
  2207. setTransformation( WrapOperation, UnwrapOperation, ( a, b ) => {
  2208. // Case 1:
  2209. //
  2210. // Wrapping element from graveyard got unwrapped. See `UnwrapOperation` x `WrapOperation` mirror case.
  2211. //
  2212. if ( a.graveyardPosition && b.targetPosition.isEqual( a.graveyardPosition ) ) {
  2213. return [ new NoOperation( 0 ) ];
  2214. }
  2215. const transformed = a.wrappedRange._getTransformedByUnwrapOperation( b );
  2216. a.position = transformed.start;
  2217. a.howMany = transformed.end.offset - transformed.start.offset;
  2218. if ( a.graveyardPosition ) {
  2219. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  2220. }
  2221. return [ a ];
  2222. } );
  2223. // -----------------------
  2224. setTransformation( UnwrapOperation, InsertOperation, ( a, b ) => {
  2225. // Case 1:
  2226. //
  2227. // Insert operation inserts nodes into the unwrapped element.
  2228. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  2229. //
  2230. if ( a.position.hasSameParentAs( b.position ) ) {
  2231. a.howMany += b.howMany;
  2232. }
  2233. a.position = a.position._getTransformedByInsertOperation( b );
  2234. return [ a ];
  2235. } );
  2236. setTransformation( UnwrapOperation, MergeOperation, ( a, b, context ) => {
  2237. // Case 1:
  2238. //
  2239. // The element to unwrap got merged.
  2240. //
  2241. // There are multiple possible solution to resolve this conflict:
  2242. //
  2243. // * unwrap the merge target element (all nodes are unwrapped),
  2244. // * cancel the unwrap (no nodes stayed unwrapped),
  2245. // * reverse the merge and apply the original unwrap (some nodes are unwrapped and some are not).
  2246. //
  2247. if ( a.position.isEqual( b.sourcePosition ) ) {
  2248. return [ b.getReversed(), a ];
  2249. }
  2250. // The default case.
  2251. //
  2252. // There are some scenarios here where `UnwrapOperation#howMany` needs to be changed, but they are pretty straightforward.
  2253. //
  2254. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  2255. a.howMany += b.howMany;
  2256. }
  2257. if ( a.position.hasSameParentAs( b.graveyardPosition ) ) {
  2258. a.howMany++;
  2259. }
  2260. if ( a.position.hasSameParentAs( b.deletionPosition ) ) {
  2261. a.howMany--;
  2262. }
  2263. a.position = a.position._getTransformedByMergeOperation( b );
  2264. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  2265. a.graveyardPosition = a.graveyardPosition._getTransformedByMergeOperation( b );
  2266. }
  2267. return [ a ];
  2268. } );
  2269. setTransformation( UnwrapOperation, MoveOperation, ( a, b ) => {
  2270. // Case 1:
  2271. //
  2272. // Move operation moves nodes from the unwrapped element.
  2273. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  2274. //
  2275. if ( a.position.hasSameParentAs( b.sourcePosition ) ) {
  2276. a.howMany -= b.howMany;
  2277. }
  2278. // Case 2:
  2279. //
  2280. // Move operation moves nodes into the unwrapped element.
  2281. // This does not have any impact on `UnwrapOperation#position`, but `#howMany` has to be changed.
  2282. // Note, that case 1 and case 2 may happen together.
  2283. //
  2284. if ( a.position.hasSameParentAs( b.targetPosition ) ) {
  2285. a.howMany += b.howMany;
  2286. }
  2287. // The default case.
  2288. //
  2289. a.position = a.position._getTransformedByMoveOperation( b );
  2290. if ( !a.graveyardPosition.isEqual( b.targetPosition ) ) {
  2291. a.graveyardPosition = a.graveyardPosition._getTransformedByMoveOperation( b );
  2292. }
  2293. return [ a ];
  2294. } );
  2295. setTransformation( UnwrapOperation, SplitOperation, ( a, b ) => {
  2296. // Case 1:
  2297. //
  2298. // The element to unwrap got split, so now there are two elements to unwrap.
  2299. // This can be solved either by providing two unwrap operations or by reversing the split and applying the original unwrap.
  2300. //
  2301. if ( a.position.hasSameParentAs( b.position ) ) {
  2302. const reversed = b.getReversed();
  2303. // Merge operation (reversed split) always puts a node into a graveyard. Not every split operation pulls a node
  2304. // from the graveyard, though. This means that after reversing a split operation, there might be a need to
  2305. // update a position in graveyard.
  2306. if ( !b.graveyardPosition ) {
  2307. a.graveyardPosition = a.graveyardPosition._getTransformedByInsertion( reversed.graveyardPosition, 1 );
  2308. }
  2309. return [ reversed, a ];
  2310. }
  2311. // Case 2:
  2312. //
  2313. // The split element is the last element in unwrapped element. In this case, we need to manually modify
  2314. // `howMany` property because it wouldn't be correctly calculated by `_getTransformedBySplitOperation`.
  2315. //
  2316. if ( a.position.hasSameParentAs( b.insertionPosition ) ) {
  2317. a.howMany++;
  2318. return [ a ];
  2319. }
  2320. // The default case.
  2321. //
  2322. a.position = a.position._getTransformedBySplitOperation( b );
  2323. if ( b.graveyardPosition && b.graveyardPosition.hasSameParentAs( a.position ) ) {
  2324. a.howMany--;
  2325. }
  2326. a.graveyardPosition = a.graveyardPosition._getTransformedBySplitOperation( b );
  2327. return [ a ];
  2328. } );
  2329. setTransformation( UnwrapOperation, WrapOperation, ( a, b ) => {
  2330. // Case 1:
  2331. //
  2332. // Unwrapping an element that was in graveyard and was used to wrap nodes.
  2333. //
  2334. // Unwrap operation that unwraps in graveyard is a result of transformation of two same `UnwrapOperation`.
  2335. // Then `a` unwraps an element in graveyard and has `howMany` set to `0`. Now, the unwrap operation
  2336. // is going to be transformed by a wrap operation which undoes the previous operation. Operation `a` is
  2337. // in a state that is ready for this, but `howMany` has to be set again to a proper value.
  2338. //
  2339. if ( b.graveyardPosition && a.targetPosition.isEqual( b.graveyardPosition ) ) {
  2340. a.howMany = b.howMany;
  2341. }
  2342. // The default case.
  2343. //
  2344. if ( a.position.hasSameParentAs( b.position ) ) {
  2345. a.howMany = a.howMany - b.howMany + 1;
  2346. }
  2347. a.position = a.position._getTransformedByWrapOperation( b );
  2348. a.graveyardPosition = a.graveyardPosition._getTransformedByWrapOperation( b );
  2349. return [ a ];
  2350. } );
  2351. setTransformation( UnwrapOperation, UnwrapOperation, ( a, b, context ) => {
  2352. // Case 1:
  2353. //
  2354. // Operations unwrap the same element.
  2355. //
  2356. // This situation is similar to `MergeOperation` x `MergeOperation` case. See it for more details.
  2357. //
  2358. if ( a.position.isEqual( b.position ) ) {
  2359. const path = b.graveyardPosition.path.slice();
  2360. path.push( 0 );
  2361. a.position = new Position( b.graveyardPosition.root, path );
  2362. a.howMany = 0;
  2363. a.graveyardPosition = Position.createFromPosition( b.graveyardPosition );
  2364. return [ a ];
  2365. }
  2366. // The default case.
  2367. //
  2368. a.position = a.position._getTransformedByUnwrapOperation( b );
  2369. if ( !a.graveyardPosition.isEqual( b.graveyardPosition ) || !context.aIsStrong ) {
  2370. a.graveyardPosition = a.graveyardPosition._getTransformedByUnwrapOperation( b );
  2371. }
  2372. return [ a ];
  2373. } );
  2374. // Checks whether `MoveOperation` `targetPosition` is inside a node from the moved range of the other `MoveOperation`.
  2375. //
  2376. // @private
  2377. // @param {module:engine/model/operation/moveoperation~MoveOperation} a
  2378. // @param {module:engine/model/operation/moveoperation~MoveOperation} b
  2379. // @returns {Boolean}
  2380. function _moveTargetIntoMovedRange( a, b ) {
  2381. return a.targetPosition._getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
  2382. }
  2383. // Helper function for `MoveOperation` x `MoveOperation` transformation. Converts given ranges and target position to
  2384. // move operations and returns them.
  2385. //
  2386. // Ranges and target position will be transformed on-the-fly when generating operations.
  2387. //
  2388. // Given `ranges` should be in the order of how they were in the original transformed operation.
  2389. //
  2390. // Given `targetPosition` is the target position of the first range from `ranges`.
  2391. //
  2392. // @private
  2393. // @param {Array.<module:engine/model/range~Range>} ranges
  2394. // @param {module:engine/model/position~Position} targetPosition
  2395. // @returns {Array.<module:engine/model/operation/moveoperation~MoveOperation>}
  2396. function _makeMoveOperationsFromRanges( ranges, targetPosition ) {
  2397. // At this moment we have some ranges and a target position, to which those ranges should be moved.
  2398. // Order in `ranges` array is the go-to order of after transformation.
  2399. //
  2400. // We are almost done. We have `ranges` and `targetPosition` to make operations from.
  2401. // Unfortunately, those operations may affect each other. Precisely, first operation after move
  2402. // may affect source range and target position of second and third operation. Same with second
  2403. // operation affecting third.
  2404. //
  2405. // We need to fix those source ranges and target positions once again, before converting `ranges` to operations.
  2406. const operations = [];
  2407. // Keep in mind that nothing will be transformed if there is just one range in `ranges`.
  2408. for ( let i = 0; i < ranges.length; i++ ) {
  2409. // Create new operation out of a range and target position.
  2410. const range = ranges[ i ];
  2411. const op = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, 0 );
  2412. operations.push( op );
  2413. // Transform other ranges by the generated operation.
  2414. for ( let j = i + 1; j < ranges.length; j++ ) {
  2415. // All ranges in `ranges` array should be:
  2416. //
  2417. // * non-intersecting (these are part of original operation source range), and
  2418. // * `targetPosition` does not target into them (opposite would mean that transformed operation targets "inside itself").
  2419. //
  2420. // This means that the transformation will be "clean" and always return one result.
  2421. ranges[ j ] = ranges[ j ]._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany )[ 0 ];
  2422. }
  2423. targetPosition = targetPosition._getTransformedByMove( op.sourcePosition, op.targetPosition, op.howMany );
  2424. }
  2425. return operations;
  2426. }