8
0

wrapdelta.js 3.8 KB

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