react-md-editor icon indicating copy to clipboard operation
react-md-editor copied to clipboard

THIS IS NOT AN ISSUE: HACK to Paste Image directly from clipboard

Open dhenry123 opened this issue 1 year ago • 3 comments

First of all, thank you very much for your excellent work.

Prepare your server

2 routes :

  • Updload image /upload/new (formdata with the attribut "image" is used in this present case)
    • server store image
    • response (json): {url: "[url to retrive image calling getImage], alt: "image name alternative"}
  • get image /getImage/:imagename (for example)

UI

The process is as follows:

  • copy the image to the clipboard
  • focus on the markdown editor
  • paste
  • the manager tries to identify the context; if it is an image, it sends the image to the server
  • the server returns the url from which the image can be accessed from the server
  • the handler finishes by adding the context of the markdow : ![altname](url) image to the clipboard and the contents of the clipboard are pasted into the markdown editor
  • the markdown editor is refreshed and renders the image by calling the provided url
  • +++

With React, build your component

[...]

// Image upload to server
const imageUploadHandler = async (
    image: File
  ): Promise<{ alt: string; url: string } | null> => {
    if (image && image.size === 0) return null;
    const formData = new FormData();
    formData.append("image", image);
    //formData.append("what else you want", "your value");
    const response = await fetch('/upload/new', {
          method: 'POST',
          body: formData
        })
    return  (await response.json()) as { alt: string, url: string }
};

  const handlePaste = async (event: React.ClipboardEvent<HTMLDivElement>) => {
    // Access the clipboard data using event.clipboardData
    const clipboardData = event.clipboardData;
   // only if clipboard payload is file
    if (clipboardData.files.length === 1) {
      const myfile = clipboardData.files[0] as File;
     // you could perform some test: image size, type mime ....
      const url = await imageUploadHandler(myfile);
      event.preventDefault();
      if (url) {
       // change clipboard payload,
       // document execCommand is obsolete, you could replace with navigator.clipboard API but some browser
      // accept write() method only if the connexion is secure (https)...
        document.execCommand(
          "insertText",
          false,
          `![${url.alt}](${url.url})\n`
        );
      } else {
        document.execCommand(
          "insertText",
          false,
          "ERROR Image has not been stored on server"
        );
      }
    }
  };
 return (
    <div className={`MarkDownEditor`}>
      <MDEditor
       [...]
       // because the MDEditor interface is an extends of textarea props
       // you can use all the methods provided by this interface
      // e.g. add a method for dragging and dropping a file...
        onPaste={handlePaste}
       [...]
      />
    </div>
  );
[...]

dhenry123 avatar Feb 03 '24 17:02 dhenry123

This works too

 useEffect(() => {
      const handlePaste = async (event) => {
        const clipboardData = event.clipboardData || window.clipboardData;
        if (clipboardData && clipboardData.items) {
          for (const item of clipboardData.items) {
            if (item.type.indexOf('image') !== -1) {
              const blob = item.getAsFile();
              const reader = new FileReader();
              reader.onload = () => {
                const base64Data = reader.result.split(',')[1];
                // console.log('Base64 Image Data:', base64Data);
                uploadImageToAPI(base64Data, event)
              };
  
              reader.readAsDataURL(blob);
            }
          }
        } else {
          // console.log('Text Data:', clipboardData.getData('text'));
        }
      };
  
      const textInput = document.querySelector('.w-md-editor-text-input');
      if (textInput) {
        textInput.addEventListener('paste', handlePaste);
      }
  
      return () => {
        if (textInput) {
          textInput.removeEventListener('paste', handlePaste);
        }
      };
    }, []);

I also added a little loader in the toolbar :

const upload = {
    name: "upload",
    keyCommand: "upload",
    buttonProps: { "aria-label": "Insert help" },
    icon: (
          <div className="material-icons-outlined" style={{fontSize:'12px',animation:'spin 2s linear infinite',display:['none','block'][uploading]}}>cached</div>
    ),
  };

BenchBadr avatar Feb 21 '24 15:02 BenchBadr

thanks you so much, It helps me a lot

AizenSosike avatar Apr 23 '24 03:04 AizenSosike

Can't thank you enough sir! image

vprakia avatar Jul 11 '24 07:07 vprakia