Copy or Drag and Drop multiple images to TipTap Editor for Vue

Kailaash Balachandran
2 min readJan 17, 2020

In my project, I had a requirement where users should be able to copy bulk images or drag and drop them into the editor. The editor should then upload the pasted images and display them using the access URL. I followed these steps, and I’m pleased to say that it worked successfully.

Tested Versions:

"tiptap": "1.26.6",
"tiptap-extensions": "1.28.6",
"vue": "2.6.11"

Step 1:

Create a file ImageUpload.js from the source below. Here, the class ImageUpload extends the default Image from tiptap-extensions. The constructor accepts uploader which is the upload event handler defined in step 2.

import { Plugin } from 'tiptap'
import { Image } from 'tiptap-extensions'
/**
* Plugin for the tiptap editor that adds images to the editor
* @see https://github.com/scrumpy/tiptap/blob/0f17abeee6df1a8b40c6c96413a158918ec45d34/packages/tiptap-extensions/src/nodes/Image.js
* This class overwrites the default `image`. You need to make sure to **not** use the original class.
*/
export default class ImageUpload extends Image {
constructor (options = {}) {
super(options)
this.uploader = options.uploader
}
  get name () {
return 'image'
}
  get plugins () {
const uploader = this.uploader
return [
new Plugin({
props: {
handleDOMEvents: {
paste (view, event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items
if (!uploader) {
return
}
              items.forEach(async item => {
const { schema } = view.state
const image = item.getAsFile()
                // Return here, otherwise copying texts won't possible anymore
if (!image) {
return
}
event.preventDefault()
const imageSrc = await uploader(image)
const node = schema.nodes.image.create({
src: imageSrc,
})
const transaction = view.state.tr.replaceSelectionWith(node)
view.dispatch(transaction)
})
},
drop (view, event) {
const hasFiles = event.dataTransfer.files.length > 0
if (!hasFiles) {
return
}
event.preventDefault()
              const images = event.dataTransfer.files
const { schema } = view.state
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
images.forEach(async image => {
if (!uploader) {
return
}
const imageSrc = await uploader(image)
if (imageSrc) {
const node = schema.nodes.image.create({
src: imageSrc,
})
const transaction = view.state.tr.insert(coordinates.pos, node)
view.dispatch(transaction)
}
})
},
},
},
}),
]
}
}

Step 2:

Import ImageUpload.js into the component where Tiptap editor is setup

import ImageUpload from './plugins/ImageUpload'

and add the file in the extensions array.

return {
editor: new Editor({
extensions: [
new ImageUpload({
uploader: (image) => {
// Method that handles the image upload and returns a url(string)
return this.uploadImage(image)
},
}),
],
content: `
<h1>Yay! Images has been upload on copy/paste and drag/drop !</h1>
`,
}),
}

Step 3:

Create an async method that uploads the file and returns a public URL.

async uploadImage (selectedFile) {
// Restricts maxAllowedFileSizeMb to 5
if (selectedFile.size / 1024 / 1024 > 5) {
// handle error
return
}

// Check the allowed content type
if (!['image/jpeg', 'image/png', 'image/gif'].includes(selectedFile.type)) {
// handle error
return
}

// Optionally you can set a state here so as to toggle the editor to readonly mode until the image is uploaded.
this.imageUploadInProgress = true

// Handle Upload and return URL
return startDirectUpload(selectedFile, <service>)
.then((registerDirectUploadResponse) => {
// get from loadash
return get(registerDirectUploadResponse, 'publicThumbnailUrl', undefined)
})
.catch((e) => {
// handle catches
})
.finally(() => {
this.imageUploadInProgress = false
})
},

--

--

Kailaash Balachandran

Sr. Software Engineer at Tesla. Follow me on Twitter @BKailaash