Writing in the front

Recently there was a project that needed an editor, and the boss said that he wanted to add some extensions with a simpler framework, rather than using a heavier one like Ckeditor. After comparison, the UEditor was not maintained and the others seemed not to meet my needs, so I chose Tinymce because tinymCE is lightweight and easy to expand, and is fully open source and can be used for commercial applications.

After a preliminary look at the document, I need to have a picture uploading plug-in, but the existing plug-in does not meet my requirements. What I need is to click or drag to dialog to open the picture directory immediately. The existing plug-in adopts tab-panel mode, with many unnecessary things like pasting URL. Looking at Github, it seems that someone else’s implementation doesn’t quite fit my needs either, hence the events described in the title.

To prepare

Download generator

npm install --global yo generator-tinymce
Copy the code

Create a plug-in template with the generator

The generated directory can be the corresponding directory of the source code or you can use the directory you want to use, fill in the name of the plug-in, and then next

yo tinymce
Copy the code

Once you have created the template, you will see the corresponding script

# Hot update project
yarn start

# build project
yarn build
Copy the code

The main development directory is under SRC /main, and plugin.ts is the main plugin entry where you can write plugin content

The realization of picture upload plug-in

Declare the plug-in

To write a plugin, declare it and add it to the editor, like this:

declare const tinymce: any;

const setup = (editor, url) = > {
  editor.ui.registry.addButton('image-t', {
    icon: 'image'.tooltip: 'image-t'.onAction: () = > {
      // tslint:disable-next-line:no-console
      editor.execCommand('mceInsertContent'.false.`<p>Hello world</p>`); }}); };export default () => {
  tinymce.PluginManager.add('image-t', setup);
};
Copy the code

Copy the image-t to the tinymce/plugins directory to use the image-t plugin. For example, copy the image-t to the plugins directory. You’ll see an image tool in the toolbar, and clicking on it will insert ‘Hello World ‘into the editor.

tinymce.init({
  selector: 'textarea.tinymce'.plugins: 'image-t'.toolbar: 'image-t'});Copy the code

2. Image uploading interface realization

In the last step, we have tried to experience the pleasure of writing plugins, but this is just the beginning, it seems that I can only insert some text, it is not useful, I want to insert pictures. Think about what we usually do when we want to insert an image?

First of all, there should be a UI that prompts you to upload an image. Clicking on a button will take you to a local directory, you can select an image, and clicking OK will insert the image.

The first step is to click on the button generated in the previous step and a box will pop up with some information. Before writing this demo, take a look at the source code of the existing image plugin.

The image plug-in uses the Dialog UI component, which is used to open a pop-up box. We first look at the relevant UI components, and find that there are two modes to choose from. The Image plug-in uses the TabPanel mode, which can have multiple tabs. There’s no need to use this, so go with Panel.

In addition to the first time, the Dialog also has to configure buttons, which is required, so I left it empty.

Armed with this knowledge, we can now write a popbox with an interface. We are extending the demo above

declare const tinymce: any;

const setup = (editor, url) = > {
  editor.ui.registry.addButton('image-t', {
    icon: 'image'.tooltip: 'image-t'.onAction: () = > {
      // tslint:disable-next-line:no-console
      const dialogConfig = {
        title: 'Upload picture'.body: {
          type: 'panel'.items: []},buttons: [],}; editor.windowManager.open(dialogConfig); }}); };export default () => {
  tinymce.PluginManager.add('image-t', setup);
};
Copy the code

To preview the rest of the demo, click the image button and a blank box will pop up indicating that the first step of the modification was successful. Compile it again and put it under Tinymce /plugins.

3. Select an image and upload it

Normally, to open a local directory, we would think of input, but here we have to exclude this option first. Why? Because we are in the development of a plug-in ah, the source code must be exposed to the relevant interface.

Let’s go back to the image plugin. UploadTab has a layer of dialog configuration as follows

const makeTab = (_info: ImageDialogInfo) = > {
  const items: Dialog.BodyComponentSpec[] = [
    {
      type: 'dropzone'.name: 'fileinput',},];return {
    title: 'Upload'.name: 'upload',
    items,
  };
};
Copy the code

Dropzone = dropzone = dropzone = dropzone = dropzone = dropzone

declare const tinymce: any;

const setup = (editor, url) = > {
  editor.ui.registry.addButton('image-t', {
    icon: 'image'.tooltip: 'image-t'.onAction: () = > {
      // tslint:disable-next-line:no-console
      const dialogConfig = {
        title: 'Upload picture'.body: {
          type: 'panel'.items: [{type: 'dropzone'.name: 'fileinput',}]},buttons: [],}; editor.windowManager.open(dialogConfig); }}); };export default () => {
  tinymce.PluginManager.add('image-t', setup);
};
Copy the code

Repeat the steps above, and the dialog now has a few more prompts and a browse image button that opens the local directory and selects images.

Is that all right? Our goal is to insert an image into the edit box, so we have to upload the image first, and uploading the image requires taking the image.

In the previous Dialog document we saw some configuration items, including an onChange method that is triggered when the input value is changed. The method first parameter is dialogApi, which contains some input value related meta information, analysis will know that this is the data we want. However, uploading takes a period of time. Here, we select an image and load it, and then upload it. After uploading, we insert the image into the editor, cancel loading, close the popover, and write a simple demo

declare const tinymce: any;

const setup = (editor, url) = > {
  editor.ui.registry.addButton('image-t', {
    icon: 'image'.tooltip: 'image-t'.onAction: () = > {
      // tslint:disable-next-line:no-console
      const dialogConfig = {
        title: 'Upload picture'.body: {
          type: 'panel'.items: [{type: 'dropzone'.name: 'fileinput',}]},buttons: [].onChange(api) {
          api.block('On... '); uploadImg(api, editor); }}; editor.windowManager.open(dialogConfig); }}); };export default () => {
  tinymce.PluginManager.add('image-t', setup);
};
Copy the code

In the demo above, loading was implemented after the image was opened and uploadImg function was added to realize the uploading logic. So far, the plug-in has gradually formed, but it seems that the code is a little bloated, let’s split it

const dialog = (editor) = > {
  return {
    title: 'Upload picture'.body: {
      type: 'panel'.items: [{type: 'dropzone'.name: 'fileinput'.label: ' ',}]},buttons: [].onChange(api) {
      api.block('On... '); uploadImg(api, editor); }}; };const setup = (editor, url) = > {
  editor.ui.registry.addButton('image-t', {
    icon: 'image'.tooltip: 'image-t'.onAction: () = > {
      // tslint:disable-next-line:no-console

      constdialogConfig = dialog(editor); editor.windowManager.open(dialogConfig); }}); };export default () => {
  tinymce.PluginManager.add('image-t', setup);
};
Copy the code

It’s much more comfortable to break it down, so go on.

Upload pictures to Qiniu

To upload a resource, you first have to think of using a network request. I thought of Axios because it was compact and compatible, but realized that AXIos was wrapped in CJS and had some problems compiling, so I ended up using XHR to reduce dependencies. Image upload seven cows need to have image meta information, can be obtained through api.getData, in addition to the need for file type, can also be obtained through this method, implementation can be so

function getUrl(formdata, callback) {
  const xhr = new XMLHttpRequest();

  xhr.open("POST", <url>, true);

  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      callback(JSON.parse(xhr.response)); }}; xhr.send(formdata); }Copy the code

Through the above method we can get the url uploaded to the seven cows picture, of course, this is only a demo, specific can be achieved according to their own situation

Insert the image into the editor

So we finally got our image URL, so we can happily insert it into our editor, and combined with the previous step, our final uploadImage function could be written like this

function uploadImg(api, editor) {
  const data = api.getData();
  const image = data.fileinput[0];
  const fileExt = image.name.split('. ') [1];

  const getToken = (resp) = > {
    const token = resp.F_token;

    const formdata = new FormData();
    formdata.append('file', image);
    formdata.append('token', token);

    getUrl(formdata, (res) = > {
      const img = res.url;
      api.setData({ src: { value: res.url, meta: {}}}); editor.execCommand('mceInsertContent'.false.`<img src=${img} class="img-t" alt='img-t'/>`
      );
      api.unblock();
      api.close();
    });
  };

  getQiniuToken(fileExt, getToken);
}
Copy the code

I spent a day and a half studying this, taking notes, and plug-in development is that simple

References:

HTML rich text compiler UEditor, CKEditor, TinyMCE, HTMLArea, eWebEditor, KindEditor

TinyMCE plugin Yeoman generator

Create a plugin for TinyMCE