diff --git a/docs/src/demos/Extensions/Image/index.vue b/docs/src/demos/Extensions/Image/index.vue new file mode 100644 index 00000000..52615a3b --- /dev/null +++ b/docs/src/demos/Extensions/Image/index.vue @@ -0,0 +1,45 @@ + + + diff --git a/docs/src/docPages/api/extensions/image.md b/docs/src/docPages/api/extensions/image.md new file mode 100644 index 00000000..e811d826 --- /dev/null +++ b/docs/src/docPages/api/extensions/image.md @@ -0,0 +1,16 @@ +# Image + +## Installation +```bash +# With npm +npm install @tiptap/extension-image + +# Or: With Yarn +yarn add @tiptap/extension-image +``` + +## Source code +[packages/extension-image/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-image/) + +## Usage + diff --git a/docs/src/links.yaml b/docs/src/links.yaml index ce4a8bc9..1b6e7e8d 100644 --- a/docs/src/links.yaml +++ b/docs/src/links.yaml @@ -132,6 +132,9 @@ link: /api/extensions/history - title: HorizontalRule link: /api/extensions/horizontal-rule + - title: Image + link: /api/extensions/image + draft: true - title: Italic link: /api/extensions/italic - title: Link @@ -163,6 +166,7 @@ link: /api/extensions/text - title: Text Align link: /api/extensions/text-align + draft: true # - title: TodoItem # link: /api/extensions/todo-item # draft: true diff --git a/packages/extension-image/index.ts b/packages/extension-image/index.ts new file mode 100644 index 00000000..605a4b52 --- /dev/null +++ b/packages/extension-image/index.ts @@ -0,0 +1,125 @@ +import { Command, createNode, nodeInputRule } from '@tiptap/core' +import { Plugin } from 'prosemirror-state' + +const IMAGE_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/ + +const Image = createNode({ + name: 'image', + + inline: true, + + group: 'inline', + + addAttributes() { + return { + src: { + default: null, + }, + alt: { + default: null, + }, + title: { + default: null, + }, + } + }, + + parseHTML() { + return [ + { + tag: 'img[src]', + }, + ] + }, + + renderHTML({ attributes }) { + return ['img', attributes] + }, + + addCommands() { + return { + image: (attrs: any): Command => ({ tr }) => { + const { selection } = tr + console.log({ selection }) + // const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos + const position = selection.$anchor ? selection.$anchor.pos : selection.$to.pos + const node = this.type.create(attrs) + tr.insert(position, node) + + return true + }, + } + }, + + addInputRules() { + return [ + nodeInputRule(IMAGE_INPUT_REGEX, this.type, match => { + const [, alt, src, title] = match + return { + src, + alt, + title, + } + }), + ] + }, + + addProseMirrorPlugins() { + return [ + new Plugin({ + props: { + handleDOMEvents: { + drop(view, event) { + const hasFiles = event.dataTransfer + && event.dataTransfer.files + && event.dataTransfer.files.length + + if (!hasFiles) { + return false + } + + const images = Array + // @ts-ignore + .from(event.dataTransfer.files) + .filter(file => (/image/i).test(file.type)) + + if (images.length === 0) { + return false + } + + event.preventDefault() + + const { schema } = view.state + const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY }) + + images.forEach(image => { + const reader = new FileReader() + + reader.onload = readerEvent => { + const node = schema.nodes.image.create({ + // @ts-ignore + src: readerEvent.target.result, + }) + // @ts-ignore + const transaction = view.state.tr.insert(coordinates.pos, node) + view.dispatch(transaction) + } + reader.readAsDataURL(image) + }) + + return true + }, + }, + }, + }), + ] + }, +}) + +export default Image + +declare module '@tiptap/core/src/Editor' { + interface AllExtensions { + Image: typeof Image, + } +} diff --git a/packages/extension-image/package.json b/packages/extension-image/package.json new file mode 100644 index 00000000..5b017870 --- /dev/null +++ b/packages/extension-image/package.json @@ -0,0 +1,17 @@ +{ + "name": "@tiptap/extension-image", + "version": "1.0.0", + "source": "index.ts", + "main": "dist/tiptap-extension-image.js", + "umd:main": "dist/tiptap-extension-image.umd.js", + "module": "dist/tiptap-extension-image.mjs", + "unpkg": "dist/tiptap-extension-image.js", + "jsdelivr": "dist/tiptap-extension-image.js", + "files": [ + "src", + "dist" + ], + "peerDependencies": { + "@tiptap/core": "2.x" + } +}