Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using amazon s3 storage with summernote #72

Closed
ankur1708 opened this issue Oct 13, 2013 · 77 comments
Closed

Using amazon s3 storage with summernote #72

ankur1708 opened this issue Oct 13, 2013 · 77 comments
Assignees
Milestone

Comments

@ankur1708
Copy link

@ankur1708 ankur1708 commented Oct 13, 2013

Summernote converts uploaded image as a base64 data which is too large to store.
Is there way to inject amazon s3 service, so that only link gets stored in db?

@ghost ghost assigned hackerwins Oct 13, 2013
@drewhamlett
Copy link

@drewhamlett drewhamlett commented Oct 31, 2013

This is what I have. Sorry for the coffeescript. It only does the first image dragged in right now.

sendFile = (file, callback) ->

  data = new FormData()
  data.append("file", file)
  $.ajax {
    url: '/upload',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    type: 'POST',
    success: (data) ->
      callback(data)
  }

$ () ->
  $post = $('#post-create')
  $post.summernote {
    height: "350px",
    onImageUpload: (files, editor, welEditable) ->
      sendFile files[0], (data) ->
        url = "#{data.scheme}://#{data.host}#{data.path}"
        editor.insertImage(welEditable, url)

Rails controller

class UploadsController < ApplicationController

  def create
    @file = params[:file].tempfile
    s3 = AWS::S3.new
    random = SecureRandom.hex
    image = s3.buckets['my-bucket'].objects.create("#{random}.jpg", @file)

    render(json: image.public_url)
  end
end
@ankur1708
Copy link
Author

@ankur1708 ankur1708 commented Nov 2, 2013

Thanks drewhjava. That helps.
I will modify it to delete images from s3 as well on removing images from editor content and post it here soon.

@hackerwins
Copy link
Member

@hackerwins hackerwins commented Nov 3, 2013

@ankur1708, @drewhjava Great!!! 👍

hackerwins added a commit that referenced this issue Nov 3, 2013
@moffepoffe
Copy link

@moffepoffe moffepoffe commented Nov 4, 2013

based on @drewhjava code.

jQuery(document).ready(function() {
    jQuery('#summernote').summernote({
        height: "500px",
        onImageUpload: function(files, editor, welEditable) {
            sendFile(files[0],editor,welEditable);
        }
    });
});
function sendFile(file,editor,welEditable) {
    data = new FormData();
    data.append("file", file);
    $.ajax({
        data: data,
        type: "POST",
        url: "/ajax/saveimage",
        cache: false,
        contentType: false,
        processData: false,
        success: function(url) {
                editor.insertImage(welEditable, url);
        }
    });
}

PHP server side

$f3->route('POST /ajax/saveimage', function($f3){
        $name = randomString();
        $ext = explode('.',$_FILES['file']['name']);
        $filename = $name.'.'.$ext[1];
        $destination = '/home/xxx/public_html/images/'.$filename;
        $location =  $_FILES["file"]["tmp_name"];
        move_uploaded_file($location,$destination);
        echo 'http://xxx.com/images/'.$filename;
    });
function randomString() {
    return md5(rand(100, 200));
}

will work on it to be dynamic :-)

@eyeris
Copy link

@eyeris eyeris commented Nov 13, 2013

Hi I have tried this code several times and everything works out until the,

success: function(url) {
editor.insertImage(welEditable, url);

The php function prints the correct image path and I can even access the stored file in browser address bar. but its doesn't insert to the editor. any suggestions

@titosemi
Copy link

@titosemi titosemi commented Nov 16, 2013

@eyeris Your php code has to return back the url to the file in order to be able to render it.

@mirivlad
Copy link

@mirivlad mirivlad commented Nov 18, 2013

@moffepoffe, @ankur1708 How do remove the picture from the server if it is removed in the editor? Please show me an example, do not have a high skill in javascript :(

@drewhamlett
Copy link

@drewhamlett drewhamlett commented Nov 18, 2013

@mirivlad I just leave it on the server. Your talking less then a cent. You should always run your images through imagemagick before uploading.

If I were doing it, I would implement a scraper that runs every day and listed out all images(older ones) on s3 and see if they existed in the posts.

Put it this way.

You store 10,000 100kb images(some will be smaller). That's 0.953674 gb. That's $0.01 on Amazon S3. I think it actually counts as free usage tier also.

http://calculator.s3.amazonaws.com/calc5.html

@mirivlad
Copy link

@mirivlad mirivlad commented Nov 18, 2013

@drewhjava Yes, I think you're right. Old habits are still with me :)

@drewhamlett
Copy link

@drewhamlett drewhamlett commented Nov 18, 2013

@mirivlad Look at the bright side, if you start storing millions of images on Amazon S3 that means your product is successful enough to hopefully have some income. Haha.

@mirivlad
Copy link

@mirivlad mirivlad commented Nov 18, 2013

@drewhjava I'm not going to store images in AS3. The project is small and will be located at the customer's hosting about which I know nothing. If the customer will be required to AS3 to store pictures, I realize it feature for an additional cash :)

@moffepoffe
Copy link

@moffepoffe moffepoffe commented Nov 18, 2013

@mirivlad i will try to fix the code for you later today :)

@mirivlad
Copy link

@mirivlad mirivlad commented Nov 19, 2013

@moffepoffe I would be inordinately grateful to you :)

@soomtong
Copy link
Contributor

@soomtong soomtong commented Nov 29, 2013

thanks friends!

@soomtong soomtong closed this Nov 29, 2013
@uro
Copy link

@uro uro commented Jan 15, 2014

Hello can anyone help me out?
Since two days i'm trying to figure out how to fix something that should work, but it doesn't (for whatever silly reason).
Sorry for posting this here, just thought it's not big enought to create a new issue.
I belive that a clever person would has a quick look an be able to point the problem whitin the matter of second :) apparently i'm not the cleverest one.

I'ive applied @moffepoffe's method into my project and when i try upload the pic, javascript console comes back with "Uncaught TypeError: Cannot read property '0' of undefined ", second line of pasted text

      onImageUpload: function(files, editor, welEditable) {
            sendFile(files[0],editor,welEditable);
        }

My code:

  $(document).ready(function(){
    $('select').select2();
    $('.note-editable').css('overflow','auto');
    $('.summernote').summernote({
        height: 400,
        onImageUpload: function(files, editor, welEditable) {
            
            sendFile(files[0],editor,welEditable);
        }
      });
    function sendFile(file,editor,welEditable) {
      data = new FormData();
      data.append("file", file);
      console.log('image upload:', file, editor, welEditable);
      console.log(data);
      $.ajax({
          data: data,
          type: "POST",
          url: "upload/index/news",
          cache: false,
          contentType: 'multipart/form-data',
          processData: false,
          success: function(url) {
            editor.insertImage(welEditable, url);
          }
      });
    }
  });

PS: I have tested the upload function on the server side and it works.
PPS: When i uploading the pic to base64 it works fine too.

Now, using the mantioned method uploading pic to server does not work, (looks like the pic not be chosen correctly or for some reason it didn't follow the procedure of chooseing it)

Let me show debug:
a15005656

A big thank you to anyone who can put a hit of light on to this problem, any clues would highly appreciated 👍

@mjim
Copy link

@mjim mjim commented Feb 6, 2014

@oparkadiusz Did you ever resolve this? I'm having the same problem. Summernote is my preferred editor but if my clients can't upload images, then it is useless.

@derek3x
Copy link

@derek3x derek3x commented Feb 11, 2014

In case anyone is looking through this issue for help.

I am using Summernote in a Python(Flask) web app. I used python with regex to find the base64 images that my form was submitting. I then converted them back to a proper image, saved it on the server and changed the img src link to be that of the image. It works great.

It could be used as a work around for some people if they have not thought of catching the base64 this way. I agree it seems a bit much considering it gets converted twice now, but I simply could not get the image upload to work with flask.

@pRdm
Copy link

@pRdm pRdm commented Feb 20, 2014

Amazon recently opened up support for CORS, which allows direct javascript interaction with its buckets.

In order to achieve this, first you have to update the CORS configuration of your S3 bucket.

This is what I use for allowing uploads via the POST action:

<CORSConfiguration>
    <CORSRule>
        <AllowedOrigin>https://www.yourdomain.com</AllowedOrigin>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
    </CORSRule>
</CORSConfiguration>

Here's a helpful article from Amazon(http://aws.amazon.com/articles/1434) detailing what you need inside the POST request.

I pre-render the base64 encoded signature and policy strings in the controller:

def load_s3_metadata
  @signature = signature
  @policy = policy
end

def signature(options = {})
  Base64.encode64(
    OpenSSL::HMAC.digest(
      OpenSSL::Digest::Digest.new('sha1'),
      SECRET_ACCESS_KEY,
      policy({ secret_access_key: SECRET_ACCESS_KEY })
    )
  ).gsub(/\n/, '')
end

def policy(options = {})
  Base64.encode64(
    {
      expiration: 30.minutes.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
      conditions: [
        { bucket: MY_BUCKET },
        { acl: 'public-read' },
        { success_action_status: '201' },
        ["starts-with", "$key", "my_folder/"],
        ["content-length-range", 0, 2097152]
      ]
    }.to_json
  ).gsub(/\n|\r/, '')
end

The conditions inside policy can be modified to fit your requirements. They are outlined in the above Amazon article.

Now in the javascript code (this upload gets triggered in summernote's onImageUpload callback)

$(document).ready(function() {
  $('.summernote').summernote({
    height: 500,
    onImageUpload: function(files, editor, welEditable) {
      sendFile(files[0], editor, welEditable);
    }
  });

  function sendFile(file, editor, welEditable) {
    formData = new FormData();
    formData.append('key', 'my_folder/' + file.name);
    formData.append('AWSAccessKeyId', '<%= ACCESS_KEY %>');
    formData.append('acl', 'public-read');
    formData.append('policy', '<%= @policy %>');
    formData.append('signature', '<%= @signature %>');
    formData.append('success_action_status', '201');
    formData.append('file', file);

    $.ajax({
      data: formData,
      dataType: 'xml',
      type: "POST",
      cache: false,
      contentType: false,
      processData: false,
      url: "https://<%= MY_BUCKET %>.s3-ap-southeast-2.amazonaws.com/",
      success: function(data) {

        // getting the url of the file from amazon and insert it into the editor
        var url = $(data).find('Location').text();
        editor.insertImage(welEditable, url);
      }
    });
  }
});

And voila!

p.s. note that its important to have the following in the ajax or jQuery is going to try and encode the request, resulting in a malformed POST error from Amazon.

  contentType: false,
  processData: false,
@mjim
Copy link

@mjim mjim commented Feb 24, 2014

@pRdm Thank you for the in-depth response! It now works for me using Php.

Here is what I use:
(Php from this page: http://www.plupload.com/docs/Upload-to-Amazon-S3#prepare-server-side )

<?php
$bucket = 'my_bucketname';
$folder = 'demo';

// these can be found on your Account page, under Security Credentials > Access Keys
$accessKeyId = 'replace with your access key id';
$secret = 'replace with your secret access key';

$policy = base64_encode(json_encode(array(
  // ISO 8601 - date('c'); generates uncompatible date, so better do it manually
  'expiration' => date('Y-m-d\TH:i:s.000\Z', strtotime('+2 days')), 
  'conditions' => array(
    array('bucket' => $bucket),
    array('acl' => 'public-read'),
    array('success_action_status' => '201'),
    array('starts-with', '$key', $folder.'/')
  )
)));

$signature = base64_encode(hash_hmac('sha1', $policy, $secret, true));

?>

And then my sendFile function with Php:

        function sendFile(file, editor, welEditable) {
          formData = new FormData();
          formData.append('key', '<?php echo $folder; ?>/' + file.name);
          formData.append('AWSAccessKeyId', '<?php echo $accessKeyId; ?>');
          formData.append('acl', 'public-read');
          formData.append('policy', '<?php echo $policy; ?>');
          formData.append('signature', '<?php echo $signature; ?>');
          formData.append('success_action_status', '201');
          formData.append('file', file);

          $.ajax({
            data: formData,
            dataType: 'xml',
            type: "POST",
            cache: false,
            contentType: false,
            processData: false,
            url: "https://<?php echo $bucket ?>.s3.amazonaws.com/",
            success: function(data) {
              // getting the url of the file from amazon and insert it into the editor
              var url = $(data).find('Location').text();
              editor.insertImage(welEditable, url);
            }
          });
        }
@jaequery
Copy link

@jaequery jaequery commented Mar 9, 2014

Hello all, someone please help. i don't know why image upload works in the github example page, but not in my local app. i'm just testing on the example page, is there some restriction I should be aware of?

@seanbotha123
Copy link

@seanbotha123 seanbotha123 commented May 2, 2014

anyone who has .NET code for this (preferably VB)? I love summernote, but need to use s3 and .net, I have no idea of how Ruby works so struggling with converting.

Thanks! :)

@hamerw
Copy link

@hamerw hamerw commented Jul 19, 2014

This is my 2-cents for a php solution...
setting up summernote...

jQuery(document).ready(function() {
    jQuery('#summernote').summernote({
        height: "500px",
        onImageUpload: function(files, editor, $editable) {
           sendFile(files[0],editor,$editable);
        }
    });
    function sendFile(file,editor,welEditable) {
    data = new FormData();
    data.append("file", file);
      $.ajax({
          url: "saveimage.php",
          data: data,
          cache: false,
          contentType: false,
          processData: false,
          type: 'POST',
          success: function(data){
          alert(data);
            editor.insertImage(welEditable, data);
          },
         error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus+" "+errorThrown);
         }
      });
    }
});

php file... saveimage.php

<?php
// A list of permitted file extensions
$allowed = array('png', 'jpg', 'gif','zip');

if(isset($_FILES['file']) && $_FILES['file']['error'] == 0){

    $extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);

    if(!in_array(strtolower($extension), $allowed)){
        echo '{"status":"error"}';
        exit;
    }

    if(move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/'.$_FILES['file']['name'])){
        $tmp='uploads/'.$_FILES['file']['name'];
        echo 'uploads/'.$_FILES['file']['name'];
        //echo '{"status":"success"}';
        exit;
    }
}

echo '{"status":"error"}';
exit;

and that's it. Unless you want to move the image to a different directory, but that's for you to figure out.. :)

@hamerw
Copy link

@hamerw hamerw commented Jul 19, 2014

Hey

I'm not sure how you have things setup.. but when I replace your url: img_upload.php with my url:saveimage.php it works fine. I have posted my saveimage.php in the blog.

Wayne.

On Saturday, July 19, 2014 12:43:53 PM, suyudo notifications@github.com wrote:

i get error like that.... how to solved it ?
my code
$(document).ready(function() { $('#summernote').summernote({ height: 300, minHeight: null, maxHeight: null, onImageUpload: function(files, editor, welEditable) { data = new FormData(); data.append("file", files[0]); $.ajax({ data: data, type: "POST", url: "img_upload.php", cache: false, contentType: false, processData: false, success: function(data) { alert(data); editor.insertImage(welEditable,data); }, error: function(jqXHR, textStatus, errorThrown) { console.log(textStatus+" "+errorThrown); } }); }, focus: true, toolbar: [ //[groupname, [button list]] ['style', ['bold', 'italic', 'underline', 'clear']], ['font', ['strikethrough']], ['fontsize', ['fontsize']], ['color', ['color']], ['para', ['ul', 'ol', 'paragraph']], ['height', ['height']], ['picture',['picture']] ] , onkeyup: function(e) { console.log(sHTML); }
}); });

Reply to this email directly or view it on GitHub.

@greerde
Copy link

@greerde greerde commented Aug 31, 2014

Is there an API call to add a class to the generated HTML IMG tag? Specifically, I would like the ability to add the bootstrap img-responsive class to any image uploaded via Summernote.

I have thought about doing it when the the rich text is actually displayed on another page (not in the editor) - but that's not pretty.

Another option is to modify the code within the editor during onblur or similar.

@Xeoncross
Copy link

@Xeoncross Xeoncross commented Oct 14, 2014

I keep seeing sendFile(files[0],editor,$editable); in these comments. Shouldn't that be a foreach/each loop over files?

https://github.com/HackerWins/summernote/blob/develop/dist/summernote.js#L3737

onImageUpload: function(files, editor, $editable) {
        $.each(files, function (idx, file) {
            sendFile(file,editor,$editable,file.name);
        });
},
@sangyoo91
Copy link

@sangyoo91 sangyoo91 commented Nov 29, 2014

@pRdm Hello Patrick. I'm trying to follow some of these methods. I'm using Meteor. So I'm working with Javascript / Jquery, and your answer is the most closest I am getting. I am a completely new beginner and linking summer note to S3 has been a big difficult task for me. Could you kindly explain to me which parts I have to edit to my own settings from your example so I can study it and try to make it work? Thank you :)

@Gorbulev-Sergey
Copy link

@Gorbulev-Sergey Gorbulev-Sergey commented Dec 26, 2016

Спасибо авторам за Summernote, отличный редактор.
Спасибо ребятам за примеры. На основании ваших примеров написал свой код для asn.net mvc:

VIEW:

<textarea name="содержимое" id="содержимое"></textarea>

`<script>

$(document).ready(function () {
    $('#содержимое').summernote({
       callbacks: {
           onImageUpload: function (files) {
                for (var i = 0; i < files.length; i++) {
                    send(files[i]);
                }
            }
        }
    });
});
function send(file) {
    if (file.type.includes('image')) {
        var name = file.name.split(".");
        name = name[0];
        var data = new FormData();
        data.append('file', file);
        $.ajax({
            url: '/Записи/Сохранить',
            type: 'POST',
            contentType: false,
            cache: false,
            processData: false,
            dataType: 'JSON',
            data: data,
            success: function (url) {
                $('#содержимое').summernote('insertImage', url);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                alert(textStatus + " " + errorThrown);
            }
        });
    }
}

</script>`

CONTROLLER:

[HttpPost]
public ActionResult Сохранить(System.Web.HttpPostedFileWrapper file)
{
if (!System.IO.File.Exists(Server.MapPath(file.FileName)))
{
file.SaveAs( Server.MapPath(file.FileName));
}
return Json(file.FileName);
}

@patourasalexandros
Copy link

@patourasalexandros patourasalexandros commented Jan 31, 2017

@titosemi can you explain me more the answer you gave to @eyeris ? i have the same problem (i work inside codeigniter) and the url of the image is correct

@feldy
Copy link

@feldy feldy commented Feb 19, 2017

Thanks @alejandrosc. your script work for me. btw can you explain how to handle the process so that can't duplicate files on folder?

@gomako
Copy link

@gomako gomako commented Mar 27, 2017

Just to throw my Laravel (5.2) version into the mix.

Using @Gorbulev-Sergey javascript with the addition of the Laravel csrf token.

$('#summernote').summernote({
  height: 500,
  callbacks: {
    onImageUpload: function(files) {
      for(var i=0; i < files.length; i++) {
        sendCMSFile(files[i]);
      } 
    }
  }
});

function sendCMSFile(file) {
  if (file.type.includes('image')) {
    var name = file.name.split(".");
    name = name[0];
    var data = new FormData();
    data.append('file', file);
    data.append('_token', window.Laravel.csrfToken); // https://laravel.com/docs/5.4/csrf#csrf-introduction
    $.ajax({
        url: '/uploadCMSFile',
        type: 'POST',
        contentType: false,
        cache: false,
        processData: false,
        dataType: 'JSON',
        data: data,
        success: function (url) {
          $('#summernote').summernote('insertImage', url);
        },
        error: function (jqXHR, textStatus, errorThrown) {
          console.error(textStatus + " " + errorThrown);
        }
    });
  }
}

The controller reached by posting to /uploadCMSFlle which uses the excellent Intervention Image package

public function uploadCMSFile(Request $request)
{
        $storageName = \Faker\Provider\Uuid::uuid();
        $ext = $file->guessExtension();

        // The publicly accessible URL (you will want to change this to suit your needs)
        $url = '/assets/' . $storageName . '.' . $ext;

        // The actual path to store the file
        $fullPath =  public_path($url);

        // Resize the image and save
        InterventionImage::make($file)->resize(800, null, function ($constraint) {
            $constraint->aspectRatio(); // Maintain aspect ratio
            $constraint->upsize(); // Don't scale up
        })->save($fullPath); // Save

        return response()->json($url);
}

If you want to avoid any duplicate files, you could skip the UUID bit and just rely on the original filename, but you may find you run into issues with overwriting files.

@crsssl
Copy link

@crsssl crsssl commented Jul 19, 2017

Here's a workable MeteorJS version for you:

On the client:

var snDefaults = _.extend(YOUR_SUMMERNOTE_DEFAULTS, {
  callbacks: {
    // Upload an image to AWS and replace with an IMG node (with src attr as URL)
    onImageUpload: function(files) {
      var self = this;
      var reader = new FileReader();
      reader.onload = function () {
        var imageFile = _.pick(files[0], 'name', 'type', 'size');   // Convert to EJSON-able object
        var imageData = reader.result;                              // Binary string version
        Meteor.call('saveImageFile', imageFile, imageData, function(err, res) {
          if (!err) {
            $(self).summernote('insertImage', res, slugify(imageFile.name));
          }
        });  
      };
      reader.readAsBinaryString(new Blob([files[0]]));              // 
    }
  }
});

$("#inputNode").summernote(snDefaults);

And on the server:

Meteor.methods({
  saveImageFile: function(file, blob) {
    check(file, Object);    // Recall this is an EJSON object (no blobs allowed!)
    check(blob, String);    // The image blob is passed as a binary string instead

    // SETUP:

    var bucketName        = Meteor.settings.public.bucketName;    // **Replace with yours**
    var bucketUrl         = Meteor.settings.public.bucketUrl;     // **Replace with the full URL**
    var imageStore        = Meteor.settings.public.imageFolder;   // **Replace with yours**

    var allowedImageExtensions = /\.(gif|jpg|jpeg|png)$/;
    var allowedImageTypes      = ["image/gif", "image/jpeg", "image/png"];
    var allowedImageSize       = 5242880;  // 5 MB

    // Simple filename cleanup function
    function cleanName(str) {
      if (str) { return slugify(str.replace(/\.\./g,'')); }
    }

    // CHECK:

    if (! allowedImageExtensions.test(file.name.toLowerCase())) { throw new Meteor.Error('Invalid image file extension.'); return; }
    if (allowedImageTypes.indexOf(file.type.toLowerCase()) < 0) { throw new Meteor.Error('Invalid image file type.'); return; }
    if (allowedImageSize < file.size) { throw new Meteor.Error('Invalid image file size.'); return; }

    // PROCESS:

    var serverFileName = Random.id() + '_' + cleanName(file.name);
    var key = imageStore + "/" + serverFileName;

    try {

      var s3 = new AWS.S3();
      var res = s3.putObjectSync({
        "Bucket": bucketName,
        "ACL": 'public-read',
        "Body": new Buffer(blob, 'binary'),
        "ContentType": file.type,
        "Key": key
      });

    } catch(error) {
      throw new Meteor.Error(error.message);
    }

    return bucketUrl + key;        // Return the full URL for the client to use
  }

});

Where:

  • YOUR_SUMMERNOTE_DEFAULTS are the usual setup parms for summernote, or {} for defaults,
  • AWS.S3() is the AWS SDK service for S3 (I use the peerlibrary:aws-sdk package for this), and
  • slugify() is your slugification function of choice, to ensure a good/readable URL

Hope this helps someone. It was a major pain in the ass getting this right. ;)

@ft0907
Copy link

@ft0907 ft0907 commented Aug 29, 2017

Pictures uploaded in the tomcat8.5 JDK1.8 environment, upload pictures can not be displayed, you need to delay the implementation of the background JAVA upload code, 5 seconds to normal display Why does this problem arise? May I ask if you have similar problems?

@Ontokrat
Copy link
Contributor

@Ontokrat Ontokrat commented Sep 5, 2017

@crsssl & every Meteor user
I manage it also with the FileReader API in a faster and lighter (in code & server charge) way: no Blob needed, no Binary string version

  • To manage the upload to S3, I use the jamiesoncj:slingshot package (fork of mainstream edgee:slingshot but enabling AWS side encryption): no file sent to the server.
  • I rewrite the name of files to avoid slugification issues (but see very easy way by @crsssl above if you want to keep the files' name) and of course I clean HTML elsewhere.

So everything is on client, except slingshot upload directive on server (see).

// inside summernote callbacks
onImageUpload: function(files) {
  var self = this;
  
  // Read & write with FileReader API
  var reader = new FileReader();
  
  reader.onload = function () {
    // Convert to EJSON-able object
    var imageFile = _.pick(files[0], 'name', 'type', 'size');
    // Check image's size (in bytes)
    if (imageFile.size > (1024 * 1024 * 1)) {
      // inform client here
    } 
    // check if file is an image
    else if (!imageFile.type.includes('image')) {
      // inform client here
    } else {
      // get a random filename
      var storagefilename = Random.hexString(18) + ".jpg";
      // create a new file with new name 
      var renamedFile = new File([files[0]], storagefilename, {type: "image/jpeg", lastModified: new Date()});
      // Upload to S3
      var uploader = new Slingshot.Upload("<nameofuploaddirective>", {sse: true, key: <yourKey>});
      uploader.send(renamedFile, function (error, downloadUrl) {
        if (error) {
          // inform client here
        } else {
          // insert image in editor
          $(self).summernote('insertImage', downloadUrl, storagefilename);
        }
      });        
    } 
  };

  reader.readAsArrayBuffer(files[0]);

},

@crsssl
Copy link

@crsssl crsssl commented Sep 6, 2017

Nice job @Ontokrat, and thanks for pointing out that package. It'll really help.

@rafyong
Copy link

@rafyong rafyong commented Dec 20, 2017

Hi, how to upload image to server instead of base64 using asp.net webform? not the MVC.
I don't know using ajax. what needs to put on URL?

Im writing this javascript inside news_create.aspx. Then I define a function to upload the image in server.
So. how to call the function from this sendFile javascript function?

function sendFile(file,editor,welEditable) {
data = new FormData();
data.append("file", file);
$.ajax({
data: data,
type: "POST",
url: "/ajax/saveimage",
cache: false,
contentType: false,
processData: false,
success: function(url) {
editor.insertImage(welEditable, url);
}
});
}

@jonalport
Copy link

@jonalport jonalport commented Feb 12, 2018

I had the same problem as @piaoyizhimei and a few others...

If you are updating the img src in the insertImage callback, you may need to trigger a change event manually after doing so:

$summernote.trigger('summernote.change');
@Laeng
Copy link

@Laeng Laeng commented May 28, 2018

if you using laravel 5.5 or higher, Try to using my code!

$('#editor').summernote({
        callbacks: {
            onImageUpload: function(files) {
                for(let i=0; i < files.length; i++) {
                    $.upload('image', files[i]);
                }
            }
        },
        placeholder: 'Image upload test',
        width: 500,
        height: 500,
    });
$.upload = function (file) {
    let out = new FormData();
    out.append('file', file, file.name);

    $.ajax({
        method: 'POST',
        url: '/post/upload',
        //check laravel document: https://laravel.com/docs/5.6/csrf#csrf-x-csrf-token
        headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')},
        contentType: false,
        cache: false,
        processData: false,
        dataType: 'JSON',
        data: out,
        success: function (r) {
            $('#editor').summernote('insertImage', r.url);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            console.error(textStatus + " " + errorThrown);
        }
    });
};
@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 22, 2018

@Laeng i keep getting error 500, when trying to upload an image through summernote, could you post your php method to handle these DB uploads, please?

I have figured out, you have the error on this line:

$('#editor').summernote('insertImage', r.url);

it should be just "r", why you have used "r.url" - i have no idea.

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

@heihachi88
letter 'r' is mean returned json value from server! so, 'r' mean return.

I using 'Xpresss Engine 3' and it's based laravel 5.5.
Some methods is not support at pure laravel 5.5 and other CMS.

public function upload(Request $request)
    {
        $r = (object)[]; //it's will return values.

        if (($uploadedFile = $request->file('file')) === null) {
            $r->error = "Upload Fail: File is empty.";
            return json_encode($r);
        }

        if ($uploadedFile->getSize() > 10 * pow(1024, 2)) {
            $r->error = "Upload Fail: File is exceed 10 MB.";
            return json_encode($r);
        }

        $extensions = array(
            'png', 'jpg', 'jpeg', 'jpe', 'svg', 'bmp', 'tif', 'tiff', 'ico',
            'asf', 'asx', 'wmv', 'wmx', 'wm', 'avi', 'divx', 'flv', 'mov', 'qt', 'mpeg', 'mpg', 'mpe', 'mp4', 'm4v', 'ogv', 'webm', 'mkv',
            'mp3', 'm4a', 'm4b', 'ogg', 'oga', 'mid', 'midi', 'wma', 'wmx', 'mka',
            'txt', 'pdf', 'zip', 'gz', 'gzip', 'rar', '7z', 'hwp',
            'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx',
            'obt', 'odp', 'ods', 'odg', 'odc', 'odb', 'odf',
            'key', 'numbers', 'pages'
        );

        if (!in_array(strtolower($uploadedFile->getClientOriginalExtension()), $extensions)) {
            $r->error = "Upload Fail: Unacceptable extension.";
            return json_encode($r);
        }


        /**
           * Save File
         **/

        $file = XeStorage::upload($uploadedFile, 'public/editor');
        $r->url = sprintf("/storage/app/%s/%s", $file->path, $file->filename); 

        return json_encode($r);
    }
@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 23, 2018

@Laeng

Can't i get ajax data with the following method:

$file = $request->input('file');

It returns me nothing.

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

@heihachi88

ajax post to server with query 'file' and server is most receive 'file'.

larave 5.5 is not used at file upload to $requset->input('file');
Could you change as shown below?

$request->file('file');
@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 23, 2018

@Laeng

I am using Laravel 5.6, but seems like ajax post not actually posting data to my controller.

$file = $request->file('file');
return response()->json($file->getRealPath());

Still returns nothing:

"message": "Call to a member function getRealPath() on null"

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

let's check debug!
using method dd();

dd($requset->file('file'));

Please put it before 'return'.

if image is successful upload,

@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 23, 2018

Why you wrapped dd() with quotes?

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

@heihachi88 it's my mistake.... haha!
Don't wrap the quotes!

@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 23, 2018

Here's response i get with dd():

<script> Sfdump = window.Sfdump || (function (doc) { var refStyle = doc.createElement('style'), rxEsc = /([.*+?^${}()|\[\]\/\\])/g, idRx = /\bsf-dump-\d+-ref[012]\w+\b/, keyHint = 0 <= navigator.platform.toUpperCase().indexOf('MAC') ? 'Cmd' : 'Ctrl', addEventListener = function (e, n, cb) { e.addEventListener(n, cb, false); }; (doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle); if (!doc.addEventListener) { addEventListener = function (element, eventName, callback) { element.attachEvent('on' + eventName, function (e) { e.preventDefault = function () {e.returnValue = false;}; e.target = e.srcElement; callback(e); }); }; } function toggle(a, recursive) { var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass; if (/\bsf-dump-compact\b/.test(oldClass)) { arrow = '&#9660;'; newClass = 'sf-dump-expanded'; } else if (/\bsf-dump-expanded\b/.test(oldClass)) { arrow = '&#9654;'; newClass = 'sf-dump-compact'; } else { return false; } if (doc.createEvent && s.dispatchEvent) { var event = doc.createEvent('Event'); event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false); s.dispatchEvent(event); } a.lastChild.innerHTML = arrow; s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass); if (recursive) { try { a = s.querySelectorAll('.'+oldClass); for (s = 0; s < a.length; ++s) { if (-1 == a[s].className.indexOf(newClass)) { a[s].className = newClass; a[s].previousSibling.lastChild.innerHTML = arrow; } } } catch (e) { } } return true; }; function collapse(a, recursive) { var s = a.nextSibling || {}, oldClass = s.className; if (/\bsf-dump-expanded\b/.test(oldClass)) { toggle(a, recursive); return true; } return false; }; function expand(a, recursive) { var s = a.nextSibling || {}, oldClass = s.className; if (/\bsf-dump-compact\b/.test(oldClass)) { toggle(a, recursive); return true; } return false; }; function collapseAll(root) { var a = root.querySelector('a.sf-dump-toggle'); if (a) { collapse(a, true); expand(a); return true; } return false; } function reveal(node) { var previous, parents = []; while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) { parents.push(previous); } if (0 !== parents.length) { parents.forEach(function (parent) { expand(parent); }); return true; } return false; } function highlight(root, activeNode, nodes) { resetHighlightedNodes(root); Array.from(nodes||[]).forEach(function (node) { if (!/\bsf-dump-highlight\b/.test(node.className)) { node.className = node.className + ' sf-dump-highlight'; } }); if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) { activeNode.className = activeNode.className + ' sf-dump-highlight-active'; } } function resetHighlightedNodes(root) { Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) { strNode.className = strNode.className.replace(/\bsf-dump-highlight\b/, ''); strNode.className = strNode.className.replace(/\bsf-dump-highlight-active\b/, ''); }); } return function (root, x) { root = doc.getElementById(root); var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'), options = {"maxDepth":1,"maxStringLength":160,"fileLinkFormat":false}, elt = root.getElementsByTagName('A'), len = elt.length, i = 0, s, h, t = []; while (i < len) t.push(elt[i++]); for (i in x) { options[i] = x[i]; } function a(e, f) { addEventListener(root, e, function (e) { if ('A' == e.target.tagName) { f(e.target, e); } else if ('A' == e.target.parentNode.tagName) { f(e.target.parentNode, e); } else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) { f(e.target.nextElementSibling, e, true); } }); }; function isCtrlKey(e) { return e.ctrlKey || e.metaKey; } function xpathString(str) { var parts = str.match(/[^'"]+|['"]/g).map(function (part) { if ("'" == part) { return '"\'"'; } if ('"' == part) { return "'\"'"; } return "'" + part + "'"; }); return "concat(" + parts.join(",") + ", '')"; } function xpathHasClass(className) { return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')"; } addEventListener(root, 'mouseover', function (e) { if ('' != refStyle.innerHTML) { refStyle.innerHTML = ''; } }); a('mouseover', function (a, e, c) { if (c) { e.target.style.cursor = "pointer"; } else if (a = idRx.exec(a.className)) { try { refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}'; } catch (e) { } } }); a('click', function (a, e, c) { if (/\bsf-dump-toggle\b/.test(a.className)) { e.preventDefault(); if (!toggle(a, isCtrlKey(e))) { var r = doc.getElementById(a.getAttribute('href').substr(1)), s = r.previousSibling, f = r.parentNode, t = a.parentNode; t.replaceChild(r, a); f.replaceChild(a, s); t.insertBefore(s, r); f = f.firstChild.nodeValue.match(indentRx); t = t.firstChild.nodeValue.match(indentRx); if (f && t && f[0] !== t[0]) { r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]); } if (/\bsf-dump-compact\b/.test(r.className)) { toggle(s, isCtrlKey(e)); } } if (c) { } else if (doc.getSelection) { try { doc.getSelection().removeAllRanges(); } catch (e) { doc.getSelection().empty(); } } else { doc.selection.empty(); } } else if (/\bsf-dump-str-toggle\b/.test(a.className)) { e.preventDefault(); e = a.parentNode.parentNode; e.className = e.className.replace(/\bsf-dump-str-(expand|collapse)\b/, a.parentNode.className); } }); elt = root.getElementsByTagName('SAMP'); len = elt.length; i = 0; while (i < len) t.push(elt[i++]); len = t.length; for (i = 0; i < len; ++i) { elt = t[i]; if ('SAMP' == elt.tagName) { a = elt.previousSibling || {}; if ('A' != a.tagName) { a = doc.createElement('A'); a.className = 'sf-dump-ref'; elt.parentNode.insertBefore(a, elt); } else { a.innerHTML += ' '; } a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; a.innerHTML += '<span>&#9660;</span>'; a.className += ' sf-dump-toggle'; x = 1; if ('sf-dump' != elt.parentNode.className) { x += elt.parentNode.getAttribute('data-depth')/1; } elt.setAttribute('data-depth', x); var className = elt.className; elt.className = 'sf-dump-expanded'; if (className ? 'sf-dump-expanded' !== className : (x > options.maxDepth)) { toggle(a); } } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) { a = a.substr(1); elt.className += ' '+a; if (/[\[{]$/.test(elt.previousSibling.nodeValue)) { a = a != elt.nextSibling.id && doc.getElementById(a); try { s = a.nextSibling; elt.appendChild(a); s.parentNode.insertBefore(a, s); if (/^[@#]/.test(elt.innerHTML)) { elt.innerHTML += ' <span>&#9654;</span>'; } else { elt.innerHTML = '<span>&#9654;</span>'; elt.className = 'sf-dump-ref'; } elt.className += ' sf-dump-toggle'; } catch (e) { if ('&' == elt.innerHTML.charAt(0)) { elt.innerHTML = '&hellip;'; elt.className = 'sf-dump-ref'; } } } } } if (doc.evaluate && Array.from && root.children.length > 1) { root.setAttribute('tabindex', 0); SearchState = function () { this.nodes = []; this.idx = 0; }; SearchState.prototype = { next: function () { if (this.isEmpty()) { return this.current(); } this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0; return this.current(); }, previous: function () { if (this.isEmpty()) { return this.current(); } this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1); return this.current(); }, isEmpty: function () { return 0 === this.count(); }, current: function () { if (this.isEmpty()) { return null; } return this.nodes[this.idx]; }, reset: function () { this.nodes = []; this.idx = 0; }, count: function () { return this.nodes.length; }, }; function showCurrent(state) { var currentNode = state.current(); if (currentNode) { reveal(currentNode); highlight(root, currentNode, state.nodes); } counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count(); } var search = doc.createElement('div'); search.className = 'sf-dump-search-wrapper sf-dump-search-hidden'; search.innerHTML = ' <input type="text" class="sf-dump-search-input"> <span class="sf-dump-search-count">0 of 0<\/span> <button type="button" class="sf-dump-search-input-previous" tabindex="-1"> <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"> <path d="M1683 1331l-166 165q-19 19-45 19t-45-19l-531-531-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/> <\/svg> <\/button> <button type="button" class="sf-dump-search-input-next" tabindex="-1"> <svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"> <path d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/> <\/svg> <\/button> '; root.insertBefore(search, root.firstChild); var state = new SearchState(); var searchInput = search.querySelector('.sf-dump-search-input'); var counter = search.querySelector('.sf-dump-search-count'); var searchInputTimer = 0; var previousSearchQuery = ''; addEventListener(searchInput, 'keyup', function (e) { var searchQuery = e.target.value; /* Don't perform anything if the pressed key didn't change the query */ if (searchQuery === previousSearchQuery) { return; } previousSearchQuery = searchQuery; clearTimeout(searchInputTimer); searchInputTimer = setTimeout(function () { state.reset(); collapseAll(root); resetHighlightedNodes(root); if ('' === searchQuery) { counter.textContent = '0 of 0'; return; } var classMatches = [ "sf-dump-str", "sf-dump-key", "sf-dump-public", "sf-dump-protected", "sf-dump-private", ].map(xpathHasClass).join(' or '); var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while (node = xpathResult.iterateNext()) state.nodes.push(node); showCurrent(state); }, 400); }); Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) { addEventListener(btn, 'click', function (e) { e.preventDefault(); -1 !== e.target.className.indexOf('next') ? state.next() : state.previous(); searchInput.focus(); collapseAll(root); showCurrent(state); }) }); addEventListener(root, 'keydown', function (e) { var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className); if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) { /* F3 or CMD/CTRL + F */ e.preventDefault(); search.className = search.className.replace(/\bsf-dump-search-hidden\b/, ''); searchInput.focus(); } else if (isSearchActive) { if (27 === e.keyCode) { /* ESC key */ search.className += ' sf-dump-search-hidden'; e.preventDefault(); resetHighlightedNodes(root); searchInput.value = ''; } else if ( (isCtrlKey(e) && 71 === e.keyCode) /* CMD/CTRL + G */ || 13 === e.keyCode /* Enter */ || 114 === e.keyCode /* F3 */ ) { e.preventDefault(); e.shiftKey ? state.previous() : state.next(); collapseAll(root); showCurrent(state); } } }); } if (0 >= options.maxStringLength) { return; } try { elt = root.querySelectorAll('.sf-dump-str'); len = elt.length; i = 0; t = []; while (i < len) t.push(elt[i++]); len = t.length; for (i = 0; i < len; ++i) { elt = t[i]; s = elt.innerText || elt.textContent; x = s.length - options.maxStringLength; if (0 < x) { h = elt.innerHTML; elt[elt.innerText ? 'innerText' : 'textContent'] = s.substring(0, options.maxStringLength); elt.className += ' sf-dump-str-collapse'; elt.innerHTML = '<span class=sf-dump-str-collapse>'+h+'<a class="sf-dump-ref sf-dump-str-toggle" title="Collapse"> &#9664;</a></span>'+ '<span class=sf-dump-str-expand>'+elt.innerHTML+'<a class="sf-dump-ref sf-dump-str-toggle" title="'+x+' remaining characters"> &#9654;</a></span>'; } } } catch (e) { } }; })(document); </script><style> pre.sf-dump { display: block; white-space: pre; padding: 5px; } pre.sf-dump:after { content: ""; visibility: hidden; display: block; height: 0; clear: both; } pre.sf-dump span { display: inline; } pre.sf-dump .sf-dump-compact { display: none; } pre.sf-dump abbr { text-decoration: none; border: none; cursor: help; } pre.sf-dump a { text-decoration: none; cursor: pointer; border: 0; outline: none; color: inherit; } pre.sf-dump .sf-dump-ellipsis { display: inline-block; overflow: visible; text-overflow: ellipsis; max-width: 5em; white-space: nowrap; overflow: hidden; vertical-align: top; } pre.sf-dump .sf-dump-ellipsis+.sf-dump-ellipsis { max-width: none; } pre.sf-dump code { display:inline; padding:0; background:none; } .sf-dump-str-collapse .sf-dump-str-collapse { display: none; } .sf-dump-str-expand .sf-dump-str-expand { display: none; } .sf-dump-public.sf-dump-highlight, .sf-dump-protected.sf-dump-highlight, .sf-dump-private.sf-dump-highlight, .sf-dump-str.sf-dump-highlight, .sf-dump-key.sf-dump-highlight { background: rgba(111, 172, 204, 0.3); border: 1px solid #7DA0B1; border-radius: 3px; } .sf-dump-public.sf-dump-highlight-active, .sf-dump-protected.sf-dump-highlight-active, .sf-dump-private.sf-dump-highlight-active, .sf-dump-str.sf-dump-highlight-active, .sf-dump-key.sf-dump-highlight-active { background: rgba(253, 175, 0, 0.4); border: 1px solid #ffa500; border-radius: 3px; } pre.sf-dump .sf-dump-search-hidden { display: none; } pre.sf-dump .sf-dump-search-wrapper { float: right; font-size: 0; white-space: nowrap; max-width: 100%; text-align: right; } pre.sf-dump .sf-dump-search-wrapper > * { vertical-align: top; box-sizing: border-box; height: 21px; font-weight: normal; border-radius: 0; background: #FFF; color: #757575; border: 1px solid #BBB; } pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input { padding: 3px; height: 21px; font-size: 12px; border-right: none; width: 140px; border-top-left-radius: 3px; border-bottom-left-radius: 3px; color: #000; } pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next, pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous { background: #F2F2F2; outline: none; border-left: none; font-size: 0; line-height: 0; } pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next { border-top-right-radius: 3px; border-bottom-right-radius: 3px; } pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next > svg, pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous > svg { pointer-events: none; width: 12px; height: 12px; } pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-count { display: inline-block; padding: 0 5px; margin: 0; border-left: none; line-height: 21px; font-size: 12px; }pre.sf-dump, pre.sf-dump .sf-dump-default{background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all}pre.sf-dump .sf-dump-num{font-weight:bold; color:#1299DA}pre.sf-dump .sf-dump-const{font-weight:bold}pre.sf-dump .sf-dump-str{font-weight:bold; color:#56DB3A}pre.sf-dump .sf-dump-note{color:#1299DA}pre.sf-dump .sf-dump-ref{color:#A0A0A0}pre.sf-dump .sf-dump-public{color:#FFFFFF}pre.sf-dump .sf-dump-protected{color:#FFFFFF}pre.sf-dump .sf-dump-private{color:#FFFFFF}pre.sf-dump .sf-dump-meta{color:#B729D9}pre.sf-dump .sf-dump-key{color:#56DB3A}pre.sf-dump .sf-dump-index{color:#1299DA}pre.sf-dump .sf-dump-ellipsis{color:#FF8400}</style><pre class=sf-dump id=sf-dump-2122892111 data-indent-pad="  "><span class=sf-dump-const>null</span>
</pre><script>Sfdump("sf-dump-2122892111")</script>

Something wrong with the ajax post.

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

@heihachi88

Okay, image is not received to server.
hm.....

@Laeng
Copy link

@Laeng Laeng commented Jun 23, 2018

@heihachi88
Sorry, heihachi88, I don't know your error reasons....
The obvious thing is that the image was not received to the server.

@webcoderkz
Copy link

@webcoderkz webcoderkz commented Jun 23, 2018

I can confirm, that #72 (comment) works in Laravel 5.6 perfectly.

In case someone stuck with it, just like a did, i'm posting my method for accepting post images:

    public function summerUploads(Request $request)
    {
        if ($request->ajax()) {
            if ($request->hasFile('file')) {
                $file = $request->file('file');
                $store = $file->store('summer-uploads');
            }

            return response()->json($request->root() . '/' . $store, 200, [], JSON_UNESCAPED_SLASHES);
        }

        return App::abort(404);
    }
@gusgeek
Copy link

@gusgeek gusgeek commented Feb 8, 2020

$(document).ready(function () {
    var $summernote = $('.summernote-textarea').summernote({
        lang: 'ru-RU',
        height: 400,
        callbacks: {
            onImageUpload: function (files) {
                sendFile($summernote, files[0]);
            }
        }
    });
});

function sendFile($summernote, file) {
    var formData = new FormData();
    formData.append("file", file);
    $.ajax({
        url: '/new/files/upload',
        data: formData,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function (data) {
            $summernote.summernote('insertImage', data, function ($image) {
                $image.attr('src', data);
            });
        }
    });

}

THIS THE FKN SOLUTION

@kevinluo201
Copy link

@kevinluo201 kevinluo201 commented Mar 11, 2020

the original solution for Rails is not working any more
I use carrierwave and Google Storage, but I think the spirit is the same

var sendFile = function(file, callback) {
  var data;
  data = new FormData();
  data.append("file", file);
  return $.ajax({
    url: 'url_for_rails_to_handle_image_upload',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    type: 'POST',
    success: function(data) {
      return callback(data);
    }
  });
};

$(document).ready(function () {
  $('.summernote').summernote({
    callbacks: {
      onImageUpload(files) {
        sendFile(files[0], data => {
          let imgNode = document.createElement("img");
          imgNode.setAttribute('src', data.url)
          $(this).summernote('insertNode', imgNode);
        })
      }
    }
  });
});
# rails side
  def upload_editor_images
    storage = Google::Cloud::Storage.new(
      project_id: "project-id",
      credentials: "path-to-credential-file"
    )
    bucket = storage.bucket "bucket-name"

    image = bucket.create_file params[:file].tempfile.path, "/uploads/#{Rails.env}/by_editors/#{random = SecureRandom.hex}.jpg"
    render json: { url: image.media_url }
  end
@PMertens93
Copy link

@PMertens93 PMertens93 commented Mar 31, 2020

After some playing around with the codes provided in this issue I made an updated version compatible with Laravel 7.0. Feel free to use it.

In web.php

Route::post('/summernote-upload', 'Admin\SummernoteUploadController@upload');

App\Http\Controllers\Admon\SummernoteUploadController


namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Intervention\Image\Facades\Image;

class SummernoteUploadController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function upload(Request $request)
    {
        // check if the request comes as an ajax-request
        if ($request->ajax()) {
            // validate if the file is an image to protect against bad files uploaded
            $request->validate([
                'file' => 'required|image',
            ]);

            // store the file
            $file = $request->file('file');
            $store = $file->store('summer-uploads', 'public');

            // resize the file, optional (intervention/image package)
            $image = Image::make(public_path('storage/' . $store));
            if($image->width() > 600) {
                $image->resize(600, null, function ($constraint) {
                    $constraint->aspectRatio();
                });
            }

            $image->save();

            //respons to the editor
            return response()->json($request->root() . '/storage/' . $store, 200, [], JSON_UNESCAPED_SLASHES);
        }

        return App::abort(404);
    }
}

The AJAX-function (can be placed in a global js-file or the file of the editor between script-tags

    var formData = new FormData();
    formData.append("file", file);
    formData.append('_token', $('meta[name="csrf-token"]').attr('content'));
    $.ajax({
        url: '/summernote-upload',
        data: formData,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function (data) {
            $summernote.summernote('insertImage', data, function ($image) {
                $image.attr('src', data);
            });
        }
    });
}

The callback included into the call to the form

        //Add text editor
        var $summernote = $('#compose-textarea').summernote({
            height: 300,
            lang: 'nl-NL',
            callbacks: {
                onImageUpload: function (files) {
                    sendFile($summernote, files[0]);
                }
            }
        });
    });

Make sure you have your csrf token @csrf placed in de blade-file of the editor

@zenakent
Copy link

@zenakent zenakent commented Apr 24, 2020

been working on this for days and I still don't know why it is not working for me.
seems that my "let data = new FormData()" is empty always that's why it's giving me a status 500 everytime I attached an image. hopefully someone could help? using NodeJs+Express+Multer btw

my form

<form id="newBlog" action="/dashboard/blog/create" method="POST">              
  <div class="form-group">
    <textarea id="summernote" name="blogBody"></textarea>
  </div>
  <div class="form-group">
    <input type="submit" class="btn btn-primary">
  </div>
  <input type="hidden" name="_csrf" value="<%#= csrfToken %>">
</form>

my route

const express = require('express');
const router = express.Router();
const db = require('../../models');

//multer
const multer = require('multer')
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname)
  }
})
const upload = multer({
  storage: storage
})

//upload images from content editor
router.post('/image/upload', upload.single('blogBody'), async function (req, res) {
  try { 
    res.send(req.file.originalname)
  } catch (error) {
    console.log(error)
  }
})

module.exports = router;

my initiate-summernote.js

// Initiate summernote wysiwyg editor
$('#summernote').summernote({
    callbacks: {
        onImageUpload: function (files) {
            if (!files.length) return;            
            saveFile(files)
        }
    }
});

//get csrf token for ajax post
let token = $('input[name="_csrf"]').attr('value')
$.ajaxSetup({
    beforeSend: function (xhr) {
        xhr.setRequestHeader('Csrf-Token', token);
    }
});

 function saveFile(file) {
    console.log(file)
    let data = new FormData();
    data.append("file", file[0]);
    console.log(data.get('file'))
    for (let key of data.entries()) {
        console.log(key[0] + ', ' + key[1]);
    }
    $.ajax({
        data: data,
        type: "POST",
        url: "/dashboard/blog/image/upload",
        cache: false,
        contentType: false,
        processData: false,
        success: function (url) {
            console.log(url)
            editor.insertImage(url);
        }
    });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet