click here for image description
I am looking to include an image along with an editable caption using the tiptap extension
Check out this link for more information
I found a great example with ProseMirror, but I'm wondering if it's possible to achieve the same with tiptap.
Can someone guide me on what code needs to be written?
Below is the code snippet that I have worked on.
The image and caption are successfully displayed, but the caption is not yet editable.
// ./CustomImage.ts
// @ts-ignore
import { Node, Plugin } from 'tiptap'
// @ts-ignore
import { nodeInputRule } from 'tiptap-commands'
const IMAGE_INPUT_REGEX = /!\[(.+|:?)\]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
export default class CustomImage extends Node {
get name () {
return 'customImage'
}
get schema () {
return {
attrs: {
src: {
default: null
},
alt: {
default: null
},
title: {
default: null
},
caption: {
default: null
}
},
group: 'block',
selectable: false,
draggable: true,
parseDOM: [
{
tag: 'figure'
},
[
{
tag: 'img[src]',
getAttrs: (dom: any) => ({
src: dom.getAttribute('src'),
title: dom.getAttribute('title'),
alt: dom.getAttribute('alt')
})
},
{
tag: 'figcaption'
}
]
],
toDOM: (node: any) => [
'figure',
[
'img',
{
src: node.attrs.src,
title: node.attrs.title,
alt: node.attrs.alt
}
],
[
'figcaption',
{
contenteditable: 'true'
},
node.attrs.caption
]
]
}
}
commands ({ type }: any) {
return (attrs: any) => (state: any, dispatch: any) => {
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 }: any) {
return [
nodeInputRule(IMAGE_INPUT_REGEX, type, (match: any) => {
const [, alt, src, title] = match
return {
src,
alt,
title
}
})
]
}
get plugins () {
return [
new Plugin({
props: {
handleDOMEvents: {
drop (view: any, event: any) {
const hasFiles = event.dataTransfer &&
event.dataTransfer.files &&
event.dataTransfer.files.length
if (!hasFiles) {
return
}
const images = Array
.from(event.dataTransfer.files)
.filter((file: any) => (/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((image: any) => {
const reader = new FileReader()
reader.onload = (readerEvent: any) => {
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)
})
}
}
}
})
]
}
}