import Image from "@tiptap/extension-image"; export const CustomImage = Image.extend({ name: "image", group: "block", selectable: true, draggable: true, addKeyboardShortcuts() { return { Backspace: () => { const { selection } = this.editor.state; if (!selection.empty) { const selectedNode = this.editor.state.doc.nodeAt(selection.from); if (selectedNode?.type.name === this.name) { return true; // Prevent default deletion if image is selected } } return false; }, Delete: () => { const { selection } = this.editor.state; if (!selection.empty) { const selectedNode = this.editor.state.doc.nodeAt(selection.from); if (selectedNode?.type.name === this.name) { return true; // Prevent default deletion if image is selected } } return false; }, }; }, addNodeView() { return ({ node, getPos, editor }) => { const wrapper = document.createElement('div'); // Give it position: relative and flex/inline-block to wrap the image tightly wrapper.classList.add('image-wrapper', 'relative', 'inline-block', 'my-4', 'group', 'w-fit', 'max-w-full'); wrapper.style.display = 'block'; // Block level to not mess up flow wrapper.style.marginInline = 'auto'; // Center if desired, or let it flow const img = document.createElement('img'); Object.entries(node.attrs).forEach(([key, value]) => { if (value !== null && value !== undefined) { img.setAttribute(key, value); } }); img.classList.add('rounded-lg', 'border', 'border-border', 'max-w-full', 'h-auto', 'cursor-default'); // Delete button overlay const overlay = document.createElement('div'); overlay.classList.add('absolute', 'top-2', 'right-2', 'hidden', 'group-hover:flex', 'bg-background/80', 'backdrop-blur-sm', 'p-1', 'rounded-lg', 'border', 'border-border', 'shadow-sm', 'z-10'); const deleteBtn = document.createElement('button'); deleteBtn.innerHTML = ''; deleteBtn.classList.add('p-1', 'cursor-pointer'); deleteBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); if (typeof getPos === 'function') { const pos = getPos(); if (typeof pos === 'number') { editor.commands.deleteRange({ from: pos, to: pos + node.nodeSize }); } } }; overlay.appendChild(deleteBtn); wrapper.appendChild(img); wrapper.appendChild(overlay); return { dom: wrapper, stopEvent: () => true, ignoreMutation: () => true, }; }; }, });