zenoamaro / react-quill

A Quill component for React.
https://zenoamaro.github.io/react-quill
MIT License
6.61k stars 912 forks source link

Custom image handler #743

Open sonaimenext opened 2 years ago

sonaimenext commented 2 years ago

Ticket due diligence

ReactQuill version

FAQ

Is this a bug in Quill or ReactQuill?

Bugs

When I use imageHandler, ReactQuill's content show and disappeared immediately.

My code


const imageHandler = async () => {
        const input = document.createElement('input');

        input.setAttribute('type', 'file');
        input.setAttribute('accept', 'image/*');
        input.click();
        input.onchange = async () => {
            var file: any = input && input.files ? input.files[0] : null;
            var formData = new FormData();
            formData.append("file", file);
            let quillObj = quillRef.current.getEditor();
            await UploadService.uploadFile(formData)
                .then((res) => {
                    let data = get(res, "data.data.url");
                    const range = quillObj.getEditorSelection();
                    quillObj.getEditor().insertEmbed(range.index, 'image', data);
                })
                .catch((err) => {
                    message.error("This is an error message");
                    return false;
                });
        };
    }

const modules = {
        toolbar: {
            container: [
                [{ 'header': [1,2,3,4,5,6,false] }, { 'font': [] }],
                [{ size: [] }],
                ['bold', 'italic', 'underline', 'strike', 'blockquote'],
                [{ 'list': 'ordered' }, { 'list': 'bullet' },
                { 'indent': '-1' }, { 'indent': '+1' }],
                ['link', 'image', 'video'],
                ['clean'],
                [{ 'align': [] }],
            ],
            'handlers': {
               image: imageHandler
            }
        },
        clipboard: {
            matchVisual: false,
        },
        imageResize: {
            parchment: Quill.import('parchment'),
            modules: ['Resize', 'DisplaySize']
        }
    }

<ReactQuill
    ref={quillRef}
    value={val}
    onChange={handleChange}
    modules={modules}
    formats={formats}
    // tag="textarea"
    theme="snow"
    style={{
        height: '500px'
    }}
/>
mubashirjamali101 commented 2 years ago

Thank You πŸ±β€πŸβ€

thanks for the code! it helped me a lot.

Notes!

in your image handler, I wrote that code in mine and when did some console logs, I saw that there were no methods like getEditorSelection() and getEditor() later I found that instead of these methods, there are two others that exist and do the same job: for getting range/selection:

const range = quillObj.getSelection();

for inserting an embed:

quillObj.editor.insertEmbed(range.index, 'image', data);

after this the imageHandler

const imageHandler = async () => {
        const input = document.createElement('input');

        input.setAttribute('type', 'file');
        input.setAttribute('accept', 'image/*');
        input.click();
        input.onchange = async () => {
            var file: any = input && input.files ? input.files[0] : null;
            var formData = new FormData();
            formData.append("file", file);
            let quillObj = quillRef.current.getEditor();
            await UploadService.uploadFile(formData)
                .then((res) => {
                    let data = get(res, "data.data.url");
                    const range = quillObj.getSelection();
                    quillObj.editor.insertEmbed(range.index, 'image', data);
                })
                .catch((err) => {
                    message.error("This is an error message");
                    return false;
                });
        };
    }
codigoisaac commented 2 years ago

When I add

'handlers': {
  image: imageHandler
}

I get the warning: addRange(): The given range isn't in document.

Any help would be cool.

mdbaniani commented 1 year ago

addRange(): The given range isn't in document.

try this

brodwen83 commented 1 year ago

if anyone still having an error accessing the Editor's getSelection function, here's the solution I have found. useRef does not work in dynamic import in Next.js. here's the solution:

const QuillNoSSRWrapper = dynamic(
  async () => {
    const { default: RQ } = await import('react-quill');
    // eslint-disable-next-line react/display-name
    return ({ forwardedRef, ...props }) => <RQ ref={forwardedRef} {...props} />;
  },
  { ssr: false }
);

handler

const quillImageCallback = async () => {
    const input = document.createElement('input');
    input.setAttribute('type', 'file');
    input.setAttribute('accept', 'image/*');
    input.click();

    input.onchange = async () => {
      const file = input.files ? input.files[0] : null;
      let data = null;
      const formData = new FormData();

      const quillObj = quillRef?.current?.getEditor();
      const range = quillObj?.getSelection();

      if (file) {
        formData.append('file', file);
        formData.append('resource_type', 'raw');

        const responseUpload = await fetch(
          `${process.env.NEXT_PUBLIC_IMAGE_UPLOAD}/upload`,
          { method: 'POST', body: formData }
        );

        data = await responseUpload.json();
        if (data.error) {
          console.error(data.error);
        }

        quillObj.editor.insertEmbed(range.index, 'image', data?.secure_url);
      }
    };
  };
codecret commented 1 year ago
const imageHandler = async () => {
        const input = document.createElement('input');

        input.setAttribute('type', 'file');
        input.setAttribute('accept', 'image/*');
        input.click();
        input.onchange = async () => {
            var file: any = input && input.files ? input.files[0] : null;
            var formData = new FormData();
            formData.append("file", file);
            let quillObj = quillRef.current.getEditor();
            await UploadService.uploadFile(formData)
                .then((res) => {
                    let data = get(res, "data.data.url");
                    const range = quillObj.getEditorSelection();
                    quillObj.getEditor().insertEmbed(range.index, 'image', data);
                })
                .catch((err) => {
                    message.error("This is an error message");
                    return false;
                });
        };
    }

can you write all the code please where is modules how can we use this thing

dhboys commented 1 year ago

Hello, I've been trying to upload image with react-quill & nextJS. Actually, I try a lot just what you do, but not working. if you let me know what is the problem, I will very thanks to you.

const QuillWrapper = ({ theme, id, value, onChange, readonly }) => { const ref = useRef(); const quillRef = useRef(); const quill = quillRef.current; const [uploadLoading, setUploadLoading] = useState(false); const imageHandler = useCallback(() => { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('multiple', 'multiple'); input.setAttribute('accept', 'image/*'); input.click();

input.onchange = async () => {
  const files = input.files;
  const fileDatas = [];
  const fileFormData = new FormData();
  let checkEmptyFormData = true;
  [].forEach.call(files, (f) => {
    if (f.type.indexOf('image') < 0) {
      NotificationManager.warning('이미지 파일만 등둝 κ°€λŠ₯ν•©λ‹ˆλ‹€.', '', 2000);
      setUploadLoading(false);
      return;
    } else if (f.size > 19999999) {
      NotificationManager.warning('20MB μ΄ν•˜μ˜ 파일만 등둝 κ°€λŠ₯ν•©λ‹ˆλ‹€.', '', 2000);
      setUploadLoading(false);
      return;
    } else {
      fileFormData.append('file', f);
      fileFormData.append('type', 'image');
      checkEmptyFormData = false;
    }
  });
  if (!checkEmptyFormData) {
    try{
      await uploadFileMulti(fileFormData)
      .then((result) => {
        result?.files?.map((v) => {
          fileDatas.push(v);
          fileDatas.map((file) => {
            const range = quillRef.current?.getEditor().getSelection();
            let quill = quillRef.current?.getEditor();
            if (range.index !== null && range.index !== undefined) {
              quill.editor.insertEmbed(range.index, 'image', `${process.env.NEXT_PUBLIC_BACKURL}${file?.file_path}`)
              quill.setSelection(range.index + 1);
            }
          })
        });
      })
    } catch(error) {
      console.error(error)
    }

  }
};

}, [quillRef]);

let toolbarOptions = [[{ size: ['small', false, 'large', 'huge'] }], ['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }], ['image']];

const modules = useMemo(() => ({ toolbar: { container: toolbarOptions, handlers: { image: imageHandler } }, }), []);

const formats = ['header', 'font', 'size', 'bold', 'italic', 'underline', 'strike', 'align', 'blockquote', 'list', 'bullet', 'indent', 'background', 'color', 'link', 'width'];

useEffect(() => { ref.current.setAttribute('spellcheck', false); }, []); };

AlexXi19 commented 1 year ago

I ran into the exact issue and was able to fix this by doing:

 const quillRef = useRef(null)

  useEffect(() => {
    // @ts-ignore
    quillRef.current
      .getEditor()
      .getModule('toolbar')
      .addHandler('image', () => {
        const input = document.createElement('input')
        input.setAttribute('type', 'file')
        input.setAttribute('accept', 'image/*')
        input.click()
        input.onchange = async () => {
          if (!input.files || !input?.files?.length || !input?.files?.[0])
            return
          // @ts-ignore
          const editor = quillRef?.current?.getEditor()
          const file = input.files[0]

           // Do what you want with the file 

          const range = editor.getSelection(true)
          editor.insertEmbed(
            range.index,
            'image',
            '<image-url>'
          )
        }
      })
  }, [quillRef])

  return (
    <ReactQuill
      ref={quillRef}
      ...
AlfainCoder commented 1 year ago

Hello Coding Guru's! I got stuck in this from the past week. Get me out of it Please.

i wanna impliment custom image handling in react-quill. my whole toolbar just got disappeared after a small change in the modules handlers.

Hope you Got the solution..........................
@sonaimenext @mubashirjamali101 @mdbaniani @brodwen83 @codigoisaac @me

Sharing is caring. Mail : usman@alfain.tech

Love you.

AlfainCoder commented 1 year ago

BOOOOOOOM

Got the solution, keep the modules and formats out of the component mean after imports , register the quill image uploader then place the modules and formats. below is my code also, `import React from "react"; import ReactQuill, { Quill } from "react-quill"; import "react-quill/dist/quill.snow.css";

// #1 import quill-image-uploader import ImageUploader from "quill-image-uploader";

import "quill-image-uploader/dist/quill.imageUploader.min.css";

Quill.register("modules/imageUploader", ImageUploader);

const modules = { toolbar: { container: [ ["bold", "italic", "underline", "strike"], [{ list: "ordered" }, { list: "bullet" }], ["blockquote", "code-block"], [{ image: "customImageHandler" }], ["link"], [{ color: [] }, { background: [] }], [{ indent: "-1" }, { indent: "+1" }], [{ align: [] }], [{ header: [1, 2, 3, 4, 5, 6, false] }], ["clean"], ], }, imageUploader: { upload: (file) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve( "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/JavaScript-logo.png/480px-JavaScript-logo.png" ); }, 3500); }); }, }, };

const formats = [ "header", "bold", "italic", "underline", "strike", "blockquote", "list", "bullet", "indent", "link", "image", "imageBlot", ];

function RichTextFeild({ options, setOptions, showQuestion }) { return ( <> <ReactQuill theme="snow" modules={modules} formats={formats} value={options} onChange={setOptions} /> </> ); }

export default RichTextFeild; `

you can write image uploader according to your needs and wants. buddy, hurrah. 😍😍

AbdulMutaal commented 1 year ago

BOOOOOOOM

Got the solution, keep the modules and formats out of the component mean after imports , register the quill image uploader then place the modules and formats. below is my code also, `import React from "react"; import ReactQuill, { Quill } from "react-quill"; import "react-quill/dist/quill.snow.css";

// #1 import quill-image-uploader import ImageUploader from "quill-image-uploader";

import "quill-image-uploader/dist/quill.imageUploader.min.css";

Quill.register("modules/imageUploader", ImageUploader);

const modules = { toolbar: { container: [ ["bold", "italic", "underline", "strike"], [{ list: "ordered" }, { list: "bullet" }], ["blockquote", "code-block"], [{ image: "customImageHandler" }], ["link"], [{ color: [] }, { background: [] }], [{ indent: "-1" }, { indent: "+1" }], [{ align: [] }], [{ header: [1, 2, 3, 4, 5, 6, false] }], ["clean"], ], }, imageUploader: { upload: (file) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve( "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/JavaScript-logo.png/480px-JavaScript-logo.png" ); }, 3500); }); }, }, };

const formats = [ "header", "bold", "italic", "underline", "strike", "blockquote", "list", "bullet", "indent", "link", "image", "imageBlot", ];

function RichTextFeild({ options, setOptions, showQuestion }) { return ( <> </> ); }

export default RichTextFeild; `

you can write image uploader according to your needs and wants. buddy, hurrah. 😍😍

Nice solution. But doesn't work for typescript

mubashirjamali101 commented 1 year ago

Note: THIS IS A TRICK NOT A COMPLETE SOLUTION, THIS DOES NOT USE IMAGE HANDLER, BASED SOLELY ON TEXT VALUE PROVIDED BY THE EDITOR INSTANCE.

Hi all,

pretty late to the discussion. I wanted to process the images my way too. So I didn't do it the perfect way, the registering a custom-handler way. Instead, what I did was, I got the text (HTML) from the editor, did a bit of text processing and got the image tag with Base64 as its source, got that source pop-out from it and made a blob from it (whole thing in vanilla javascript).

That's how I got it done. Tell me if anyone wants more detail on that.

MY CASE WAS DIFFERENT: In my case, client didn't want to let the image remain at it's place, instead they wanted to pop it out, save it separately and show it to the end-user separately.

nmaddp1995 commented 9 months ago

Hi @sonaimenext , I see that you have a ImageResize module, could you share with me about that part of code, I'm working with resize module and all I found is I have to install another 3rd-party package. And those aren't written by the author of quill or react-quill

maykon-oliveira commented 6 months ago

When I add

'handlers': {
  image: imageHandler
}

I get the warning: addRange(): The given range isn't in document.

Any help would be cool.

Wrap your imageHandler function in an useCallback.

Kartik0899 commented 3 weeks ago

Hi all, I am working with react-quill and my image handler is not working below is my code --

import dynamic from "next/dynamic";
import "react-quill/dist/quill.snow.css";

const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });

const handleEditorImageUpload = async () => {
    const input = document.createElement("input");
    input.setAttribute("type", "file");
    input.setAttribute("accept", "image/*");
    input.click();
    input.onchange = async () => {
      const file = input.files[0];
      const uploadedImage = await uploadImage(file);
      if (uploadedImage) {
        let quill = quillRef.current.getEditor();
        const range = quill.getSelection();
        quill.insertEmbed(range.index, "image", uploadedImage.url);
      }
    };
  };

  let toolbarOptions = [
    [{ header: [1, 2, 3, 4, 5, 6] }, { font: [] }],
    [{ list: "ordered" }, { list: "bullet" }],
    ["bold", "italic", "underline", "strike"],
    ["image"],
  ];

  const modules = useMemo(
    () => ({
      toolbar: {
        container: toolbarOptions,
        handlers: { image: handleEditorImageUpload },
      },
    }),
    []
  );

  <ReactQuill
                ref={quillRef}
                value={body}
                onChange={setBody}
                modules={modules}
                placeholder="Write your blog content here..."
              />

It is giving me an error of getEditor Error - TypeError: quillRef.current.getEditor is not a function

Any help would be much appreciated!