8
0

modelconversiondispatcher.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module engine/conversion/modelconversiondispatcher
  7. */
  8. import Consumable from './modelconsumable';
  9. import Range from '../model/range';
  10. import TextProxy from '../model/textproxy';
  11. import EmitterMixin from 'ckeditor5-utils/src/emittermixin';
  12. import mix from 'ckeditor5-utils/src/mix';
  13. import extend from 'ckeditor5-utils/src/lib/lodash/extend';
  14. /**
  15. * `ModelConversionDispatcher` is a central point of {@link module:engine/model/model model} conversion, which is
  16. * a process of reacting to changes in the model and reflecting them by listeners that listen to those changes.
  17. * In default application, {@link module:engine/model/model model} is converted to {@link module:engine/view/view view}. This means
  18. * that changes in the model are reflected by changing the view (i.e. adding view nodes or changing attributes on view elements).
  19. *
  20. * During conversion process, `ModelConversionDispatcher` fires data-manipulation events, basing on state of the model and prepares
  21. * data for those events. It is important to note that the events are connected with "change actions" like "inserting"
  22. * or "removing" so one might say that we are converting "changes". This is in contrary to view to model conversion,
  23. * where we convert view nodes (the structure, not "changes" to the view). Note, that because changes are converted
  24. * and not the structure itself, there is a need to have a mapping between model and the structure on which changes are
  25. * reflected. To map elements during model to view conversion use {@link module:engine/conversion/mapper~Mapper}.
  26. *
  27. * The main use for this class is to listen to {@link module:engine/model/document~Document#event:change Document change event}, process it
  28. * and then fire specific events telling what exactly has changed. For those events, `ModelConversionDispatcher`
  29. * creates {@link module:engine/conversion/modelconsumable~ModelConsumable list of consumable values} that should be handled by event
  30. * callbacks. Those events are listened to by model-to-view converters which convert changes done in the
  31. * {@link module:engine/model/model model} to changes in the {@link module:engine/view/view view}. `ModelConversionController` also checks
  32. * the current state of consumables, so it won't fire events for parts of model that were already consumed. This is
  33. * especially important in callbacks that consume multiple values. See {@link module:engine/conversion/modelconsumable~ModelConsumable}
  34. * for an example of such callback.
  35. *
  36. * Although the primary usage for this class is the model-to-view conversion, `ModelConversionDispatcher` can be used
  37. * to build custom data processing pipelines that converts model to anything that is needed. Existing model structure can
  38. * be used to generate events (listening to {@link module:engine/model/document~Document#event:change Document change event} is not
  39. * required)
  40. * and custom callbacks can be added to the events (these does not have to be limited to changes in the view).
  41. *
  42. * When providing your own event listeners for `ModelConversionDispatcher` keep in mind that any callback that had
  43. * {@link module:engine/conversion/modelconsumable~ModelConsumable#consume consumed} a value from consumable (and did some changes, i.e. to
  44. * the view) should also stop the event. This is because whenever a callback is fired it is assumed that there is something
  45. * to be consumed. Thanks to that approach, you do not have to test whether there is anything to consume at the beginning
  46. * of your listener callback.
  47. *
  48. * Example of providing a converter for `ModelConversionDispatcher`:
  49. *
  50. * // We will convert inserting "paragraph" model element into the model.
  51. * modelDispatcher.on( 'insert:paragraph', ( evt, data, consumable, conversionApi ) => {
  52. * // Remember to consume the part of consumable.
  53. * consumable.consume( data.item, 'insert' );
  54. *
  55. * // Translate position in model to position in the view.
  56. * const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
  57. *
  58. * // Create a P element (note that this converter is for inserting P elements -> 'insert:paragraph').
  59. * const viewElement = new ViewElement( 'p' );
  60. *
  61. * // Bind the newly created view element to model element so positions will map accordingly in future.
  62. * conversionApi.mapper.bindElements( data.item, viewElement );
  63. *
  64. * // Add the newly created view element to the view.
  65. * viewWriter.insert( viewPosition, viewElement );
  66. *
  67. * // Remember to stop the event propagation if the data.item was consumed.
  68. * evt.stop();
  69. * } );
  70. *
  71. * Callback that "overrides" other callback:
  72. *
  73. * // Special converter for `linkHref` attribute added on custom `quote` element. Note, that this
  74. * // attribute may be the same as the attribute added by other features (link feature in this case).
  75. * // It might be even added by that feature! It makes sense that a part of content that is a quote is linked
  76. * // to an external source so it makes sense that link feature works on the custom quote element.
  77. * // However, we have to make sure that the attributes added by link feature are correctly converted.
  78. * // To block default `linkHref` conversion we have to:
  79. * // 1) add this callback with higher priority than link feature callback,
  80. * // 2) consume `linkHref` attribute add change.
  81. * modelConversionDispatcher.on( 'addAttribute:linkHref:quote', ( evt, data, consumable, conversionApi ) => {
  82. * consumable.consume( data.item, 'addAttribute:linkHref' );
  83. *
  84. * // Create a button that will represent the `linkHref` attribute.
  85. * let viewSourceBtn = new ViewElement( 'a', {
  86. * href: data.item.getAttribute( 'linkHref' ),
  87. * title: 'source'
  88. * } );
  89. *
  90. * // Add a class for the button.
  91. * viewSourceBtn.addClass( 'source' );
  92. *
  93. * // Insert the button using writer API.
  94. * // If `addAttribute` event is fired by `module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher#convertInsert` it
  95. * // is fired
  96. * // after `data.item` insert conversion was done. If the event is fired due to attribute insertion coming from
  97. * // different source, `data.item` already existed. This means we are safe to get `viewQuote` from mapper.
  98. * const viewQuote = conversionApi.mapper.toViewElement( data.item );
  99. * const position = new ViewPosition( viewQuote, viewQuote.childCount );
  100. * viewWriter.insert( position, viewSourceBtn );
  101. *
  102. * evt.stop();
  103. * }, { priority: 'high' } );
  104. */
  105. export default class ModelConversionDispatcher {
  106. /**
  107. * Creates a `ModelConversionDispatcher` that operates using passed API.
  108. *
  109. * @param {Object} [conversionApi] Interface passed by dispatcher to the events callbacks.
  110. */
  111. constructor( conversionApi = {} ) {
  112. /**
  113. * Interface passed by dispatcher to the events callbacks.
  114. *
  115. * @member {Object}
  116. */
  117. this.conversionApi = extend( { dispatcher: this }, conversionApi );
  118. }
  119. /**
  120. * Prepares data and fires a proper event.
  121. *
  122. * The method is crafted to take use of parameters passed in {@link module:engine/model/document~Document#event:change Document change
  123. * event}.
  124. *
  125. * @see module:engine/model/document~Document#event:change
  126. * @fires insert
  127. * @fires move
  128. * @fires remove
  129. * @fires rename
  130. * @fires addAttribute
  131. * @fires removeAttribute
  132. * @fires changeAttribute
  133. * @param {String} type Change type.
  134. * @param {Object} data Additional information about the change.
  135. */
  136. convertChange( type, data ) {
  137. // Do not convert changes if they happen in graveyard.
  138. // Graveyard is a special root that has no view / no other representation and changes done in it should not be converted.
  139. if ( type !== 'remove' && data.range && data.range.root.rootName == '$graveyard' ) {
  140. return;
  141. }
  142. if ( type == 'remove' && data.sourcePosition.root.rootName == '$graveyard' ) {
  143. return;
  144. }
  145. if ( type == 'rename' && data.element.root.rootName == '$graveyard' ) {
  146. return;
  147. }
  148. // We can safely dispatch changes.
  149. if ( type == 'insert' || type == 'reinsert' ) {
  150. this.convertInsertion( data.range );
  151. } else if ( type == 'move' ) {
  152. this.convertMove( data.sourcePosition, data.range );
  153. } else if ( type == 'remove' ) {
  154. this.convertRemove( data.sourcePosition, data.range );
  155. } else if ( type == 'addAttribute' || type == 'removeAttribute' || type == 'changeAttribute' ) {
  156. this.convertAttribute( type, data.range, data.key, data.oldValue, data.newValue );
  157. } else if ( type == 'rename' ) {
  158. this.convertRename( data.element, data.oldName );
  159. }
  160. }
  161. /**
  162. * Analyzes given range and fires insertion-connected events with data based on that range.
  163. *
  164. * **Note**: This method will fire separate events for node insertion and attributes insertion. All
  165. * attributes that are set on inserted nodes are treated like they were added just after node insertion.
  166. *
  167. * @fires insert
  168. * @fires addAttribute
  169. * @param {module:engine/model/range~Range} range Inserted range.
  170. */
  171. convertInsertion( range ) {
  172. // Create a list of things that can be consumed, consisting of nodes and their attributes.
  173. const consumable = this._createInsertConsumable( range );
  174. // Fire a separate insert event for each node and text fragment contained in the range.
  175. for ( let value of range ) {
  176. const item = value.item;
  177. const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
  178. const data = {
  179. item: item,
  180. range: itemRange
  181. };
  182. this._testAndFire( 'insert', data, consumable );
  183. // Fire a separate addAttribute event for each attribute that was set on inserted items.
  184. // This is important because most attributes converters will listen only to add/change/removeAttribute events.
  185. // If we would not add this part, attributes on inserted nodes would not be converted.
  186. for ( let key of item.getAttributeKeys() ) {
  187. data.attributeKey = key;
  188. data.attributeOldValue = null;
  189. data.attributeNewValue = item.getAttribute( key );
  190. this._testAndFire( 'addAttribute:' + key, data, consumable );
  191. }
  192. }
  193. }
  194. /**
  195. * Fires move event with data based on passed values.
  196. *
  197. * @fires move
  198. * @param {module:engine/model/position~Position} sourcePosition Position from where the range has been moved.
  199. * @param {module:engine/model/range~Range} range Moved range (after move).
  200. */
  201. convertMove( sourcePosition, range ) {
  202. // Keep in mind that move dispatcher expects flat range.
  203. const consumable = this._createConsumableForRange( range, 'move' );
  204. const items = Array.from( range.getItems( { shallow: true } ) );
  205. const inSameParent = sourcePosition.parent == range.start.parent;
  206. const targetsAfter = range.start.isAfter( sourcePosition );
  207. let offset = 0;
  208. for ( let item of items ) {
  209. const data = {
  210. sourcePosition: sourcePosition,
  211. targetPosition: inSameParent && targetsAfter ? range.end : range.start.getShiftedBy( offset ),
  212. item: item
  213. };
  214. offset += data.item.offsetSize;
  215. this._testAndFire( 'move', data, consumable );
  216. }
  217. }
  218. /**
  219. * Fires remove event with data based on passed values.
  220. *
  221. * @fires remove
  222. * @param {module:engine/model/position~Position} sourcePosition Position from where the range has been removed.
  223. * @param {module:engine/model/range~Range} range Removed range (after remove, in {@link module:engine/model/document~Document#graveyard
  224. * graveyard root}).
  225. */
  226. convertRemove( sourcePosition, range ) {
  227. const consumable = this._createConsumableForRange( range, 'remove' );
  228. for ( let item of range.getItems( { shallow: true } ) ) {
  229. const data = {
  230. sourcePosition: sourcePosition,
  231. item: item
  232. };
  233. this._testAndFire( 'remove', data, consumable );
  234. }
  235. }
  236. /**
  237. * Analyzes given attribute change and fires attributes-connected events with data based on passed values.
  238. *
  239. * @fires addAttribute
  240. * @fires removeAttribute
  241. * @fires changeAttribute
  242. * @param {String} type Change type. Possible values: `addAttribute`, `removeAttribute`, `changeAttribute`.
  243. * @param {module:engine/model/range~Range} range Changed range.
  244. * @param {String} key Attribute key.
  245. * @param {*} oldValue Attribute value before the change or `null` if attribute has not been set.
  246. * @param {*} newValue New attribute value or `null` if attribute has been removed.
  247. */
  248. convertAttribute( type, range, key, oldValue, newValue ) {
  249. // Create a list with attributes to consume.
  250. const consumable = this._createConsumableForRange( range, type + ':' + key );
  251. // Create a separate attribute event for each node in the range.
  252. for ( let value of range ) {
  253. const item = value.item;
  254. const itemRange = Range.createFromPositionAndShift( value.previousPosition, value.length );
  255. const data = {
  256. item: item,
  257. range: itemRange,
  258. attributeKey: key,
  259. attributeOldValue: oldValue,
  260. attributeNewValue: newValue
  261. };
  262. this._testAndFire( type + ':' + key, data, consumable, this.conversionApi );
  263. }
  264. }
  265. /**
  266. * Fires rename event with data based on passed values.
  267. *
  268. * @fires rename
  269. * @param {module:engine/view/element~Element} element Renamed element.
  270. * @param {String} oldName Name of the renamed element before it was renamed.
  271. */
  272. convertRename( element, oldName ) {
  273. const consumable = new Consumable();
  274. consumable.add( element, 'rename' );
  275. const data = { element, oldName };
  276. this.fire( 'rename:' + element.name + ':' + oldName, data, consumable, this.conversionApi );
  277. }
  278. /**
  279. * Fires events for given {@link module:engine/model/selection~Selection selection} to start selection conversion.
  280. *
  281. * @fires selection
  282. * @fires selectionAttribute
  283. * @param {module:engine/model/selection~Selection} selection Selection to convert.
  284. */
  285. convertSelection( selection ) {
  286. const consumable = this._createSelectionConsumable( selection );
  287. const data = {
  288. selection: selection
  289. };
  290. this.fire( 'selection', data, consumable, this.conversionApi );
  291. for ( let key of selection.getAttributeKeys() ) {
  292. data.key = key;
  293. data.value = selection.getAttribute( key );
  294. // Do not fire event if the attribute has been consumed.
  295. if ( consumable.test( selection, 'selectionAttribute:' + data.key ) ) {
  296. this.fire( 'selectionAttribute:' + data.key, data, consumable, this.conversionApi );
  297. }
  298. }
  299. }
  300. /**
  301. * Fires event for given marker change.
  302. *
  303. * @fires addMarker
  304. * @fires removeMarker
  305. * @param {String} type Change type.
  306. * @param {String} name Marker name.
  307. * @param {module:engine/model/range~Range} range Marker range.
  308. */
  309. convertMarker( type, name, range ) {
  310. const consumable = this._createMarkerConsumable( type, range );
  311. const data = { name, range };
  312. this.fire( type + ':' + name, data, consumable, this.conversionApi );
  313. }
  314. /**
  315. * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values to consume from given range, assuming that
  316. * given range has just been inserted to the model.
  317. *
  318. * @private
  319. * @param {module:engine/model/range~Range} range Inserted range.
  320. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} Values to consume.
  321. */
  322. _createInsertConsumable( range ) {
  323. const consumable = new Consumable();
  324. for ( let value of range ) {
  325. const item = value.item;
  326. consumable.add( item, 'insert' );
  327. for ( let key of item.getAttributeKeys() ) {
  328. consumable.add( item, 'addAttribute:' + key );
  329. }
  330. }
  331. return consumable;
  332. }
  333. /**
  334. * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with values of given `type`
  335. * for each item from given `range`.
  336. *
  337. * @private
  338. * @param {module:engine/model/range~Range} range Affected range.
  339. * @param {String} type Consumable type.
  340. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} Values to consume.
  341. */
  342. _createConsumableForRange( range, type ) {
  343. const consumable = new Consumable();
  344. for ( let item of range.getItems() ) {
  345. consumable.add( item, type );
  346. }
  347. return consumable;
  348. }
  349. /**
  350. * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} with selection consumable values.
  351. *
  352. * @private
  353. * @param {module:engine/model/selection~Selection} selection Selection to create consumable from.
  354. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} Values to consume.
  355. */
  356. _createSelectionConsumable( selection ) {
  357. const consumable = new Consumable();
  358. consumable.add( selection, 'selection' );
  359. for ( let key of selection.getAttributeKeys() ) {
  360. consumable.add( selection, 'selectionAttribute:' + key );
  361. }
  362. return consumable;
  363. }
  364. /**
  365. * Creates {@link module:engine/conversion/modelconsumable~ModelConsumable} for adding or removing marker on given `range`.
  366. *
  367. * @private
  368. * @param {'addMarker'|'removeMarker'} type Change type.
  369. * @param {module:engine/model/range~Range} range Range on which marker was added or removed.
  370. * @returns {module:engine/conversion/modelconsumable~ModelConsumable} Values to consume.
  371. */
  372. _createMarkerConsumable( type, range ) {
  373. const consumable = new Consumable();
  374. consumable.add( range, type );
  375. return consumable;
  376. }
  377. /**
  378. * Tests passed `consumable` to check whether given event can be fired and if so, fires it.
  379. *
  380. * @private
  381. * @fires insert
  382. * @fires addAttribute
  383. * @fires removeAttribute
  384. * @fires changeAttribute
  385. * @param {String} type Event type.
  386. * @param {Object} data Event data.
  387. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  388. */
  389. _testAndFire( type, data, consumable ) {
  390. if ( !consumable.test( data.item, type ) ) {
  391. // Do not fire event if the item was consumed.
  392. return;
  393. }
  394. if ( type === 'insert' || type === 'remove' || type == 'move' ) {
  395. if ( data.item instanceof TextProxy ) {
  396. // Example: insert:$text.
  397. this.fire( type + ':$text', data, consumable, this.conversionApi );
  398. } else {
  399. // Example: insert:paragraph.
  400. this.fire( type + ':' + data.item.name, data, consumable, this.conversionApi );
  401. }
  402. } else {
  403. // Example addAttribute:alt:img.
  404. // Example addAttribute:bold:$text.
  405. const name = data.item.name || '$text';
  406. this.fire( type + ':' + name, data, consumable, this.conversionApi );
  407. }
  408. }
  409. /**
  410. * Fired for inserted nodes.
  411. *
  412. * `insert` is a namespace for a class of events. Names of actually called events follow this pattern:
  413. * `insert:<type>:<elementName>`. `type` is either `text` when one or more characters has been inserted or `element`
  414. * when {@link module:engine/model/element~Element} has been inserted. If `type` is `element`, `elementName` is added and is
  415. * equal to the {@link module:engine/model/element~Element#name name} of inserted element. This way listeners can either
  416. * listen to very general `insert` event or, i.e., very specific `insert:paragraph` event, which is fired only for
  417. * model elements with name `paragraph`.
  418. *
  419. * @event insert
  420. * @param {Object} data Additional information about the change.
  421. * @param {module:engine/model/item~Item} data.item Inserted item.
  422. * @param {module:engine/model/range~Range} data.range Range spanning over inserted item.
  423. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  424. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  425. */
  426. /**
  427. * Fired for moved nodes.
  428. *
  429. * @event move
  430. * @param {Object} data Additional information about the change.
  431. * @param {module:engine/model/position~Position} data.sourcePosition Position from where the range has been moved.
  432. * @param {module:engine/model/range~Range} data.range Moved range (after move).
  433. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  434. */
  435. /**
  436. * Fired for removed nodes.
  437. *
  438. * @event remove
  439. * @param {Object} data Additional information about the change.
  440. * @param {module:engine/model/position~Position} data.sourcePosition Position from where the range has been removed.
  441. * @param {module:engine/model/range~Range} data.range Removed range (in {@link module:engine/model/document~Document#graveyard graveyard
  442. * root}).
  443. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  444. */
  445. /**
  446. * Fired for renamed element.
  447. *
  448. * @event rename
  449. * @param {Object} data Additional information about the change.
  450. * @param {module:engine/model/element~Element} data.element Renamed element.
  451. * @param {String} data.oldName Old name of the renamed element.
  452. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  453. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  454. */
  455. /**
  456. * Fired when attribute has been added on a node.
  457. *
  458. * `addAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
  459. * `addAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of added attribute. `elementName` is
  460. * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute. This way listeners
  461. * can either listen to adding certain attribute, i.e. `addAttribute:bold`, or be more specific, i.e. `addAttribute:link:img`.
  462. *
  463. * @event addAttribute
  464. * @param {Object} data Additional information about the change.
  465. * @param {module:engine/model/item~Item} data.item Changed item.
  466. * @param {module:engine/model/range~Range} data.range Range spanning over changed item.
  467. * @param {String} data.attributeKey Attribute key.
  468. * @param {null} data.attributeOldValue Attribute value before the change - always `null`. Kept for the sake of unifying events.
  469. * @param {*} data.attributeNewValue New attribute value.
  470. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  471. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  472. */
  473. /**
  474. * Fired when attribute has been removed from a node.
  475. *
  476. * `removeAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
  477. * `removeAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of removed attribute. `elementName` is
  478. * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute removed. This way listeners
  479. * can either listen to removing certain attribute, i.e. `removeAttribute:bold`, or be more specific, i.e. `removeAttribute:link:img`.
  480. *
  481. * @event removeAttribute
  482. * @param {Object} data Additional information about the change.
  483. * @param {module:engine/model/item~Item} data.item Changed item.
  484. * @param {module:engine/model/range~Range} data.range Range spanning over changed item.
  485. * @param {String} data.attributeKey Attribute key.
  486. * @param {*} data.attributeOldValue Attribute value before it was removed.
  487. * @param {null} data.attributeNewValue New attribute value - always `null`. Kept for the sake of unifying events.
  488. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  489. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  490. */
  491. /**
  492. * Fired when attribute of a node has been changed.
  493. *
  494. * `changeAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
  495. * `changeAttribute:<attributeKey>:<elementName>`. `attributeKey` is the key of changed attribute. `elementName` is
  496. * equal to the {@link module:engine/model/element~Element#name name} of the element which got the attribute changed. This way listeners
  497. * can either listen to changing certain attribute, i.e. `changeAttribute:link`, or be more specific, i.e. `changeAttribute:link:img`.
  498. *
  499. * @event changeAttribute
  500. * @param {Object} data Additional information about the change.
  501. * @param {module:engine/model/item~Item} data.item Changed item.
  502. * @param {module:engine/model/range~Range} data.range Range spanning over changed item.
  503. * @param {String} data.attributeKey Attribute key.
  504. * @param {*} data.attributeOldValue Attribute value before the change.
  505. * @param {*} data.attributeNewValue New attribute value.
  506. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  507. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  508. */
  509. /**
  510. * Fired for {@link module:engine/model/selection~Selection selection} changes.
  511. *
  512. * @event selection
  513. * @param {module:engine/model/selection~Selection} selection `Selection` instance that is converted.
  514. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  515. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  516. */
  517. /**
  518. * Fired for {@link module:engine/model/selection~Selection selection} attributes changes.
  519. *
  520. * `selectionAttribute` is a namespace for a class of events. Names of actually called events follow this pattern:
  521. * `selectionAttribute:<attributeKey>`. `attributeKey` is the key of selection attribute. This way listen can listen to
  522. * certain attribute, i.e. `addAttribute:bold`.
  523. *
  524. * @event selectionAttribute
  525. * @param {Object} data Additional information about the change.
  526. * @param {module:engine/model/selection~Selection} data.selection Selection that is converted.
  527. * @param {String} data.attributeKey Key of changed attribute.
  528. * @param {*} data.attributeValue Value of changed attribute.
  529. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  530. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  531. */
  532. /**
  533. * Fired when a new marker is added to the model.
  534. *
  535. * `addMarker` is a namespace for a class of events. Names of actually called events follow this pattern:
  536. * `addMarker:<markerName>`. By specifying certain marker names, you can make the events even more gradual. For example,
  537. * markers can be named `foo:abc`, `foo:bar`, then it is possible to listen to `addMarker:foo` or `addMarker:foo:abc` and
  538. * `addMarker:foo:bar` events.
  539. *
  540. * @event addMarker
  541. * @param {Object} data Additional information about the change.
  542. * @param {String} data.name Marker name.
  543. * @param {module:engine/model/range~Range} data.range Marker range.
  544. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  545. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  546. */
  547. /**
  548. * Fired when marker is removed from the model.
  549. *
  550. * `removeMarker` is a namespace for a class of events. Names of actually called events follow this pattern:
  551. * `removeMarker:<markerName>`. By specifying certain marker names, you can make the events even more gradual. For example,
  552. * markers can be named `foo:abc`, `foo:bar`, then it is possible to listen to `removeMarker:foo` or `removeMarker:foo:abc` and
  553. * `removeMarker:foo:bar` events.
  554. *
  555. * @event removeMarker
  556. * @param {Object} data Additional information about the change.
  557. * @param {String} data.name Marker name.
  558. * @param {module:engine/model/range~Range} data.range Marker range.
  559. * @param {module:engine/conversion/modelconsumable~ModelConsumable} consumable Values to consume.
  560. * @param {Object} conversionApi Conversion interface to be used by callback, passed in `ModelConversionDispatcher` constructor.
  561. */
  562. }
  563. mix( ModelConversionDispatcher, EmitterMixin );