Skip to content

Instantly share code, notes, and snippets.

@zachjharris
Created June 29, 2020 12:56
Show Gist options
  • Save zachjharris/a5442efbdff11948d085b6b1406dfbe6 to your computer and use it in GitHub Desktop.
Save zachjharris/a5442efbdff11948d085b6b1406dfbe6 to your computer and use it in GitHub Desktop.
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