/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module link/linkimageediting
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
import LinkEditing from './linkediting';
export default class LinkImageEditing extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ImageEditing, LinkEditing ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'LinkImageEditing';
}
init() {
const editor = this.editor;
editor.model.schema.extend( 'image', { allowAttributes: [ 'linkHref' ] } );
editor.conversion.for( 'upcast' ).add( this._upcastLink() );
editor.conversion.for( 'downcast' ).add( this._downcastImageLink() );
}
/**
* Returns a converter that consumes the 'href' attribute if a link contains an image.
*
* @private
* @returns {Function}
*/
_upcastLink() {
return dispatcher => {
dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
const viewLink = data.viewItem;
const imageInLink = Array.from( viewLink.getChildren() ).find( child => child.name === 'img' );
if ( !imageInLink ) {
return;
}
// There's an image inside an element - we consume it so it won't be picked up by the Link plugin.
const consumableAttributes = { attributes: [ 'href' ] };
// Consume the `href` attribute so the default one will not convert it to $text attribute.
if ( !conversionApi.consumable.consume( viewLink, consumableAttributes ) ) {
// Might be consumed by something else - i.e. other converter with priority=highest - a standard check.
return;
}
const linkHref = viewLink.getAttribute( 'href' );
// Missing the 'href' attribute.
if ( !linkHref ) {
return;
}
// A full definition of the image feature.
// figure > a > img: parent of the link element is an image element.
let modelElement = data.modelCursor.parent;
if ( !modelElement.is( 'image' ) ) {
// a > img: parent of the link is not the image element. We need to convert it manually.
const conversionResult = conversionApi.convertItem( imageInLink, data.modelCursor );
// Set image range as conversion result.
data.modelRange = conversionResult.modelRange;
// Continue conversion where image conversion ends.
data.modelCursor = conversionResult.modelCursor;
modelElement = data.modelCursor.nodeBefore;
}
if ( modelElement && modelElement.is( 'image' ) ) {
// Set the linkHref attribute from link element on model image element.
conversionApi.writer.setAttribute( 'linkHref', linkHref, modelElement );
}
}, { priority: 'high' } );
};
}
/**
* Return a converter that adds the `` element to data.
*
* @private
* @returns {Function}
*/
_downcastImageLink() {
return dispatcher => {
dispatcher.on( 'attribute:linkHref:image', ( evt, data, conversionApi ) => {
// The image will be already converted - so it will be present in the view.
const viewFigure = conversionApi.mapper.toViewElement( data.item );
const writer = conversionApi.writer;
// But we need to check whether the link element exist.
const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
// If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
if ( linkInImage ) {
if ( data.attributeNewValue ) {
writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
} else {
const viewImage = Array.from( linkInImage.getChildren() ).find( child => child.name === 'img' );
writer.move( writer.createRangeOn( viewImage ), writer.createPositionAt( viewFigure, 0 ) );
writer.remove( linkInImage );
}
} else {
// But if it does not exist. Let's wrap already converted image by newly created link element.
// 1. Create an empty link element.
const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
// 2. Insert link inside the associated image.
writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
// 3. Move the image to the link.
writer.move( writer.createRangeOn( viewFigure.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
}
} );
};
}
}