web-client icon indicating copy to clipboard operation
web-client copied to clipboard

Implement copy/paste image like github

Open psyray opened this issue 2 years ago • 5 comments

Hi,

According to this demo, it's possible to upload images by copy/pasting directly into the React Markdown Editor used by reconmap.

Here is the discussion on the issue : https://github.com/andrerpena/react-mde/issues/189 And the PR which add the functionality : https://github.com/andrerpena/react-mde/pull/243 There is also a drag and drop support implemented here : https://github.com/andrerpena/react-mde/pull/268

I've checked the version used in reconmap (11.5) and the version where this functionality has been implemented (10.0), it's all good.

I'm not a react developer and I don't know how to correctly implement this. I think you should be more quicker than me.

It could be a killing feature. Currently, it's a pain to put image in text

https://codesandbox.io/s/react-mde-latest-forked-f9ti5?file=/src/index.js

import * as React from "react";
import ReactMde from "react-mde";
import ReactDOM from "react-dom";
import ReactMarkdown from "react-markdown";
import "./styles.css";
import "react-mde/lib/styles/css/react-mde-all.css";

[...] REDACTED


  const save = async function* (data) {
    // Promise that waits for "time" milliseconds
    const wait = function (time) {
      return new Promise((a, r) => {
        setTimeout(() => a(), time);
      });
    };

    // Upload "data" to your server
    // Use XMLHttpRequest.send to send a FormData object containing
    // "data"
    // Check this question: https://stackoverflow.com/questions/18055422/how-to-receive-php-image-data-over-copy-n-paste-javascript-with-xmlhttprequest

    await wait(2000);
    // yields the URL that should be inserted in the markdown
    yield "https://picsum.photos/300";
    await wait(2000);

    // returns true meaning that the save was successful
    return true;
  };

  return (
    <div className="container">
      <ReactMde
        value={value}
        onChange={setValue}
        selectedTab={selectedTab}
        onTabChange={setSelectedTab}
        generateMarkdownPreview={(markdown) =>
          Promise.resolve(<ReactMarkdown source={markdown} />)
        }
        loadSuggestions={loadSuggestions}
        childProps={{
          writeButton: {
            tabIndex: -1
          }
        }}
        paste={{
          saveImage: save
        }}
      />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

psyray avatar Mar 05 '23 12:03 psyray

Here is the code to handle upload from PHP, but maybe this code is always available in reconmap as we can upload file in attachments section https://stackoverflow.com/questions/18055422/how-to-receive-php-image-data-over-copy-n-paste-javascript-with-xmlhttpreques

<?php
if( isset( $_FILES['file'] ) ) {
    $file_contents = file_get_contents( $_FILES['file']['tmp_name'] );
    header("Content-Type: " . $_FILES['file']['type'] );
    die($file_contents);
}
else {
    header("HTTP/1.1 400 Bad Request");
}
print_r($_FILES);
?>
<script>
document.onpaste = function (e) {
    var items = e.clipboardData.items;
    var files = [];
    for( var i = 0, len = items.length; i < len; ++i ) {
        var item = items[i];
        if( item.kind === "file" ) {
            submitFileForm(item.getAsFile(), "paste");
        }
    }

};

function submitFileForm(file, type) {
    var extension = file.type.match(/\/([a-z0-9]+)/i)[1].toLowerCase();
    var formData = new FormData();
    formData.append('file', file, "image_file");
    formData.append('extension', extension );
    formData.append("mimetype", file.type );
    formData.append('submission-type', type);

    var xhr = new XMLHttpRequest();
    xhr.responseType = "blob";
    xhr.open('POST', '<?php echo basename(__FILE__); ?>');
    xhr.onload = function () {
        if (xhr.status == 200) {
            var img = new Image();
            img.src = (window.URL || window.webkitURL)
                .createObjectURL( xhr.response );
            document.body.appendChild(img);
        }
    };

    xhr.send(formData);
}
</script>

psyray avatar Mar 05 '23 12:03 psyray

I have a quick look into it, the attachments API endpoint could be used

psyray avatar Mar 05 '23 12:03 psyray

The https://github.com/reconmap/web-client/blob/master/src/components/ui/forms/MarkdownEditor.js could look like this


import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
import ReactMde from 'react-mde';
import './MarkdownEditor.css';

const MarkdownEditor = ({ name: editorName, value, onChange: onFormChange }) => {

    const [selectedTab, setSelectedTab] = useState('write');

	const save = async function* (data) {
	  // Promise that waits for "time" milliseconds
	  const wait = function (time) {
	    return new Promise((a, r) => {
	      setTimeout(() => a(), time);
	    });
	  };
	
	  // Upload "data" to your server
	  // Use XMLHttpRequest.send to send a FormData object containing
	  // "data"
	  // Check this question: https://stackoverflow.com/questions/18055422/how-to-receive-php-image-data-over-copy-n-paste-javascript-with-xmlhttprequest
	
	  await wait(2000);
	  // yields the URL that should be inserted in the markdown
	  yield "https://picsum.photos/300";
	  await wait(2000);
	
	  // returns true meaning that the save was successful
	  return true;
	};

    return <ReactMde
        value={value}
        onChange={editorValue => onFormChange({ target: { name: editorName, value: editorValue } })}
        selectedTab={selectedTab}
        onTabChange={setSelectedTab}
        generateMarkdownPreview={markdown =>
            Promise.resolve(<ReactMarkdown>{markdown}</ReactMarkdown>)
        }
        paste={{
          saveImage: save
        }}
    />
}

export default MarkdownEditor;

It miss only the upload part, which could reuse https://github.com/reconmap/web-client/blob/master/src/components/attachments/Dropzone.js, precisely this part

    const onUploadButtonClick = ev => {
        const formData = new FormData();
        formData.append('parentType', parentType);
        formData.append('parentId', parentId);
        acceptedFiles.forEach(file => {
            formData.append('attachment[]', file);
        })

        let uri = '/attachments';
        if (attachmentId) {
            formData.append('attachmentId', attachmentId);
            uri = `/attachments/${attachmentId}`;
        }

        secureApiFetch(uri, {
            method: 'POST',
            body: formData
        })
            .then(() => {
                setAcceptedFiles([]);
                if (onUploadFinished) onUploadFinished();
            })
            .catch(err => console.error(err));
    }

@santiagolizardo What do you think ?

psyray avatar Mar 05 '23 13:03 psyray

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 04 '23 19:04 stale[bot]

@santiagolizardo Could you reopen this issue please and prevent the stale bot to close it Thanks

psyray avatar Apr 11 '23 14:04 psyray