{"version":3,"file":"index.js","names":[],"sources":["../src/image.ts","../src/index.ts"],"sourcesContent":["import type { ResizableNodeViewDirection, ResizableNodeViewOptions } from '@tiptap/core'\nimport {\n getRenderedAttributes,\n mergeAttributes,\n Node,\n nodeInputRule,\n ResizableNodeView,\n} from '@tiptap/core'\n\nexport interface ImageOptions {\n /**\n * Controls if the image node should be inline or not.\n * @default false\n * @example true\n */\n inline: boolean\n\n /**\n * Controls if base64 images are allowed. Enable this if you want to allow\n * base64 image urls in the `src` attribute.\n * @default false\n * @example true\n */\n allowBase64: boolean\n\n /**\n * HTML attributes to add to the image element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record\n\n /**\n * Controls if the image should be resizable and how the resize is configured.\n * @default false\n * @example { directions: { top: true, right: true, bottom: true, left: true, topLeft: true, topRight: true, bottomLeft: true, bottomRight: true }, minWidth: 100, minHeight: 100 }\n */\n resize:\n | {\n enabled: boolean\n directions?: ResizableNodeViewDirection[]\n minWidth?: number\n minHeight?: number\n alwaysPreserveAspectRatio?: boolean\n }\n | false\n}\n\nexport interface SetImageOptions {\n src: string\n alt?: string\n title?: string\n width?: number\n height?: number\n}\n\ndeclare module '@tiptap/core' {\n interface Commands {\n image: {\n /**\n * Add an image\n * @param options The image attributes\n * @example\n * editor\n * .commands\n * .setImage({ src: 'https://tiptap.dev/logo.png', alt: 'tiptap', title: 'tiptap logo' })\n */\n setImage: (options: SetImageOptions) => ReturnType\n }\n }\n}\n\n/**\n * Matches an image to a ![image](src \"title\") on input.\n */\nexport const inputRegex = /(?:^|\\s)(!\\[(.+|:?)]\\((\\S+)(?:(?:\\s+)[\"'](\\S+)[\"'])?\\))$/\n\n/**\n * This extension allows you to insert images.\n * @see https://www.tiptap.dev/api/nodes/image\n */\nexport const Image = Node.create({\n name: 'image',\n\n addOptions() {\n return {\n inline: false,\n allowBase64: false,\n HTMLAttributes: {},\n resize: false,\n }\n },\n\n inline() {\n return this.options.inline\n },\n\n group() {\n return this.options.inline ? 'inline' : 'block'\n },\n\n draggable: true,\n\n addAttributes() {\n return {\n src: {\n default: null,\n },\n alt: {\n default: null,\n },\n title: {\n default: null,\n },\n width: {\n default: null,\n },\n height: {\n default: null,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: this.options.allowBase64 ? 'img[src]' : 'img[src]:not([src^=\"data:\"])',\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]\n },\n\n parseMarkdown: (token, helpers) => {\n return helpers.createNode('image', {\n src: token.href,\n title: token.title,\n alt: token.text,\n })\n },\n\n renderMarkdown: node => {\n const src = node.attrs?.src ?? ''\n const alt = node.attrs?.alt ?? ''\n const title = node.attrs?.title ?? ''\n\n return title ? `![${alt}](${src} \"${title}\")` : `![${alt}](${src})`\n },\n\n addNodeView() {\n if (!this.options.resize || !this.options.resize.enabled || typeof document === 'undefined') {\n return null\n }\n\n const { directions, minWidth, minHeight, alwaysPreserveAspectRatio } = this.options.resize\n const resizeManagedAttributes = new Set(['src', 'width', 'height'])\n\n return ({ node, getPos, HTMLAttributes, editor }) => {\n const el = document.createElement('img')\n el.draggable = false\n\n const mergedAttributes = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)\n\n Object.entries(mergedAttributes).forEach(([key, value]) => {\n if (value != null) {\n switch (key) {\n case 'src':\n case 'width':\n case 'height':\n break\n default:\n el.setAttribute(key, value)\n break\n }\n }\n })\n\n if (mergedAttributes.src !== null) {\n el.src = mergedAttributes.src\n }\n\n let previousHTMLAttributes = { ...HTMLAttributes }\n const syncImageSource = (src: unknown) => {\n if (typeof src === 'string' && src !== '') {\n if (el.getAttribute('src') !== src) {\n el.src = src\n }\n\n return\n }\n\n if (el.hasAttribute('src')) {\n el.removeAttribute('src')\n }\n\n if (el.src !== '') {\n el.src = ''\n }\n }\n\n syncImageSource(HTMLAttributes.src)\n\n const onUpdate: ResizableNodeViewOptions['onUpdate'] = (\n updatedNode: Parameters[0],\n ) => {\n if (updatedNode.type !== node.type) {\n return false\n }\n\n const extensionAttributes = editor.extensionManager.attributes.filter(\n attribute => attribute.type === updatedNode.type.name,\n )\n const newHTMLAttributes = getRenderedAttributes(updatedNode, extensionAttributes)\n\n // Remove attributes that were previously rendered but are no longer present\n Object.keys(previousHTMLAttributes).forEach(key => {\n if (!resizeManagedAttributes.has(key) && !(key in newHTMLAttributes)) {\n el.removeAttribute(key)\n }\n })\n\n Object.entries(newHTMLAttributes).forEach(([key, value]) => {\n if (resizeManagedAttributes.has(key)) {\n return\n }\n\n if (value != null) {\n el.setAttribute(key, value)\n } else {\n el.removeAttribute(key)\n }\n })\n\n syncImageSource(newHTMLAttributes.src)\n\n previousHTMLAttributes = newHTMLAttributes\n\n return true\n }\n\n const nodeView = new ResizableNodeView({\n element: el,\n editor,\n node,\n getPos,\n onResize: (width, height) => {\n el.style.width = `${width}px`\n el.style.height = `${height}px`\n },\n onCommit: (width, height) => {\n const pos = getPos()\n if (pos === undefined) {\n return\n }\n\n this.editor\n .chain()\n .setNodeSelection(pos)\n .updateAttributes(this.name, {\n width,\n height,\n })\n .run()\n },\n onUpdate,\n options: {\n directions,\n min: {\n width: minWidth,\n height: minHeight,\n },\n preserveAspectRatio: alwaysPreserveAspectRatio === true,\n },\n })\n\n const dom = nodeView.dom as HTMLElement\n\n // when image is loaded, show the node view to get the correct dimensions\n const showNodeView = () => {\n dom.style.visibility = ''\n dom.style.pointerEvents = ''\n }\n\n dom.style.visibility = 'hidden'\n dom.style.pointerEvents = 'none'\n\n if (el.complete && el.naturalWidth > 0) {\n // A cached image is already complete before this node view mounts, so\n // `load` never fires again and the image would stay hidden forever.\n showNodeView()\n } else {\n el.onload = showNodeView\n // Reveal on error too, so a broken src renders as an inspectable broken\n // image instead of an invisible node.\n el.onerror = showNodeView\n }\n\n return nodeView\n }\n },\n\n addCommands() {\n return {\n setImage:\n options =>\n ({ commands }) => {\n return commands.insertContent({\n type: this.name,\n attrs: options,\n })\n },\n }\n },\n\n addInputRules() {\n return [\n nodeInputRule({\n find: inputRegex,\n type: this.type,\n getAttributes: match => {\n const [, , alt, src, title] = match\n\n return { src, alt, title }\n },\n }),\n ]\n },\n})\n","import { Image } from './image.js'\n\nexport * from './image.js'\n\nexport default Image\n"],"mappings":"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"}