import { mergeAttributes, Node } from "@tiptap/react" import { ReactNodeViewRenderer } from "@tiptap/react" import { ImageUploadNode as ImageUploadNodeComponent } from "@/components/tiptap-node/image-upload-node/image-upload-node" export type UploadFunction = ( file: File, onProgress?: (event: { progress: number }) => void, abortSignal?: AbortSignal ) => Promise export interface ImageUploadNodeOptions { /** * Acceptable file types for upload. * @default 'image/*' */ accept?: string /** * Maximum number of files that can be uploaded. * @default 1 */ limit?: number /** * Maximum file size in bytes (0 for unlimited). * @default 0 */ maxSize?: number /** * Function to handle the upload process. */ upload?: UploadFunction /** * Callback for upload errors. */ onError?: (error: Error) => void /** * Callback for successful uploads. */ onSuccess?: (url: string) => void } declare module "@tiptap/react" { interface Commands { imageUpload: { setImageUploadNode: (options?: ImageUploadNodeOptions) => ReturnType } } } /** * A Tiptap node extension that creates an image upload component. * @see registry/tiptap-node/image-upload-node/image-upload-node */ export const ImageUploadNode = Node.create({ name: "imageUpload", group: "block", draggable: true, atom: true, addOptions() { return { accept: "image/*", limit: 1, maxSize: 0, upload: undefined, onError: undefined, onSuccess: undefined, } }, addAttributes() { return { accept: { default: this.options.accept, }, limit: { default: this.options.limit, }, maxSize: { default: this.options.maxSize, }, } }, parseHTML() { return [{ tag: 'div[data-type="image-upload"]' }] }, renderHTML({ HTMLAttributes }) { return [ "div", mergeAttributes({ "data-type": "image-upload" }, HTMLAttributes), ] }, addNodeView() { return ReactNodeViewRenderer(ImageUploadNodeComponent) }, addCommands() { return { setImageUploadNode: (options = {}) => ({ commands }) => { return commands.insertContent({ type: this.name, attrs: options, }) }, } }, /** * Adds Enter key handler to trigger the upload component when it's selected. */ addKeyboardShortcuts() { return { Enter: ({ editor }) => { const { selection } = editor.state const { nodeAfter } = selection.$from if ( nodeAfter && nodeAfter.type.name === "imageUpload" && editor.isActive("imageUpload") ) { const nodeEl = editor.view.nodeDOM(selection.$from.pos) if (nodeEl && nodeEl instanceof HTMLElement) { // Since NodeViewWrapper is wrapped with a div, we need to click the first child const firstChild = nodeEl.firstChild if (firstChild && firstChild instanceof HTMLElement) { firstChild.click() return true } } } return false }, } }, }) export default ImageUploadNode