model.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/model/model
  7. */
  8. import Batch from './batch';
  9. import Writer from './writer';
  10. import Schema from './schema';
  11. import Document from './document';
  12. import MarkerCollection from './markercollection';
  13. import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
  14. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  15. import ModelElement from './element';
  16. import ModelRange from './range';
  17. import insertContent from './utils/insertcontent';
  18. import deleteContent from './utils/deletecontent';
  19. import modifySelection from './utils/modifyselection';
  20. import getSelectedContent from './utils/getselectedcontent';
  21. import { injectSelectionPostFixer } from './utils/selection-post-fixer';
  22. /**
  23. * Editor's data model. Read about the model in the
  24. * {@glink framework/guides/architecture/editing-engine engine architecture guide}.
  25. *
  26. * @mixes module:utils/observablemixin~ObservableMixin
  27. */
  28. export default class Model {
  29. constructor() {
  30. /**
  31. * Model's marker collection.
  32. *
  33. * @readonly
  34. * @member {module:engine/model/markercollection~MarkerCollection}
  35. */
  36. this.markers = new MarkerCollection();
  37. /**
  38. * Model's document.
  39. *
  40. * @readonly
  41. * @member {module:engine/model/document~Document}
  42. */
  43. this.document = new Document( this );
  44. /**
  45. * Model's schema.
  46. *
  47. * @readonly
  48. * @member {module:engine/model/schema~Schema}
  49. */
  50. this.schema = new Schema();
  51. /**
  52. * All callbacks added by {@link module:engine/model/model~Model#change} or
  53. * {@link module:engine/model/model~Model#enqueueChange} methods waiting to be executed.
  54. *
  55. * @private
  56. * @type {Array.<Function>}
  57. */
  58. this._pendingChanges = [];
  59. /**
  60. * The last created and currently used writer instance.
  61. *
  62. * @private
  63. * @member {module:engine/model/writer~Writer}
  64. */
  65. this._currentWriter = null;
  66. [ 'insertContent', 'deleteContent', 'modifySelection', 'getSelectedContent', 'applyOperation' ]
  67. .forEach( methodName => this.decorate( methodName ) );
  68. // Adding operation validation with `highest` priority, so it is called before any other feature would like
  69. // to do anything with the operation. If the operation has incorrect parameters it should throw on the earliest occasion.
  70. this.on( 'applyOperation', ( evt, args ) => {
  71. const operation = args[ 0 ];
  72. operation._validate();
  73. }, { priority: 'highest' } );
  74. // Register some default abstract entities.
  75. this.schema.register( '$root', {
  76. isLimit: true
  77. } );
  78. this.schema.register( '$block', {
  79. allowIn: '$root',
  80. isBlock: true
  81. } );
  82. this.schema.register( '$text', {
  83. allowIn: '$block'
  84. } );
  85. this.schema.register( '$clipboardHolder', {
  86. allowContentOf: '$root',
  87. isLimit: true
  88. } );
  89. this.schema.extend( '$text', { allowIn: '$clipboardHolder' } );
  90. // Element needed by `upcastElementToMarker` converter.
  91. // This element temporarily represents marker bound during conversion process and is removed
  92. // at the end of conversion. `UpcastDispatcher` or at least `Conversion` class looks like a better for this
  93. // registration but both know nothing about Schema.
  94. this.schema.register( '$marker', {
  95. allowIn: [ '$root', '$block' ]
  96. } );
  97. injectSelectionPostFixer( this );
  98. }
  99. /**
  100. * The `change()` method is the primary way of changing the model. You should use it to modify all document nodes
  101. * (including detached nodes – i.e. nodes not added to the {@link module:engine/model/model~Model#document model document}),
  102. * the {@link module:engine/model/document~Document#selection document's selection}, and
  103. * {@link module:engine/model/model~Model#markers model markers}.
  104. *
  105. * model.change( writer => {
  106. * writer.insertText( 'foo', paragraph, 'end' );
  107. * } );
  108. *
  109. * All changes inside the change block use the same {@link module:engine/model/batch~Batch} so they are combined
  110. * into a single undo step.
  111. *
  112. * model.change( writer => {
  113. * writer.insertText( 'foo', paragraph, 'end' ); // foo.
  114. *
  115. * model.change( writer => {
  116. * writer.insertText( 'bar', paragraph, 'end' ); // foobar.
  117. * } );
  118. *
  119. * writer.insertText( 'bom', paragraph, 'end' ); // foobarbom.
  120. * } );
  121. *
  122. * The callback of the `change()` block is executed synchronously.
  123. *
  124. * You can also return a value from the change block.
  125. *
  126. * const img = model.change( writer => {
  127. * return writer.createElement( 'img' );
  128. * } );
  129. *
  130. * @see #enqueueChange
  131. * @param {Function} callback Callback function which may modify the model.
  132. * @returns {*} Value returned by the callback.
  133. */
  134. change( callback ) {
  135. if ( this._pendingChanges.length === 0 ) {
  136. // If this is the outermost block, create a new batch and start `_runPendingChanges` execution flow.
  137. this._pendingChanges.push( { batch: new Batch(), callback } );
  138. return this._runPendingChanges()[ 0 ];
  139. } else {
  140. // If this is not the outermost block, just execute the callback.
  141. return callback( this._currentWriter );
  142. }
  143. }
  144. /**
  145. * The `enqueueChange()` method performs similar task as the {@link #change `change()` method}, with two major differences.
  146. *
  147. * First, the callback of `enqueueChange()` is executed when all other enqueued changes are done. It might be executed
  148. * immediately if it is not nested in any other change block, but if it is nested in another (enqueue)change block,
  149. * it will be delayed and executed after the outermost block.
  150. *
  151. * model.change( writer => {
  152. * console.log( 1 );
  153. *
  154. * model.enqueueChange( writer => {
  155. * console.log( 2 );
  156. * } );
  157. *
  158. * console.log( 3 );
  159. * } ); // Will log: 1, 3, 2.
  160. *
  161. * Second, it lets you define the {@link module:engine/model/batch~Batch} into which you want to add your changes.
  162. * By default, a new batch is created. In the sample above, `change` and `enqueueChange` blocks use a different
  163. * batch (and different {@link module:engine/model/writer~Writer} since each of them operates on the separate batch).
  164. *
  165. * When using the `enqueueChange()` block you can also add some changes to the batch you used before.
  166. *
  167. * model.enqueueChange( batch, writer => {
  168. * writer.insertText( 'foo', paragraph, 'end' );
  169. * } );
  170. *
  171. * The batch instance can be obtained from {@link module:engine/model/writer~Writer#batch the writer}.
  172. *
  173. * @param {module:engine/model/batch~Batch|'transparent'|'default'} batchOrType Batch or batch type should be used in the callback.
  174. * If not defined, a new batch will be created.
  175. * @param {Function} callback Callback function which may modify the model.
  176. */
  177. enqueueChange( batchOrType, callback ) {
  178. if ( typeof batchOrType === 'string' ) {
  179. batchOrType = new Batch( batchOrType );
  180. } else if ( typeof batchOrType == 'function' ) {
  181. callback = batchOrType;
  182. batchOrType = new Batch();
  183. }
  184. this._pendingChanges.push( { batch: batchOrType, callback } );
  185. if ( this._pendingChanges.length == 1 ) {
  186. this._runPendingChanges();
  187. }
  188. }
  189. /**
  190. * {@link module:utils/observablemixin~ObservableMixin#decorate Decorated} function for applying
  191. * {@link module:engine/model/operation/operation~Operation operations} to the model.
  192. *
  193. * This is a low-level way of changing the model. It is exposed for very specific use cases (like the undo feature).
  194. * Normally, to modify the model, you will want to use {@link module:engine/model/writer~Writer `Writer`}.
  195. * See also {@glink framework/guides/architecture/editing-engine#changing-the-model Changing the model} section
  196. * of the {@glink framework/guides/architecture/editing-engine Editing architecture} guide.
  197. *
  198. * @param {module:engine/model/operation/operation~Operation} operation The operation to apply.
  199. */
  200. applyOperation( operation ) {
  201. operation._execute();
  202. }
  203. /**
  204. * Inserts content into the editor (specified selection) as one would expect the paste
  205. * functionality to work.
  206. *
  207. * This is a high-level method. It takes the {@link #schema schema} into consideration when inserting
  208. * the content, clears the given selection's content before inserting nodes and moves the selection
  209. * to its target position at the end of the process.
  210. * It can split elements, merge them, wrap bare text nodes in paragraphs, etc. – just like the
  211. * pasting feature should do.
  212. *
  213. * For lower-level methods see {@link module:engine/model/writer~Writer `Writer`}.
  214. *
  215. * This method, unlike {@link module:engine/model/writer~Writer `Writer`}'s methods, does not have to be used
  216. * inside a {@link #change `change()` block}.
  217. *
  218. * # Conversion and schema
  219. *
  220. * Inserting elements and text nodes into the model is not enough to make CKEditor 5 render that content
  221. * to the user. CKEditor 5 implements a model-view-controller architecture and what `model.insertContent()` does
  222. * is only adding nodes to the model. Additionally, you need to define
  223. * {@glink framework/guides/architecture/editing-engine#conversion converters} between the model and view
  224. * and define those nodes in the {@glink framework/guides/architecture/editing-engine#schema schema}.
  225. *
  226. * So, while this method may seem similar to CKEditor 4's `editor.insertHtml()` (in fact, both methods
  227. * are used for paste-like content insertion), CKEditor 5's method cannot be use to insert arbitrary HTML
  228. * unless converters are defined for all elements and attributes in that HTML.
  229. *
  230. * # Examples
  231. *
  232. * Using `insertContent()` with a manually created model structure:
  233. *
  234. * // Let's create a document fragment containing such a content:
  235. * //
  236. * // <paragrap>foo</paragraph>
  237. * // <blockQuote>
  238. * // <paragraph>bar</paragraph>
  239. * // </blockQuote>
  240. * const docFrag = editor.model.change( writer => {
  241. * const p1 = writer.createElement( 'paragraph' );
  242. * const p2 = writer.createElement( 'paragraph' );
  243. * const blockQuote = writer.createElement( 'blockQuote' );
  244. * const docFrag = writer.createDocumentFragment();
  245. *
  246. * writer.append( p1, docFrag );
  247. * writer.append( blockQuote, docFrag );
  248. * writer.append( p2, blockQuote );
  249. * writer.insertText( 'foo', p1 );
  250. * writer.insertText( 'bar', p2 );
  251. *
  252. * return docFrag;
  253. * } );
  254. *
  255. * // insertContent() doesn't have to be used in a change() block. It can, though,
  256. * // so this code could be moved to the callback defined above.
  257. * editor.model.insertContent( docFrag );
  258. *
  259. * Using `insertContent()` with HTML string converted to a model document fragment (similar to the pasting mechanism):
  260. *
  261. * // You can create your own HtmlDataProcessor instance or use editor.data.processor
  262. * // if you haven't overridden the default one (which is HtmlDataProcessor instance).
  263. * const htmlDP = new HtmlDataProcessor();
  264. *
  265. * // Convert an HTML string to a view document fragment.
  266. * const viewFragment = htmlDP.toView( htmlString );
  267. *
  268. * // Convert a view document fragment to a model document fragment
  269. * // in the context of $root. This conversion takes schema into
  270. * // the account so if e.g. the view document fragment contained a bare text node
  271. * // then that text node cannot be a child of $root, so it will be automatically
  272. * // wrapped with a <paragraph>. You can define the context yourself (in the 2nd parameter),
  273. * // and e.g. convert the content like it would happen in a <paragraph>.
  274. * // Note: the clipboard feature uses a custom context called $clipboardHolder
  275. * // which has a loosened schema.
  276. * const modelFragment = editor.data.toModel( viewFragment );
  277. *
  278. * editor.model.insertContent( modelFragment );
  279. *
  280. * By default this method will use the document selection but it can also be used with a position, range or selection instance.
  281. *
  282. * // Insert text at the current document selection position.
  283. * editor.model.change( writer => {
  284. * editor.model.insertContent( writer.createText( 'x' ) );
  285. * } );
  286. *
  287. * // Insert text at given position - document selection will not be modified.
  288. * editor.model.change( writer => {
  289. * editor.model.insertContent( writer.createText( 'x' ), Position.createAt( doc.getRoot(), 2 ) );
  290. * } );
  291. *
  292. * If an instance of {@link module:engine/model/selection~Selection} is passed as `selectable`
  293. * it will be moved to the target position (where the document selection should be moved after the insertion).
  294. *
  295. * // Insert text replacing given selection instance.
  296. * const selection = new Selection( paragraph, 'in' );
  297. *
  298. * editor.model.change( writer => {
  299. * editor.model.insertContent( writer.createText( 'x' ), selection );
  300. *
  301. * // insertContent() modifies the passed selection instance so it can be used to set the document selection.
  302. * // Note: This is not necessary when you passed document selection to insertContent().
  303. * writer.setSelection( selection );
  304. * } );
  305. *
  306. * @fires insertContent
  307. * @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
  308. * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
  309. * module:engine/model/position~Position|module:engine/model/element~Element|
  310. * Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} [selectable=model.document.selection]
  311. * Selection into which the content should be inserted. If not provided the current model document selection will be used.
  312. */
  313. insertContent( content, selectable ) {
  314. insertContent( this, content, selectable );
  315. }
  316. /**
  317. * Deletes content of the selection and merge siblings. The resulting selection is always collapsed.
  318. *
  319. * **Note:** For the sake of predictability, the resulting selection should always be collapsed.
  320. * In cases where a feature wants to modify deleting behavior so selection isn't collapsed
  321. * (e.g. a table feature may want to keep row selection after pressing <kbd>Backspace</kbd>),
  322. * then that behavior should be implemented in the view's listener. At the same time, the table feature
  323. * will need to modify this method's behavior too, e.g. to "delete contents and then collapse
  324. * the selection inside the last selected cell" or "delete the row and collapse selection somewhere near".
  325. * That needs to be done in order to ensure that other features which use `deleteContent()` will work well with tables.
  326. *
  327. * @fires deleteContent
  328. * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  329. * Selection of which the content should be deleted.
  330. * @param {Object} [options]
  331. * @param {Boolean} [options.leaveUnmerged=false] Whether to merge elements after removing the content of the selection.
  332. *
  333. * For example `<heading1>x[x</heading1><paragraph>y]y</paragraph>` will become:
  334. *
  335. * * `<heading1>x^y</heading1>` with the option disabled (`leaveUnmerged == false`)
  336. * * `<heading1>x^</heading1><paragraph>y</paragraph>` with enabled (`leaveUnmerged == true`).
  337. *
  338. * Note: {@link module:engine/model/schema~Schema#isObject object} and {@link module:engine/model/schema~Schema#isLimit limit}
  339. * elements will not be merged.
  340. *
  341. * @param {Boolean} [options.doNotResetEntireContent=false] Whether to skip replacing the entire content with a
  342. * paragraph when the entire content was selected.
  343. *
  344. * For example `<heading1>[x</heading1><paragraph>y]</paragraph>` will become:
  345. *
  346. * * `<paragraph>^</paragraph>` with the option disabled (`doNotResetEntireContent == false`)
  347. * * `<heading1>^</heading1>` with enabled (`doNotResetEntireContent == true`)
  348. */
  349. deleteContent( selection, options ) {
  350. deleteContent( this, selection, options );
  351. }
  352. /**
  353. * Modifies the selection. Currently, the supported modifications are:
  354. *
  355. * * Extending. The selection focus is moved in the specified `options.direction` with a step specified in `options.unit`.
  356. * Possible values for `unit` are:
  357. * * `'character'` (default) - moves selection by one user-perceived character. In most cases this means moving by one
  358. * character in `String` sense. However, unicode also defines "combing marks". These are special symbols, that combines
  359. * with a symbol before it ("base character") to create one user-perceived character. For example, `q̣̇` is a normal
  360. * letter `q` with two "combining marks": upper dot (`Ux0307`) and lower dot (`Ux0323`). For most actions, i.e. extending
  361. * selection by one position, it is correct to include both "base character" and all of it's "combining marks". That is
  362. * why `'character'` value is most natural and common method of modifying selection.
  363. * * `'codePoint'` - moves selection by one unicode code point. In contrary to, `'character'` unit, this will insert
  364. * selection between "base character" and "combining mark", because "combining marks" have their own unicode code points.
  365. * However, for technical reasons, unicode code points with values above `UxFFFF` are represented in native `String` by
  366. * two characters, called "surrogate pairs". Halves of "surrogate pairs" have a meaning only when placed next to each other.
  367. * For example `𨭎` is represented in `String` by `\uD862\uDF4E`. Both `\uD862` and `\uDF4E` do not have any meaning
  368. * outside the pair (are rendered as ? when alone). Position between them would be incorrect. In this case, selection
  369. * extension will include whole "surrogate pair".
  370. * * `'word'` - moves selection by a whole word.
  371. *
  372. * **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
  373. *
  374. * @fires modifySelection
  375. * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  376. * The selection to modify.
  377. * @param {Object} [options]
  378. * @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
  379. * @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
  380. */
  381. modifySelection( selection, options ) {
  382. modifySelection( this, selection, options );
  383. }
  384. /**
  385. * Gets a clone of the selected content.
  386. *
  387. * For example, for the following selection:
  388. *
  389. * ```html
  390. * <paragraph>x</paragraph>
  391. * <blockQuote>
  392. * <paragraph>y</paragraph>
  393. * <heading1>fir[st</heading1>
  394. * </blockQuote>
  395. * <paragraph>se]cond</paragraph>
  396. * <paragraph>z</paragraph>
  397. * ```
  398. *
  399. * It will return a document fragment with such a content:
  400. *
  401. * ```html
  402. * <blockQuote>
  403. * <heading1>st</heading1>
  404. * </blockQuote>
  405. * <paragraph>se</paragraph>
  406. * ```
  407. *
  408. * @fires getSelectedContent
  409. * @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
  410. * The selection of which content will be returned.
  411. * @returns {module:engine/model/documentfragment~DocumentFragment}
  412. */
  413. getSelectedContent( selection ) {
  414. return getSelectedContent( this, selection );
  415. }
  416. /**
  417. * Checks whether given {@link module:engine/model/range~Range range} or {@link module:engine/model/element~Element element}
  418. * has any content.
  419. *
  420. * Content is any text node or element which is registered in {@link module:engine/model/schema~Schema schema}.
  421. *
  422. * @param {module:engine/model/range~Range|module:engine/model/element~Element} rangeOrElement Range or element to check.
  423. * @returns {Boolean}
  424. */
  425. hasContent( rangeOrElement ) {
  426. if ( rangeOrElement instanceof ModelElement ) {
  427. rangeOrElement = ModelRange.createIn( rangeOrElement );
  428. }
  429. if ( rangeOrElement.isCollapsed ) {
  430. return false;
  431. }
  432. for ( const item of rangeOrElement.getItems() ) {
  433. // Remember, `TreeWalker` returns always `textProxy` nodes.
  434. if ( item.is( 'textProxy' ) || this.schema.isObject( item ) ) {
  435. return true;
  436. }
  437. }
  438. return false;
  439. }
  440. /**
  441. * Removes all events listeners set by model instance and destroys {@link module:engine/model/document~Document}.
  442. */
  443. destroy() {
  444. this.document.destroy();
  445. this.stopListening();
  446. }
  447. /**
  448. * Common part of {@link module:engine/model/model~Model#change} and {@link module:engine/model/model~Model#enqueueChange}
  449. * which calls callbacks and returns array of values returned by these callbacks.
  450. *
  451. * @private
  452. * @returns {Array.<*>} Array of values returned by callbacks.
  453. */
  454. _runPendingChanges() {
  455. const ret = [];
  456. this.fire( '_beforeChanges' );
  457. while ( this._pendingChanges.length ) {
  458. // Create a new writer using batch instance created for this chain of changes.
  459. const currentBatch = this._pendingChanges[ 0 ].batch;
  460. this._currentWriter = new Writer( this, currentBatch );
  461. // Execute changes callback and gather the returned value.
  462. const callbackReturnValue = this._pendingChanges[ 0 ].callback( this._currentWriter );
  463. ret.push( callbackReturnValue );
  464. // Fire internal `_change` event.
  465. this.fire( '_change', this._currentWriter );
  466. this._pendingChanges.shift();
  467. this._currentWriter = null;
  468. }
  469. this.fire( '_afterChanges' );
  470. return ret;
  471. }
  472. /**
  473. * Fired after leaving each {@link module:engine/model/model~Model#enqueueChange} block or outermost
  474. * {@link module:engine/model/model~Model#change} block.
  475. *
  476. * **Note:** This is an internal event! Use {@link module:engine/model/document~Document#event:change} instead.
  477. *
  478. * @protected
  479. * @event _change
  480. * @param {module:engine/model/writer~Writer} writer `Writer` instance that has been used in the change block.
  481. */
  482. /**
  483. * Fired when entering the outermost {@link module:engine/model/model~Model#enqueueChange} or
  484. * {@link module:engine/model/model~Model#change} block.
  485. *
  486. * @protected
  487. * @event _beforeChanges
  488. */
  489. /**
  490. * Fired when leaving the outermost {@link module:engine/model/model~Model#enqueueChange} or
  491. * {@link module:engine/model/model~Model#change} block.
  492. *
  493. * @protected
  494. * @event _afterChanges
  495. */
  496. /**
  497. * Fired every time any {@link module:engine/model/operation/operation~Operation operation} is applied on the model
  498. * using {@link #applyOperation}.
  499. *
  500. * Note that this event is suitable only for very specific use-cases. Use it if you need to listen to every single operation
  501. * applied on the document. However, in most cases {@link module:engine/model/document~Document#event:change} should
  502. * be used.
  503. *
  504. * A few callbacks are already added to this event by engine internal classes:
  505. *
  506. * * with `highest` priority operation is validated,
  507. * * with `normal` priority operation is executed,
  508. * * with `low` priority the {@link module:engine/model/document~Document} updates its version,
  509. * * with `low` priority {@link module:engine/model/liveposition~LivePosition} and {@link module:engine/model/liverange~LiveRange}
  510. * update themselves.
  511. *
  512. * @event applyOperation
  513. * @param {Array} args Arguments of the `applyOperation` which is an array with a single element - applied
  514. * {@link module:engine/model/operation/operation~Operation operation}.
  515. */
  516. /**
  517. * Event fired when {@link #insertContent} method is called.
  518. *
  519. * The {@link #insertContent default action of that method} is implemented as a
  520. * listener to this event so it can be fully customized by the features.
  521. *
  522. * **Note** The `selectable` parameter for the {@link #insertContent} is optional. When `undefined` value is passed the method uses
  523. * `model.document.selection`.
  524. *
  525. * @event insertContent
  526. * @param {Array} args The arguments passed to the original method.
  527. */
  528. /**
  529. * Event fired when {@link #deleteContent} method is called.
  530. *
  531. * The {@link #deleteContent default action of that method} is implemented as a
  532. * listener to this event so it can be fully customized by the features.
  533. *
  534. * @event deleteContent
  535. * @param {Array} args The arguments passed to the original method.
  536. */
  537. /**
  538. * Event fired when {@link #modifySelection} method is called.
  539. *
  540. * The {@link #modifySelection default action of that method} is implemented as a
  541. * listener to this event so it can be fully customized by the features.
  542. *
  543. * @event modifySelection
  544. * @param {Array} args The arguments passed to the original method.
  545. */
  546. /**
  547. * Event fired when {@link #getSelectedContent} method is called.
  548. *
  549. * The {@link #getSelectedContent default action of that method} is implemented as a
  550. * listener to this event so it can be fully customized by the features.
  551. *
  552. * @event getSelectedContent
  553. * @param {Array} args The arguments passed to the original method.
  554. */
  555. }
  556. mix( Model, ObservableMixin );