import Image from "@tiptap/extension-image"; import { ImageOptions as DefaultImageOptions } from "@tiptap/extension-image"; import { ReactNodeViewRenderer } from "@tiptap/react"; import { ImageUploadPlugin } from "./image-upload"; import { mergeAttributes, Range } from "@tiptap/core"; export interface ImageOptions extends DefaultImageOptions { view: any; } export interface ImageAttributes { src?: string; alt?: string; title?: string; align?: string; attachmentId?: string; size?: number; width?: number; } declare module "@tiptap/core" { interface Commands { imageBlock: { setImage: (attributes: ImageAttributes) => ReturnType; setImageAt: ( attributes: ImageAttributes & { pos: number | Range }, ) => ReturnType; setImageAlign: (align: "left" | "center" | "right") => ReturnType; setImageWidth: (width: number) => ReturnType; }; } } export const TiptapImage = Image.extend({ name: "image", inline: false, group: "block", isolating: true, atom: true, defining: true, addOptions() { return { ...this.parent?.(), view: null, }; }, addAttributes() { return { src: { default: "", parseHTML: (element) => element.getAttribute("src"), renderHTML: (attributes) => ({ src: attributes.src, }), }, width: { default: "100%", parseHTML: (element) => element.getAttribute("width"), renderHTML: (attributes: ImageAttributes) => ({ width: attributes.width, }), }, align: { default: "center", parseHTML: (element) => element.getAttribute("data-align"), renderHTML: (attributes: ImageAttributes) => ({ "data-align": attributes.align, }), }, alt: { default: undefined, parseHTML: (element) => element.getAttribute("alt"), renderHTML: (attributes: ImageAttributes) => ({ alt: attributes.alt, }), }, attachmentId: { default: undefined, parseHTML: (element) => element.getAttribute("data-attachment-id"), renderHTML: (attributes: ImageAttributes) => ({ "data-attachment-id": attributes.align, }), }, size: { default: null, parseHTML: (element) => element.getAttribute("data-size"), renderHTML: (attributes: ImageAttributes) => ({ "data-size": attributes.size, }), }, }; }, renderHTML({ HTMLAttributes }) { return [ "img", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ]; }, addCommands() { return { setImage: (attrs: ImageAttributes) => ({ commands }) => { return commands.insertContent({ type: "image", attrs: attrs, }); }, setImageAt: (attrs) => ({ commands }) => { return commands.insertContentAt(attrs.pos, { type: "image", attrs: attrs, }); }, setImageAlign: (align) => ({ commands }) => commands.updateAttributes("image", { align }), setImageWidth: (width) => ({ commands }) => commands.updateAttributes("image", { width: `${Math.max(0, Math.min(100, width))}%`, }), }; }, addNodeView() { return ReactNodeViewRenderer(this.options.view); }, addProseMirrorPlugins() { return [ ImageUploadPlugin({ placeholderClass: "image-upload", }), ]; }, });