- Create a file
Image.js
from the source below (it is almost a copy of Image.js from tiptap-extensions except that it has a constructor that acceptsuploadFunc
(function to be called withimage
being uploaded) and additional logicif(upload) { ... } else { ... previous base64 logic .. }
in thenew Plugin
section.
import {Node, Plugin} from 'tiptap'
import {nodeInputRule} from 'tiptap-commands'
/**
* Matches following attributes in Markdown-typed image: [, alt, src, title]
*
* Example:
* ![Lorem](image.jpg) -> [, "Lorem", "image.jpg"]
* ![](image.jpg "Ipsum") -> [, "", "image.jpg", "Ipsum"]
* ![Lorem](image.jpg "Ipsum") -> [, "Lorem", "image.jpg", "Ipsum"]
*/
const IMAGE_INPUT_REGEX = /!\[(.+|:?)\]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
export default class Image extends Node {
constructor(name, parent, uploadFunc = null) {
super(name, parent);
this.uploadFunc = uploadFunc;
}
get name() {
return 'image'
}
get schema() {
return {
inline: true,
attrs: {
src: {},
alt: {
default: null,
},
title: {
default: null,
},
},
group: 'inline',
draggable: true,
parseDOM: [
{
tag: 'img[src]',
getAttrs: dom => ({
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
alt: dom.getAttribute('alt'),
}),
},
],
toDOM: node => ['img', node.attrs],
}
}
commands({ type }) {
return attrs => (state, dispatch) => {
const { selection } = state;
const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos;
const node = type.create(attrs);
const transaction = state.tr.insert(position, node);
dispatch(transaction)
}
}
inputRules({ type }) {
return [
nodeInputRule(IMAGE_INPUT_REGEX, type, match => {
const [, alt, src, title] = match;
return {
src,
alt,
title,
}
}),
]
}
get plugins() {
const upload = this.uploadFunc;
return [
new Plugin({
props: {
handleDOMEvents: {
drop(view, event) {
const hasFiles = event.dataTransfer
&& event.dataTransfer.files
&& event.dataTransfer.files.length;
if (!hasFiles) {
return
}
const images = Array
.from(event.dataTransfer.files)
.filter(file => (/image/i).test(file.type));
if (images.length === 0) {
return
}
event.preventDefault();
const { schema } = view.state;
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
images.forEach(async image => {
const reader = new FileReader();
if(upload) {
const node = schema.nodes.image.create({
src: await upload(image),
});
const transaction = view.state.tr.insert(coordinates.pos, node);
view.dispatch(transaction)
} else {
reader.onload = readerEvent => {
const node = schema.nodes.image.create({
src: readerEvent.target.result,
});
const transaction = view.state.tr.insert(coordinates.pos, node);
view.dispatch(transaction)
};
reader.readAsDataURL(image)
}
})
},
},
},
}),
]
}
}
- Import it:
import Image from './Image';
async function upload(file) {
...
}
new Editor({
extensions: [
...
new Image(null, null, upload),
...
- Implement the
upload
function:
async function upload(file) {
let formData = new FormData();
formData.append('file', file);
const headers = {'Content-Type': 'multipart/form-data'};
const response = await axios.post('/upload', formData, {headers: headers} );
return response.data.src;
},
This POST
s using axios
to /upload
and expects a JSON back of this form:
{"src": "https://yoursite.com/images/uploadedimage.jpg"}
-
Implement server-side logic for
/upload
-
If you want to support pasting of images, modify
Image.js
starting atprops:
, ending athandleDOMEvents
(re-factor common parts if you want to)
props: {
handlePaste(view, event, slice) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (const item of items) {
if (item.type.indexOf("image") === 0) {
event.preventDefault();
const { schema } = view.state;
const image = item.getAsFile();
if(upload) {
upload(image).then(src => {
const node = schema.nodes.image.create({
src: src,
});
const transaction = view.state.tr.replaceSelectionWith(node);
view.dispatch(transaction)
});
} else {
const reader = new FileReader();
reader.onload = readerEvent => {
const node = schema.nodes.image.create({
src: readerEvent.target.result,
});
const transaction = view.state.tr.replaceSelectionWith(node);
view.dispatch(transaction)
};
reader.readAsDataURL(image)
}
}
}
return false;
},
handleDOMEvents: {
Hi. I was looking for a way to use tiptap v2 with image upload (ueberdosis/tiptap#819) in react and came across this comment.
Can you please guide me as to how to use it in react or precisely how which command to call in order to invoke this extension?
Also it seems there's some typedef error with
addCommands()
UPDATE
Finally got it working: https://gist.github.com/waptik/f44b0d3c803fade75456817b1b1df6b4