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

i/7794: Introduce Insert Image via URL feature. #7835

Merged
merged 40 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
07b099a
Enable imageUpload command when image is selected.
panr Aug 11, 2020
e5c0c9a
Update imageUploadUI plugin.
panr Aug 11, 2020
4afc030
Add imageUploadFormRowView.
panr Aug 11, 2020
bbce659
Add imageUploadPanelView.
panr Aug 11, 2020
e679c5b
Add manual test.
panr Aug 11, 2020
5639bb6
Clean srcset and sizes attributes.
panr Aug 11, 2020
de78af5
Update styles.
panr Aug 11, 2020
2f80172
Update manual test.
panr Aug 11, 2020
eb5c719
Update styles for the CKFInder button in the panel view.
panr Aug 11, 2020
f5d550e
Add integrations.
panr Aug 11, 2020
661f7c2
Move prepareIntegrations helper to utils.
panr Aug 12, 2020
1faf552
Update imageUploadCommand
panr Aug 13, 2020
7b1d719
Update ImageUploadUI.
panr Aug 13, 2020
ebed510
Update prepateIntegrations util.
panr Aug 13, 2020
c11ccf9
Update imageUploadPanelView.
panr Aug 13, 2020
22b879a
Update tests.
panr Aug 13, 2020
9aac6b1
Update manual test.
panr Aug 13, 2020
2278b79
FIx @param.
panr Aug 13, 2020
538d409
Fix API docs for imageUploadFormRowView.
panr Aug 17, 2020
87a81c0
Fix the module not found error.
panr Aug 17, 2020
8934a3e
Merge remote-tracking branch 'origin/master' into i/7794
pomek Aug 17, 2020
51714c3
Added a missing devDep.
pomek Aug 17, 2020
d0689d6
Fixed paths in tests to real assets.
pomek Aug 17, 2020
075baf5
Fill the gap for the missing translations.
panr Aug 18, 2020
5b9da5d
Simplify the ImageUploadUI.
panr Aug 18, 2020
8d3c41d
Replace options object with integration object.
panr Aug 18, 2020
cd20947
Update testes.
panr Aug 18, 2020
632bfe9
Refactor ImageUploadPanelView and utils.
panr Aug 18, 2020
0e4c1f6
Register `imageUpload` component on `init()`.
panr Aug 18, 2020
29fb059
Fix the type of returned object from `prepareIntegrations`
panr Aug 18, 2020
ea98988
Small fixes.
panr Aug 19, 2020
0777f4d
Remove redundant element check.
panr Aug 19, 2020
9f22791
Implement ck-error for missing integration view.
panr Aug 19, 2020
b0d192f
Update context.json.
panr Aug 19, 2020
f772911
Merge branch 'master' into i/7794
panr Aug 19, 2020
2218987
Remove blocking code
panr Aug 20, 2020
ffddbcb
Merge branch 'master' into i/7794
Reinmar Aug 20, 2020
d2c3e39
Refactor the plugin and make it opt-in.
panr Aug 20, 2020
48287c0
Merge branch 'master' into i/7794
Reinmar Aug 20, 2020
10d6159
Added insert image via url to the all-features manual test.
Reinmar Aug 20, 2020
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
7 changes: 6 additions & 1 deletion packages/ckeditor5-image/lang/contexts.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,10 @@
"Resize image to %0": "The label used for the standalone resize options buttons in the image toolbar",
"Resize image to the original size": "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers",
"Original": "Default label for the resize option that resets the size of the image.",
"Image resize list": "The accessibility label of the image resize dropdown list for the screen readers."
"Image resize list": "The accessibility label of the image resize dropdown list for the screen readers.",
"Insert": "The label of submit form button if image src URL input has no value",
"Update": "The label of submit form button if image src URL input has value",
"Cancel": "The label of cancel form button",
"Insert image via URL": "The input label for the Insert image via URL form",
"Paste the image source URL.": "The tip label below the Insert image via URL form"
}
1 change: 1 addition & 0 deletions packages/ckeditor5-image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@ckeditor/ckeditor5-basic-styles": "^21.0.0",
"@ckeditor/ckeditor5-block-quote": "^21.0.0",
"@ckeditor/ckeditor5-cloud-services": "^21.0.0",
"@ckeditor/ckeditor5-ckfinder": "^21.0.0",
"@ckeditor/ckeditor5-editor-classic": "^21.0.0",
"@ckeditor/ckeditor5-enter": "^21.0.0",
"@ckeditor/ckeditor5-easy-image": "^21.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export default class ImageUploadCommand extends Command {
* @inheritDoc
*/
refresh() {
this.isEnabled = isImageAllowed( this.editor.model );
const imageElement = this.editor.model.document.selection.getSelectedElement();
const isImage = imageElement && imageElement.name === 'image' || false;

this.isEnabled = isImageAllowed( this.editor.model ) || isImage;
}

/**
Expand Down
147 changes: 123 additions & 24 deletions packages/ckeditor5-image/src/imageupload/imageuploadui.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,155 @@
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ImageUploadPanelView from './ui/imageuploadpanelview';

import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview';
import { createImageTypeRegExp, prepareIntegrations } from './utils';

import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
import { createImageTypeRegExp } from './utils';

import { isImage } from '../image/utils';

/**
* The image upload button plugin.
*
* For a detailed overview, check the {@glink features/image-upload/image-upload Image upload feature} documentation.
*
* Adds the `'imageUpload'` button to the {@link module:ui/componentfactory~ComponentFactory UI component factory}.
* Adds the `'imageUpload'` dropdown to the {@link module:ui/componentfactory~ComponentFactory UI component factory}.
*
* @extends module:core/plugin~Plugin
*/
export default class ImageUploadUI extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'ImageUploadUI';
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const t = editor.t;
const command = editor.commands.get( 'imageUpload' );

// Setup `imageUpload` button.
editor.ui.componentFactory.add( 'imageUpload', locale => {
const view = new FileDialogButtonView( locale );
const command = editor.commands.get( 'imageUpload' );
const imageTypes = editor.config.get( 'image.upload.types' );
const imageTypesRegExp = createImageTypeRegExp( imageTypes );
const imageUploadView = new ImageUploadPanelView( locale, prepareIntegrations( editor ) );

const dropdownView = imageUploadView.dropdownView;
const panelView = dropdownView.panelView;
const splitButtonView = dropdownView.buttonView;

view.set( {
acceptedType: imageTypes.map( type => `image/${ type }` ).join( ',' ),
allowMultipleFiles: true
} );
splitButtonView.actionView = this._createFileDialogButtonView( locale );

view.buttonView.set( {
label: t( 'Insert image' ),
icon: imageIcon,
tooltip: true
} );
panelView.children.add( imageUploadView );

view.buttonView.bind( 'isEnabled' ).to( command );
return this._setUpDropdown( dropdownView, imageUploadView, command );
} );
}

/**
* Sets up the dropdown view.
*
* @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdownView.
* @param {module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView} imageUploadView An imageUploadView.
* @param {module:core/command~Command} command An imageUpload command
*
* @private
* @returns {module:ui/dropdown/dropdownview~DropdownView}
*/
_setUpDropdown( dropdownView, imageUploadView, command ) {
const editor = this.editor;
const t = editor.t;
const insertButtonView = imageUploadView.insertButtonView;

dropdownView.bind( 'isEnabled' ).to( command );

view.on( 'done', ( evt, files ) => {
const imagesToUpload = Array.from( files ).filter( file => imageTypesRegExp.test( file.type ) );
dropdownView.on( 'change:isOpen', () => {
const selectedElement = editor.model.document.selection.getSelectedElement();

if ( imagesToUpload.length ) {
editor.execute( 'imageUpload', { file: imagesToUpload } );
if ( dropdownView.isOpen ) {
imageUploadView.focus();

if ( isImage( selectedElement ) ) {
imageUploadView.imageURLInputValue = selectedElement.getAttribute( 'src' );
insertButtonView.label = t( 'Update' );
panr marked this conversation as resolved.
Show resolved Hide resolved
} else {
imageUploadView.imageURLInputValue = '';
insertButtonView.label = t( 'Insert' );
panr marked this conversation as resolved.
Show resolved Hide resolved
}
} );
}
} );

return view;
imageUploadView.delegate( 'submit', 'cancel' ).to( dropdownView );
this.delegate( 'cancel' ).to( dropdownView );

dropdownView.on( 'submit', () => {
closePanel();
onSubmit();
} );

dropdownView.on( 'cancel', () => {
closePanel();
} );

function onSubmit() {
const selectedElement = editor.model.document.selection.getSelectedElement();

if ( isImage( selectedElement ) ) {
editor.model.change( writer => {
writer.setAttribute( 'src', imageUploadView.imageURLInputValue, selectedElement );
writer.removeAttribute( 'srcset', selectedElement );
writer.removeAttribute( 'sizes', selectedElement );
} );
} else {
editor.execute( 'imageInsert', { source: imageUploadView.imageURLInputValue } );
}
}

function closePanel() {
editor.editing.view.focus();
dropdownView.isOpen = false;
}

return dropdownView;
}

/**
* Creates and sets up file dialog button view.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
*
* @private
* @returns {module:upload/ui/filedialogbuttonview~FileDialogButtonView}
*/
_createFileDialogButtonView( locale ) {
const editor = this.editor;
const t = locale.t;
const imageTypes = editor.config.get( 'image.upload.types' );
const fileDialogButtonView = new FileDialogButtonView( locale );
const imageTypesRegExp = createImageTypeRegExp( imageTypes );

fileDialogButtonView.set( {
acceptedType: imageTypes.map( type => `image/${ type }` ).join( ',' ),
allowMultipleFiles: true
} );

fileDialogButtonView.buttonView.set( {
label: t( 'Insert image' ),
icon: imageIcon,
tooltip: true
} );

fileDialogButtonView.on( 'done', ( evt, files ) => {
const imagesToUpload = Array.from( files ).filter( file => imageTypesRegExp.test( file.type ) );

if ( imagesToUpload.length ) {
editor.execute( 'imageUpload', { file: imagesToUpload } );
}
} );

return fileDialogButtonView;
}
}
103 changes: 103 additions & 0 deletions packages/ckeditor5-image/src/imageupload/ui/imageuploadformrowview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @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 image/imageupload/ui/imageuploadformrowview
*/

import View from '@ckeditor/ckeditor5-ui/src/view';

import '../../../theme/imageuploadformrowview.css';

/**
* The class representing a single row in a complex form,
* used by {@link module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView}.
*
* **Note**: For now this class is private. When more use cases arrive (beyond ckeditor5-table and ckeditor5-image),
* it will become a component in ckeditor5-ui.
*
* @private
* @extends module:ui/view~View
*/
export default class ImageUploadFormRowView extends View {
/**
* Creates an instance of the form row class.
*
* @param {module:utils/locale~Locale} locale The locale instance.
* @param {Object} options
* @param {Array.<module:ui/view~View>} [options.children]
* @param {String} [options.class]
* @param {module:ui/view~View} [options.labelView] When passed, the row gets the `group` and `aria-labelledby`
* DOM attributes and gets described by the label.
*/
constructor( locale, options = {} ) {
pomek marked this conversation as resolved.
Show resolved Hide resolved
super( locale );

const bind = this.bindTemplate;

/**
* An additional CSS class added to the {@link #element}.
*
* @observable
* @member {String} #class
*/
this.set( 'class', options.class || null );

/**
* A collection of row items (buttons, dropdowns, etc.).
*
* @readonly
* @member {module:ui/viewcollection~ViewCollection}
*/
this.children = this.createCollection();

if ( options.children ) {
options.children.forEach( child => this.children.add( child ) );
}

/**
* The role property reflected by the `role` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #role
*/
this.set( '_role', null );

/**
* The ARIA property reflected by the `aria-labelledby` DOM attribute of the {@link #element}.
*
* **Note**: Used only when a `labelView` is passed to constructor `options`.
*
* @private
* @observable
* @member {String} #ariaLabelledBy
*/
this.set( '_ariaLabelledBy', null );

if ( options.labelView ) {
this.set( {
_role: 'group',
_ariaLabelledBy: options.labelView.id
} );
}

this.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-form__row',
bind.to( 'class' )
],
role: bind.to( '_role' ),
'aria-labelledby': bind.to( '_ariaLabelledBy' )
},
children: this.children
} );
}
}
Loading