82 lines
3.5 KiB
TypeScript
82 lines
3.5 KiB
TypeScript
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 = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground hover:text-destructive transition-colors"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>';
|
|
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,
|
|
};
|
|
};
|
|
},
|
|
});
|