From 2906ebf7e353b665bb50b5d9c5131595c01ce78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 15 Nov 2018 18:46:17 +0100 Subject: [PATCH 1/7] Implement the 'imageInsert' command. --- src/image/imageediting.js | 12 +- src/image/imageinsertcommand.js | 44 +++++++ src/image/utils.js | 75 +++++++++++- src/imageupload/imageuploadcommand.js | 61 ++-------- src/imageupload/imageuploadediting.js | 2 +- tests/image/imageediting.js | 5 + tests/image/imageinsertcommand.js | 159 ++++++++++++++++++++++++++ 7 files changed, 300 insertions(+), 58 deletions(-) create mode 100644 src/image/imageinsertcommand.js create mode 100644 tests/image/imageinsertcommand.js diff --git a/src/image/imageediting.js b/src/image/imageediting.js index 789060c8..1dd38fe7 100644 --- a/src/image/imageediting.js +++ b/src/image/imageediting.js @@ -20,11 +20,16 @@ import { toImageWidget } from './utils'; import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; import { upcastElementToElement, upcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters'; +import ImageInsertCommand from './imageinsertcommand'; /** * The image engine plugin. - * It registers `` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes. - * It also registers converters for editing and data pipelines. + * + * It registers: + * + * * `` as a block element in the document schema, and allows `alt`, `src` and `srcset` attributes. + * * converters for editing and data pipelines. + * * `'imageInsert'` command. * * @extends module:core/plugin~Plugin */ @@ -102,6 +107,9 @@ export default class ImageEditing extends Plugin { } } ) ) .add( viewFigureToModel() ); + + // Register imageUpload command. + editor.commands.add( 'imageInsert', new ImageInsertCommand( editor ) ); } } diff --git a/src/image/imageinsertcommand.js b/src/image/imageinsertcommand.js new file mode 100644 index 00000000..cf479673 --- /dev/null +++ b/src/image/imageinsertcommand.js @@ -0,0 +1,44 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Command from '@ckeditor/ckeditor5-core/src/command'; +import { insertImage, isImageAllowed } from './utils'; + +/** + * @module image/image/imageinsertcommand + */ + +/** + * Insert image command. + * + * @extends module:core/command~Command + */ +export default class ImageInsertCommand extends Command { + /** + * @inheritDoc + */ + refresh() { + this.isEnabled = isImageAllowed( this.editor.model ); + } + + /** + * Executes the command. + * + * @fires execute + * @param {Object} options Options for the executed command. + * @param {String|Array.} options.sources The image source or an array of image sources to insert. + */ + execute( options ) { + const editor = this.editor; + + editor.model.change( writer => { + const sources = Array.isArray( options.sources ) ? options.sources : [ options.sources ]; + + for ( const src of sources ) { + insertImage( writer, editor, { src } ); + } + } ); + } +} diff --git a/src/image/utils.js b/src/image/utils.js index 37728e90..3160cd1c 100644 --- a/src/image/utils.js +++ b/src/image/utils.js @@ -7,7 +7,7 @@ * @module image/image/utils */ -import { toWidget, isWidget } from '@ckeditor/ckeditor5-widget/src/utils'; +import { findOptimalInsertionPosition, isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; const imageSymbol = Symbol( 'isImage' ); @@ -65,3 +65,76 @@ export function isImageWidgetSelected( selection ) { export function isImage( modelElement ) { return !!modelElement && modelElement.is( 'image' ); } + +/** + * Handles inserting single file. + * + * @param {module:engine/model/writer~writer} writer + * @param {module:core/editor/editor~Editor} editor + * @param {Object} attributes + */ +export function insertImage( writer, editor, attributes ) { + const model = editor.model; + const doc = model.document; + + const imageElement = writer.createElement( 'image', attributes ); + + const insertAtSelection = findOptimalInsertionPosition( doc.selection, model ); + + model.insertContent( imageElement, insertAtSelection ); + + // Inserting an image might've failed due to schema regulations. + if ( imageElement.parent ) { + writer.setSelection( imageElement, 'on' ); + } +} + +/** + * Checks if image can be inserted at current model selection. + * + * @param {module:engine/model/model~Model} model + * @returns {Boolean} + */ +export function isImageAllowed( model ) { + const schema = model.schema; + const selection = model.document.selection; + + return isImageAllowedInParent( selection, schema, model ) && checkSelectionWithObject( selection, schema ); +} + +// Checks if image is allowed by schema in optimal insertion parent. +// +// @returns {Boolean} +function isImageAllowedInParent( selection, schema, model ) { + const parent = getInsertImageParent( selection, model ); + + return schema.checkChild( parent, 'image' ); +} + +// Check used in image commands for additional cases when the command should be disabled: +// +// - selection is on object +// - selection is inside object +// +// @returns {Boolean} +function checkSelectionWithObject( selection, schema ) { + const selectedElement = selection.getSelectedElement(); + + const isSelectionOnObject = !!selectedElement && schema.isObject( selectedElement ); + const isSelectionInObject = !![ ...selection.focus.getAncestors() ].find( ancestor => schema.isObject( ancestor ) ); + + return !isSelectionOnObject && !isSelectionInObject; +} + +// Returns a node that will be used to insert image with `model.insertContent` to check if image can be placed there. +function getInsertImageParent( selection, model ) { + const insertAt = findOptimalInsertionPosition( selection, model ); + + let parent = insertAt.parent; + + if ( !parent.is( '$root' ) ) { + parent = parent.parent; + } + + return parent; +} diff --git a/src/imageupload/imageuploadcommand.js b/src/imageupload/imageuploadcommand.js index 4e18bc3a..c1ded7d2 100644 --- a/src/imageupload/imageuploadcommand.js +++ b/src/imageupload/imageuploadcommand.js @@ -5,7 +5,7 @@ import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository'; import Command from '@ckeditor/ckeditor5-core/src/command'; -import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils'; +import { insertImage, isImageAllowed } from '../image/utils'; /** * @module image/imageupload/imageuploadcommand @@ -21,11 +21,7 @@ export default class ImageUploadCommand extends Command { * @inheritDoc */ refresh() { - const model = this.editor.model; - const selection = model.document.selection; - const schema = model.schema; - - this.isEnabled = isImageAllowedInParent( selection, schema, model ) && checkSelectionWithObject( selection, schema ); + this.isEnabled = isImageAllowed( this.editor.model ); } /** @@ -38,11 +34,13 @@ export default class ImageUploadCommand extends Command { execute( options ) { const editor = this.editor; + const fileRepository = editor.plugins.get( FileRepository ); + editor.model.change( writer => { const filesToUpload = Array.isArray( options.files ) ? options.files : [ options.files ]; for ( const file of filesToUpload ) { - uploadImage( writer, editor, file ); + uploadImage( writer, editor, fileRepository, file ); } } ); } @@ -53,11 +51,7 @@ export default class ImageUploadCommand extends Command { // @param {module:engine/model/writer~writer} writer // @param {module:core/editor/editor~Editor} editor // @param {File} file -function uploadImage( writer, editor, file ) { - const model = editor.model; - const doc = model.document; - const fileRepository = editor.plugins.get( FileRepository ); - +function uploadImage( writer, editor, fileRepository, file ) { const loader = fileRepository.createLoader( file ); // Do not throw when upload adapter is not set. FileRepository will log an error anyway. @@ -65,46 +59,5 @@ function uploadImage( writer, editor, file ) { return; } - const imageElement = writer.createElement( 'image', { uploadId: loader.id } ); - - const insertAtSelection = findOptimalInsertionPosition( doc.selection, model ); - - model.insertContent( imageElement, insertAtSelection ); - - // Inserting an image might've failed due to schema regulations. - if ( imageElement.parent ) { - writer.setSelection( imageElement, 'on' ); - } -} - -// Checks if image is allowed by schema in optimal insertion parent. -function isImageAllowedInParent( selection, schema, model ) { - const parent = getInsertImageParent( selection, model ); - - return schema.checkChild( parent, 'image' ); -} - -// Additional check for when the command should be disabled: -// - selection is on object -// - selection is inside object -function checkSelectionWithObject( selection, schema ) { - const selectedElement = selection.getSelectedElement(); - - const isSelectionOnObject = !!selectedElement && schema.isObject( selectedElement ); - const isSelectionInObject = !![ ...selection.focus.getAncestors() ].find( ancestor => schema.isObject( ancestor ) ); - - return !isSelectionOnObject && !isSelectionInObject; -} - -// Returns a node that will be used to insert image with `model.insertContent` to check if image can be placed there. -function getInsertImageParent( selection, model ) { - const insertAt = findOptimalInsertionPosition( selection, model ); - - let parent = insertAt.parent; - - if ( !parent.is( '$root' ) ) { - parent = parent.parent; - } - - return parent; + insertImage( writer, editor, { uploadId: loader.id } ); } diff --git a/src/imageupload/imageuploadediting.js b/src/imageupload/imageuploadediting.js index f09c488d..a3193f46 100644 --- a/src/imageupload/imageuploadediting.js +++ b/src/imageupload/imageuploadediting.js @@ -15,7 +15,7 @@ import ImageUploadCommand from '../../src/imageupload/imageuploadcommand'; import { isImageType } from '../../src/imageupload/utils'; /** - * The editing part of the image upload feature. + * The editing part of the image upload feature. It registers the `'imageUpload'` command. * * @extends module:core/plugin~Plugin */ diff --git a/tests/image/imageediting.js b/tests/image/imageediting.js index 35da6eaa..dab907f3 100644 --- a/tests/image/imageediting.js +++ b/tests/image/imageediting.js @@ -9,6 +9,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import ImageEditing from '../../src/image/imageediting'; import ImageLoadObserver from '../../src/image/imageloadobserver'; +import ImageInsertCommand from '../../src/image/imageinsertcommand'; import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; import { isImageWidget } from '../../src/image/utils'; @@ -58,6 +59,10 @@ describe( 'ImageEditing', () => { expect( view.getObserver( ImageLoadObserver ) ).to.be.instanceOf( ImageLoadObserver ); } ); + it( 'should register imageInsert command', () => { + expect( editor.commands.get( 'imageInsert' ) ).to.be.instanceOf( ImageInsertCommand ); + } ); + // See https://github.com/ckeditor/ckeditor5-image/issues/142. it( 'should update the ui after image has been loaded in the DOM', () => { const element = document.createElement( 'div' ); diff --git a/tests/image/imageinsertcommand.js b/tests/image/imageinsertcommand.js new file mode 100644 index 00000000..4bc4fce4 --- /dev/null +++ b/tests/image/imageinsertcommand.js @@ -0,0 +1,159 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; + +import ImageInsertCommand from '../../src/image/imageinsertcommand'; +import Image from '../../src/image/imageediting'; + +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; +import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; + +describe( 'ImageInsertCommand', () => { + let editor, command, model; + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ Image, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + + command = new ImageInsertCommand( editor ); + + const schema = model.schema; + schema.extend( 'image', { allowAttributes: 'uploadId' } ); + } ); + } ); + + afterEach( () => { + return editor.destroy(); + } ); + + describe( 'isEnabled', () => { + it( 'should be true when the selection directly in the root', () => { + model.enqueueChange( 'transparent', () => { + setModelData( model, '[]' ); + + command.refresh(); + expect( command.isEnabled ).to.be.true; + } ); + } ); + + it( 'should be true when the selection is in empty block', () => { + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be true when the selection directly in a paragraph', () => { + setModelData( model, 'foo[]' ); + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be true when the selection directly in a block', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( '$text', { allowIn: 'block' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) ); + + setModelData( model, 'foo[]' ); + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be false when the selection is on other image', () => { + setModelData( model, '[]' ); + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be false when the selection is inside other image', () => { + model.schema.register( 'caption', { + allowIn: 'image', + allowContentOf: '$block', + isLimit: true + } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'caption', view: 'figcaption' } ) ); + setModelData( model, '[]' ); + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be false when the selection is on other object', () => { + model.schema.register( 'object', { isObject: true, allowIn: '$root' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'object', view: 'object' } ) ); + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be false when the selection is inside other object', () => { + model.schema.register( 'object', { isObject: true, allowIn: '$root' } ); + model.schema.extend( '$text', { allowIn: 'object' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'object', view: 'object' } ) ); + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be false when schema disallows image', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( 'paragraph', { allowIn: 'block' } ); + // Block image in block. + model.schema.addChildCheck( ( context, childDefinition ) => { + if ( childDefinition.name === 'image' && context.last.name === 'block' ) { + return false; + } + } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) ); + + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.false; + } ); + } ); + + describe( 'execute()', () => { + it( 'should insert image at selection position as other widgets', () => { + const imgSrc = 'foo/bar.jpg'; + + setModelData( model, 'f[o]o' ); + + command.execute( { sources: imgSrc } ); + + expect( getModelData( model ) ).to.equal( `[]foo` ); + } ); + + it( 'should insert multiple images at selection position as other widgets', () => { + const imgSrc1 = 'foo/bar.jpg'; + const imgSrc2 = 'foo/baz.jpg'; + + setModelData( model, 'f[o]o' ); + + command.execute( { sources: [ imgSrc1, imgSrc2 ] } ); + + expect( getModelData( model ) ) + .to.equal( `[]foo` ); + } ); + + it( 'should not insert image nor crash when image could not be inserted', () => { + const imgSrc = 'foo/bar.jpg'; + + model.schema.register( 'other', { + allowIn: '$root', + isLimit: true + } ); + model.schema.extend( '$text', { allowIn: 'other' } ); + + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'other', view: 'p' } ) ); + + setModelData( model, '[]' ); + + command.execute( { sources: imgSrc } ); + + expect( getModelData( model ) ).to.equal( '[]' ); + } ); + } ); +} ); From bbabaa8ad858a14f90bd3adc13c335e4d8f441b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 19 Nov 2018 14:45:45 +0100 Subject: [PATCH 2/7] Add tests for image/utils methods. --- src/image/imageinsertcommand.js | 6 +- src/image/utils.js | 17 +-- src/imageupload/imageuploadcommand.js | 11 +- tests/image/utils.js | 160 +++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 18 deletions(-) diff --git a/src/image/imageinsertcommand.js b/src/image/imageinsertcommand.js index cf479673..788bdf82 100644 --- a/src/image/imageinsertcommand.js +++ b/src/image/imageinsertcommand.js @@ -31,13 +31,13 @@ export default class ImageInsertCommand extends Command { * @param {String|Array.} options.sources The image source or an array of image sources to insert. */ execute( options ) { - const editor = this.editor; + const model = this.editor.model; - editor.model.change( writer => { + model.change( writer => { const sources = Array.isArray( options.sources ) ? options.sources : [ options.sources ]; for ( const src of sources ) { - insertImage( writer, editor, { src } ); + insertImage( writer, model, { src } ); } } ); } diff --git a/src/image/utils.js b/src/image/utils.js index 3160cd1c..e04bf128 100644 --- a/src/image/utils.js +++ b/src/image/utils.js @@ -67,19 +67,20 @@ export function isImage( modelElement ) { } /** - * Handles inserting single file. + * Handles inserting single file. This method unifies image insertion using {@link module:widget/utils~findOptimalInsertionPosition} method. + * + * model.change( writer => { + * insertImage( writer, model, { src: 'path/to/image.jpg' } ); + * } ); * * @param {module:engine/model/writer~writer} writer - * @param {module:core/editor/editor~Editor} editor - * @param {Object} attributes + * @param {module:engine/model/model~Model} model + * @param {Object} [attributes={}] Attributes of inserted image */ -export function insertImage( writer, editor, attributes ) { - const model = editor.model; - const doc = model.document; - +export function insertImage( writer, model, attributes = {} ) { const imageElement = writer.createElement( 'image', attributes ); - const insertAtSelection = findOptimalInsertionPosition( doc.selection, model ); + const insertAtSelection = findOptimalInsertionPosition( model.document.selection, model ); model.insertContent( imageElement, insertAtSelection ); diff --git a/src/imageupload/imageuploadcommand.js b/src/imageupload/imageuploadcommand.js index c1ded7d2..065ab009 100644 --- a/src/imageupload/imageuploadcommand.js +++ b/src/imageupload/imageuploadcommand.js @@ -33,14 +33,15 @@ export default class ImageUploadCommand extends Command { */ execute( options ) { const editor = this.editor; + const model = editor.model; const fileRepository = editor.plugins.get( FileRepository ); - editor.model.change( writer => { + model.change( writer => { const filesToUpload = Array.isArray( options.files ) ? options.files : [ options.files ]; for ( const file of filesToUpload ) { - uploadImage( writer, editor, fileRepository, file ); + uploadImage( writer, model, fileRepository, file ); } } ); } @@ -49,9 +50,9 @@ export default class ImageUploadCommand extends Command { // Handles uploading single file. // // @param {module:engine/model/writer~writer} writer -// @param {module:core/editor/editor~Editor} editor +// @param {module:engine/model/model~Model} model // @param {File} file -function uploadImage( writer, editor, fileRepository, file ) { +function uploadImage( writer, model, fileRepository, file ) { const loader = fileRepository.createLoader( file ); // Do not throw when upload adapter is not set. FileRepository will log an error anyway. @@ -59,5 +60,5 @@ function uploadImage( writer, editor, fileRepository, file ) { return; } - insertImage( writer, editor, { uploadId: loader.id } ); + insertImage( writer, model, { uploadId: loader.id } ); } diff --git a/tests/image/utils.js b/tests/image/utils.js index 85985a71..1471054a 100644 --- a/tests/image/utils.js +++ b/tests/image/utils.js @@ -7,8 +7,13 @@ import ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfr import ViewDowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwriter'; import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document'; import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element'; -import { toImageWidget, isImageWidget, isImageWidgetSelected, isImage } from '../../src/image/utils'; +import { toImageWidget, isImageWidget, isImageWidgetSelected, isImage, isImageAllowed, insertImage } from '../../src/image/utils'; import { isWidget, getLabel } from '@ckeditor/ckeditor5-widget/src/utils'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { downcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'; +import Image from '../../src/image/imageediting'; describe( 'image widget utils', () => { let element, image, writer; @@ -87,7 +92,7 @@ describe( 'image widget utils', () => { } ); } ); - describe( 'isImage', () => { + describe( 'isImage()', () => { it( 'should return true for image element', () => { const image = new ModelElement( 'image' ); @@ -105,4 +110,155 @@ describe( 'image widget utils', () => { expect( isImage( undefined ) ).to.be.false; } ); } ); + + describe( 'isImageAllowed()', () => { + let editor, model; + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ Image, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + + const schema = model.schema; + schema.extend( 'image', { allowAttributes: 'uploadId' } ); + } ); + } ); + + it( 'should return true when the selection directly in the root', () => { + model.enqueueChange( 'transparent', () => { + setModelData( model, '[]' ); + + expect( isImageAllowed( model ) ).to.be.true; + } ); + } ); + + it( 'should return true when the selection is in empty block', () => { + setModelData( model, '[]' ); + + expect( isImageAllowed( model ) ).to.be.true; + } ); + + it( 'should return true when the selection directly in a paragraph', () => { + setModelData( model, 'foo[]' ); + expect( isImageAllowed( model ) ).to.be.true; + } ); + + it( 'should return true when the selection directly in a block', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( '$text', { allowIn: 'block' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) ); + + setModelData( model, 'foo[]' ); + expect( isImageAllowed( model ) ).to.be.true; + } ); + + it( 'should return false when the selection is on other image', () => { + setModelData( model, '[]' ); + expect( isImageAllowed( model ) ).to.be.false; + } ); + + it( 'should return false when the selection is inside other image', () => { + model.schema.register( 'caption', { + allowIn: 'image', + allowContentOf: '$block', + isLimit: true + } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'caption', view: 'figcaption' } ) ); + setModelData( model, '[]' ); + expect( isImageAllowed( model ) ).to.be.false; + } ); + + it( 'should return false when the selection is on other object', () => { + model.schema.register( 'object', { isObject: true, allowIn: '$root' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'object', view: 'object' } ) ); + setModelData( model, '[]' ); + + expect( isImageAllowed( model ) ).to.be.false; + } ); + + it( 'should return false when the selection is inside other object', () => { + model.schema.register( 'object', { isObject: true, allowIn: '$root' } ); + model.schema.extend( '$text', { allowIn: 'object' } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'object', view: 'object' } ) ); + setModelData( model, '[]' ); + + expect( isImageAllowed( model ) ).to.be.false; + } ); + + it( 'should return false when schema disallows image', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( 'paragraph', { allowIn: 'block' } ); + // Block image in block. + model.schema.addChildCheck( ( context, childDefinition ) => { + if ( childDefinition.name === 'image' && context.last.name === 'block' ) { + return false; + } + } ); + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'block', view: 'block' } ) ); + + setModelData( model, '[]' ); + + expect( isImageAllowed( model ) ).to.be.false; + } ); + } ); + + describe( 'insertImage()', () => { + let editor, model; + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ Image, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + + const schema = model.schema; + schema.extend( 'image', { allowAttributes: 'uploadId' } ); + } ); + } ); + + it( 'should insert image at selection position as other widgets', () => { + setModelData( model, 'f[o]o' ); + + model.change( writer => { + insertImage( writer, model ); + } ); + + expect( getModelData( model ) ).to.equal( '[]foo' ); + } ); + + it( 'should insert image with given attributes', () => { + setModelData( model, 'f[o]o' ); + + model.change( writer => { + insertImage( writer, model, { src: 'bar' } ); + } ); + + expect( getModelData( model ) ).to.equal( '[]foo' ); + } ); + + it( 'should not insert image nor crash when image could not be inserted', () => { + model.schema.register( 'other', { + allowIn: '$root', + isLimit: true + } ); + model.schema.extend( '$text', { allowIn: 'other' } ); + + editor.conversion.for( 'downcast' ).add( downcastElementToElement( { model: 'other', view: 'p' } ) ); + + setModelData( model, '[]' ); + + model.change( writer => { + insertImage( writer, model ); + } ); + + expect( getModelData( model ) ).to.equal( '[]' ); + } ); + } ); } ); From 3f402cea9173c42ed9bae6039a6d7a84a7843571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 20 Nov 2018 12:36:03 +0100 Subject: [PATCH 3/7] Rename `options.sources` to `options.source` in `ImageInsertCommand`. --- src/image/imageinsertcommand.js | 4 ++-- tests/image/imageinsertcommand.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/image/imageinsertcommand.js b/src/image/imageinsertcommand.js index 788bdf82..f77d91c7 100644 --- a/src/image/imageinsertcommand.js +++ b/src/image/imageinsertcommand.js @@ -28,13 +28,13 @@ export default class ImageInsertCommand extends Command { * * @fires execute * @param {Object} options Options for the executed command. - * @param {String|Array.} options.sources The image source or an array of image sources to insert. + * @param {String|Array.} options.source The image source or an array of image sources to insert. */ execute( options ) { const model = this.editor.model; model.change( writer => { - const sources = Array.isArray( options.sources ) ? options.sources : [ options.sources ]; + const sources = Array.isArray( options.source ) ? options.source : [ options.source ]; for ( const src of sources ) { insertImage( writer, model, { src } ); diff --git a/tests/image/imageinsertcommand.js b/tests/image/imageinsertcommand.js index 4bc4fce4..2fc0df54 100644 --- a/tests/image/imageinsertcommand.js +++ b/tests/image/imageinsertcommand.js @@ -121,7 +121,7 @@ describe( 'ImageInsertCommand', () => { setModelData( model, 'f[o]o' ); - command.execute( { sources: imgSrc } ); + command.execute( { source: imgSrc } ); expect( getModelData( model ) ).to.equal( `[]foo` ); } ); @@ -132,7 +132,7 @@ describe( 'ImageInsertCommand', () => { setModelData( model, 'f[o]o' ); - command.execute( { sources: [ imgSrc1, imgSrc2 ] } ); + command.execute( { source: [ imgSrc1, imgSrc2 ] } ); expect( getModelData( model ) ) .to.equal( `[]foo` ); @@ -151,7 +151,7 @@ describe( 'ImageInsertCommand', () => { setModelData( model, '[]' ); - command.execute( { sources: imgSrc } ); + command.execute( { source: imgSrc } ); expect( getModelData( model ) ).to.equal( '[]' ); } ); From 69e175c0e484c451f33166aa3f7accf0181214ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 20 Nov 2018 12:37:01 +0100 Subject: [PATCH 4/7] Rename `options.files` to `options.file` in `ImageUploadCommand`. --- src/imageupload/imageuploadcommand.js | 4 ++-- src/imageupload/imageuploadediting.js | 2 +- src/imageupload/imageuploadui.js | 2 +- tests/imageupload/imageuploadcommand.js | 8 ++++---- tests/imageupload/imageuploadediting.js | 20 ++++++++++---------- tests/imageupload/imageuploadprogress.js | 14 +++++++------- tests/imageupload/imageuploadui.js | 6 +++--- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/imageupload/imageuploadcommand.js b/src/imageupload/imageuploadcommand.js index 065ab009..2e8b27e1 100644 --- a/src/imageupload/imageuploadcommand.js +++ b/src/imageupload/imageuploadcommand.js @@ -29,7 +29,7 @@ export default class ImageUploadCommand extends Command { * * @fires execute * @param {Object} options Options for the executed command. - * @param {File|Array.} options.files The image file or an array of image files to upload. + * @param {File|Array.} options.file The image file or an array of image files to upload. */ execute( options ) { const editor = this.editor; @@ -38,7 +38,7 @@ export default class ImageUploadCommand extends Command { const fileRepository = editor.plugins.get( FileRepository ); model.change( writer => { - const filesToUpload = Array.isArray( options.files ) ? options.files : [ options.files ]; + const filesToUpload = Array.isArray( options.file ) ? options.file : [ options.file ]; for ( const file of filesToUpload ) { uploadImage( writer, model, fileRepository, file ); diff --git a/src/imageupload/imageuploadediting.js b/src/imageupload/imageuploadediting.js index a3193f46..356c6ce4 100644 --- a/src/imageupload/imageuploadediting.js +++ b/src/imageupload/imageuploadediting.js @@ -68,7 +68,7 @@ export default class ImageUploadEditing extends Plugin { // Upload images after the selection has changed in order to ensure the command's state is refreshed. editor.model.enqueueChange( 'default', () => { - editor.execute( 'imageUpload', { files: images } ); + editor.execute( 'imageUpload', { file: images } ); } ); } } ); diff --git a/src/imageupload/imageuploadui.js b/src/imageupload/imageuploadui.js index d6912d76..57469b4d 100644 --- a/src/imageupload/imageuploadui.js +++ b/src/imageupload/imageuploadui.js @@ -51,7 +51,7 @@ export default class ImageUploadUI extends Plugin { const imagesToUpload = Array.from( files ).filter( isImageType ); if ( imagesToUpload.length ) { - editor.execute( 'imageUpload', { files: imagesToUpload } ); + editor.execute( 'imageUpload', { file: imagesToUpload } ); } } ); diff --git a/tests/imageupload/imageuploadcommand.js b/tests/imageupload/imageuploadcommand.js index eaeb7dfd..5207da68 100644 --- a/tests/imageupload/imageuploadcommand.js +++ b/tests/imageupload/imageuploadcommand.js @@ -139,7 +139,7 @@ describe( 'ImageUploadCommand', () => { const file = createNativeFileMock(); setModelData( model, 'f[o]o' ); - command.execute( { files: file } ); + command.execute( { file } ); const id = fileRepository.getLoader( file ).id; expect( getModelData( model ) ) @@ -154,7 +154,7 @@ describe( 'ImageUploadCommand', () => { model.change( writer => { expect( writer.batch.operations ).to.length( 0 ); - command.execute( { files: file } ); + command.execute( { file } ); expect( writer.batch.operations ).to.length.above( 0 ); } ); @@ -173,7 +173,7 @@ describe( 'ImageUploadCommand', () => { setModelData( model, '[]' ); - command.execute( { files: file } ); + command.execute( { file } ); expect( getModelData( model ) ).to.equal( '[]' ); } ); @@ -188,7 +188,7 @@ describe( 'ImageUploadCommand', () => { setModelData( model, 'fo[]o' ); expect( () => { - command.execute( { files: file } ); + command.execute( { file } ); } ).to.not.throw(); expect( getModelData( model ) ).to.equal( 'fo[]o' ); diff --git a/tests/imageupload/imageuploadediting.js b/tests/imageupload/imageuploadediting.js index afe81bd2..233b7f66 100644 --- a/tests/imageupload/imageuploadediting.js +++ b/tests/imageupload/imageuploadediting.js @@ -294,7 +294,7 @@ describe( 'ImageUploadEditing', () => { it( 'should use read data once it is present', done => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); model.once( '_change', () => { expect( getViewData( view ) ).to.equal( @@ -314,7 +314,7 @@ describe( 'ImageUploadEditing', () => { it( 'should replace read data with server response once it is present', done => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); model.document.once( 'change', () => { model.document.once( 'change', () => { @@ -345,7 +345,7 @@ describe( 'ImageUploadEditing', () => { }, { priority: 'high' } ); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); nativeReaderMock.mockError( 'Reading error.' ); } ); @@ -361,7 +361,7 @@ describe( 'ImageUploadEditing', () => { }, { priority: 'high' } ); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); nativeReaderMock.abort(); setTimeout( () => { @@ -385,7 +385,7 @@ describe( 'ImageUploadEditing', () => { } ); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); sinon.assert.calledOnce( loadSpy ); @@ -422,7 +422,7 @@ describe( 'ImageUploadEditing', () => { evt.stop(); }, { priority: 'high' } ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); model.document.once( 'change', () => { model.document.once( 'change', () => { @@ -439,7 +439,7 @@ describe( 'ImageUploadEditing', () => { it( 'should abort upload if image is removed', () => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); const abortSpy = testUtils.sinon.spy( loader, 'abort' ); @@ -458,7 +458,7 @@ describe( 'ImageUploadEditing', () => { it( 'should not abort and not restart upload when image is moved', () => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); const abortSpy = testUtils.sinon.spy( loader, 'abort' ); const loadSpy = testUtils.sinon.spy( loader, 'read' ); @@ -483,7 +483,7 @@ describe( 'ImageUploadEditing', () => { evt.stop(); }, { priority: 'high' } ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); model.document.once( 'change', () => { // This is called after "manual" remove. @@ -519,7 +519,7 @@ describe( 'ImageUploadEditing', () => { it( 'should create responsive image if server return multiple images', done => { const file = createNativeFileMock(); setModelData( model, '{}foo bar' ); - editor.execute( 'imageUpload', { files: file } ); + editor.execute( 'imageUpload', { file } ); model.document.once( 'change', () => { model.document.once( 'change', () => { diff --git a/tests/imageupload/imageuploadprogress.js b/tests/imageupload/imageuploadprogress.js index ffef57b9..6715af20 100644 --- a/tests/imageupload/imageuploadprogress.js +++ b/tests/imageupload/imageuploadprogress.js @@ -74,7 +74,7 @@ describe( 'ImageUploadProgress', () => { it( 'should convert image\'s "reading" uploadStatus attribute', () => { setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); expect( getViewData( view ) ).to.equal( '[
' + @@ -86,7 +86,7 @@ describe( 'ImageUploadProgress', () => { it( 'should convert image\'s "uploading" uploadStatus attribute', done => { setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); model.document.once( 'change', () => { expect( getViewData( view ) ).to.equal( @@ -167,7 +167,7 @@ describe( 'ImageUploadProgress', () => { it( 'should update progressbar width on progress', done => { setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); model.document.once( 'change', () => { adapterMock.mockProgress( 40, 100 ); @@ -189,7 +189,7 @@ describe( 'ImageUploadProgress', () => { const clock = testUtils.sinon.useFakeTimers(); setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); model.document.once( 'change', () => { model.document.once( 'change', () => { @@ -222,7 +222,7 @@ describe( 'ImageUploadProgress', () => { uploadProgress.placeholder = base64Sample; setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); expect( getViewData( view ) ).to.equal( '[
' + @@ -238,7 +238,7 @@ describe( 'ImageUploadProgress', () => { }, { priority: 'highest' } ); setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); expect( getViewData( view ) ).to.equal( '[
]

foo

' @@ -276,7 +276,7 @@ describe( 'ImageUploadProgress', () => { testUtils.sinon.stub( env, 'isEdge' ).get( () => true ); setModelData( model, '[]foo' ); - editor.execute( 'imageUpload', { files: createNativeFileMock() } ); + editor.execute( 'imageUpload', { file: createNativeFileMock() } ); model.document.once( 'change', () => { model.document.once( 'change', () => { diff --git a/tests/imageupload/imageuploadui.js b/tests/imageupload/imageuploadui.js index 68326d4a..7ae0f074 100644 --- a/tests/imageupload/imageuploadui.js +++ b/tests/imageupload/imageuploadui.js @@ -99,7 +99,7 @@ describe( 'ImageUploadUI', () => { button.fire( 'done', files ); sinon.assert.calledOnce( executeStub ); expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' ); - expect( executeStub.firstCall.args[ 1 ].files ).to.deep.equal( files ); + expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files ); } ); it( 'should execute imageUpload command with multiple files', () => { @@ -110,7 +110,7 @@ describe( 'ImageUploadUI', () => { button.fire( 'done', files ); sinon.assert.calledOnce( executeStub ); expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' ); - expect( executeStub.firstCall.args[ 1 ].files ).to.deep.equal( files ); + expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( files ); } ); it( 'should optimize the insertion position', () => { @@ -171,6 +171,6 @@ describe( 'ImageUploadUI', () => { button.fire( 'done', files ); sinon.assert.calledOnce( executeStub ); expect( executeStub.firstCall.args[ 0 ] ).to.equal( 'imageUpload' ); - expect( executeStub.firstCall.args[ 1 ].files ).to.deep.equal( [ files[ 0 ] ] ); + expect( executeStub.firstCall.args[ 1 ].file ).to.deep.equal( [ files[ 0 ] ] ); } ); } ); From a4c215b034e3be1f3a90e16c5717dd37b6b5330d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 20 Nov 2018 14:13:38 +0100 Subject: [PATCH 5/7] Review image commands documentation. --- docs/features/image.md | 14 ++++++++++++++ src/image/imageinsertcommand.js | 16 ++++++++++++++++ src/imageupload/imageuploadcommand.js | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/docs/features/image.md b/docs/features/image.md index d3841124..fa763b40 100644 --- a/docs/features/image.md +++ b/docs/features/image.md @@ -40,6 +40,20 @@ You can see the demo of an editor with the base image feature enabled below: The base image feature, unlike in CKEditor 4, does not support any user interface for inserting or managing images. Its sole purpose is to lay ground for other plugins (mentioned above) to build the target user experience. This pattern (composition of atomic features) is common for CKEditor 5 and allows the developers to build their own customized experience by implementing specific subfeatures differently. +### Image insert command + +The Image feature introduces the {@link module:image/image/imageinsertcommand~ImageInsertCommand `'imageInsert'` command. The command accepts one parameter `source` of image to insert: + +```js +editor.execute( 'imageInsert', { source: '/path/to/image.jpg' } ); +``` + +The command can be also used to insert multiple images at once by passing an array of sources: + +```js +editor.execute( 'imageInsert', { source: [ '/path/to/image1.jpg', '/path/to/image2.jpg' ] } ); +``` + ## Image contextual toolbar The {@link module:image/imagetoolbar~ImageToolbar} plugin introduces a contextual toolbar for images. The toolbar appears when an image is selected and can be configured to contain any buttons you want. Usually, these will be image-related options such as the text alternative (which is introduced by the base image plugin) button and [image styles buttons](#image-styles). diff --git a/src/image/imageinsertcommand.js b/src/image/imageinsertcommand.js index f77d91c7..873726a0 100644 --- a/src/image/imageinsertcommand.js +++ b/src/image/imageinsertcommand.js @@ -13,6 +13,22 @@ import { insertImage, isImageAllowed } from './utils'; /** * Insert image command. * + * The command is registered by the {@link module:image/image/imageediting~ImageEditing} as `'imageInsert'`. + * + * To insert image at the current selection (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), + * execute the command and specify the image source: + * + * editor.execute( 'imageInsert', { source: 'http://url.to.the/image' } ); + * + * It is also possible to insert multiple images at once: + * + * editor.execute( 'imageInsert', { + * source: [ + * 'path/to/image.jpg', + * 'path/to/other-image.jpg' + * ] + * } ); + * * @extends module:core/command~Command */ export default class ImageInsertCommand extends Command { diff --git a/src/imageupload/imageuploadcommand.js b/src/imageupload/imageuploadcommand.js index 2e8b27e1..4a4a66b6 100644 --- a/src/imageupload/imageuploadcommand.js +++ b/src/imageupload/imageuploadcommand.js @@ -14,6 +14,28 @@ import { insertImage, isImageAllowed } from '../image/utils'; /** * Image upload command. * + * The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} as `'imageUpload'`. + * + * To upload an image at the current selection (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), + * execute the command and pass the native image file instance: + * + * this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => { + * // Assuming that only images were pasted: + * const images = Array.from( data.dataTransfer.files ); + * + * // Upload the first image: + * editor.execute( 'imageUpload', { file: images[ 0 ] } ); + * } ); + * + * It is also possible to insert multiple images at once: + * + * editor.execute( 'imageUpload', { + * file: [ + * file1, + * file2 + * ] + * } ); + * * @extends module:core/command~Command */ export default class ImageUploadCommand extends Command { From f089e22ae2abc05e6be7826e02239b6cda4bffa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 21 Nov 2018 10:44:31 +0100 Subject: [PATCH 6/7] Fix docs link. --- src/image/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/image/utils.js b/src/image/utils.js index e04bf128..b8c29b04 100644 --- a/src/image/utils.js +++ b/src/image/utils.js @@ -73,7 +73,7 @@ export function isImage( modelElement ) { * insertImage( writer, model, { src: 'path/to/image.jpg' } ); * } ); * - * @param {module:engine/model/writer~writer} writer + * @param {module:engine/model/writer~Writer} writer * @param {module:engine/model/model~Model} model * @param {Object} [attributes={}] Attributes of inserted image */ From f458c251f9939ab76d240fe827866fe19f84f46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 22 Nov 2018 15:58:24 +0100 Subject: [PATCH 7/7] Corrected the docs. --- docs/features/image.md | 17 ++--------------- src/image/imageinsertcommand.js | 7 ++++--- src/imageupload/imageuploadcommand.js | 7 ++++--- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/docs/features/image.md b/docs/features/image.md index fa763b40..034d4e7f 100644 --- a/docs/features/image.md +++ b/docs/features/image.md @@ -40,20 +40,6 @@ You can see the demo of an editor with the base image feature enabled below: The base image feature, unlike in CKEditor 4, does not support any user interface for inserting or managing images. Its sole purpose is to lay ground for other plugins (mentioned above) to build the target user experience. This pattern (composition of atomic features) is common for CKEditor 5 and allows the developers to build their own customized experience by implementing specific subfeatures differently. -### Image insert command - -The Image feature introduces the {@link module:image/image/imageinsertcommand~ImageInsertCommand `'imageInsert'` command. The command accepts one parameter `source` of image to insert: - -```js -editor.execute( 'imageInsert', { source: '/path/to/image.jpg' } ); -``` - -The command can be also used to insert multiple images at once by passing an array of sources: - -```js -editor.execute( 'imageInsert', { source: [ '/path/to/image1.jpg', '/path/to/image2.jpg' ] } ); -``` - ## Image contextual toolbar The {@link module:image/imagetoolbar~ImageToolbar} plugin introduces a contextual toolbar for images. The toolbar appears when an image is selected and can be configured to contain any buttons you want. Usually, these will be image-related options such as the text alternative (which is introduced by the base image plugin) button and [image styles buttons](#image-styles). @@ -227,7 +213,8 @@ ClassicEditor The {@link module:image/image~Image} plugin registers: * The `'imageTextAlternative'` button. -* * The {@link module:image/imagetextalternative/imagetextalternativecommand~ImageTextAlternativeCommand `'imageTextAlternative'` command} +* The {@link module:image/imagetextalternative/imagetextalternativecommand~ImageTextAlternativeCommand `'imageTextAlternative'` command} +* The {@link module:image/image/imageinsertcommand~ImageInsertCommand `'imageInsert'` command} which accepts a source (e.g. an URL) of an image to insert. The {@link module:image/imagestyle~ImageStyle} plugin registers: diff --git a/src/image/imageinsertcommand.js b/src/image/imageinsertcommand.js index 873726a0..7dcef633 100644 --- a/src/image/imageinsertcommand.js +++ b/src/image/imageinsertcommand.js @@ -13,14 +13,15 @@ import { insertImage, isImageAllowed } from './utils'; /** * Insert image command. * - * The command is registered by the {@link module:image/image/imageediting~ImageEditing} as `'imageInsert'`. + * The command is registered by the {@link module:image/image/imageediting~ImageEditing} plugin as `'imageInsert'`. * - * To insert image at the current selection (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), + * In order to insert an image at the current selection position + * (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), * execute the command and specify the image source: * * editor.execute( 'imageInsert', { source: 'http://url.to.the/image' } ); * - * It is also possible to insert multiple images at once: + * It is also possible to insert multiple images at once: * * editor.execute( 'imageInsert', { * source: [ diff --git a/src/imageupload/imageuploadcommand.js b/src/imageupload/imageuploadcommand.js index 4a4a66b6..2958b399 100644 --- a/src/imageupload/imageuploadcommand.js +++ b/src/imageupload/imageuploadcommand.js @@ -14,9 +14,10 @@ import { insertImage, isImageAllowed } from '../image/utils'; /** * Image upload command. * - * The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} as `'imageUpload'`. + * The command is registered by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin as `'imageUpload'`. * - * To upload an image at the current selection (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), + * In order to upload an image at the current selection position + * (according to the {@link module:widget/utils~findOptimalInsertionPosition} algorithm), * execute the command and pass the native image file instance: * * this.listenTo( editor.editing.view.document, 'clipboardInput', ( evt, data ) => { @@ -27,7 +28,7 @@ import { insertImage, isImageAllowed } from '../image/utils'; * editor.execute( 'imageUpload', { file: images[ 0 ] } ); * } ); * - * It is also possible to insert multiple images at once: + * It is also possible to insert multiple images at once: * * editor.execute( 'imageUpload', { * file: [