diff --git a/README.md b/README.md index c1034bd4..ee50840b 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ export default { | `editable` | `Boolean` | `true` | When set to `false` the editor is read-only. | | `doc` | `Object` | `null` | The editor state object used by Prosemirror. You can also pass HTML to the `content` slot. When used both, the `content` slot will be ignored. | | `extensions` | `Array` | `[]` | A list of extensions used, by the editor. This can be `Nodes`, `Marks` or `Plugins`. | +| `@init` | `Object` | `undefined` | This will return an Object with the current `state` and `view` of Prosemirror on init. | | `@update` | `Object` | `undefined` | This will return an Object with the current `state` of Prosemirror, a `getJSON()` and `getHTML()` function on every change. | ## Scoped Slots @@ -104,6 +105,7 @@ import { CodeBlockNode, HardBreakNode, HeadingNode, + ImageNode, ListItemNode, OrderedListNode, TodoItemNode, @@ -127,6 +129,7 @@ export default { new CodeBlockNode(), new HardBreakNode(), new HeadingNode({ maxLevel: 3 }), + new ImageNode(), new ListItemNode(), new OrderedListNode(), new TodoItemNode(), @@ -352,6 +355,24 @@ export default { ``` +## Development Setup + +Currently only Yarn is supported for development because of a feature called workspaces we are using here. + +``` bash +# install deps +yarn install + +# serve examples at localhost:3000 +yarn start + +# build dist files for packages +yarn build:packages + +# build dist files for examples +yarn build:examples +``` + ## Contributing Please see [CONTRIBUTING](CONTRIBUTING.md) for details. diff --git a/examples/Components/App/style.scss b/examples/Components/App/style.scss index b712758f..cea996d2 100644 --- a/examples/Components/App/style.scss +++ b/examples/Components/App/style.scss @@ -72,6 +72,12 @@ margin: 0; } } + + img { + max-width: 100%; + border-radius: 3px; + } + } } diff --git a/examples/Components/Routes/Images/index.vue b/examples/Components/Routes/Images/index.vue new file mode 100644 index 00000000..5b4e4f8f --- /dev/null +++ b/examples/Components/Routes/Images/index.vue @@ -0,0 +1,67 @@ + + + \ No newline at end of file diff --git a/examples/Components/Routes/TodoList/index.vue b/examples/Components/Routes/TodoList/index.vue index d736c1e5..929a6c3d 100644 --- a/examples/Components/Routes/TodoList/index.vue +++ b/examples/Components/Routes/TodoList/index.vue @@ -94,6 +94,7 @@ export default { }, data() { return { + customProp: 2, extensions: [ new BlockquoteNode(), new BulletListNode(), diff --git a/examples/Components/Subnavigation/index.vue b/examples/Components/Subnavigation/index.vue index 9989d6bb..5122532d 100644 --- a/examples/Components/Subnavigation/index.vue +++ b/examples/Components/Subnavigation/index.vue @@ -9,6 +9,9 @@ Links + + Images + Hiding Menu Bar diff --git a/examples/main.js b/examples/main.js index 9aa50e46..d7da7a3b 100644 --- a/examples/main.js +++ b/examples/main.js @@ -6,6 +6,7 @@ import App from 'Components/App' import RouteBasic from 'Components/Routes/Basic' import RouteMenuBubble from 'Components/Routes/MenuBubble' import RouteLinks from 'Components/Routes/Links' +import RouteImages from 'Components/Routes/Images' import RouteHidingMenuBar from 'Components/Routes/HidingMenuBar' import RouteTodoList from 'Components/Routes/TodoList' import RouteMarkdownShortcuts from 'Components/Routes/MarkdownShortcuts' @@ -42,6 +43,13 @@ const routes = [ githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Links', }, }, + { + path: '/images', + component: RouteImages, + meta: { + githubUrl: 'https://github.com/heyscrumpy/tiptap/tree/master/examples/Components/Routes/Images', + }, + }, { path: '/hiding-menu-bar', component: RouteHidingMenuBar, diff --git a/packages/tiptap-extensions/package.json b/packages/tiptap-extensions/package.json index 27a47e59..ed4bd5d5 100644 --- a/packages/tiptap-extensions/package.json +++ b/packages/tiptap-extensions/package.json @@ -1,6 +1,6 @@ { "name": "tiptap-extensions", - "version": "0.5.0", + "version": "0.7.0", "description": "Extensions for tiptap", "homepage": "https://tiptap.scrumpy.io", "license": "MIT", @@ -21,7 +21,7 @@ }, "dependencies": { "prosemirror-history": "^1.0.2", - "tiptap": "^0.7.0", + "tiptap": "^0.9.0", "tiptap-commands": "^0.2.4" } } diff --git a/packages/tiptap-extensions/src/index.js b/packages/tiptap-extensions/src/index.js index fccaf491..8e176784 100644 --- a/packages/tiptap-extensions/src/index.js +++ b/packages/tiptap-extensions/src/index.js @@ -3,6 +3,7 @@ export { default as BulletListNode } from './nodes/BulletList' export { default as CodeBlockNode } from './nodes/CodeBlock' export { default as HardBreakNode } from './nodes/HardBreak' export { default as HeadingNode } from './nodes/Heading' +export { default as ImageNode } from './nodes/Image' export { default as ListItemNode } from './nodes/ListItem' export { default as OrderedListNode } from './nodes/OrderedList' export { default as TodoItemNode } from './nodes/TodoItem' diff --git a/packages/tiptap-extensions/src/nodes/Image.js b/packages/tiptap-extensions/src/nodes/Image.js new file mode 100644 index 00000000..e01fcb53 --- /dev/null +++ b/packages/tiptap-extensions/src/nodes/Image.js @@ -0,0 +1,82 @@ +import { Node, Plugin } from 'tiptap' + +export default class ImageNode extends Node { + + get name() { + return 'image' + } + + get schema() { + return { + inline: true, + attrs: { + src: {}, + alt: { + default: null, + }, + title: { + default: null, + }, + }, + group: 'inline', + draggable: true, + parseDOM: [ + { + tag: 'img[src]', + getAttrs: dom => ({ + src: dom.getAttribute('src'), + title: dom.getAttribute('title'), + alt: dom.getAttribute('alt'), + }), + }, + ], + toDOM: node => ['img', node.attrs], + } + } + + get plugins() { + return [ + new Plugin({ + props: { + handleDOMEvents: { + drop(view, event) { + const hasFiles = event.dataTransfer + && event.dataTransfer.files + && event.dataTransfer.files.length + + if (!hasFiles) { + return + } + + const images = [...event.dataTransfer.files] + .filter(file => (/image/i).test(file.type)) + + if (images.length === 0) { + return + } + + 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({ + src: readerEvent.target.result, + }) + const transaction = view.state.tr.insert(coordinates.pos, node) + view.dispatch(transaction) + } + reader.readAsDataURL(image) + }) + }, + }, + }, + }), + ] + } + +} diff --git a/packages/tiptap/package.json b/packages/tiptap/package.json index 8c2ae593..5f9b8f34 100644 --- a/packages/tiptap/package.json +++ b/packages/tiptap/package.json @@ -1,6 +1,6 @@ { "name": "tiptap", - "version": "0.7.0", + "version": "0.9.0", "description": "A rich-text editor for Vue.js", "homepage": "https://tiptap.scrumpy.io", "license": "MIT", diff --git a/packages/tiptap/src/components/editor.js b/packages/tiptap/src/components/editor.js index 7ffd9f82..461aa0ba 100644 --- a/packages/tiptap/src/components/editor.js +++ b/packages/tiptap/src/components/editor.js @@ -100,6 +100,10 @@ export default { this.view = this.createView() this.commands = this.createCommands() this.updateMenuActions() + this.$emit('init', { + view: this.view, + state: this.state, + }) }, createSchema() { diff --git a/packages/tiptap/src/index.js b/packages/tiptap/src/index.js index 47c1fa3f..49281527 100644 --- a/packages/tiptap/src/index.js +++ b/packages/tiptap/src/index.js @@ -2,3 +2,4 @@ export { default as Editor } from './components/editor' export { default as Extension } from './utils/extension' export { default as Node } from './utils/node' export { default as Mark } from './utils/mark' +export { default as Plugin } from './utils/plugin' diff --git a/packages/tiptap/src/utils/plugin.js b/packages/tiptap/src/utils/plugin.js new file mode 100644 index 00000000..61f42341 --- /dev/null +++ b/packages/tiptap/src/utils/plugin.js @@ -0,0 +1,3 @@ +import { Plugin } from 'prosemirror-state' + +export default Plugin