Resizable images using TipTap Editor
<template> | |
<div class="tiptap-content"> | |
<editor-content :editor="editor" /> | |
</div> | |
</template> | |
<script> | |
import { | |
Editor, | |
EditorContent | |
} from 'tiptap'; | |
import TipTapCustomImage from './TipTapImage.js'; | |
export default { | |
components: { | |
EditorContent | |
}, | |
data() { | |
return { | |
editor: null | |
} | |
}, | |
mounted() { | |
this.editor = new Editor({ | |
content: `<p>This is a paragraph</p><p><img src="https://i.ibb.co/nbRN3S2/undraw-upload-87y9.png" /></p>`, | |
extensions: [ | |
new TipTapCustomImage() | |
] | |
}); | |
}, | |
beforeDestroy() { | |
this.editor.destroy(); | |
} | |
} | |
</script> |
import { Node, Plugin } from 'tiptap'; | |
import { nodeInputRule } from 'tiptap-commands'; | |
import TipTapImageComponent from '~/components/editor/TipTapImageComponent'; | |
const IMAGE_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/; | |
export default class CustomImage extends Node { | |
get name() { | |
return 'image' | |
} | |
get schema() { | |
return { | |
inline: true, | |
attrs: { | |
src: {}, | |
alt: { | |
default: null, | |
}, | |
title: { | |
default: null, | |
}, | |
width: { | |
default: 300, | |
}, | |
height: { | |
default: 300 | |
} | |
}, | |
group: 'inline', | |
content: 'inline*', | |
draggable: false, | |
parseDOM: [ | |
{ | |
tag: 'img[src]', | |
getAttrs: dom => ({ | |
src: dom.getAttribute('src'), | |
title: dom.getAttribute('title'), | |
alt: dom.getAttribute('alt'), | |
height: dom.getAttribute('height') || 300, | |
width: dom.getAttribute('width') || 300 | |
}), | |
}, | |
], | |
toDOM: (node) => { | |
return ['img', { | |
src: node.attrs.src, | |
height: node.attrs.height, | |
width: node.attrs.width, | |
alt: node.attrs.alt, | |
title: node.attrs.title | |
}, 0]; | |
}, | |
} | |
} | |
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(context) { | |
const { type } = context; | |
return [ | |
nodeInputRule(IMAGE_INPUT_REGEX, type, match => { | |
const [, alt, src, title, height, width] = match; | |
return { | |
src, | |
alt, | |
title, | |
height, | |
width | |
} | |
}), | |
]; | |
} | |
get plugins() { | |
return [ | |
new Plugin({ | |
props: { | |
handleDOMEvents: { | |
drop(view, event) { | |
// I don't want to allow this | |
return false; | |
} | |
} | |
}, | |
}), | |
] | |
} | |
get view() { | |
return TipTapImageComponent; | |
} | |
} |
<template> | |
<div class="tiptap-custom-image-container"> | |
<vue-draggable-resizable :w="width" :h="height" @resizestop="onResize" :draggable="false" :lock-aspect-ratio="true"> | |
<div :style="`background-image:url('${src}');background-size:cover;background-repeat:no-repeat;position:absolute;top:0;left:0;right:0;bottom:0;`"></div> | |
</vue-draggable-resizable> | |
</div> | |
</template> | |
<script> | |
import VueDraggableResizable from 'vue-draggable-resizable'; | |
import 'vue-draggable-resizable/dist/VueDraggableResizable.css'; | |
export default { | |
props: ['node', 'updateAttrs', 'view', 'selected', 'getPos', 'options'], | |
components: { | |
'vue-draggable-resizable': VueDraggableResizable | |
}, | |
computed: { | |
src: { | |
get() { | |
return this.node.attrs.src; | |
}, | |
set(src) { | |
this.updateAttrs({src}); | |
} | |
}, | |
width: { | |
get() { | |
return parseInt(this.node.attrs.width); | |
}, | |
set(width) { | |
this.updateAttrs({ | |
width: width | |
}); | |
} | |
}, | |
height: { | |
get() { | |
return parseInt(this.node.attrs.height); | |
}, | |
set(height) { | |
this.updateAttrs({ | |
height: height | |
}); | |
} | |
} | |
}, | |
methods: { | |
onResize(x, y, width, height) { | |
this.width = width; | |
this.height = height; | |
} | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment