Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#8871: Integrate inline images with the link feature #9438

Merged
merged 46 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
57bd33c
Tests: Made link image manual test more useful.
oleq Apr 7, 2021
dcd31a0
Made inline images in links more distinguishable.
oleq Apr 7, 2021
5727e41
Compatibility with link image.
oleq Apr 7, 2021
2b575d9
Made the link image UI recognize selected inline images.
oleq Apr 7, 2021
0b52c8e
Made the link command support inline images.
oleq Apr 7, 2021
b4a232d
Made the unlink command support inline images.
oleq Apr 7, 2021
5d4d335
Adjusted the upcast of the linked images so that the linked block ima…
oleq Apr 7, 2021
4686135
Renamed isImageAllowed->isLinkableImage and adjusted the logic to sup…
oleq Apr 7, 2021
0203c17
Improved rendering of selected inline images.
oleq Apr 7, 2021
f2e91e7
Preserve the link around a block image in certain cases when pasting …
oleq Apr 7, 2021
34cc772
Simplified the linked image indicator, now using CSS only.
oleq Apr 8, 2021
6028201
Code refactoring.
oleq Apr 8, 2021
5ccafa5
Don't show the link UI when there's a single linked inline image sele…
oleq Apr 8, 2021
58e3651
Improvements to inserting inline images into links.
oleq Apr 8, 2021
5c16680
Tests: Added LinkImage to the upload command manual test.
oleq Apr 8, 2021
abd279b
Aligned image upload indicator and image link indicator visibility th…
oleq Apr 12, 2021
f17966d
Tests: Added a AutoImage + LinkImage integration test.
oleq Apr 12, 2021
169f85b
Tests: Added a test for ImageInlineEditing + LinkImage integration.
oleq Apr 12, 2021
0e609c4
Tests: Added test for InsertImageCommand integration with LinkImage.
oleq Apr 12, 2021
e8183ca
Tests: Added tests for the getSelectedImageWidget() helper integratio…
oleq Apr 12, 2021
8947aef
Tests: Added tests for UploadImageCommand to make sure the integratio…
oleq Apr 12, 2021
1fa13f7
Docs.
oleq Apr 12, 2021
b72a34e
Tests: Added tests fro isLinkableImage() helper integration with inli…
oleq Apr 13, 2021
10ff8b5
Tests: Added tests for LinkCommand integration with LinkImage.
oleq Apr 13, 2021
bd6f85e
Tests: Added LinkImageEditing tests for integration with inline images.
oleq Apr 13, 2021
5722357
Tests: Added tests for LinkImageUI integration with inline images.
oleq Apr 13, 2021
3a41931
Tests: Added tests for LinkUI and LinkImageUI to check integration wi…
oleq Apr 13, 2021
6b861c4
Code refactoring.
oleq Apr 13, 2021
b549ad4
Tests: UnlinkCommand tests for integration with inline images.
oleq Apr 13, 2021
25ade6f
Made image utils a plugin.
oleq Apr 14, 2021
c604020
Made the link feature use the new image utils. Made link and unlink c…
oleq Apr 14, 2021
f7b4ad4
Fixed a bug which caused the image toolbar to be wrongly positioned a…
oleq Apr 14, 2021
fe1747c
Docs.
oleq Apr 14, 2021
e5bd248
Docs.
oleq Apr 14, 2021
f537410
Tests.
oleq Apr 15, 2021
708fa76
Tests.
oleq Apr 15, 2021
5ac48c5
Docs.
oleq Apr 15, 2021
c7f6ac5
Merge branch 'i/2052-inline-images' into i/8871-inline-link-image
oleq Apr 15, 2021
8757481
Merge branch 'i/2052-inline-images' into i/8871-inline-link-image
oleq Apr 19, 2021
67008af
Code refactoring.
oleq Apr 19, 2021
24a7329
Tests.
oleq Apr 19, 2021
898502a
Tests.
oleq Apr 19, 2021
0cb8e4c
Code refactoring.
oleq Apr 20, 2021
1115d58
Merge branch 'i/2052-inline-images' into i/8871-inline-link-image
oleq Apr 20, 2021
41d1b36
Code refactoring.
oleq Apr 20, 2021
23ac235
Update packages/ckeditor5-image/src/imageutils.js
oleq Apr 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/ckeditor5-image/src/autoimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { LivePosition, LiveRange } from 'ckeditor5/src/engine';
import { Undo } from 'ckeditor5/src/undo';
import { global } from 'ckeditor5/src/utils';

import { insertImage } from './image/utils';
import ImageUtils from './imageutils';

// Implements the pattern: http(s)://(www.)example.com/path/to/resource.ext?query=params&maybe=too.
const IMAGE_URL_REGEXP = new RegExp( String( /^(http(s)?:\/\/)?[\w-]+\.[\w.~:/[\]@!$&'()*+,;=%-]+/.source +
Expand All @@ -32,7 +32,7 @@ export default class AutoImage extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ Clipboard, Undo ];
return [ Clipboard, ImageUtils, Undo ];
}

/**
Expand Down Expand Up @@ -118,6 +118,8 @@ export default class AutoImage extends Plugin {
// TODO: Use a marker instead of LiveRange & LivePositions.
const urlRange = new LiveRange( leftPosition, rightPosition );
const walker = urlRange.getWalker( { ignoreElementEnd: true } );
const selectionAttributes = Object.fromEntries( editor.model.document.selection.getAttributes() );
const imageUtils = this.editor.plugins.get( 'ImageUtils' );
niegowski marked this conversation as resolved.
Show resolved Hide resolved

let src = '';

Expand Down Expand Up @@ -166,7 +168,7 @@ export default class AutoImage extends Plugin {
insertionPosition = this._positionToInsert.toPosition();
}

insertImage( editor, { src }, insertionPosition );
imageUtils.insertImage( { ...selectionAttributes, src }, insertionPosition );

this._positionToInsert.detach();
this._positionToInsert = null;
Expand Down
11 changes: 0 additions & 11 deletions packages/ckeditor5-image/src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Plugin } from 'ckeditor5/src/core';

import ImageBlock from './imageblock';
import ImageInline from './imageinline';
import { isImageWidget } from './image/utils';

import '../theme/image.css';

Expand Down Expand Up @@ -44,16 +43,6 @@ export default class Image extends Plugin {
static get pluginName() {
return 'Image';
}

/**
* Checks if a given view element is an image widget.
*
* @param {module:engine/view/element~Element} viewElement
* @returns {Boolean}
*/
isImageWidget( viewElement ) {
return isImageWidget( viewElement );
}
}

/**
Expand Down
16 changes: 9 additions & 7 deletions packages/ckeditor5-image/src/image/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import { first } from 'ckeditor5/src/utils';
import { getViewImageFromWidget } from './utils';

/**
* Returns a function that converts the image view representation:
Expand All @@ -22,9 +21,10 @@ import { getViewImageFromWidget } from './utils';
* The entire content of the `<figure>` element except the first `<img>` is being converted as children
* of the `<image>` model element.
*
* @param {module:image/imageutils~ImageUtils} imageUtils
* @returns {Function}
*/
export function viewFigureToModel() {
export function viewFigureToModel( imageUtils ) {
return dispatcher => {
dispatcher.on( 'element:figure', converter );
};
Expand All @@ -36,7 +36,7 @@ export function viewFigureToModel() {
}

// Find an image element inside the figure element.
const viewImage = getViewImageFromWidget( data.viewItem );
const viewImage = imageUtils.getViewImageFromWidget( data.viewItem );

// Do not convert if image element is absent, is missing src attribute or was already converted.
if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !conversionApi.consumable.test( viewImage, { name: true } ) ) {
Expand Down Expand Up @@ -64,10 +64,11 @@ export function viewFigureToModel() {
/**
* Converter used to convert the `srcset` model image attribute to the `srcset`, `sizes` and `width` attributes in the view.
*
* @param {module:image/imageutils~ImageUtils} imageUtils
* @param {'image'|'imageInline'} imageType The type of the image.
* @returns {Function}
*/
export function srcsetAttributeConverter( imageType ) {
export function srcsetAttributeConverter( imageUtils, imageType ) {
return dispatcher => {
dispatcher.on( `attribute:srcset:${ imageType }`, converter );
};
Expand All @@ -79,7 +80,7 @@ export function srcsetAttributeConverter( imageType ) {

const writer = conversionApi.writer;
const element = conversionApi.mapper.toViewElement( data.item );
const img = getViewImageFromWidget( element );
const img = imageUtils.getViewImageFromWidget( element );

if ( data.attributeNewValue === null ) {
const srcset = data.attributeOldValue;
Expand Down Expand Up @@ -111,11 +112,12 @@ export function srcsetAttributeConverter( imageType ) {
/**
* Converter used to convert a given image attribute from the model to the view.
*
* @param {module:image/imageutils~ImageUtils} imageUtils
* @param {'image'|'imageInline'} imageType The type of the image.
* @param {String} attributeKey The name of the attribute to convert.
* @returns {Function}
*/
export function modelToViewAttributeConverter( imageType, attributeKey ) {
export function modelToViewAttributeConverter( imageUtils, imageType, attributeKey ) {
return dispatcher => {
dispatcher.on( `attribute:${ attributeKey }:${ imageType }`, converter );
};
Expand All @@ -127,7 +129,7 @@ export function modelToViewAttributeConverter( imageType, attributeKey ) {

const viewWriter = conversionApi.writer;
const element = conversionApi.mapper.toViewElement( data.item );
const img = getViewImageFromWidget( element );
const img = imageUtils.getViewImageFromWidget( element );

viewWriter.setAttribute( data.attributeKey, data.attributeNewValue || '', img );
}
Expand Down
34 changes: 17 additions & 17 deletions packages/ckeditor5-image/src/image/imageblockediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
import { UpcastWriter } from 'ckeditor5/src/engine';

import { modelToViewAttributeConverter, srcsetAttributeConverter, viewFigureToModel } from './converters';
import {
toImageWidget,
createImageViewElement,
getImageTypeMatcher,
determineImageTypeForInsertionAtSelection,
isInlineImageView
} from './utils';

import ImageEditing from './imageediting';
import ImageTypeCommand from './imagetypecommand';
import ImageUtils from '../imageutils';
import {
getImageTypeMatcher,
createImageViewElement,
determineImageTypeForInsertionAtSelection
} from '../image/utils';

/**
* The image block plugin.
Expand All @@ -40,7 +39,7 @@ export default class ImageBlockEditing extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ ImageEditing, ClipboardPipeline ];
return [ ImageEditing, ImageUtils, ClipboardPipeline ];
}

/**
Expand Down Expand Up @@ -84,6 +83,7 @@ export default class ImageBlockEditing extends Plugin {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
const imageUtils = editor.plugins.get( 'ImageUtils' );

conversion.for( 'dataDowncast' )
.elementToElement( {
Expand All @@ -94,23 +94,23 @@ export default class ImageBlockEditing extends Plugin {
conversion.for( 'editingDowncast' )
.elementToElement( {
model: 'image',
view: ( modelElement, { writer } ) => toImageWidget(
view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
createImageViewElement( writer, 'image' ), writer, t( 'image widget' )
)
} );

conversion.for( 'downcast' )
.add( modelToViewAttributeConverter( 'image', 'src' ) )
.add( modelToViewAttributeConverter( 'image', 'alt' ) )
.add( srcsetAttributeConverter( 'image' ) );
.add( modelToViewAttributeConverter( imageUtils, 'image', 'src' ) )
.add( modelToViewAttributeConverter( imageUtils, 'image', 'alt' ) )
.add( srcsetAttributeConverter( imageUtils, 'image' ) );

// More image related upcasts are in 'ImageEditing' plugin.
conversion.for( 'upcast' )
.elementToElement( {
view: getImageTypeMatcher( 'image', editor ),
view: getImageTypeMatcher( editor, 'image' ),
model: ( viewImage, { writer } ) => writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
} )
.add( viewFigureToModel() );
.add( viewFigureToModel( imageUtils ) );
}

/**
Expand All @@ -132,16 +132,16 @@ export default class ImageBlockEditing extends Plugin {
_setupClipboardIntegration() {
const editor = this.editor;
const model = editor.model;
const schema = model.schema;
const editingView = editor.editing.view;
const imageUtils = editor.plugins.get( 'ImageUtils' );

this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
const docFragmentChildren = Array.from( data.content.getChildren() );
let modelRange;

// Make sure only <img> elements are dropped or pasted. Otherwise, if there some other HTML
// mixed up, this should be handled as a regular paste.
if ( !docFragmentChildren.every( isInlineImageView ) ) {
if ( !docFragmentChildren.every( imageUtils.isInlineImageView ) ) {
return;
}

Expand All @@ -160,7 +160,7 @@ export default class ImageBlockEditing extends Plugin {

// Convert inline images into block images only when the currently selected block is empty
// (e.g. an empty paragraph) or some object is selected (to replace it).
if ( determineImageTypeForInsertionAtSelection( schema, selection ) === 'image' ) {
if ( determineImageTypeForInsertionAtSelection( model.schema, selection ) === 'image' ) {
const writer = new UpcastWriter( editingView.document );

// Wrap <img ... /> -> <figure class="image"><img .../></figure>
Expand Down
8 changes: 8 additions & 0 deletions packages/ckeditor5-image/src/image/imageediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Plugin } from 'ckeditor5/src/core';
import ImageLoadObserver from './imageloadobserver';
import InsertImageCommand from './insertimagecommand';
import ImageUtils from '../imageutils';

/**
* The image engine plugin. This module loads common code shared between
Expand All @@ -21,6 +22,13 @@ import InsertImageCommand from './insertimagecommand';
* @extends module:core/plugin~Plugin
*/
export default class ImageEditing extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ ImageUtils ];
}

/**
* @inheritDoc
*/
Expand Down
36 changes: 18 additions & 18 deletions packages/ckeditor5-image/src/image/imageinlineediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@ import { Plugin } from 'ckeditor5/src/core';
import { ClipboardPipeline } from 'ckeditor5/src/clipboard';
import { UpcastWriter } from 'ckeditor5/src/engine';

import {
toImageWidget,
createImageViewElement,
getImageTypeMatcher,
getViewImageFromWidget,
determineImageTypeForInsertionAtSelection,
isBlockImageView
} from './utils';
import { modelToViewAttributeConverter, srcsetAttributeConverter } from './converters';

import ImageEditing from './imageediting';
import ImageTypeCommand from './imagetypecommand';
import ImageUtils from '../imageutils';
import {
getImageTypeMatcher,
createImageViewElement,
determineImageTypeForInsertionAtSelection
} from '../image/utils';

/**
* The image inline plugin.
Expand All @@ -41,7 +39,7 @@ export default class ImageInlineEditing extends Plugin {
* @inheritDoc
*/
static get requires() {
return [ ImageEditing, ClipboardPipeline ];
return [ ImageEditing, ImageUtils, ClipboardPipeline ];
}

/**
Expand Down Expand Up @@ -85,6 +83,7 @@ export default class ImageInlineEditing extends Plugin {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
const imageUtils = editor.plugins.get( 'ImageUtils' );

conversion.for( 'dataDowncast' )
.elementToElement( {
Expand All @@ -95,20 +94,20 @@ export default class ImageInlineEditing extends Plugin {
conversion.for( 'editingDowncast' )
.elementToElement( {
model: 'imageInline',
view: ( modelElement, { writer } ) => toImageWidget(
view: ( modelElement, { writer } ) => imageUtils.toImageWidget(
createImageViewElement( writer, 'imageInline' ), writer, t( 'inline image widget' )
)
} );

conversion.for( 'downcast' )
.add( modelToViewAttributeConverter( 'imageInline', 'src' ) )
.add( modelToViewAttributeConverter( 'imageInline', 'alt' ) )
.add( srcsetAttributeConverter( 'imageInline' ) );
.add( modelToViewAttributeConverter( imageUtils, 'imageInline', 'src' ) )
.add( modelToViewAttributeConverter( imageUtils, 'imageInline', 'alt' ) )
.add( srcsetAttributeConverter( imageUtils, 'imageInline' ) );

// More image related upcasts are in 'ImageEditing' plugin.
conversion.for( 'upcast' )
.elementToElement( {
view: getImageTypeMatcher( 'imageInline', editor ),
view: getImageTypeMatcher( editor, 'imageInline' ),
model: ( viewImage, { writer } ) => writer.createElement( 'imageInline', { src: viewImage.getAttribute( 'src' ) } )
} );
}
Expand All @@ -133,16 +132,16 @@ export default class ImageInlineEditing extends Plugin {
_setupClipboardIntegration() {
const editor = this.editor;
const model = editor.model;
const schema = model.schema;
const editingView = editor.editing.view;
const imageUtils = editor.plugins.get( 'ImageUtils' );

this.listenTo( editor.plugins.get( 'ClipboardPipeline' ), 'inputTransformation', ( evt, data ) => {
const docFragmentChildren = Array.from( data.content.getChildren() );
let modelRange;

// Make sure only <figure class="image"></figure> elements are dropped or pasted. Otherwise, if there some other HTML
// mixed up, this should be handled as a regular paste.
if ( !docFragmentChildren.every( isBlockImageView ) ) {
if ( !docFragmentChildren.every( imageUtils.isBlockImageView ) ) {
return;
}

Expand All @@ -161,17 +160,18 @@ export default class ImageInlineEditing extends Plugin {

// Convert block images into inline images only when pasting or dropping into non-empty blocks
// and when the block is not an object (e.g. pasting to replace another widget).
if ( determineImageTypeForInsertionAtSelection( schema, selection ) === 'imageInline' ) {
if ( determineImageTypeForInsertionAtSelection( model.schema, selection ) === 'imageInline' ) {
const writer = new UpcastWriter( editingView.document );

// Unwrap <figure class="image"><img .../></figure> -> <img ... />
// but <figure class="image"><img .../><figcaption>...</figcaption></figure> -> stays the same
const inlineViewImages = docFragmentChildren.map( blockViewImage => {
// If there's just one child, it can be either <img /> or <a><img></a>.
// If there are other children than <img>, this means that the block image
// has a caption or some other features and this kind of image should be
// pasted/dropped without modifications.
if ( blockViewImage.childCount === 1 ) {
return getViewImageFromWidget( blockViewImage );
return blockViewImage.getChild( 0 );
} else {
return blockViewImage;
}
Expand Down
Loading