8
0

history.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. // Load all basic deltas and transformations, they register themselves, but they need to be imported somewhere.
  7. import deltas from './delta/basic-deltas.js'; // jshint ignore:line
  8. import transformations from './delta/basic-transformations.js'; // jshint ignore:line
  9. import transform from './delta/transform.js';
  10. import CKEditorError from '../../utils/ckeditorerror.js';
  11. /**
  12. * History keeps the track of all the deltas applied to the {@link engine.model.Document document} and provides
  13. * utility tools to operate on the history. Most of times history is needed to transform a delta that has wrong
  14. * {@link engine.model.delta.Delta#baseVersion} to a state where it can be applied to the document.
  15. *
  16. * @memberOf engine.model
  17. */
  18. export default class History {
  19. /**
  20. * Creates an empty History instance.
  21. */
  22. constructor() {
  23. /**
  24. * Deltas added to the history.
  25. *
  26. * @private
  27. * @member {Array.<engine.model.delta.Delta>} engine.model.History#_deltas
  28. */
  29. this._deltas = [];
  30. /**
  31. * Helper structure that maps added delta's base version to the index in {@link engine.model.History#_deltas}
  32. * at which the delta was added.
  33. *
  34. * @private
  35. * @member {Map} engine.model.History#_historyPoints
  36. */
  37. this._historyPoints = new Map();
  38. }
  39. /**
  40. * Gets the number of base version which an up-to-date operation should have.
  41. *
  42. * @private
  43. * @type {Number}
  44. */
  45. get _nextHistoryPoint() {
  46. const lastDelta = this._deltas[ this._deltas.length - 1 ];
  47. return lastDelta.baseVersion + lastDelta.operations.length;
  48. }
  49. /**
  50. * Adds an operation to the history.
  51. *
  52. * @param {engine.model.operation.Operation} operation Operation to add.
  53. */
  54. addOperation( operation ) {
  55. const delta = operation.delta;
  56. // History cares about deltas not singular operations.
  57. // Operations from a delta are added one by one, from first to last.
  58. // Operations from one delta cannot be mixed with operations from other deltas.
  59. // This all leads us to the conclusion that we could just save deltas history.
  60. // What is more, we need to check only the last position in history to check if delta is already in the history.
  61. if ( delta && this._deltas[ this._deltas.length - 1 ] !== delta ) {
  62. const index = this._deltas.length;
  63. this._deltas[ index ] = delta;
  64. this._historyPoints.set( delta.baseVersion, index );
  65. }
  66. }
  67. /**
  68. * Transforms out-dated delta by all deltas that were added to the history since the given delta's base version. In other
  69. * words, it makes the delta up-to-date with the history. The transformed delta(s) is (are) ready to be applied
  70. * to the {@link engine.model.Document document}.
  71. *
  72. * @param {engine.model.delta.Delta} delta Delta to update.
  73. * @returns {Array.<engine.model.delta.Delta>} Result of transformation which is an array containing one or more deltas.
  74. */
  75. getTransformedDelta( delta ) {
  76. if ( delta.baseVersion === this._nextHistoryPoint ) {
  77. return [ delta ];
  78. }
  79. let transformed = [ delta ];
  80. for ( let historyDelta of this.getDeltas( delta.baseVersion ) ) {
  81. let allResults = [];
  82. for ( let deltaToTransform of transformed ) {
  83. const transformedDelta = History._transform( deltaToTransform, historyDelta );
  84. allResults = allResults.concat( transformedDelta );
  85. }
  86. transformed = allResults;
  87. }
  88. // Fix base versions.
  89. let baseVersion = transformed[ 0 ].operations[ 0 ].baseVersion;
  90. for ( let i = 0; i < transformed.length; i++ ) {
  91. transformed[ i ].baseVersion = baseVersion;
  92. baseVersion += transformed[ i ].operations.length;
  93. }
  94. return transformed;
  95. }
  96. /**
  97. * Returns all deltas from history, starting from given history point (if passed).
  98. *
  99. * @param {Number} from History point.
  100. * @returns {Iterator.<engine.model.delta.Delta>} Deltas from given history point to the end of history.
  101. */
  102. *getDeltas( from = 0 ) {
  103. let i = this._historyPoints.get( from );
  104. if ( i === undefined ) {
  105. throw new CKEditorError( 'history-wrong-version: Cannot retrieve given point in the history.' );
  106. }
  107. for ( ; i < this._deltas.length; i++ ) {
  108. yield this._deltas[ i ];
  109. }
  110. }
  111. /**
  112. * Transforms given delta by another given delta. Exposed for testing purposes.
  113. *
  114. * @protected
  115. * @param {engine.model.delta.Delta} toTransform Delta to be transformed.
  116. * @param {engine.model.delta.Delta} transformBy Delta to transform by.
  117. * @returns {Array.<engine.model.delta.Delta>} Result of the transformation.
  118. */
  119. static _transform( toTransform, transformBy ) {
  120. return transform( toTransform, transformBy, true );
  121. }
  122. }