reactjstiptap

How to upload inserted images with Image extension in tiptap?


i hope you are well. I am working on a medium like text editor and i am using tiptap for that and im implementing it with react. One thing that im stuck in that is uploading images. I want to implement upload image using Image extension or anything else like this:

  1. When a user inserts an image, i want to insert it with a base64 src.
  2. Then, it starts uploading that image and user can do anything while that image is being uploaded.
  3. After that the image was uploaded, it changes the base64 image to the uploaded image url.

I want to achive something like this system but i couldn't get close to the solution. I searched about it and i found this gist on github but it didnt help me to create such an extension with upload feature. THE LINK OF THE GITHUB GIST

So i need your help to achive this. thanks for helping.


Solution

  • I finaly found the solution for this. As i am using tiptap with react, and i wanted to let users write caption for images, i decided to use node view feature of tiptap extensions. I added addNodeView to tiptap Image extension to render my component(you need to extend the Image extension. you can read about extending a tiptap extension here):

    addNodeView() {
      return ReactNodeViewRenderer(ImageNode);
    }
    

    You can read about creating and rendering node views in tiptap by using vue, react, vanilla js or etc in tiptap docs here

    In the next step, i added uploadImageHandler to existing attributes of Image extension. Like this:

    addAttributes() {
      return {
        ...this.parent(),
        uploadImageHandler: { default: undefined },
      };
    }
    

    So when you want to add image with editor.commands.setImage add your upload image handler function to it. And make sure to not add uploadImageHandler to the HTML attributes of the rendering element. So to do this, you can do something like this(the code might be a bit different for you because i render HTML related to caption):

      renderHTML({ HTMLAttributes }) {
        return [
          'figure',
          [
            'img',
            //This line removed uploadImageHandler from attributes
            mergeAttributes(HTMLAttributes, { uploadImageHandler: undefined }),
          ],
          ['figcaption', HTMLAttributes.caption],
        ];
      }
    

    There will be a node prop in the props of the rendering component and you can access to the attributes like this:

    const { src, alt, caption, uploadImageHandler } = node.attrs;
    

    Then i added a useEffect to start uploading after the component is rendered:

      useEffect(() => {
        //src can be a base64 string. Only upload the image when the src is base64
        if (uploadImageHandler && src?.startsWith('data') && !isUploading) {
          setIsUploading(true);
          uploadImageHandler().then(imgUrl => {
            //you can use updateAttributes from the component props
            updateAttributes({ src: imgUrl });
            setIsUploading(false);
          });
        }
      }, [uploadImageHandler, src, isUploading]);
    

    My uploadImageHandler looks like this:

      const uploadImageHandler = (imgSrc, imgType) => async () => {
        const { Image, PreSignedURL } = await getPreSignedUrl(imgType);
    
        await uploadImage({ url: PreSignedURL, image: imgSrc });
    
        return Image;
      };
    

    I pass this function to editor.commands.setImage like this:

        editor.commands.setImage({
          src: reader.result,
          uploadImageHandler: uploadImageHandler(reader.result, imageFile.type),
        });
    

    An other solution for this is to execute your uploadImageHandler in the onload event of img element. Base on what you want, you can choose between these two solutions. The onload solution is not very different with the solution i explained. I couldnt share all parts of my codes but i think you can implement the upload image feature with parts of codes that i shared and things that i explained. I hope it is useful for you.