document.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. CKEDITOR.define( [
  7. 'document/element',
  8. 'document/rootelement',
  9. 'document/transaction',
  10. 'emittermixin',
  11. 'utils',
  12. 'ckeditorerror'
  13. ], ( Element, RootElement, Transaction, EmitterMixin, utils, CKEditorError ) => {
  14. const graveyardSymbol = Symbol( 'graveyard' );
  15. /**
  16. * Document tree model describes all editable data in the editor. It may contain multiple {@link #roots root elements},
  17. * for example if the editor have multiple editable areas, each area will be represented by the separate root.
  18. *
  19. * All changes in the document are done by {@link document.operation.Operation operations}. To create operations in
  20. * the simple way use use the {@link document.Transaction transaction} API, for example:
  21. *
  22. * document.createTransaction().insert( position, nodes ).split( otherPosition );
  23. *
  24. * @see #createTransaction
  25. *
  26. * @class document.Document
  27. */
  28. class Document {
  29. /**
  30. * Creates an empty document instance with no {@link #roots}.
  31. *
  32. * @constructor
  33. */
  34. constructor() {
  35. /**
  36. * List of roots that are owned and managed by this document.
  37. *
  38. * @readonly
  39. * @property {Map} roots
  40. */
  41. this.roots = new Map();
  42. // Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
  43. this.createRoot( graveyardSymbol );
  44. /**
  45. * Document version. It starts from `0` and every operation increases the version number. It is used to ensure that
  46. * operations are applied on the proper document version. If the {@link document.operation.Operation#baseVersion} will
  47. * not match document version the {@link document-applyOperation-wrong-version} error is thrown.
  48. *
  49. * @readonly
  50. * @property {Number} version
  51. */
  52. this.version = 0;
  53. }
  54. /**
  55. * Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
  56. *
  57. * @protected
  58. * @readonly
  59. * @property {document.RootElement} _graveyard
  60. */
  61. get _graveyard() {
  62. return this.getRoot( graveyardSymbol );
  63. }
  64. /**
  65. * This is the entry point for all document changes. All changes on the document are done using
  66. * {@link document.operation.Operation operations}. To create operations in the simple way use the
  67. * {@link document.Transaction} API available via {@link #createTransaction} method.
  68. *
  69. * @param {document.operation.Operation} operation Operation to be applied.
  70. */
  71. applyOperation( operation ) {
  72. if ( operation.baseVersion !== this.version ) {
  73. /**
  74. * Only operations with matching versions can be applied.
  75. *
  76. * @error document-applyOperation-wrong-version
  77. * @param {document.operation.Operation} operation
  78. */
  79. throw new CKEditorError(
  80. 'document-applyOperation-wrong-version: Only operations with matching versions can be applied.',
  81. { operation: operation } );
  82. }
  83. let changes = operation._execute();
  84. this.version++;
  85. this.fire( 'change', operation.type, changes );
  86. }
  87. /**
  88. * Creates a new top-level root.
  89. *
  90. * @param {String|Symbol} name Unique root name.
  91. * @returns {document.RootElement} Created root.
  92. */
  93. createRoot( name ) {
  94. if ( this.roots.has( name ) ) {
  95. /**
  96. * Root with specified name already exists.
  97. *
  98. * @error document-createRoot-name-exists
  99. * @param {document.Document} doc
  100. * @param {String} name
  101. */
  102. throw new CKEditorError(
  103. 'document-createRoot-name-exists: Root with specified name already exists.',
  104. { name: name }
  105. );
  106. }
  107. const root = new RootElement( this );
  108. this.roots.set( name, root );
  109. return root;
  110. }
  111. /**
  112. * Creates a {@link document.Transaction} instance which allows to change the document.
  113. *
  114. * @returns {document.Transaction} Transaction instance.
  115. */
  116. createTransaction() {
  117. return new Transaction( this );
  118. }
  119. /**
  120. * Returns top-level root by it's name.
  121. *
  122. * @param {String|Symbol} name Name of the root to get.
  123. * @returns (document.RootElement} Root registered under given name.
  124. */
  125. getRoot( name ) {
  126. if ( !this.roots.has( name ) ) {
  127. /**
  128. * Root with specified name does not exist.
  129. *
  130. * @error document-createRoot-root-not-exist
  131. * @param {String} name
  132. */
  133. throw new CKEditorError(
  134. 'document-createRoot-root-not-exist: Root with specified name does not exist.',
  135. { name: name }
  136. );
  137. }
  138. return this.roots.get( name );
  139. }
  140. /**
  141. * Fired when document changes by applying an operation.
  142. *
  143. * There are 5 types of change:
  144. *
  145. * * 'insert' when nodes are inserted,
  146. * * 'remove' when nodes are removed,
  147. * * 'reinsert' when remove is undone,
  148. * * 'move' when nodes are moved,
  149. * * 'attr' when attributes change.
  150. *
  151. * Change event is fired after the change is done. This means that any ranges or positions passed in
  152. * `changeInfo` are referencing nodes and paths in updated tree model.
  153. *
  154. * @event change
  155. * @param {String} type Change type, possible option: 'insert', 'remove', 'reinsert', 'move', 'attr'.
  156. * @param {Object} changeInfo Additional information about the change.
  157. * @param {document.Range} changeInfo.range Range containing changed nodes. Note that for 'remove' the range will be in the
  158. * {@link #_graveyard graveyard root}.
  159. * @param {document.Position} [changeInfo.sourcePosition] Change source position. Exists for 'remove', 'reinsert' and 'move'.
  160. * Note that for 'reinsert' the source position will be in the {@link #_graveyard graveyard root}.
  161. * @param {document.Attribute} [changeInfo.oldAttr] Only for 'attr' type. If the type is 'attr' and `oldAttr`
  162. * is `undefined` it means that new attribute was inserted. Otherwise it contains changed or removed attribute.
  163. * @param {document.Attribute} [changeInfo.newAttr] Only for 'attr' type. If the type is 'attr' and `newAttr`
  164. * is `undefined` it means that attribute was removed. Otherwise it contains changed or inserted attribute.
  165. */
  166. }
  167. utils.extend( Document.prototype, EmitterMixin );
  168. return Document;
  169. } );