Skip to content

Commit

Permalink
Merge pull request #15255 from ckeditor/ck/15195-toolbar-nesting-split
Browse files Browse the repository at this point in the history
Feature (image): Introduced the image insert dropdown as a consistent UI to insert images through different available integrations like image upload, insert an image with the asset manager, and insert an image via URL. Closes #15303. Closes #15149.

Other (image): The `ImageUploadUI` plugin is loaded by default when the `ImageBlock` or `ImageInline` plugins are loaded. See #15149.

Other (list, ui): The `CollapsibleView` moved from the `list` package to the `ui` package. See #15149.

Other (ui): The `SplitButtonView` constructor and `createDropdown()` helper accepts an instance of a `ButtonView` as an action view customization. See #15149.

Other (upload): The `FileDialogButtonView` is now an instance of the `ButtonView`, not just a wrapper on it. See #15149.

Internal (ckbox, ckfinder, image): The `ImageUploadUI`, `CKBoxUI`, `CKFinderUI`, and `ImageInsertViaUrlUI` plugins are registering integrations in the `ImageInsertUI`.

Internal (core): Added icons for `ImageUploadUI` and its integrations. Added translation contexts for common labels for images insert/replace via file manager.

MINOR BREAKING CHANGE (list): The `CollapsibleView` moved from the `list` package to the `ui` package. You can import it like this: `import { CollapsibleView } from '@ckeditor/ckeditor5-ui';`
  • Loading branch information
niegowski authored Dec 5, 2023
2 parents 9ea94d4 + a809c47 commit 0647ba6
Show file tree
Hide file tree
Showing 79 changed files with 3,541 additions and 1,940 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ ClassicEditor.defaultConfig = {
'|', 'bulletedList', 'numberedList', 'outdent', 'indent'
]
},
insert: {
integrations: [
'insertImageViaUrl'
]
},
ui: {
viewportOffset: {
top: window.getViewportTopOffsetConfig()
Expand Down
42 changes: 41 additions & 1 deletion packages/ckeditor5-ckbox/src/ckboxui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
* @module ckbox/ckboxui
*/

import { Plugin } from 'ckeditor5/src/core';
import { icons, Plugin } from 'ckeditor5/src/core';
import { ButtonView } from 'ckeditor5/src/ui';

import type { ImageInsertUI } from '@ckeditor/ckeditor5-image';

import browseFilesIcon from '../theme/icons/browse-files.svg';
import type CKBoxCommand from './ckboxcommand';

Expand Down Expand Up @@ -57,5 +59,43 @@ export default class CKBoxUI extends Plugin {

return button;
} );

if ( editor.plugins.has( 'ImageInsertUI' ) ) {
const imageInsertUI: ImageInsertUI = editor.plugins.get( 'ImageInsertUI' );

imageInsertUI.registerIntegration( {
name: 'assetManager',
observable: command,

buttonViewCreator: () => {
const button = this.editor.ui.componentFactory.create( 'ckbox' ) as ButtonView;

button.icon = icons.imageAssetManager;
button.bind( 'label' ).to( imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
t( 'Replace image with file manager' ) :
t( 'Insert image with file manager' )
);

return button;
},

formViewCreator: () => {
const button = this.editor.ui.componentFactory.create( 'ckbox' ) as ButtonView;

button.icon = icons.imageAssetManager;
button.withText = true;
button.bind( 'label' ).to( imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
t( 'Replace with file manager' ) :
t( 'Insert with file manager' )
);

button.on( 'execute', () => {
imageInsertUI.dropdownView!.isOpen = false;
} );

return button;
}
} );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,33 @@ describe( 'CKBoxImageEditCommand', () => {

sinon.assert.calledOnce( focusSpy );
} );

it( 'should refresh the command after closing the CKBox Image Editor dialog', async () => {
const ckboxImageId = 'example-id';

setModelData( model,
`[<imageBlock alt="alt text" ckboxImageId="${ ckboxImageId }" src="/assets/sample.png"></imageBlock>]`
);

const imageElement = editor.model.document.selection.getSelectedElement();

const options = await command._prepareOptions( {
element: imageElement,
ckboxImageId,
controller: new AbortController()
} );

const refreshSpy = testUtils.sinon.spy( command, 'refresh' );

expect( command.value ).to.be.false;

command.execute();
expect( command.value ).to.be.true;

options.onClose();
expect( command.value ).to.be.false;
sinon.assert.calledOnce( refreshSpy );
} );
} );

describe( 'saving edited asset', () => {
Expand Down
106 changes: 106 additions & 0 deletions packages/ckeditor5-ckbox/tests/ckboxui.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import ImageInlineEditing from '@ckeditor/ckeditor5-image/src/image/imageinlinee
import CloudServices from '@ckeditor/ckeditor5-cloud-services/src/cloudservices';
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import CloudServicesCoreMock from './_utils/cloudservicescoremock';
import ImageInsertUI from '@ckeditor/ckeditor5-image/src/imageinsert/imageinsertui';
import Model from '@ckeditor/ckeditor5-ui/src/model';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { icons } from 'ckeditor5/src/core';

import CKBoxUI from '../src/ckboxui';
import CKBoxEditing from '../src/ckboxediting';
Expand All @@ -41,6 +44,7 @@ describe( 'CKBoxUI', () => {
ImageInlineEditing,
ImageUploadEditing,
ImageUploadProgress,
ImageInsertUI,
CloudServices,
CKBoxUI,
CKBoxEditing
Expand Down Expand Up @@ -144,4 +148,106 @@ describe( 'CKBoxUI', () => {
expect( executeSpy.args[ 0 ][ 0 ] ).to.equal( 'ckbox' );
} );
} );

describe( 'InsertImageUI integration', () => {
it( 'should create CKBox button in split button dropdown button', () => {
mockAssetManagerIntegration();

const spy = sinon.spy( editor.ui.componentFactory, 'create' );
const dropdown = editor.ui.componentFactory.create( 'insertImage' );
const dropdownButton = dropdown.buttonView.actionView;

expect( dropdownButton ).to.be.instanceOf( ButtonView );
expect( dropdownButton.withText ).to.be.false;
expect( dropdownButton.icon ).to.equal( icons.imageAssetManager );

expect( spy.calledTwice ).to.be.true;
expect( spy.firstCall.args[ 0 ] ).to.equal( 'insertImage' );
expect( spy.secondCall.args[ 0 ] ).to.equal( 'ckbox' );
expect( spy.firstCall.returnValue ).to.equal( dropdown.buttonView.actionView );
} );

it( 'should create CKBox button in dropdown panel', () => {
mockAssetManagerIntegration();

const dropdown = editor.ui.componentFactory.create( 'insertImage' );
const spy = sinon.spy( editor.ui.componentFactory, 'create' );

dropdown.isOpen = true;

const formView = dropdown.panelView.children.get( 0 );
const buttonView = formView.children.get( 0 );

expect( buttonView ).to.be.instanceOf( ButtonView );
expect( buttonView.withText ).to.be.true;
expect( buttonView.icon ).to.equal( icons.imageAssetManager );

expect( spy.calledOnce ).to.be.true;
expect( spy.firstCall.args[ 0 ] ).to.equal( 'ckbox' );
expect( spy.firstCall.returnValue ).to.equal( buttonView );
} );

it( 'should bind to #isImageSelected', () => {
const insertImageUI = editor.plugins.get( 'ImageInsertUI' );

mockAssetManagerIntegration();

const dropdown = editor.ui.componentFactory.create( 'insertImage' );

dropdown.isOpen = true;

const dropdownButton = dropdown.buttonView.actionView;
const formView = dropdown.panelView.children.get( 0 );
const buttonView = formView.children.get( 0 );

insertImageUI.isImageSelected = false;
expect( dropdownButton.label ).to.equal( 'Insert image with file manager' );
expect( buttonView.label ).to.equal( 'Insert with file manager' );

insertImageUI.isImageSelected = true;
expect( dropdownButton.label ).to.equal( 'Replace image with file manager' );
expect( buttonView.label ).to.equal( 'Replace with file manager' );
} );

it( 'should close dropdown on execute', () => {
mockAssetManagerIntegration();

const dropdown = editor.ui.componentFactory.create( 'insertImage' );

dropdown.isOpen = true;

const formView = dropdown.panelView.children.get( 0 );
const buttonView = formView.children.get( 0 );

sinon.stub( editor, 'execute' );

buttonView.fire( 'execute' );

expect( dropdown.isOpen ).to.be.false;
} );
} );

function mockAssetManagerIntegration() {
const insertImageUI = editor.plugins.get( 'ImageInsertUI' );
const observable = new Model( { isEnabled: true } );

insertImageUI.registerIntegration( {
name: 'url',
observable,
buttonViewCreator() {
const button = new ButtonView( editor.locale );

button.label = 'foo';

return button;
},
formViewCreator() {
const button = new ButtonView( editor.locale );

button.label = 'bar';

return button;
}
} );
}
} );
2 changes: 1 addition & 1 deletion packages/ckeditor5-ckbox/theme/icons/ckbox-image-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion packages/ckeditor5-ckfinder/src/ckfinderui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
* @module ckfinder/ckfinderui
*/

import { Plugin } from 'ckeditor5/src/core';
import { icons, Plugin } from 'ckeditor5/src/core';
import { ButtonView } from 'ckeditor5/src/ui';
import type { ImageInsertUI } from '@ckeditor/ckeditor5-image';

import type CKFinderCommand from './ckfindercommand';

Expand Down Expand Up @@ -53,5 +54,44 @@ export default class CKFinderUI extends Plugin {

return button;
} );

if ( editor.plugins.has( 'ImageInsertUI' ) ) {
const imageInsertUI: ImageInsertUI = editor.plugins.get( 'ImageInsertUI' );
const command: CKFinderCommand = editor.commands.get( 'ckfinder' )!;

imageInsertUI.registerIntegration( {
name: 'assetManager',
observable: command,

buttonViewCreator: () => {
const button = this.editor.ui.componentFactory.create( 'ckfinder' ) as ButtonView;

button.icon = icons.imageAssetManager;
button.bind( 'label' ).to( imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
t( 'Replace image with file manager' ) :
t( 'Insert image with file manager' )
);

return button;
},

formViewCreator: () => {
const button = this.editor.ui.componentFactory.create( 'ckfinder' ) as ButtonView;

button.icon = icons.imageAssetManager;
button.withText = true;
button.bind( 'label' ).to( imageInsertUI, 'isImageSelected', isImageSelected => isImageSelected ?
t( 'Replace with file manager' ) :
t( 'Insert with file manager' )
);

button.on( 'execute', () => {
imageInsertUI.dropdownView!.isOpen = false;
} );

return button;
}
} );
}
}
}
Loading

0 comments on commit 0647ba6

Please sign in to comment.