I'm trying to pass data to a custom vue component that gets rendered inside the tiptap editor
. I can pass default
properties but assigning reactive values to it doesn't seem to work.
This is the tiptap-node-extension.js
file:
import {Node, mergeAttributes} from '@tiptap/core'
import {VueNodeViewRenderer} from '@tiptap/vue-3'
import Component from '@/views/components/vue-component.vue'
export default Node.create({
parseHTML() {
return [{ tag: 'vue-component' }]
},
renderHTML({ HTMLAttributes }) {
return ['vue-component', mergeAttributes(HTMLAttributes)]
},
addNodeView() {
return VueNodeViewRenderer(Component)
},
})
the script setup
portion of the editor
component:
<script setup>
import {useEditor, EditorContent, BubbleMenu} from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import {Underline} from "@tiptap/extension-underline";
import {TextAlign} from "@tiptap/extension-text-align";
import {Link} from "@tiptap/extension-link";
import VueComponent from '@/js/tiptap-node-extension.js'
const editor = useEditor({
extensions: [
StarterKit,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
Underline,
Link,
VueComponent.extend({
name: 'vueComponent',
group: 'block',
draggable: true,
addAttributes() {
return {
src: {
default: '123',
}
}
},
}
),
],
content: props.modelValue,
onUpdate: ({ editor }) => {
emit('update:modelValue', editor.getHTML())
},
editable: props.locked ? false : store.admin
})
const sendDataToExtension = async (editor, event) => {
// Triggered upon event
...
state.src = '123'
editor.chain().focus().insertContent('<vue-component/>').run()
}
</script>
and the vue component
:
<script setup>
import {NodeViewWrapper} from '@tiptap/vue-3'
const props = defineProps({
node: {
type: Object,
required: true
},
updateAttributes: {
type: Function,
required: true,
}
})
</script>
<template>
<node-view-wrapper class="vue-component" data-drag-handle="">
<p>{{ node.attrs.src }}</p>
</node-view-wrapper>
</template>
The default
of src
gets through but when I try to assign a reactive object (that gets created after mounting the editor
component) it ends up being undefined
.
This works:
src: {
default: '123'
}
but this doesn't:
...
src: {
default: state.src
}
...
const sendDataToExtension = async (editor, event) => {
// triggered upon event
...
state.src = '123'
editor.chain().focus().insertContent('<vue-component/>').run()
}
How do I send data to the vue component
that is created after mounting editor
?
Attempt:
editor.chain().focus().insertContent('<vue-component/>', {src: state.src}).run()
First I would say that I would recommend creating a purpose built extension, instead of having the general VueComponent
that you have now. If you extend more based on that extension you will have several extension competing for the tag. Move all code that you set in extend to the actual extentions, you can set any tag-name you want.
Now to what I believe is the problem here: insertContent
look like this:
insertContent: (value: Content, options?: {
parseOptions?: ParseOptions;
updateSelection?: boolean;
})
Content is declared as
export declare type Content = HTMLContent | JSONContent | JSONContent[] | null;
export declare type HTMLContent = string;
export declare type JSONContent = {
type?: string;
attrs?: Record<string, any>;
content?: JSONContent[];
marks?: {
type: string;
attrs?: Record<string, any>;
[key: string]: any;
}[];
text?: string;
[key: string]: any;
};
In your case you will have to add the src attribute to your html string, however I would recommend using the JSONContent
type in your case then:
editor.chain().focus().insertContent({type: "vueComponent", attrs:{src: state.src}}).run()
Here the type is the name that you set of the component.
Hope this makes sense, the documentation on tiptap is kind of good as well https://tiptap.dev/guide/custom-extensions/#attributes Let me know if you have further issues.