8
0

wrapdelta.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import Delta from './delta.js';
  7. import { registerDeserializer } from './deltafactory.js';
  8. import UnwrapDelta from './unwrapdelta.js';
  9. import { register } from '../batch.js';
  10. import Position from '../position.js';
  11. import Range from '../range.js';
  12. import Element from '../element.js';
  13. import InsertOperation from '../operation/insertoperation.js';
  14. import MoveOperation from '../operation/moveoperation.js';
  15. import CKEditorError from '../../../utils/ckeditorerror.js';
  16. /**
  17. * @classdesc
  18. * To provide specific OT behavior and better collisions solving, {@link engine.treeModel.Batch#merge} method
  19. * uses the `WrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
  20. *
  21. * @memberOf engine.treeModel.delta
  22. */
  23. export default class WrapDelta extends Delta {
  24. /**
  25. * Range to wrap or `null` if there are no operations in the delta.
  26. *
  27. * @type {engine.treeModel.Range|null}
  28. */
  29. get range() {
  30. let moveOp = this._moveOperation;
  31. return moveOp ? Range.createFromPositionAndShift( moveOp.sourcePosition, moveOp.howMany ) : null;
  32. }
  33. /**
  34. * How many nodes is wrapped by the delta or `null` if there are no operations in delta.
  35. *
  36. * @type {Number}
  37. */
  38. get howMany() {
  39. let range = this.range;
  40. return range ? range.end.offset - range.start.offset : 0;
  41. }
  42. /**
  43. * Operation that inserts wrapping element or `null` if there are no operations in the delta.
  44. *
  45. * @protected
  46. * @type {engine.treeModel.operation.InsertOperation|engine.treeModel.operation.ReinsertOperation}
  47. */
  48. get _insertOperation() {
  49. return this.operations[ 0 ] || null;
  50. }
  51. /**
  52. * Operation that moves wrapped nodes to their new parent or `null` if there are no operations in the delta.
  53. *
  54. * @protected
  55. * @type {engine.treeModel.operation.MoveOperation|null}
  56. */
  57. get _moveOperation() {
  58. return this.operations[ 1 ] || null;
  59. }
  60. get _reverseDeltaClass() {
  61. return UnwrapDelta;
  62. }
  63. static get className() {
  64. return 'engine.treeModel.delta.WrapDelta';
  65. }
  66. static get _priority() {
  67. return 10;
  68. }
  69. }
  70. /**
  71. * Wraps given range with given element or with a new element of specified name if string has been passed.
  72. * **Note:** given range should be a "flat range" (see {@link engine.treeModel.Range#isFlat}). If not, error will be thrown.
  73. *
  74. * @chainable
  75. * @method engine.treeModel.Batch#wrap
  76. * @param {engine.treeModel.Range} range Range to wrap.
  77. * @param {engine.treeModel.Element|String} elementOrString Element or name of element to wrap the range with.
  78. */
  79. register( 'wrap', function( range, elementOrString ) {
  80. if ( !range.isFlat ) {
  81. /**
  82. * Range to wrap is not flat.
  83. *
  84. * @error batch-wrap-range-not-flat
  85. */
  86. throw new CKEditorError( 'batch-wrap-range-not-flat: Range to wrap is not flat.' );
  87. }
  88. let element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString );
  89. if ( element.getChildCount() > 0 ) {
  90. /**
  91. * Element to wrap with is not empty.
  92. *
  93. * @error batch-wrap-element-not-empty
  94. */
  95. throw new CKEditorError( 'batch-wrap-element-not-empty: Element to wrap with is not empty.' );
  96. }
  97. if ( element.parent !== null ) {
  98. /**
  99. * Element to wrap with is already attached to a tree model.
  100. *
  101. * @error batch-wrap-element-attached
  102. */
  103. throw new CKEditorError( 'batch-wrap-element-attached: Element to wrap with is already attached to tree model.' );
  104. }
  105. const delta = new WrapDelta();
  106. this.addDelta( delta );
  107. let insert = new InsertOperation( range.end, element, this.doc.version );
  108. delta.addOperation( insert );
  109. this.doc.applyOperation( insert );
  110. let targetPosition = Position.createFromParentAndOffset( element, 0 );
  111. let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version );
  112. delta.addOperation( move );
  113. this.doc.applyOperation( move );
  114. return this;
  115. } );
  116. registerDeserializer( WrapDelta.className, WrapDelta );