viewconsumable.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import isArray from '../../utils/lib/lodash/isArray.js';
  7. import CKEditorError from '../../utils/ckeditorerror.js';
  8. /**
  9. * This is a private helper-class for {@link engine.treeController.ViewConsumable}.
  10. * It represents and manipulates consumable parts of a single {@link engine.treeView.Element}.
  11. *
  12. * @private
  13. * @memberOf engine.treeController
  14. */
  15. class ViewElementConsumables {
  16. /**
  17. * Creates ViewElementConsumables instance.
  18. */
  19. constructor() {
  20. this._canConsumeName = null;
  21. this._consumables = {
  22. attribute: new Map(),
  23. style: new Map(),
  24. class: new Map()
  25. };
  26. }
  27. /**
  28. * Adds consumable parts of the {@link engine.treeView.Element view Element}.
  29. * Element's name itself can be marked to be consumed (when element's name is consumed its attributes, classes and
  30. * styles still could be consumed):
  31. *
  32. * consumables.add( { name: true } );
  33. *
  34. * Attributes classes and styles:
  35. *
  36. * consumables.add( { attribute: 'title', class: 'foo', style: 'color' } );
  37. * consumables.add( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  38. *
  39. * @param {Object} consumables Object describing which parts of the element can be consumed.
  40. * @param {Boolean} consumables.name If set to `true` element's name will be added as consumable.
  41. * @param {String|Array} consumables.attribute Attribute name or array of attribute names to add as consumable.
  42. * @param {String|Array} consumables.class Class name or array of class names to add as consumable.
  43. * @param {String|Array} consumables.style Style name or array of style names to add as consumable.
  44. */
  45. add( consumables ) {
  46. if ( consumables.name ) {
  47. this._canConsumeName = true;
  48. }
  49. for ( let type in this._consumables ) {
  50. if ( type in consumables ) {
  51. this._add( type, consumables[ type ] );
  52. }
  53. }
  54. }
  55. /**
  56. * Tests if parts of the {@link engine.treeView.Element view Element} can be consumed. Returns `true` when all tested
  57. * items can be consumed, `null` when even one of the items were never marked for consumption and `false` when even
  58. * one of the items were already consumed.
  59. *
  60. * Element's name can be tested:
  61. *
  62. * consumables.test( { name: true } );
  63. *
  64. * Attributes classes and styles:
  65. *
  66. * consumables.test( { attribute: 'title', class: 'foo', style: 'color' } );
  67. * consumables.test( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  68. *
  69. * @param {Object} consumables]Object describing which parts of the element should be tested.
  70. * @param {Boolean} consumables.name If set to `true` element's name will be tested.
  71. * @param {String|Array} consumables.attribute Attribute name or array of attribute names to test.
  72. * @param {String|Array} consumables.class Class name or array of class names to test.
  73. * @param {String|Array} consumables.style Style name or array of style names to test.
  74. * @returns {Boolean|null} Returns `true` when all tested items can be consumed, `null` when even one of the items
  75. * were never marked for consumption and `false` when even one of the items were already consumed.
  76. */
  77. test( consumables ) {
  78. // Check if name can be consumed
  79. if ( consumables.name && !this._canConsumeName ) {
  80. return this._canConsumeName;
  81. }
  82. for ( let type in this._consumables ) {
  83. if ( type in consumables ) {
  84. const value = this._test( type, consumables[ type ] );
  85. if ( value !== true ) {
  86. return value;
  87. }
  88. }
  89. }
  90. // Return true only if all can be consumed.
  91. return true;
  92. }
  93. /**
  94. * Consumes parts of {@link engine.treeView.Element view Element}. This function does not check if consumable item
  95. * is already consumed - it consumes all consumable items provided.
  96. * Element's name can be consumed:
  97. *
  98. * consumables.consume( { name: true } );
  99. *
  100. * Attributes classes and styles:
  101. *
  102. * consumables.consume( { attribute: 'title', class: 'foo', style: 'color' } );
  103. * consumables.consume( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  104. *
  105. * @param {Object} consumables Object describing which parts of the element should be consumed.
  106. * @param {Boolean} consumables.name If set to `true` element's name will be consumed.
  107. * @param {String|Array} consumables.attribute Attribute name or array of attribute names to consume.
  108. * @param {String|Array} consumables.class Class name or array of class names to consume.
  109. * @param {String|Array} consumables.style Style name or array of style names to consume.
  110. */
  111. consume( consumables ) {
  112. if ( consumables.name ) {
  113. this._canConsumeName = false;
  114. }
  115. for ( let type in this._consumables ) {
  116. if ( type in consumables ) {
  117. this._consume( type, consumables[ type ] );
  118. }
  119. }
  120. }
  121. /**
  122. * Revert already consumed parts of {@link engine.treeView.Element view Element}, so they can be consumed once again.
  123. * Element's name can be reverted:
  124. *
  125. * consumables.revert( { name: true } );
  126. *
  127. * Attributes classes and styles:
  128. *
  129. * consumables.revert( { attribute: 'title', class: 'foo', style: 'color' } );
  130. * consumables.revert( { attribute: [ 'title', 'name' ], class: [ 'foo', 'bar' ] );
  131. *
  132. * @param {Object} consumables Object describing which parts of the element should be reverted.
  133. * @param {Boolean} consumables.name If set to `true` element's name will be reverted.
  134. * @param {String|Array} consumables.attribute Attribute name or array of attribute names to revert.
  135. * @param {String|Array} consumables.class Class name or array of class names to revert.
  136. * @param {String|Array} consumables.style Style name or array of style names to revert.
  137. */
  138. revert( consumables ) {
  139. if ( consumables.name ) {
  140. this._canConsumeName = true;
  141. }
  142. for ( let type in this._consumables ) {
  143. if ( type in consumables ) {
  144. this._revert( type, consumables[ type ] );
  145. }
  146. }
  147. }
  148. /**
  149. * Helper method that adds consumables from one type: attribute, class or style.
  150. *
  151. * @private
  152. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  153. * @param {String|Array} item Consumable item or array of items.
  154. */
  155. _add( type, item ) {
  156. const items = isArray( item ) ? item : [ item ];
  157. const consumables = this._consumables[ type ];
  158. for ( let name of items ) {
  159. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  160. /**
  161. * Class and style attributes should be handled separately.
  162. *
  163. * @error viewconsumable-invalid-attribute
  164. */
  165. throw new CKEditorError( 'viewconsumable-invalid-attribute: Classes and styles should be handled separately.' );
  166. }
  167. consumables.set( name, true );
  168. }
  169. }
  170. /**
  171. * Helper method that tests consumables from one type: attribute, class or style.
  172. *
  173. * @private
  174. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  175. * @param {String|Array} item Consumable item or array of items.
  176. * @returns {Boolean|null} Returns `true` if all items can be consumed, `null` when one of the items cannot be
  177. * consumed and `false` when one of the items is already consumed.
  178. */
  179. _test( type, item ) {
  180. const items = isArray( item ) ? item : [ item ];
  181. const consumables = this._consumables[ type ];
  182. for ( let name of items ) {
  183. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  184. /**
  185. * Class and style attributes should be handled separately.
  186. *
  187. * @error viewconsumable-invalid-attribute
  188. */
  189. throw new CKEditorError( 'viewconsumable-invalid-attribute: Classes and styles should be handled separately.' );
  190. }
  191. const value = consumables.get( name );
  192. // Return null if attribute is not found.
  193. if ( value === undefined ) {
  194. return null;
  195. }
  196. if ( !value ) {
  197. return false;
  198. }
  199. }
  200. return true;
  201. }
  202. /**
  203. * Helper method that consumes items from one type: attribute, class or style.
  204. *
  205. * @private
  206. * @param {String} type Type of the consumable item: `attribute`, `class` or `style`.
  207. * @param {String|Array} item Consumable item or array of items.
  208. */
  209. _consume( type, item ) {
  210. const items = isArray( item ) ? item : [ item ];
  211. const consumables = this._consumables[ type ];
  212. for ( let name of items ) {
  213. consumables.set( name, false );
  214. }
  215. }
  216. /**
  217. * Helper method that reverts items from one type: attribute, class or style.
  218. *
  219. * @private
  220. * @param {String} type Type of the consumable item: `attribute`, `class` or , `style`.
  221. * @param {String|Array} item Consumable item or array of items.
  222. */
  223. _revert( type, item ) {
  224. const items = isArray( item ) ? item : [ item ];
  225. const consumables = this._consumables[ type ];
  226. for ( let name of items ) {
  227. if ( type === 'attribute' && ( name === 'class' || name === 'style' ) ) {
  228. /**
  229. * Class and style attributes should be handled separately.
  230. *
  231. * @error viewconsumable-invalid-attribute
  232. */
  233. throw new CKEditorError( 'viewconsumable-invalid-attribute: Classes and styles should be handled separately.' );
  234. }
  235. const value = consumables.get( name );
  236. if ( value === false ) {
  237. consumables.set( name, true );
  238. }
  239. }
  240. }
  241. }
  242. /**
  243. * Class used for handling consumption of {@link engine.treeView.Element view Elements}.
  244. * Element's name and its parts (attributes, classes and styles) can be consumed separately. Consuming an element's name
  245. * does not consume its attributes, classes and styles.
  246. * To add items for consumption use {@link engine.treeController.ViewConsumable#add add method}.
  247. * To test items use {@link engine.treeController.ViewConsumable#test test method}.
  248. * To consume items use {@link engine.treeController.ViewConsumable#consume consume method}.
  249. * To revert already consumed items use {@link engine.treeController.ViewConsumable#revert revert method}.
  250. *
  251. * viewConsumable.add( element, { name: true } ); // Adds element's name as ready to be consumed.
  252. * viewConsumable.test( element, { name: true } ); // Tests if element's name can be consumed.
  253. * viewConsumable.consume( element, { name: true } ); // Consume element's name.
  254. * viewConsumable.revert( element, { name: true } ); // Revert already consumed element's name.
  255. *
  256. * @memberOf engine.treeController
  257. */
  258. export default class ViewConsumable {
  259. /**
  260. * Creates new ViewConsumable.
  261. */
  262. constructor() {
  263. /**
  264. * Map of consumable elements.
  265. *
  266. * @protected
  267. * @member {Map.<engine.treeController.ViewElementConsumables>} engine.treeController.ViewConsumable#_consumables
  268. */
  269. this._consumables = new Map();
  270. }
  271. /**
  272. * Adds {@link engine.treeView.Element view Element} and its parts as ready to be consumed.
  273. *
  274. * viewConsumable.add( p, { name: true } ); // Adds element's name to consume.
  275. * viewConsumable.add( p, { attribute: 'name' } ); // Adds element's attribute.
  276. * viewConsumable.add( p, { class: 'foobar' } ); // Adds element's class.
  277. * viewConsumable.add( p, { style: 'color' } ); // Adds element's style
  278. * viewConsumable.add( p, { attribute: 'name', style: 'color' } ); // Adds attribute and style.
  279. * viewConsumable.consume( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be provided.
  280. *
  281. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  282. * attribute is provided - it should be handled separately by providing actual style/class.
  283. *
  284. * viewConsumable.add( p, { attribute: 'style' } ); // This call will throw an exception.
  285. * viewConsumable.add( p, { style: 'color' } ); // This is properly handled style.
  286. *
  287. * @param {engine.treeView.Element} element
  288. * @param {Object} consumables
  289. * @param {Boolean} consumables.name If set to true element's name will be included.
  290. * @param {String|Array} consumables.attribute Attribute name or array of attribute names.
  291. * @param {String|Array} consumables.class Class name or array of class names.
  292. * @param {String|Array} consumables.style Style name or array of style names.
  293. */
  294. add( element, consumables ) {
  295. let elementConsumables;
  296. if ( !this._consumables.has( element ) ) {
  297. elementConsumables = new ViewElementConsumables();
  298. this._consumables.set( element, elementConsumables );
  299. } else {
  300. elementConsumables = this._consumables.get( element );
  301. }
  302. elementConsumables.add( consumables );
  303. }
  304. /**
  305. * Tests {@link engine.treeView.Element view Element} and its parts to check if can be consumed.
  306. * It returns `true` when all items included in method's call can be consumed. Returns `false` when
  307. * first already consumed item is found and `null` when first non-consumable item is found.
  308. *
  309. * viewConsumable.test( p, { name: true } ); // Tests element's name.
  310. * viewConsumable.test( p, { attribute: 'name' } ); // Tests attribute.
  311. * viewConsumable.test( p, { class: 'foobar' } ); // Tests class.
  312. * viewConsumable.test( p, { style: 'color' } ); // Tests style.
  313. * viewConsumable.test( p, { attribute: 'name', style: 'color' } ); // Tests attribute and style.
  314. * viewConsumable.consume( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be tested.
  315. *
  316. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  317. * attribute is provided - it should be handled separately by providing actual style/class.
  318. *
  319. * viewConsumable.test( p, { attribute: 'style' } ); // This call will throw an exception.
  320. * viewConsumable.test( p, { style: 'color' } ); // This is properly handled style.
  321. *
  322. * @param {engine.treeView.Element} element
  323. * @param {Object} consumables
  324. * @param {Boolean} consumables.name If set to true element's name will be included.
  325. * @param {String|Array} consumables.attribute Attribute name or array of attribute names.
  326. * @param {String|Array} consumables.class Class name or array of class names.
  327. * @param {String|Array} consumables.style Style name or array of style names.
  328. * @returns {Boolean|null} Returns `true` when all items included in method's call can be consumed. Returns `false`
  329. * when first already consumed item is found and `null` when first non-consumable item is found.
  330. */
  331. test( element, consumables ) {
  332. const elementConsumables = this._consumables.get( element );
  333. if ( elementConsumables === undefined ) {
  334. return null;
  335. }
  336. return elementConsumables.test( consumables );
  337. }
  338. /**
  339. * Consumes provided {@link engine.treeView.Element view Element} and its parts.
  340. * It returns `true` when all items included in method's call can be consumed, otherwise returns `false`.
  341. *
  342. * viewConsumable.consume( p, { name: true } ); // Consumes element's name.
  343. * viewConsumable.consume( p, { attribute: 'name' } ); // Consumes element's attribute.
  344. * viewConsumable.consume( p, { class: 'foobar' } ); // Consumes element's class.
  345. * viewConsumable.consume( p, { style: 'color' } ); // Consumes element's style.
  346. * viewConsumable.consume( p, { attribute: 'name', style: 'color' } ); // Consumes attribute and style.
  347. * viewConsumable.consume( p, { class: [ 'baz', 'bar' ] } ); // Multiple consumables can be consumed.
  348. *
  349. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  350. * attribute is provided - it should be handled separately by providing actual style/class.
  351. *
  352. * viewConsumable.consume( p, { attribute: 'style' } ); // This call will throw an exception.
  353. * viewConsumable.consume( p, { style: 'color' } ); // This is properly handled style.
  354. *
  355. * @param {engine.treeView.Element} element
  356. * @param {Object} consumables
  357. * @param {Boolean} consumables.name If set to true element's name will be included.
  358. * @param {String|Array} consumables.attribute Attribute name or array of attribute names.
  359. * @param {String|Array} consumables.class Class name or array of class names.
  360. * @param {String|Array} consumables.style Style name or array of style names.
  361. * @returns {Boolean|null} Returns `true` when all items included in method's call can be consumed,
  362. * otherwise returns `false`.
  363. */
  364. consume( element, consumables ) {
  365. if ( this.test( element, consumables ) ) {
  366. this._consumables.get( element ).consume( consumables );
  367. return true;
  368. }
  369. return false;
  370. }
  371. /**
  372. * Reverts provided {@link engine.treeView.Element view Element} and its parts so they can be consumed once again.
  373. * Method does not revert items that were never previously added for consumption, even if they are included in
  374. * consumables object.
  375. *
  376. * viewConsumable.revert( p, { name: true } ); // Reverts element's name.
  377. * viewConsumable.revert( p, { attribute: 'name' } ); // Reverts element's attribute.
  378. * viewConsumable.revert( p, { class: 'foobar' } ); // Reverts element's class.
  379. * viewConsumable.revert( p, { style: 'color' } ); // Reverts element's style.
  380. * viewConsumable.revert( p, { attribute: 'name', style: 'color' } ); // Reverts attribute and style.
  381. * viewConsumable.revert( p, { class: [ 'baz', 'bar' ] } ); // Multiple names can be reverted.
  382. *
  383. * Throws {@link utils.CKEditorError CKEditorError} `viewconsumable-invalid-attribute` when `class` or `style`
  384. * attribute is provided - it should be handled separately by providing actual style/class.
  385. *
  386. * viewConsumable.revert( p, { attribute: 'style' } ); // This call will throw an exception.
  387. * viewConsumable.revert( p, { style: 'color' } ); // This is properly handled style.
  388. *
  389. * @param {engine.treeView.Element} element
  390. * @param {Object} consumables
  391. * @param {Boolean} consumables.name If set to true element's name will be included.
  392. * @param {String|Array} consumables.attribute Attribute name or array of attribute names.
  393. * @param {String|Array} consumables.class Class name or array of class names.
  394. * @param {String|Array} consumables.style Style name or array of style names.
  395. */
  396. revert( element, consumables ) {
  397. const elementConsumables = this._consumables.get( element );
  398. if ( elementConsumables !== undefined ) {
  399. elementConsumables.revert( consumables );
  400. }
  401. }
  402. }