Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge branch 'master' into ckeditor5/t/1214
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinmar committed Jan 6, 2019
2 parents f671bdf + 54059a1 commit d153d01
Show file tree
Hide file tree
Showing 38 changed files with 1,500 additions and 235 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
Changelog
=========

## [12.0.0](https://github.com/ckeditor/ckeditor5-image/compare/v11.0.0...v12.0.0) (2018-12-05)

### Features

* Improved responsiveness of the text alternative view in narrow viewports (see [ckeditor/ckeditor5#416](https://github.com/ckeditor/ckeditor5/issues/416)). ([ff5394a](https://github.com/ckeditor/ckeditor5-image/commit/ff5394a))
* Introduced the `'imageInsert'` command. Closes [#245](https://github.com/ckeditor/ckeditor5-image/issues/245). Closes [#251](https://github.com/ckeditor/ckeditor5-image/issues/251). ([cc1e7a3](https://github.com/ckeditor/ckeditor5-image/commit/cc1e7a3))
* Support for uploading images pasted with a base64 source. Closes [#246](https://github.com/ckeditor/ckeditor5-image/issues/246). Closes [ckeditor/ckeditor5-paste-from-office#24](https://github.com/ckeditor/ckeditor5-paste-from-office/issues/24). ([89ab27e](https://github.com/ckeditor/ckeditor5-image/commit/89ab27e))

### Bug fixes

* Prevent errors when (for unclear reasons) the native `DataTransfer#files` contains `null` values when drag&dropping files into the editor in Chrome. ([2a45481](https://github.com/ckeditor/ckeditor5-image/commit/2a45481))

Thanks to [@code-chris](https://github.com/code-chris)!

### Other changes

* Moved widget spacing styles from `@ckeditor/ckeditor5-theme-lark` to the feature content styles sheet (see [ckeditor/ckeditor5-theme-lark#209](https://github.com/ckeditor/ckeditor5-theme-lark/issues/209)). ([671e1b8](https://github.com/ckeditor/ckeditor5-image/commit/671e1b8))
* Removed obsolete fill attributes in SVG icons. ([0f9dad3](https://github.com/ckeditor/ckeditor5-image/commit/0f9dad3)) ([57bd34c](https://github.com/ckeditor/ckeditor5-image/commit/57bd34c)) ([ebc27e6](https://github.com/ckeditor/ckeditor5-image/commit/ebc27e6)) ([6192cf3](https://github.com/ckeditor/ckeditor5-image/commit/6192cf3))
* Updated translations. ([3c85c37](https://github.com/ckeditor/ckeditor5-image/commit/3c85c37))

### BREAKING CHANGES

* The `ImageUploadCommand#execute()`'s `files` parameter was renamed to `file`. It can still accept an array of files.


## [11.0.0](https://github.com/ckeditor/ckeditor5-image/compare/v10.2.0...v11.0.0) (2018-10-08)

### Other changes
Expand Down
2 changes: 1 addition & 1 deletion docs/_snippets/features/image-caption.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ClassicEditor
toolbar: [ 'imageTextAlternative' ]
},
toolbar: {
viewportTopOffset: 100
viewportTopOffset: window.getViewportTopOffsetConfig()
},
cloudServices: CS_CONFIG
} )
Expand Down
2 changes: 1 addition & 1 deletion docs/_snippets/features/image-style-custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ClassicEditor
toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ]
},
toolbar: {
viewportTopOffset: 100
viewportTopOffset: window.getViewportTopOffsetConfig()
},
cloudServices: CS_CONFIG
} )
Expand Down
2 changes: 1 addition & 1 deletion docs/_snippets/features/image-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud
ClassicEditor
.create( document.querySelector( '#snippet-image-style' ), {
toolbar: {
viewportTopOffset: 100
viewportTopOffset: window.getViewportTopOffsetConfig()
},
cloudServices: CS_CONFIG
} )
Expand Down
2 changes: 1 addition & 1 deletion docs/_snippets/features/image-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ClassicEditor
toolbar: [ 'imageTextAlternative' ]
},
toolbar: {
viewportTopOffset: 100
viewportTopOffset: window.getViewportTopOffsetConfig()
},
cloudServices: CS_CONFIG
} )
Expand Down
2 changes: 1 addition & 1 deletion docs/_snippets/features/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ClassicEditor
.create( document.querySelector( '#snippet-image' ), {
removePlugins: [ 'ImageToolbar', 'ImageCaption', 'ImageStyle' ],
toolbar: {
viewportTopOffset: 100
viewportTopOffset: window.getViewportTopOffsetConfig()
},
cloudServices: CS_CONFIG
} )
Expand Down
11 changes: 5 additions & 6 deletions docs/features/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The {@link module:image/image~Image} feature adds support for plain images with
```

<info-box hint>
This feature follows the markup proposed by the [Editor Recommendations](http://ckeditor.github.io/editor-recommendations/features/images.html) project.
This feature follows the markup proposed by the [Editor Recommendations](https://ckeditor.github.io/editor-recommendations/features/image.html) project.
</info-box>

You can see the demo of an editor with the base image feature enabled below:
Expand Down Expand Up @@ -144,7 +144,7 @@ See the result below:
{@snippet features/image-style-custom}

<info-box hint>
In the example above the options used represent simple "align left" and "align right" styles. Most text editors support left, center and right alignments, however, try not to think about CKEditor 5's image styles in this way. Try to understand what use cases the system needs to support and define semantical options accordingly. Defining useful and clear styles is one of the steps towards a good user experience and clear, portable output. For example, the "side image" style can be displayed as a floated image on wide screens and as a normal image on low resolution screens.
In the example above the options used represent simple "align left" and "align right" styles. Most text editors support left, center and right alignments, however, try not to think about CKEditor 5's image styles in this way. Try to understand what use cases the system needs to support and define semantic options accordingly. Defining useful and clear styles is one of the steps towards a good user experience and clear, portable output. For example, the "side image" style can be displayed as a floated image on wide screens and as a normal image on low resolution screens.
</info-box>

### Defining custom styles
Expand Down Expand Up @@ -173,9 +173,7 @@ See the {@link features/image-upload Image upload} guide.

## Responsive images

Coming soon...

<!-- TODO 6 when finished, link here from the image-upload guide. -->
Responsive images support in CKEditor 5 is brought by the {@link features/easy-image Easy Image} feature without any additional configuration. Learn more how to use the feature in your project in the {@link features/easy-image#responsive-images "Easy Image integration"} guide.

## Installation

Expand Down Expand Up @@ -213,7 +211,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:

Expand Down
283 changes: 283 additions & 0 deletions docs/framework/guides/deep-dive/upload-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
---
category: framework-deep-dive
menu-title: Custom upload adapter
---

# Custom image upload adapter

<info-box>
Check out the comprehensive {@link features/image-upload Image upload overview} to learn about other ways to upload images into CKEditor 5.
</info-box>

## How does the image upload work?

Before you can implement your own custom upload adapter, you should learn about the image upload process in CKEditor 5. The whole process boils down to the following steps:

1. First, an image (or images) need to get into the rich-text editor content. There are many ways to do that, for instance:

* pasting an image from clipboard,
* dragging a file from the file system,
* selecting an image through a file system dialog.

The images are intercepted by the {@link module:image/imageupload~ImageUpload image upload} plugin (which is enabled in all official {@link builds/guides/overview editor builds}).
2. For every image, the image upload plugin {@link module:upload/filerepository~FileRepository#createLoader creates an instance of a file loader}.

* The role of the **file loader** is to read the file from the disk and upload it to the server by using the upload adapter.
* The role of the **upload adapter** is, therefore, to securely send the file to the server and pass the response from the server (e.g. the URL to the saved file) back to the file loader (or handle an error, if there was one).

3. While the images are being uploaded, the image upload plugin:

* Creates placeholders of these images.
* Inserts them into the editor.
* Displays the progress bar for each of them.
* When an image is deleted from the editor content before the upload finishes, it aborts the upload process.

4. Once the file is uploaded, the upload adapter notifies the editor about this fact by resolving its `Promise`. It passes the URL (or URLs in case of responsive images) to the image upload plugin which replaces the `src` and `srcset` attributes of the image placeholder in the editor content.

This is just an overview of the image upload process. The truth is, the whole thing is more complicated. For instance, images can be copied and pasted within the WYSIWYG editor (while the upload takes place) and all potential upload errors must be handled, too. The good news is these tasks are handled transparently by the {@link module:image/imageupload~ImageUpload image upload} plugin so you do not have to worry about them.

To sum up, for the image upload to work in the rich-text editor, two conditions must be true:

* **The {@link module:image/imageupload~ImageUpload image upload} plugin must be enabled** in the editor. It is enabled by default in all official {@link builds/guides/overview builds}, but if you are {@link builds/guides/development/custom-builds customizing} CKEditor 5, do not forget to include it.
* **The upload adapter needs to be defined**. This can be done by using (enabling *and* configuring):

* {@link features/image-upload#official-upload-adapters One of the existing upload adapters}.
* [Your custom upload adapter](#implementing-a-custom-upload-adapter) and handling uploaded files on your server back–end.

## Implementing a custom upload adapter

In this guide you are going to implement and enable a custom upload adapter that will allow you to take the **full control** over the process of sending the files to the server as well as passing the response from the server (e.g. the URL to the saved file) back to the rich-text editor.

### The anatomy of the adapter

Define the `MyUploadAdapter` class and fill its internals step–by–step. The adapter will use the native `XMLHttpRequest` to send files returned by the loader to a pre–configured URL on the server, handling `error`, `abort`, `load`, and `progress` events fired by the request.

```js
class MyUploadAdapter {
constructor( loader, url ) {
// The FileLoader instance to use during the upload. It sounds scary but do not
// worry — the loader will be passed into the adapter later on in this guide.
this.loader = loader;

// The upload URL in your server back-end. This is the address the XMLHttpRequest
// will send the image data to.
this.url = url;
}

// ...
}
```

Your custom upload adapter must implement the {@link module:upload/filerepository~UploadAdapter `UploadAdapter` interface} in order to work, i.e. it must bring its own `upload()` and `abort()` methods.

* The {@link module:upload/filerepository~UploadAdapter#upload `upload()`} method must return a promise:
* resolved by a successful image upload (with an object containing information about the uploaded file),
* rejected because of an error, in which case no image is inserted into the content.
* The {@link module:upload/filerepository~UploadAdapter#abort `abort()`} method must allow the editor to abort the upload process. It is necessary, for instance, when the image was removed from the content by the user before the upload finished.

```js
class MyUploadAdapter {
constructor( loader, url ) {
// ...
}

// Starts the upload process.
upload() {
return new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject );
this._sendRequest();
} );
}

// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}

// ...
}
```

### Using `XMLHttpRequest` in an adapter

Let's see what the `_initRequest()` method looks like in your custom upload adapter. It should prepare the `XMLHttpRequest` object before it can be used to upload an image.

```js
class MyUploadAdapter {
constructor( loader, url ) {
// ...
}

upload() {
// ...
}

abort() {
// ...
}

// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();

// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// the POST request with JSON as a data structure but your configuration
// could be different.
xhr.open( 'POST', this.url, true );
xhr.responseType = 'json';
}
}
```

Now let's focus on the `_initListeners()` method which attaches the `error`, `abort`, `load`, and `progress` event listeners to the `XMLHttpRequest` object created in the last step.

A successful image upload will finish when the upload promise is resolved upon the `load` event fired by the `XMLHttpRequest` request. The promise must be resolved with an object containing information about the image. See the documentation of the {@link module:upload/filerepository~UploadAdapter#upload `upload()`} method to learn more.

```js
class MyUploadAdapter {
constructor( loader, url ) {
// ...
}

upload() {
// ...
}

abort() {
// ...
}

_initRequest() {
// ...
}

// Initializes XMLHttpRequest listeners.
_initListeners( resolve, reject ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = 'Couldn\'t upload file:' + ` ${ loader.file.name }.`;

xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;

// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}

// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve( {
default: response.url
} );
} );

// Upload progress when it is supported. The FileLoader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
}
```

Last but not least, the `_sendRequest()` method sends the `XMLHttpRequest`. In this example, the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) interface is used to pass the file provided by the {@link module:upload/filerepository~FileRepository#createLoader file loader}.

```js
class MyUploadAdapter {
constructor( loader, url ) {
// ...
}

upload() {
// ...
}

abort() {
// ...
}

_initRequest() {
// ...
}

_initListeners( resolve, reject ) {
// ...
}

// Prepares the data and sends the request.
_sendRequest() {
// Prepare the form data.
const data = new FormData();
data.append( 'upload', this.loader.file );

// Send the request.
this.xhr.send( data );
}
}
```

### Activating a custom upload adapter

Having implemented the adapter, you must figure out how to enable it in the editor. The good news is that it is pretty easy, and you do not need to {@link builds/guides/development/custom-builds rebuild the editor} to do that!

Crate a simple standalone plugin (`MyCustomUploadAdapterPlugin`) that will {@link module:upload/filerepository~FileRepository#createLoader create an instance of the file loader} and glue it with your custom `MyUploadAdapter`.

```js
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

class MyUploadAdapter {
constructor( loader, url ) {
// ...
}

// ...
}

function MyCustomUploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
// Configure the URL to the upload script in your back-end here!
return new MyUploadAdapter( loader, 'http://example.com/image/upload/path' );
};
}
```

Enable the `MyCustomUploadAdapterPlugin` in the editor by using the {@link module:core/editor/editorconfig~EditorConfig#extraPlugins `config.extraPlugins`} option:

```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
extraPlugins: [ MyCustomUploadAdapterPlugin ],

// ...
} )
.catch( error => {
console.log( error );
} );
```

Run the editor and see if your implementation works. Drop an image into the WYSIWYG editor content and it should be uploaded to the server thanks to the `MyUploadAdapter`.

## What's next?

Check out the comprehensive {@link features/image-upload Image upload overview} to learn more about different ways of uploading images in CKEditor 5. See the {@link features/image Image feature} guide to find out more about handling images in CKEditor 5.

0 comments on commit d153d01

Please sign in to comment.