JiHong88 / suneditor

Pure javascript based WYSIWYG html editor, with no dependencies.
http://suneditor.com
MIT License
1.72k stars 308 forks source link

Image upload to AWS S3 #414

Closed dgandhi1993 closed 4 years ago

dgandhi1993 commented 4 years ago

Can someone help me with getting the images uploaded to AWS S3 first and then using that link here in the editor?

@JiHong88 This can be integrated as a core offering since it is very common to have images on S3. It is available in many other editors out-of-the-box (check out Froala).

dgandhi1993 commented 4 years ago

I managed to get the uploads in the S3 bucket done through the editor, however, I am unable to add the image URL that I received into the editor through any method or function call. Can someone guide me?

MRB60 commented 4 years ago

Normally you should return a result as reply to Suneditor after upload in JSON format: {result: {"url": url, "name": "myname.jpg","size": 27861}} So without testing myself, the sequence should be

client -> server: upload blob
server -> AWS S3: upload blob/file
AWS S3 -> server: reply - successful (url)
server -> client: reply - url, name etc.

Thus you cannot set image url in Suneditor after you replied with the url result, at least not what I am aware about. I need to update this answer: I think you might upload directly from client to AWS S3 without going through web server? That would not work for me at least (when I decide to use AWS S3) since I need to store url in my DB (server side) also, so a built-in upload directly from Suneditor would not be of any use for me (unless I rethink my storage concept...but I want to keep track of what and how much client is uploading...)

dgandhi1993 commented 4 years ago

@MRB60 I am directly uploading to S3 and want to use the uploaded object key as a URL for the image. I see that in the upload image modal there is an option to add images via URLs. I just need access to that method and I think this problem is solved.

I don't save the URL at the moment since I am anyway saving the editor content in the DB.

JiHong88 commented 4 years ago

Hi, @dgandhi1993

This can be integrated as a core offering since it is very common to have images on S3. It is available in many other editors out-of-the-box (check out Froala).

Please provide me a related link. And.. problem is solved?

dgandhi1993 commented 4 years ago

@JiHong88 Please check this for an example link.

The issue is still not solved. What I am looking for is a way to add an image using a URL just like it happens in the Upload image modal, but using a method or function.

MRB60 commented 4 years ago

My two cents:

  1. This will create a dependency to AWS S3. If Amazon decides to change their interface in the future, it might not work anymore.
  2. CORS must be enabled.
  3. Less control what and how much client uploads if not done via web server.

I plan to use AWS S3 too but will, for reason 3 in particular, not upload directly to AWS S3 but go via web server (without using local file storage).

JiHong88 commented 4 years ago

For the same reason as above reply, I will not add option that depend on AWS. Maintenance will also be difficult. Or.. You can't implement it using Lambda?

dgandhi1993 commented 4 years ago

I have managed to upload the image and get the S3 URL. The only thing I need is to get support to add an image using a URL.

JiHong88 commented 4 years ago

Hmm.. What do you need other than the "imageUploadUrl" option?

dgandhi1993 commented 4 years ago

I already have a URL that has been uploaded on a server. I need to just use it and add it as an image. How do I do that?

JiHong88 commented 4 years ago

I don't understand. Are you asking how to add image with url?

dgandhi1993 commented 4 years ago

Yes. I see that it is available through the upload image modal. But no direct way to do it.

JiHong88 commented 4 years ago

Screenshot_20200625-195154_Chrome You can using direct url. Doesn't that mean this?

dgandhi1993 commented 4 years ago

Yes, precisely. But this is through the modal. I want it to be done via a function call and not a modal. Instead of the user putting the URL, I am doing it programmatically.

JiHong88 commented 4 years ago

How's that work? Tell me more detail.

dgandhi1993 commented 4 years ago

I am using the onImageUploadBefore to upload the image to S3. This is working fine. I want to use this returned image link to be used in the editor.

JiHong88 commented 4 years ago

Show me the onImageUploadBefore function.

dgandhi1993 commented 4 years ago

My onImageUploadBefore function uses the resize functionality provided in the documentation. Once the resize is done, I use the addPhoto function to upload the blob to AWS S3.

editor.onImageUploadBefore = function (files, info, core, uploadHandler) {
                        try {
                            resizeImage(files, uploadHandler)
                        } catch (err) {
                            uploadHandler(err.toString())
                        }
                    };

function resizeImage (files, uploadHandler) {
    const uploadFile = files[0];
    const img = document.createElement('img');
    const canvas = document.createElement('canvas');
    const reader = new FileReader();

    reader.onload = function (e) {
        img.src = e.target.result
        img.onload = function () {
            let ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0);

            const MAX_WIDTH = 1000;
            const MAX_HEIGHT = 1000;
            let width = img.width;
            let height = img.height;

            if (width > height) {
                if (width > MAX_WIDTH) {
                    height *= MAX_WIDTH / width;
                    width = MAX_WIDTH;
                }
            } else {
                if (height > MAX_HEIGHT) {
                    width *= MAX_HEIGHT / height;
                    height = MAX_HEIGHT;
                }
            }

            canvas.width = width;
            canvas.height = height;

            ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0, width, height);

            canvas.toBlob(async function (blob) {
                let res = await addPhoto([new File([blob], uploadFile.name)],loggedUser.institute._id,loggedUser._id);
                if(res.success){
// Need to implement the image URL logic here
                    uploadHandler();
                } else{
                    uploadHandler(res.message)
                }
            }, uploadFile.type, 1);
        }
    }

    reader.readAsDataURL(uploadFile);
}

This is the function that adds the image to S3

function addPhoto(files) {
    if (!files.length) {
      return alert("Please choose a file to upload first.");
    }
    var file = files[0];
    var photoKey =  "uploads/"+(file.name.split(".")[0])+file.name.split(".")[1];
    var upload = new AWS.S3.ManagedUpload({
      params: {
        Bucket: bucketName,
        Key: photoKey,
        Body: file,
        ACL: "public-read"
      }
    });

    var promise = upload.promise();

    promise.then(
      function(data) {
        return ({success: true,finalImageURL:"https://"+bucketName+".s3.ap-south-1.amazonaws.com/"+photoKey})

      },
      function(err) {
        return ({success: false,message:"There was an error uploading your photo"});
      }
    );
  }
JiHong88 commented 4 years ago

Don't run "uploadHandler", just call it like this:

const file = {name: "name", size:0};
const imageUrl = "imageUrl";

if (info.isUpdate) {
    core.plugins.image.update_src.call(core, imageUrl, info.element, file);
    break;
} else {
    core.plugins.image.create_image.call(core, imageUrl, info.linkValue, info.linkNewWindow, info.inputWidth, info.inputHeight, info.align, file);
}

core.closeLoading();

In the next version, I will separate the above code so that we can add images directly.

JiHong88 commented 4 years ago

You can call it like this from the next version.

if(res.success){
// Need to implement the image URL logic here
// uploadHandler();

// Call directly image register
const response = {
// The response must have a "result" array.
"result": [
    {
        "url": "/url",
        "name": "name.jpg",
        "size": "0"
    },
]}
core.plugins.image.register.call(core, info, response);
}
JiHong88 commented 4 years ago

The 2.30.6 version has been updated. Thank you. : )

dgandhi1993 commented 4 years ago

@JiHong88 There is one issue with this call. When I add an image using this approach the editor fails to resize to accommodate the newly added image.

Ideal behavior would be when an image is added the editor auto-expands to accommodate the image.

Interestingly, this does not happen with setContents. Can something be done about this?

JiHong88 commented 4 years ago

@dgandhi1993 Are you using the "iframe" option?

dgandhi1993 commented 4 years ago

No, just the img tag with the properties specified by the function provided by you.

This issue has been handled with setContents where the editor resizes based on the content provided.

On Sun, 28 Jun 2020 at 4:34 PM, JiHong.Lee notifications@github.com wrote:

@dgandhi1993 https://github.com/dgandhi1993 Are you using the "iframe" option?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/JiHong88/SunEditor/issues/414#issuecomment-650734804, or unsubscribe https://github.com/notifications/unsubscribe-auth/AMAUK4VBJA5JGDCJQ3F5FG3RY4PSLANCNFSM4OGFEM5A .

JiHong88 commented 4 years ago

The "height" property is "auto", but when adding an image with "register", does the height of the editor not adjust?

dgandhi1993 commented 4 years ago

No, it does not change. The new image is placed in the existing available height of the editor and a scroll gets added instead.

dgandhi1993 commented 4 years ago

@JiHong88 Are we fixing this?

JiHong88 commented 4 years ago

@dgandhi1993 Try again with a new version. If that doesn't work, please elaborate on the editor's options and test order.

dgandhi1993 commented 4 years ago

The new version has semi-fixed it. After digging a little deep, I found the cause to be the height property that is defined on the editor. Apparently, the element with class se-wrapper-inner se-wrapper-wysiwyg sun-editor-editable has a height set to 36px in some cases, whereas at times it is set to auto. The issue happens when it is set to 36px.

Also, might be relevant here to note that usually, my editors have a width property set to 100% but in cases where height is finite (like 36px), the width is also a fixed value instead of a percentage.

I am still not able to figure out why this is happening as both the editors have identical HTML code, editor options and methods. I am not sure how the editor is getting these fixed value height and width.

JiHong88 commented 4 years ago

36pxis default min-height value. Show me the options used.

dgandhi1993 commented 4 years ago

These are the options and functions used.

imageEditor = SUNEDITOR.create("imageContainer",{
    mode: "balloon-always",
    resizingBar: false,
    showPathLabel: false,
    display: "inline",  
    tabDisable: false,
    placeholder: "Enter the question image here",
    buttonList: [["image","removeFormat"]],
});
imageEditor.onImageUploadBefore = function (files, info, core, uploadHandler) {
    try {
        resizeImage(files, uploadHandler, info, core)
    } catch (err) {
        uploadHandler(err.toString())
    }
};
imageEditor.setContents("");

Apparently, these are the same options for an editor on a different page and there it works absolutely fine.

Is there anything to do with the HTML code? In my case, everything is same, except in the one where I see the issue, I have to set the textarea's width to 100% as it does not take the entire available space on its own.

JiHong88 commented 4 years ago

The editor looks for the style of "textarea" first, and if not, sets the size to the "offset" size of "textarea". Otherwise, specify the size yourself.

{
  width: "100%",
  height: "auto"
}

if there is no content of "textarea", there is no need to reset the editor value to "setContents". And If you want to set the initial value to "setContents", call it in the "onload" function. https://github.com/JiHong88/SunEditor/issues/426#issuecomment-652825439

dgandhi1993 commented 4 years ago

Even after manually setting these sizes on the textarea, I am facing the same issue. My editor height is stuck at 36px.

I just noticed that this editor does not extend even if I add text to it. It adds a scroll in the existing editor height.

I am sure there is a trivial mistake that I am making. Please help.

JiHong88 commented 4 years ago

imageEditor = SUNEDITOR.create("imageContainer",{ mode: "balloon-always", height: "auto", // are you using this option? resizingBar: false, showPathLabel: false, display: "inline",
tabDisable: false, placeholder: "Enter the question image here", buttonList: [["image","removeFormat"]], });

dgandhi1993 commented 4 years ago

@JiHong88 Thanks a lot! That solved it! :)

SorinGabriel02 commented 4 years ago

You can call it like this from the next version.

if(res.success){
// Need to implement the image URL logic here
// uploadHandler();

// Call directly image register
const response = {
// The response must have a "result" array.
"result": [
    {
        "url": "/url",
        "name": "name.jpg",
        "size": "0"
    },
]}
core.plugins.image.register.call(core, info, response);
}

How do I do this in ReactJS & ExpressJS ??

JiHong88 commented 4 years ago

@SorinGabriel02 "SunEditor" can be used as below from the latest version. ex

onImageUploadBefore(files, info, core, uploadHandler) {
  const response = {..};
  uploadHandler(response);
}

"suneditor-react" doesn't seem to have been updated yet. It will be available when the version is updated.

:React does not provide "core" object.

onImageUploadBefore(files, info, uploadHandler) {
  const response = {..};
  uploadHandler(response);
}
SorinGabriel02 commented 4 years ago

Mystery revealed...

Here is my code that relates to saving an image on the back-end server and replacing the image data with a link to where the image is being stored on the back-end using a ReactJS component:

// inside the ReactJS editor component:
<SunEditor setOptions={{ ...otherOptions,  imageUploadUrl: "http://localhost:5000/images/new"}} />

// inside the expressJS server:
const express = require("express");
const fileUpload = require("express-fileupload");
const cors = require("cors");

const app = express();

app.use(cors());
app.use(express.json());
app.use(fileUpload());
// tells express to look into this directory when working with /images
app.use("/images", express.static(`${__dirname}/upload/images/`));

// has to match the the value passed to **imageUploadUrl** prop
app.post("/images/new", (req, res, next) => {
  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).json({ error: "No files were uploaded." });
  }
  // permitted myme types
  const mymeTypes = ["image/png", "image/jpeg", "image/jpg"];
  // img object sent from client
  let image = req.files["file-0"];

  if (!mymeTypes.some((img) => img.mymetype === image.mymetype)) {
    return res
      .status(422)
      .json({ error: "Only .png, .jpeg or .jpg are allowed." });
  }

  image.mv(`${__dirname}/upload/images/${image.name}`, (err) => {
    if (err) return res.status(500).send(err);
    res.json({
      result: [
        {
          url: `http://localhost:5000/images/${image.name}`,
          name: image.name,
          size: image.size,
        },
      ],
    });
  });
};)
mylastore commented 3 years ago

@SorinGabriel02 "SunEditor" can be used as below from the latest version. ex

onImageUploadBefore(files, info, core, uploadHandler) {
  const response = {..};
  uploadHandler(response);
}

"suneditor-react" doesn't seem to have been updated yet. It will be available when the version is updated.

:React does not provide "core" object.

onImageUploadBefore(files, info, uploadHandler) {
  const response = {..};
  uploadHandler(response);
}

The above does not work on "suneditor-react": "^2.14.4" it still does not load the image as url from the response.

mylastore commented 3 years ago

Mystery revealed...

Here is my code that relates to saving an image on the back-end server and replacing the image data with a link to where the image is being stored on the back-end using a ReactJS component:

// inside the ReactJS editor component:
<SunEditor setOptions={{ ...otherOptions,  imageUploadUrl: "http://localhost:5000/images/new"}} />

// inside the expressJS server:
const express = require("express");
const fileUpload = require("express-fileupload");
const cors = require("cors");

const app = express();

app.use(cors());
app.use(express.json());
app.use(fileUpload());
// tells express to look into this directory when working with /images
app.use("/images", express.static(`${__dirname}/upload/images/`));

// has to match the the value passed to **imageUploadUrl** prop
app.post("/images/new", (req, res, next) => {
  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).json({ error: "No files were uploaded." });
  }
  // permitted myme types
  const mymeTypes = ["image/png", "image/jpeg", "image/jpg"];
  // img object sent from client
  let image = req.files["file-0"];

  if (!mymeTypes.some((img) => img.mymetype === image.mymetype)) {
    return res
      .status(422)
      .json({ error: "Only .png, .jpeg or .jpg are allowed." });
  }

  image.mv(`${__dirname}/upload/images/${image.name}`, (err) => {
    if (err) return res.status(500).send(err);
    res.json({
      result: [
        {
          url: `http://localhost:5000/images/${image.name}`,
          name: image.name,
          size: image.size,
        },
      ],
    });
  });
};)

It does not work for me, I am able to upload to server and getting the correct response format but on suneditor still inserts the data:image/jpeg;base64 image blob.

Correction its inserting 2 images one with data:image/jpeg;base64 and the server url one here is an image. 2images

JiHong88 commented 3 years ago

@mylastore Sorry for the late reply. Are two images created, base64 and url? Are there any events in use?

niltonxp commented 3 years ago

For Suneditor-React: https://github.com/mkhstar/suneditor-react/issues/128#issuecomment-761689780

JiHong88 commented 3 years ago

@niltonxp It would be better to add a catch as well. http://suneditor.com/sample/html/out/document-user.html#onImageUploadBefore

langston8182 commented 3 years ago

Hello, I try to change the url after the image has been uploaded to S3 like dgandhi1993 did. I use the last version of suneditor-react (2.15.2).

After I upload image to S3 with my method handleImageUploadBefore, I get the url from AWS. It works perfectly.

But when I try to change the URL in SunEditor, the image is displayed but the url contains base64 code and not my S3 url. What I'm doing wrong ?

Here is my react code : `handleImageUploadBefore = async (files, info, uploadHandler) => { const imageInfo = await toBase64(files[0]);

    if (imageInfo) {
        console.log(imageInfo)
        const mime = imageInfo.split(';')[0].split(':')[1];
        const rawImage = imageInfo.split(';')[1];
        this.props.uploadImageToS3(idArticle, mime, rawImage).then(url => {
            console.log(url)
            const response = {
                "result": [
                    {
                        "url": `/${url}`,
                        "name": "name.jpg",
                        "size": "0"
                    },
                ]
            };
            uploadHandler(response);
        })
    }
}`

and the creation of the component : <SunEditor lang="en" setOptions={{ height: 600, buttonList: buttonList.complex }} onImageUploadBefore={(files, imageInfo, uploadHandler) => this.handleImageUploadBefore(files, imageInfo, uploadHandler)} placeholder="Entrez le contenu de l'article ici..." {...input} />

Screenshot from 2021-03-05 19-55-58

Thank you for your help.

langston8182 commented 3 years ago

Hello, I try to change the url after the image has been uploaded to S3 like dgandhi1993 did. I use the last version of suneditor-react (2.15.2).

After I upload image to S3 with my method handleImageUploadBefore, I get the url from AWS. It works perfectly.

But when I try to change the URL in SunEditor, the image is displayed but the url contains base64 code and not my S3 url. What I'm doing wrong ?

Here is my react code : `handleImageUploadBefore = async (files, info, uploadHandler) => { const imageInfo = await toBase64(files[0]);

    if (imageInfo) {
        console.log(imageInfo)
        const mime = imageInfo.split(';')[0].split(':')[1];
        const rawImage = imageInfo.split(';')[1];
        this.props.uploadImageToS3(idArticle, mime, rawImage).then(url => {
            console.log(url)
            const response = {
                "result": [
                    {
                        "url": `/${url}`,
                        "name": "name.jpg",
                        "size": "0"
                    },
                ]
            };
            uploadHandler(response);
        })
    }
}`

and the creation of the component : <SunEditor lang="en" setOptions={{ height: 600, buttonList: buttonList.complex }} onImageUploadBefore={(files, imageInfo, uploadHandler) => this.handleImageUploadBefore(files, imageInfo, uploadHandler)} placeholder="Entrez le contenu de l'article ici..." {...input} />

Screenshot from 2021-03-05 19-55-58

Thank you for your help.

Ok I found the solution I have deleted '/' at the begining of the url in the result but I have another problem. When I submit, the image is displayed twice. One with base64 code in URL and the other with the correct S3 Url.

langston8182 commented 3 years ago

Hello, I try to change the url after the image has been uploaded to S3 like dgandhi1993 did. I use the last version of suneditor-react (2.15.2). After I upload image to S3 with my method handleImageUploadBefore, I get the url from AWS. It works perfectly. But when I try to change the URL in SunEditor, the image is displayed but the url contains base64 code and not my S3 url. What I'm doing wrong ? Here is my react code : `handleImageUploadBefore = async (files, info, uploadHandler) => { const imageInfo = await toBase64(files[0]);

    if (imageInfo) {
        console.log(imageInfo)
        const mime = imageInfo.split(';')[0].split(':')[1];
        const rawImage = imageInfo.split(';')[1];
        this.props.uploadImageToS3(idArticle, mime, rawImage).then(url => {
            console.log(url)
            const response = {
                "result": [
                    {
                        "url": `/${url}`,
                        "name": "name.jpg",
                        "size": "0"
                    },
                ]
            };
            uploadHandler(response);
        })
    }
}`

and the creation of the component : <SunEditor lang="en" setOptions={{ height: 600, buttonList: buttonList.complex }} onImageUploadBefore={(files, imageInfo, uploadHandler) => this.handleImageUploadBefore(files, imageInfo, uploadHandler)} placeholder="Entrez le contenu de l'article ici..." {...input} /> Screenshot from 2021-03-05 19-55-58 Thank you for your help.

Ok I found the solution I have deleted '/' at the begining of the url in the result but I have another problem. When I submit, the image is displayed twice. One with base64 code in URL and the other with the correct S3 Url.

langston8182 commented 3 years ago

It works now. Here is my new code :

handleImageUploadBefore = (files, info, uploadHandler) => {
        toBase64(files[0]).then(imageInfo => {
            if (imageInfo) {
                const mime = imageInfo.split(';')[0].split(':')[1];
                const rawImage = imageInfo.split(';')[1];
                this.props.uploadImageToS3(idArticle, mime, rawImage).then(url => {
                    const response = {
                        "result": [
                            {
                                "url": `${url}`,
                                "name": files[0].name,
                                "size": files[0].size
                            },
                        ]
                    };
                    uploadHandler(response);
                })
            }
        });
    }
Elsha-Destino commented 3 years ago

Hi. I'm having trouble uploading files (images, videos, audios) to my server. I am a begginer in programming and so I would really like you to help me create a URL mapping file, In java, that I could perform this action. Any help is greatly appreciated.

And I have to say that this editor is the BEST I've Ever seen. It's awesome!.

langston8182 commented 3 years ago

Hello, If you want to upload photos to AWS S3, you can use Api gateway with mapping tempate. And then you simply call your api in Java.

Elsha-Destino commented 3 years ago

Great Idea, thanks.