How to implement custom image uploader for CKEditor5 in Angular 5?
Asked Answered
T

3

6

I am looking for an example showing an implementation of custom image uploader for CKEditor 5 using Angular 5.

Trackandfield answered 10/12, 2018 at 11:47 Comment(0)
H
5

I got this to work with relatively little fuss. It does not require rebuilding CKEditor. Here are the basic steps:

In your component, define an instance variable with a CKEditor configuration object if you have not already done so. It needs to have an extraPlugins element in it:

  ckconfig = {
    // include any other configuration you want
    extraPlugins: [ this.TheUploadAdapterPlugin ]
  };

Step two create the referenced function in your component that looks like this:

  TheUploadAdapterPlugin(editor) {
    console.log('TheUploadAdapterPlugin called');
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
      return new UploadAdapter(loader, '/image');
    };
  }

Step three create the new class that was referenced in the above function. You can do this in the same Typescript file after the component definition or you can make it in a separate file and import it.

class UploadAdapter {
  loader;  // your adapter communicates to CKEditor through this
  url;
  constructor(loader, url) {
    this.loader = loader;   
    this.url = url;
    console.log('Upload Adapter Constructor', this.loader, this.url);
  }

  upload() {
    return new Promise((resolve, reject) => {
      console.log('UploadAdapter upload called', this.loader, this.url);
      console.log('the file we got was', this.loader.file);
      resolve({ default: 'http://localhost:8080/image/1359' });
    });
  }

  abort() {
    console.log('UploadAdapter abort');
  }

At the quick testing stage change the resolve call to return a URL that points to some fixed image. It can be anything. That URL string will be stuck into the editor content where user places the image.

When you go to implement, you will delete or change each of the console.log calls to whatever logic you want -- likely involving some calls to Angular's HttpClient. However note that the functions will execute outside of Angular's NgZone domain.

Your logic in resolve will have to generate the appropriate URL of course. Check the CKEditor documentation for a good example of how to interact with the loader.

Finally you need to make sure your ckeditor element is using the ckconfig configuration object something like this:

<ckeditor [editor]="Editor" [config]="ckconfig"
              [(ngModel)]="doc.text" name="editcontent"></ckeditor>

Now when you use this editor you will use the tool to upload an image. You should see the output on the console log and the string in the resolve call inserted into the editor's content. If the string is a valid URL to an image somewhere you will see that image.

If this works for you there should be little problem completing the plug in.

Remember the generated Promise has a reject parameter so use it as needed.

Hines answered 16/1, 2019 at 6:38 Comment(5)
Can you do the same thing with file uploading?Wootan
@Wootan I don't think CKEditor supports embedded generic files. Images are special because they are media to display as part of the document that CKEditor is editing.Hines
Can you inject a service into the uploadadapter? How do you actually upload from it?Wootan
@Wootan I don't think that will work. We instantiate the Upload adapter with a new keyword and injection is for objects that were instantiated by the Angular framework. However what you can do is inject your service in the Component that is in scope for TheUploadAdapterPlugin, then pass that as a parameter, which then passes it to the UploadAdapter.Hines
how do i add extra attribute to image? since resolve renders img src only?Palatine
A
4

There's no need for the Angular-specific version of that adapter.

You can use for example this: https://github.com/pourquoi/ckeditor5-simple-upload or try to integrate it with the CKFinder.

Then, all you need to do is to pass the configuration object to the <ckeditor [config]='config'> component. Don't forget to set allowJs: true in your tsconfig.json file to allow bundling local JS files.

Alternatively, you can create it on your own. This should be the base skeleton of the upload adapter that should match the UploadAdapter interface:

editor.plugins.get( 'FileRepository' ).createUploadAdapter = loader => {
    return new UploadAdapter( loader, editor.config.get( 'uploadUrl' ), editor.t );
}

class UploadAdapter {
    constructor( loader, url, t ) {
        this.t = t; // Translate function
        this.loader = loader;
        this.url = url; // Endpoint URL
    }

    upload() {
        // Perform uploading and return a promise to that action.
    }

    abort() {
        // Abort current upload process.
    }
}

The whole process is described more deeply in docs: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/upload-adapter.html. You can also look at the source code of https://github.com/pourquoi/ckeditor5-simple-upload/blob/master/src/adapter.js

Then, to get the editor property in the ckeditor5-angular package you need to do listen to the ready event and obtain the editor parameter:

<editor [ready]="onReady" ...></editor>
@Component()
class EditorComponent {
     public onReady( editor ) {
          // Now you can acess the editor.
     } 
}

And that's besically it.

Antonioantonius answered 10/12, 2018 at 15:24 Comment(6)
As per my understanding, this requires me to include the ckeditor5-simple-upload in the ckeditor source code, create a custom build and then use that custom build. Please correct me if it is wrong. Is there any way to implement custom image upload without custom build?Trackandfield
Yep, the above approach requires creating the custom build. I'll try to extend my answer with an example of how to write the custom one without the need of rebuilding.Antonioantonius
Thank you @Maciej. I have rebuilt the editor with simple upload. Now I am facing issues with including it in my project. import * as ClassicEditor from '../../ckeditor.js'; gives me error - '--allowJs' is not setTrackandfield
Yep, you should use allowJs: true in your tsconfig.json to allow importing JS files using relative paths. Actually, I'm not sure if removing the .js from the import path won't fix it too... I've updated the answer, maybe you'll find that way easier.Antonioantonius
@MaciejBukowski why wouldn't it work to simply plug in the new upload adapter when the editor is instantiated? Then no need for a custom build which is a pain.Hines
Adding your own plugin using editor.plugins.get( 'FileRepository' ). createUploadAdapter = ... as I've proposed above doesn't require creating any custom build.Antonioantonius
I
0

I am using the CKEditor 5 in this example with Angular 9. Building on the code from @Maciej-Bukowski I had to make one crucial change to get this to work, otherwise the plugin would not instantiate, giving error t is not a constructor .

After you have this working, replace the code with the example at CKEditor 5 custom upload adapter. FOr me this worked by just copy/pasting and changing the URL.

Simply replace the function in your component with a class:

class TheUploadAdapterPlugin {
  constructor(editor) {
    console.log('TheUploadAdapterPlugin called');
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
      return new TheUploadAdapter(loader);
    };
  }
}

Furthermore had some other small issues I had to figure out. So here is my complete code: In your component file (DialogEditContentComponent.ts or something alike)

class TheUploadAdapterPlugin {
  constructor(editor) {
    console.log('TheUploadAdapterPlugin called');
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
      return new TheUploadAdapter(loader);
    };
  }
}

export class DialogEditContentComponent implements OnInit {
  public ckEditorClassic = DecoupledEditor;
  public ckconfig = {};

  constructor() {
    this.ckconfig = {
      // include any other configuration you want
      // https://mcmap.net/q/1688623/-how-to-implement-custom-image-uploader-for-ckeditor5-in-angular-5
      extraPlugins: [ TheUploadAdapterPlugin ]
    };
  }// constructor

  public onReady( editor ) {

    editor.ui.getEditableElement().parentElement.insertBefore(
      editor.ui.view.toolbar.element,
      editor.ui.getEditableElement()
    );

  }


}// DialogEditContentComponent

then in the .HTML-template file use (in this case I use the Reactive forms and the formbuilder, hence the 'formControlName' instead of the [(NgModel)]!

      <ckeditor [editor]="ckEditorClassic"
                formControlName="HTML"
                [config]="ckconfig"
                (ready)="onReady($event)"></ckeditor>

and then the uploader itself: (filename the-upload-adapter.ts)

export class TheUploadAdapter {
  loader;  // your adapter communicates to CKEditor through this
  url;
  constructor(loader) {
    this.loader = loader;
    this.url = 'xxxxxxxxxxxxxxxx';
    console.log('Upload Adapter Constructor', this.loader, this.url);
  }

  upload() {
    return new Promise((resolve, reject) => {
      console.log('UploadAdapter upload called', this.loader, this.url);
      console.log('the file we got was', this.loader.file);
      resolve({ default: 'http://localhost:8080/image/1359' });
    });
  }

  abort() {
    console.log('UploadAdapter abort');
  }
}
Intransitive answered 17/10, 2020 at 19:35 Comment(1)
I'm having an issue. The image URL is not getting set in the HTML output.Legation

© 2022 - 2024 — McMap. All rights reserved.