preface

The project uses vUE framework and needs a Markdown edit box. I looked in NPM and found Simplemde is quite good. Since I am lazy, I searched in NPM and found the package of Vue-Simplemde, so LET’s start using it.

But vue-Simplemde doesn’t support drag and drop uploads, and it’s not because of Vue-Simplemde, because Vue-Simplemde encapsulates simplemde into a vUE plugin. So in the end simplemde doesn’t provide the functionality, but it’s necessary for the user experience, unless you don’t use the Markdown editor. Instead, use a rich text editor, in which case much of the project’s code will need to be changed. So I looked up articles online and some code on Github. The following will be analyzed

Drag and drop

The core of the drag API is the drop event, which is the name of the event that is triggered when we drag a file from the desktop to the browser and release it.

We all know that dragging an image into the browser will open the image directly, because the browser will open the file by default when you drag it into the browser, so we need to prevent the native action.

Let’s start by writing a piece of code that blocks the default event

window.addEventListener("drop", e => {
  e = e || event
  if (e.target.className === 'CodeMirror-scroll') { // The default event will be blocked if you enter the editor
    e.preventDefault()
  }
}, false)
Copy the code

The codemiror-Scroll Class is the Class name of the Simplemde edit box.

Now if we drag the file into this edit box and drop it, nothing will happen. If it is outside the edit box, the default event will continue to fire.

Let’s get the Simplemde method and give it the drop event handler.

// Suppose there are three edit Windows on the page, so you need to loop for events
[ this.$refs.simplemde1,
  this.$refs.simplemde2,
  this.$refs.simplemde3
].map(({simplemde}) = > {
  simplemde.codemirror.on('drop', (editor, e) => {
    if(! (e.dataTransfer && e.dataTransfer.files)) {// Popup indicates that this browser does not support this operation
      return
    }

    let dataList = e.dataTransfer.files
    let imageFiles = [] // Array of file instances to upload

    // loop, because it is possible to drag several image files simultaneously
    for (let i = 0; i < dataList.length; i++) {
    // If it is not an image, the popup warning only supports dragging and dropping image files
      if (dataList[i].type.indexOf('image') = = =- 1) {
        // Continue below, if the user drags two images and a document at the same time, the document will not be uploaded and the image will still be uploaded.
        continue
      }
      imageFiles.push(dataList[i])  // Push the current file into the array, wait for the end of the for loop, upload.
    }
    // The uploadImagesFile method is used to upload images
    // Simplemde. codemirror is used to tell which edit box the current image upload is in
    this.uploadImagesFile(simplemde.codemirror, imageFiles)
    // Now that we have the following code, we don't need to write the default event masking code
    e.preventDefault()
  })
})
Copy the code

At first glance, there seems to be too much code because of comments. Here is the uncommented code. You can make your own point of view based on the following code:

[ this.$refs.simplemde1,
  this.$refs.simplemde2,
  this.$refs.simplemde3
].map(({simplemde}) = > {
  simplemde.codemirror.on('drop', (editor, e) => {
    if(! (e.dataTransfer && e.dataTransfer.files)) {return
    }
    let dataList = e.dataTransfer.files
    let imageFiles = []
    for (let i = 0; i < dataList.length; i++) {
      if (dataList[i].type.indexOf('image') = = =- 1) {
        continue
      }
      imageFiles.push(dataList[i])
    }
    this.uploadImagesFile(simplemde.codemirror, imageFiles)
    e.preventDefault()
  })
})
Copy the code

paste

The paste API is paste method. Unlike paste, you don’t need to disable the default event, because as you can see, when you copy an image into the browser and press CTRL + V, nothing happens, so there’s no need to disable the default event.

Here’s the code:

simplemde.codemirror.on('paste', (editor, e) => { // Paste the image trigger function
  if(! (e.clipboardData && e.clipboardData.items)) {// Popup indicates that this browser does not support this operation
    return
  }
  try {
    let dataList = e.clipboardData.items
    if (dataList[0].kind === 'file' && dataList[0].getAsFile().type.indexOf('image')! = =- 1) {
      this.uploadImagesFile(simplemde.codemirror, [dataList[0].getAsFile()])
    }
  } catch (e) {
    // Popup instructions, can only paste images}})Copy the code

The reason I put try… Catch method, because if you paste, if it is a file, items will be empty, and in the if loop below, use dataList[0].kind. E.lipboarddata.items [0].kind. An error is reported when item is empty and a non-existent kind property is accessed. So we need to use try… Catch method is used to judge.

dataList[0].getAsFile().type.indexOf(‘image’) ! == -1 this sentence is a judgment, paste the thing is a picture, not other things.

[dataList[0].getasFile ()] [] = [dataList[0].getasFile ()] DataList [0].getasFile ()

upload

Uploading is a bit tricky:

uploadImagesFile (simplemde, files) {
  // Wrap each file instance in FormData and return an array
  let params = files.map(file= > {
    let param = new FormData()
    param.append('file', file, file.name)
    return param
  })

  let makeRequest = params= > {
    return this.$http.post('/Api/upload', params)
  }
  let requests = params.map(makeRequest)

  this.$http.spread = callback= > {
    return arr= > {
      return callback.apply(null, arr)
    }
  }

  State: Boolean, data: String}
  // When state is false, data is the error message returned
  // When state is true, data is the url of the uploaded image, which is the absolute path to the website. As follows:
  // /static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png
  Promise.all(requests)
    .then(this.$http.spread((. resps) = > {
      for (let i = 0; i < resps.length; i++) {
        let {state, data} = resps[i].data
        if(! state) {// Popup displays error information about data
          continue
        }
        let url = `! [] (${location.origin + data}) `  // Splice to markdown syntax
        let content = simplemde.getValue()
        simplemde.setValue(content + url + '\n')  // Concatenate the contents before the edit box}}}))Copy the code

Because I’m wrapping Axiox as a Vue plug-in to use, this.$HTTP is instantiated, not itself. The solution, axios maintainers say, is to reintroduce the Axios package to use. But I don’t think it’s necessary. Inside axios.all is promise.all. The axios.spread implementation code is relatively small, so just take it and reassign it to Axios

So there’s a piece of code up there

Promise.all(requests)
  .then(this.$http.spread((. resps) = > {
    // code
  })
Copy the code

Translate this code

axios.all(requests)
  .then(axios.spread((. resps) = > {
    // code
  })
Copy the code

Axios-all-is-not-a-function-inside-vue-component is the official explanation for this problem. You can also look at the code for axios: axios.js# l45-l48

But let’s get back to where we were.

/static/upload/ 2cfd6a50-3D30-11E8-b351-0d25ce9162A3.png /static/upload/ 2cfd6a50-3D30-11e8-b351-0d25ce9162A3

If we need to splice it, there it is! [](${location.origin + data}) The last two lines are fetch before fetch, and then append the URL.

At the end

Here’s the final look, since nuggets can’t upload giFs. So direct attach a dynamic diagram wiring: 7 xppwd.com1.z0.glb.clouddn.com/2b971f90-3d…

Complete code: subject.vue # l378-L465

Reference && thanks

Part of the code in the Laravel-Demo project by skecozo author

Simplemde drag-and-paste Image Upload by Lemon

F-loat author’s Vue-Simplemde project

Wescossick author of the Simplemde project