/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ 'use strict'; import Delta from './delta.js'; import UnwrapDelta from './unwrapdelta.js'; import { register } from '../batch.js'; import Position from '../position.js'; import Range from '../range.js'; import Element from '../element.js'; import InsertOperation from '../operation/insertoperation.js'; import MoveOperation from '../operation/moveoperation.js'; import CKEditorError from '../../../utils/ckeditorerror.js'; /** * @classdesc * To provide specific OT behavior and better collisions solving, {@link core.treeModel.Batch#merge} method * uses the `WrapDelta` class which inherits from the `Delta` class and may overwrite some methods. * * @memberOf core.treeModel.delta */ export default class WrapDelta extends Delta { /** * Range to wrap or `null` if there are no operations in the delta. * * @type {core.treeModel.Range|null} */ get range() { let moveOp = this._moveOperation; return moveOp ? Range.createFromPositionAndShift( moveOp.sourcePosition, moveOp.howMany ) : null; } /** * Operation that moves wrapped nodes to their new parent or `null` if there are no operations in the delta. * * @protected * @type {core.treeModel.operation.MoveOperation|null} */ get _moveOperation() { return this.operations[ 1 ] || null; } /** * @see core.treeModel.delta.Delta#_reverseDeltaClass * @private * @type {Object} */ get _reverseDeltaClass() { return UnwrapDelta; } static get _priority() { return 10; } } /** * Wraps given range with given element or with a new element of specified name if string has been passed. * **Note:** given range should be a "flat range" (see {@link core.treeModel.Range#isFlat}). If not, error will be thrown. * * @chainable * @method core.treeModel.Batch#wrap * @param {core.treeModel.Range} range Range to wrap. * @param {core.treeModel.Element|String} elementOrString Element or name of element to wrap the range with. */ register( 'wrap', function( range, elementOrString ) { if ( !range.isFlat ) { /** * Range to wrap is not flat. * * @error batch-wrap-range-not-flat */ throw new CKEditorError( 'batch-wrap-range-not-flat: Range to wrap is not flat.' ); } let element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString ); if ( element.getChildCount() > 0 ) { /** * Element to wrap with is not empty. * * @error batch-wrap-element-not-empty */ throw new CKEditorError( 'batch-wrap-element-not-empty: Element to wrap with is not empty.' ); } if ( element.parent !== null ) { /** * Element to wrap with is already attached to a tree model. * * @error batch-wrap-element-attached */ throw new CKEditorError( 'batch-wrap-element-attached: Element to wrap with is already attached to tree model.' ); } const delta = new WrapDelta(); let insert = new InsertOperation( range.end, element, this.doc.version ); delta.addOperation( insert ); this.doc.applyOperation( insert ); let targetPosition = Position.createFromParentAndOffset( element, 0 ); let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version ); delta.addOperation( move ); this.doc.applyOperation( move ); this.addDelta( delta ); return this; } );