automaticdecorators.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module link/utils
  7. */
  8. import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
  9. /**
  10. * Helper class that ties together all {@link module:link/link~LinkDecoratorAutomaticDefinition} and provides
  11. * the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#attributeToElement downcast dispatchers} for them.
  12. */
  13. export default class AutomaticDecorators {
  14. constructor() {
  15. /**
  16. * Stores the definition of {@link module:link/link~LinkDecoratorAutomaticDefinition automatic decorators}.
  17. * This data is used as a source for a downcast dispatcher to create a proper conversion to output data.
  18. *
  19. * @private
  20. * @type {Set}
  21. */
  22. this._definitions = new Set();
  23. }
  24. /**
  25. * Gives information about the number of decorators stored in the {@link module:link/utils~AutomaticDecorators} instance.
  26. *
  27. * @readonly
  28. * @protected
  29. * @type {Number}
  30. */
  31. get length() {
  32. return this._definitions.size;
  33. }
  34. /**
  35. * Adds automatic decorator objects or an array with them to be used during downcasting.
  36. *
  37. * @param {module:link/link~LinkDecoratorAutomaticDefinition|Array.<module:link/link~LinkDecoratorAutomaticDefinition>} item
  38. * A configuration object of automatic rules for decorating links. It might also be an array of such objects.
  39. */
  40. add( item ) {
  41. if ( Array.isArray( item ) ) {
  42. item.forEach( item => this._definitions.add( item ) );
  43. } else {
  44. this._definitions.add( item );
  45. }
  46. }
  47. /**
  48. * Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method.
  49. *
  50. * @returns {Function} A dispatcher function used as conversion helper
  51. * in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
  52. */
  53. getDispatcher() {
  54. return dispatcher => {
  55. dispatcher.on( 'attribute:linkHref', ( evt, data, conversionApi ) => {
  56. // There is only test as this behavior decorates links and
  57. // it is run before dispatcher which actually consumes this node.
  58. // This allows on writing own dispatcher with highest priority,
  59. // which blocks both native converter and this additional decoration.
  60. if ( !conversionApi.consumable.test( data.item, 'attribute:linkHref' ) ) {
  61. return;
  62. }
  63. const viewWriter = conversionApi.writer;
  64. const viewSelection = viewWriter.document.selection;
  65. for ( const item of this._definitions ) {
  66. const viewElement = viewWriter.createAttributeElement( 'a', item.attributes, {
  67. priority: 5
  68. } );
  69. viewWriter.setCustomProperty( 'link', true, viewElement );
  70. if ( item.callback( data.attributeNewValue ) ) {
  71. if ( data.item.is( 'selection' ) ) {
  72. viewWriter.wrap( viewSelection.getFirstRange(), viewElement );
  73. } else {
  74. viewWriter.wrap( conversionApi.mapper.toViewRange( data.range ), viewElement );
  75. }
  76. } else {
  77. viewWriter.unwrap( conversionApi.mapper.toViewRange( data.range ), viewElement );
  78. }
  79. }
  80. }, { priority: 'high' } );
  81. };
  82. }
  83. /**
  84. * Provides the conversion helper used in the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add} method
  85. * when linking images.
  86. *
  87. * @returns {Function} A dispatcher function used as conversion helper
  88. * in {@link module:engine/conversion/downcasthelpers~DowncastHelpers#add}.
  89. */
  90. getDispatcherForLinkedImage() {
  91. return dispatcher => {
  92. dispatcher.on( 'attribute:linkHref:image', ( evt, data, conversionApi ) => {
  93. const viewFigure = conversionApi.mapper.toViewElement( data.item );
  94. const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
  95. for ( const item of this._definitions ) {
  96. const attributes = toMap( item.attributes );
  97. if ( item.callback( data.attributeNewValue ) ) {
  98. for ( const [ key, val ] of attributes ) {
  99. if ( key === 'class' ) {
  100. conversionApi.writer.addClass( val, linkInImage );
  101. } else {
  102. conversionApi.writer.setAttribute( key, val, linkInImage );
  103. }
  104. }
  105. } else {
  106. for ( const [ key, val ] of attributes ) {
  107. if ( key === 'class' ) {
  108. conversionApi.writer.removeClass( val, linkInImage );
  109. } else {
  110. conversionApi.writer.removeAttribute( key, linkInImage );
  111. }
  112. }
  113. }
  114. }
  115. } );
  116. };
  117. }
  118. }