8
0

linkimageediting.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module link/linkimageediting
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
  10. import LinkEditing from './linkediting';
  11. export default class LinkImageEditing extends Plugin {
  12. /**
  13. * @inheritDoc
  14. */
  15. static get requires() {
  16. return [ ImageEditing, LinkEditing ];
  17. }
  18. /**
  19. * @inheritDoc
  20. */
  21. static get pluginName() {
  22. return 'LinkImageEditing';
  23. }
  24. init() {
  25. const editor = this.editor;
  26. editor.model.schema.extend( 'image', { allowAttributes: [ 'linkHref' ] } );
  27. editor.conversion.for( 'upcast' ).add( this._upcastLink() );
  28. editor.conversion.for( 'downcast' ).add( this._downcastImageLink() );
  29. }
  30. /**
  31. * Returns a converter that consumes the 'href' attribute if a link contains an image.
  32. *
  33. * @private
  34. * @returns {Function}
  35. */
  36. _upcastLink() {
  37. return dispatcher => {
  38. dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
  39. const viewLink = data.viewItem;
  40. const imageInLink = Array.from( viewLink.getChildren() ).find( child => child.name === 'img' );
  41. if ( !imageInLink ) {
  42. return;
  43. }
  44. // There's an image inside an <a> element - we consume it so it won't be picked up by the Link plugin.
  45. const consumableAttributes = { attributes: [ 'href' ] };
  46. // Consume the `href` attribute so the default one will not convert it to $text attribute.
  47. if ( !conversionApi.consumable.consume( viewLink, consumableAttributes ) ) {
  48. // Might be consumed by something else - i.e. other converter with priority=highest - a standard check.
  49. return;
  50. }
  51. const linkHref = viewLink.getAttribute( 'href' );
  52. // Missing the 'href' attribute.
  53. if ( !linkHref ) {
  54. return;
  55. }
  56. // A full definition of the image feature.
  57. // figure > a > img: parent of the link element is an image element.
  58. let modelElement = data.modelCursor.parent;
  59. if ( !modelElement.is( 'image' ) ) {
  60. // a > img: parent of the link is not the image element. We need to convert it manually.
  61. const conversionResult = conversionApi.convertItem( imageInLink, data.modelCursor );
  62. // Set image range as conversion result.
  63. data.modelRange = conversionResult.modelRange;
  64. // Continue conversion where image conversion ends.
  65. data.modelCursor = conversionResult.modelCursor;
  66. modelElement = data.modelCursor.nodeBefore;
  67. }
  68. if ( modelElement && modelElement.is( 'image' ) ) {
  69. // Set the linkHref attribute from link element on model image element.
  70. conversionApi.writer.setAttribute( 'linkHref', linkHref, modelElement );
  71. }
  72. }, { priority: 'high' } );
  73. };
  74. }
  75. /**
  76. * Return a converter that adds the `<a>` element to data.
  77. *
  78. * @private
  79. * @returns {Function}
  80. */
  81. _downcastImageLink() {
  82. return dispatcher => {
  83. dispatcher.on( 'attribute:linkHref:image', ( evt, data, conversionApi ) => {
  84. // The image will be already converted - so it will be present in the view.
  85. const viewFigure = conversionApi.mapper.toViewElement( data.item );
  86. const writer = conversionApi.writer;
  87. // But we need to check whether the link element exist.
  88. const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
  89. // If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
  90. if ( linkInImage ) {
  91. if ( data.attributeNewValue ) {
  92. writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
  93. } else {
  94. const viewImage = Array.from( linkInImage.getChildren() ).find( child => child.name === 'img' );
  95. writer.move( writer.createRangeOn( viewImage ), writer.createPositionAt( viewFigure, 0 ) );
  96. writer.remove( linkInImage );
  97. }
  98. } else {
  99. // But if it does not exist. Let's wrap already converted image by newly created link element.
  100. // 1. Create an empty link element.
  101. const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
  102. // 2. Insert link inside the associated image.
  103. writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
  104. // 3. Move the image to the link.
  105. writer.move( writer.createRangeOn( viewFigure.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
  106. }
  107. } );
  108. };
  109. }
  110. }