quill icon indicating copy to clipboard operation
quill copied to clipboard

Extending default Quill handler for image to upload base64 data.

Open cozzbie opened this issue 7 years ago • 23 comments

I am using the lib for a mobile app and I will love to be able to extend the current functionality of the image handler to grab the image data and post it before embedding it to the editor and I was wondering if this was possible and if I could get some help in implementing this.

  1. hit the image icon
  2. quill handler runs file dialogue
  3. user clicks on file
  4. file datauri is returned by quill but the I convert that to a file and upload and use the returned link instead of base64 obj.

Thanks.

cozzbie avatar Apr 11 '17 11:04 cozzbie

Yes! I am also trying to do this exact same thing. We are storing the contents in a SQL database and do not want to store the base64 data of any images.

jwstevens-ii avatar Apr 24 '17 15:04 jwstevens-ii

You can try something like this to overwrite the default image insert functionality and insert your own instead. @jwstevensii

this.editor = new Quill('#editor', {
      modules: { toolbar: "#toolbar"},
      placeholder: 'you can start here and make it a blast...',
      theme: 'snow'
});
this.editor.getModule("toolbar").addHandler("image", imgHandler);

cozzbie avatar Apr 25 '17 07:04 cozzbie

So how does this image uploading works? I just cannot find good example here. Is it possible to add button to toolbar which will open my custom gallery modal where I can select one of my uploaded images or upload new and it will return image url back to editor?

Matoo125 avatar Jun 16 '17 12:06 Matoo125

I met exactly same problem, don't know how to solve it.

Skura23 avatar Jul 17 '17 14:07 Skura23

The imgHandler in the example is a function that gets passed a parameter, you could literally rewrite it as

function(a){
 console.log(a);
 // code that uploads your image and slots the resulting url into the editor goes here...
}

Try running tests to see what gets returned by the quill editor.

cozzbie avatar Jul 17 '17 16:07 cozzbie

https://github.com/quilljs/quill/issues/863#issuecomment-240579430 This is very nice example, which helped me solved this problem. It is very easy to implement.

Matoo125 avatar Jul 17 '17 16:07 Matoo125

I finally solved the problem by using Ajax to upload image to server. Here are some links that I find useful (if you want to using ajax to implement this upload function, too). How can I upload files asynchronously? Ajax POST request via jQuery and FormData - $_POST empty on PHP Suggestions above are useful when you embed the upload function in your quill editor.

Skura23 avatar Jul 18 '17 09:07 Skura23

I solved the problem that upload image with url. This is my code, hope help you:

   const editor = new Quill('#quill-editor', {
      bounds: '#quill-editor',
      modules: {
        toolbar: this.toolbarOptions
      },
      placeholder: 'Free Write...',
      theme: 'snow'
    });

      /**
       * Step1. select local image
       *
       */
    function selectLocalImage() {
      const input = document.createElement('input');
      input.setAttribute('type', 'file');
      input.click();

      // Listen upload local image and save to server
      input.onchange = () => {
        const file = input.files[0];

        // file type is only image.
        if (/^image\//.test(file.type)) {
          saveToServer(file);
        } else {
          console.warn('You could only upload images.');
        }
      };
    }

    /**
     * Step2. save to server
     *
     * @param {File} file
     */
    function saveToServer(file: File) {
      const fd = new FormData();
      fd.append('image', file);

      const xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:3000/upload/image', true);
      xhr.onload = () => {
        if (xhr.status === 200) {
          // this is callback data: url
          const url = JSON.parse(xhr.responseText).data;
          insertToEditor(url);
        }
      };
      xhr.send(fd);
    }

    /**
     * Step3. insert image url to rich editor.
     *
     * @param {string} url
     */
    function insertToEditor(url: string) {
      // push image url to rich editor.
      const range = editor.getSelection();
      editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
    }

    // quill editor add image handler
    editor.getModule('toolbar').addHandler('image', () => {
      selectLocalImage();
    });

Quill is awesome and easy to extensible.

TaylorPzreal avatar Jul 27 '17 05:07 TaylorPzreal

@TaylorPzreal - I get the missing ) after formal parameters error on the saveToServer function, which prevent the editor from loading altogether. Any ideas?

psuplat avatar Aug 14 '17 13:08 psuplat

@TaylorPzreal thank you.

papac avatar Sep 24 '17 16:09 papac

@TaylorPzreal Don't forget to implement by ourselves the ways to manage the images uploaded.

larryisthere avatar Dec 05 '17 09:12 larryisthere

@TaylorPzreal I would add one line:

input.setAttribute('accept', 'image/*')

MarcGodard avatar Apr 05 '18 15:04 MarcGodard

are u guys talking about angular 6, iam using it in angular 6, cannot figure it out

aminnagpure avatar Sep 11 '18 01:09 aminnagpure

I solved the problem that upload image with url. This is my code, hope help you:

   const editor = new Quill('#quill-editor', {
      bounds: '#quill-editor',
      modules: {
        toolbar: this.toolbarOptions
      },
      placeholder: 'Free Write...',
      theme: 'snow'
    });

      /**
       * Step1. select local image
       *
       */
    function selectLocalImage() {
      const input = document.createElement('input');
      input.setAttribute('type', 'file');
      input.click();

      // Listen upload local image and save to server
      input.onchange = () => {
        const file = input.files[0];

        // file type is only image.
        if (/^image\//.test(file.type)) {
          saveToServer(file);
        } else {
          console.warn('You could only upload images.');
        }
      };
    }

    /**
     * Step2. save to server
     *
     * @param {File} file
     */
    function saveToServer(file: File) {
      const fd = new FormData();
      fd.append('image', file);

      const xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:3000/upload/image', true);
      xhr.onload = () => {
        if (xhr.status === 200) {
          // this is callback data: url
          const url = JSON.parse(xhr.responseText).data;
          insertToEditor(url);
        }
      };
      xhr.send(fd);
    }

    /**
     * Step3. insert image url to rich editor.
     *
     * @param {string} url
     */
    function insertToEditor(url: string) {
      // push image url to rich editor.
      const range = editor.getSelection();
      editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
    }

    // quill editor add image handler
    editor.getModule('toolbar').addHandler('image', () => {
      selectLocalImage();
    });

Quill is awesome and easy to extensible.

Your answer is very helpful, but how can I detect which image is being deleted by the user in the editor, As I want to delete the image from my server when the user deletes it

saimAbdullah avatar Nov 23 '18 05:11 saimAbdullah

const editor = new Quill('#quill-editor', { bounds: '#quill-editor', modules: { toolbar: this.toolbarOptions }, placeholder: 'Free Write...', theme: 'snow' });

  /**
   * Step1. select local image
   *
   */
function selectLocalImage() {
  const input = document.createElement('input');
  input.setAttribute('type', 'file');
  input.click();

  // Listen upload local image and save to server
  input.onchange = () => {
    const file = input.files[0];

    // file type is only image.
    if (/^image\//.test(file.type)) {
      saveToServer(file);
    } else {
      console.warn('You could only upload images.');
    }
  };
}

/**
 * Step2. save to server
 *
 * @param {File} file
 */
function saveToServer(file: File) {
  const fd = new FormData();
  fd.append('image', file);

  const xhr = new XMLHttpRequest();
  xhr.open('POST', 'http://localhost:3000/upload/image', true);
  xhr.onload = () => {
    if (xhr.status === 200) {
      // this is callback data: url
      const url = JSON.parse(xhr.responseText).data;
      insertToEditor(url);
    }
  };
  xhr.send(fd);
}

/**
 * Step3. insert image url to rich editor.
 *
 * @param {string} url
 */
function insertToEditor(url: string) {
  // push image url to rich editor.
  const range = editor.getSelection();
  editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
}

// quill editor add image handler
editor.getModule('toolbar').addHandler('image', () => {
  selectLocalImage();
});

basilmnm avatar Nov 23 '18 10:11 basilmnm

Your answer is very helpful, but how can I detect which image is being deleted by the user in the editor, As I want to delete the image from my server when the user deletes it

I am not sure why anyone haven't talked about it? If we are working on uploading images to server, we need to know how we can reverse it, I mean delete it just by backspacing the image in the editor. I have tried to read through onContentChange/text-change etc. But can't find a proper way to do it. Have anyone done it, please share your here.

raydvard avatar Dec 23 '18 19:12 raydvard

So, I have done so far is this to delete the uploaded image >

this.editorInstance.on('text-change', function(delta, oldDelta, source) {
      if (source == 'api') {
        console.log("An API call triggered this change.");
        console.log(delta);
        console.log(oldDelta);
      } else if (source == 'user') {
        console.log("A user action triggered this change."); // when a user is deleting the image from editor
        console.log(delta);
        console.log(oldDelta.ops[0].insert.image); // So you will get the link of the image
      }
});

raydvard avatar Dec 23 '18 19:12 raydvard

I solved the problem that upload image with url. This is my code, hope help you:

   const editor = new Quill('#quill-editor', {
      bounds: '#quill-editor',
      modules: {
        toolbar: this.toolbarOptions
      },
      placeholder: 'Free Write...',
      theme: 'snow'
    });

      /**
       * Step1. select local image
       *
       */
    function selectLocalImage() {
      const input = document.createElement('input');
      input.setAttribute('type', 'file');
      input.click();

      // Listen upload local image and save to server
      input.onchange = () => {
        const file = input.files[0];

        // file type is only image.
        if (/^image\//.test(file.type)) {
          saveToServer(file);
        } else {
          console.warn('You could only upload images.');
        }
      };
    }

    /**
     * Step2. save to server
     *
     * @param {File} file
     */
    function saveToServer(file: File) {
      const fd = new FormData();
      fd.append('image', file);

      const xhr = new XMLHttpRequest();
      xhr.open('POST', 'http://localhost:3000/upload/image', true);
      xhr.onload = () => {
        if (xhr.status === 200) {
          // this is callback data: url
          const url = JSON.parse(xhr.responseText).data;
          insertToEditor(url);
        }
      };
      xhr.send(fd);
    }

    /**
     * Step3. insert image url to rich editor.
     *
     * @param {string} url
     */
    function insertToEditor(url: string) {
      // push image url to rich editor.
      const range = editor.getSelection();
      editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
    }

    // quill editor add image handler
    editor.getModule('toolbar').addHandler('image', () => {
      selectLocalImage();
    });

Quill is awesome and easy to extensible.

I am getting a missing ) error at saveToServer function, Kindly help I am in trouble figuring out, I am using javascript

eat-code-repeat avatar Dec 28 '18 18:12 eat-code-repeat

@TaylorPzreal - I get the missing ) after formal parameters error on the saveToServer function, which prevent the editor from loading altogether. Any ideas?

Same here, do you get the solution if so then please share it with me..

eat-code-repeat avatar Dec 28 '18 18:12 eat-code-repeat

Hi, I took the help from this discussion but I am facing an issue after uploading the image, I need to press enter event. Then only, image Url is being inserted into the editor content. Is there a way to do it while uploading the image?

Utkarsh3587 avatar Mar 28 '19 14:03 Utkarsh3587

@Utkarsh3587 Yes, I am having this same issue, it's a really annoying issue for content creators, is there any solution to that ? Please help needed from devs.

After inserting embed, somehow the selection should be at the end of the inserted thumbnail image to insert the url link in behind.

raydvard avatar Mar 30 '19 05:03 raydvard

@TaylorPzreal - I get the missing ) after formal parameters error on the saveToServer function, which prevent the editor from loading altogether. Any ideas?

Same here, do you get the solution if so then please share it with me..

I tried this code on the quill Interactive Playground just to check for invalid syntax, and I found that some function arguments have an invalid type specification. On the definition of the function "saveToServer" and "insertToEditor" try removing the argument type (": string", ": File"). After doing this I had no errors showing up on console and the editor showed up. Actually, it gave me a different error, but this is still invalid js syntax

ghost avatar May 07 '19 14:05 ghost

@TaylorPzreal Its so useful Lots of thanks

kevin0616 avatar Jul 20 '22 03:07 kevin0616

For those interested in selecting and uploading multiple images at once (modified the code provided by @TaylorPzreal):


  function selectLocalImage() {
    const input = document.createElement('input');
    input.setAttribute('type', 'file');
    input.setAttribute('multiple', 'multiple');
    input.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/webp')
    input.click();
  
    // Listen upload local image and save to server
    input.onchange = () => {
      const fileList = Array.from(input.files);
      saveToServer(fileList);
    }
  }
    
  function saveToServer(files) {
    const formData = new FormData();
    files.forEach(file => formData.append('images[]', file));

    const xhr = new XMLHttpRequest();
    xhr.open('POST', '//' + window.location.host + '/upload/image', true);
    xhr.onload = () => {
      if (xhr.status === 200) {
        const data = JSON.parse(xhr.responseText).data;
        data.forEach(url => insertToEditor(url));
      }
    };
    xhr.send(formData);
  }
  
  function insertToEditor(url) {
    // push image url to rich editor.
    const range = quill.getSelection();
    quill.insertEmbed(range.index, 'image', url);
  }  

Backend should return image urls like this:

{"data":["https://cdn.example.org/793de663-e648-462d-bd80-5697c920c63f.png"]}

sebastianwestberg avatar Feb 22 '23 19:02 sebastianwestberg

Complete working Quill Editor with Uploading Image

import PropTypes from "prop-types";
import axios from "axios";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import Quill from "quill";

// eslint-disable-next-line
class CustomToolbar extends React.Component {
  constructor() {
    super();
    this.imageHandler = this.imageHandler.bind(this);
  }

  imageHandler() {
    const input = document.createElement("input");
    input.setAttribute("type", "file");
    input.setAttribute("accept", "image/*");
    input.click();
    // eslint-disable-next-line
    // eslint-disable-next-line
    input.onchange = async function () {
      const file = input.files[0];
      const fd = new FormData();
      fd.append("file", file);
      const response = await axios.post(
        `${process.env.REACT_APP_API_URL}/upload`,
        fd
      );
// return object with url property from server
      const { url } = response.data;
      const range = this.quill.getSelection();
      this.quill.insertEmbed(range.index, "image", url);
    }.bind(this);
  }

  render() {
    return (
      <div id="toolbar">
        <select className="ql-header" defaultValue="">
          <option value="1" />
          <option value="2" />
          <option value="" />
        </select>
        {/*     eslint-disable-next-line       */}
        <button className="ql-bold" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-italic" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-underline" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-strike" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-blockquote" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-code" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-align" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-link" />
        {/*     eslint-disable-next-line       */}
        <select className="ql-color" />
        {/*     eslint-disable-next-line       */}
        <select className="ql-background" />
        {/*     eslint-disable-next-line       */}
        <button className="ql-image" onClick={this.imageHandler} />
      </div>
    );
  }
}
// eslint-disable-next-line
Quill.register("modules/imageUploader", function (quill, options) {
  // eslint-disable-next-line
  quill.getModule("toolbar").addHandler("image", function () {
    const input = document.createElement("input");
    input.setAttribute("type", "file");
    input.setAttribute("accept", "image/*");
    input.click();
    // eslint-disable-next-line
    input.onchange = async function () {
      const file = input.files[0];
      const fd = new FormData();
      fd.append("file", file);
      const response = await axios.post(
        `${process.env.REACT_APP_API_URL}/upload`,
        fd
      );
      const { url } = response.data;
      const range = quill.getSelection();
      quill.insertEmbed(range.index, "image", url);
    };
  });
});

QuillEditor.propTypes = {
  id: PropTypes.string,
  value: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  error: PropTypes.bool,
};
// eslint-disable-next-line
export default function QuillEditor({
  id,
  error,
  value,
  onChange,
  simple = false,
  sx,
  ...other
}) {
  return (
    <>
      <CustomToolbar />
      <ReactQuill
        value={value}
        onChange={onChange}
        modules={{
          toolbar: {
            container: "#toolbar",
          },
          imageUploader: {},
        }}
        placeholder="Write something awesome..."
        theme="snow"
        {...other}
      />
    </>
  );
} 

dotsimplify avatar May 28 '23 07:05 dotsimplify

Quill 2.0 has been released (announcement post) with many changes and fixes. If this is still an issue please create a new issue after reviewing our updated Contributing guide :pray:

quill-bot avatar Apr 17 '24 11:04 quill-bot