From b8886fa4085d90cf187c1834e29f60b925f5687b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Wed, 18 Nov 2020 22:50:07 +0100 Subject: [PATCH 01/28] add node view playground --- docs/src/components/Demo/index.vue | 2 +- .../src/demos/Examples/NodeView/Component.vue | 15 +++++ docs/src/demos/Examples/NodeView/Test.ts | 29 +++++++++ docs/src/demos/Examples/NodeView/index.vue | 65 +++++++++++++++++++ docs/src/docPages/examples/node-view.md | 3 + packages/core/src/ExtensionManager.ts | 1 + packages/core/src/types.ts | 1 + packages/vue/src/VueRenderer.ts | 25 ++++++- 8 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 docs/src/demos/Examples/NodeView/Component.vue create mode 100644 docs/src/demos/Examples/NodeView/Test.ts create mode 100644 docs/src/demos/Examples/NodeView/index.vue create mode 100644 docs/src/docPages/examples/node-view.md diff --git a/docs/src/components/Demo/index.vue b/docs/src/components/Demo/index.vue index b1c888c5..ce7aaf05 100644 --- a/docs/src/components/Demo/index.vue +++ b/docs/src/components/Demo/index.vue @@ -120,7 +120,7 @@ export default { } }) .filter(item => { - return ['vue', 'jsx', 'scss'].includes(item.extension) + return ['vue', 'ts', 'jsx', 'scss'].includes(item.extension) }) .sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue')) .toArray() diff --git a/docs/src/demos/Examples/NodeView/Component.vue b/docs/src/demos/Examples/NodeView/Component.vue new file mode 100644 index 00000000..0d99908c --- /dev/null +++ b/docs/src/demos/Examples/NodeView/Component.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/docs/src/demos/Examples/NodeView/Test.ts b/docs/src/demos/Examples/NodeView/Test.ts new file mode 100644 index 00000000..13fae1f8 --- /dev/null +++ b/docs/src/demos/Examples/NodeView/Test.ts @@ -0,0 +1,29 @@ +import { Node } from '@tiptap/core' +import { VueRenderer } from '@tiptap/vue' +import Component from './Component.vue' + +export default Node.create({ + name: 'test', + + group: 'block', + + draggable: true, + + selectable: false, + + parseHTML() { + return [ + { + tag: 'div[data-type="test"]', + }, + ] + }, + + renderHTML() { + return ['div', { 'data-type': 'test' }] + }, + + addNodeView() { + return VueRenderer(Component) + }, +}) diff --git a/docs/src/demos/Examples/NodeView/index.vue b/docs/src/demos/Examples/NodeView/index.vue new file mode 100644 index 00000000..9103e856 --- /dev/null +++ b/docs/src/demos/Examples/NodeView/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/docs/src/docPages/examples/node-view.md b/docs/src/docPages/examples/node-view.md new file mode 100644 index 00000000..1040abeb --- /dev/null +++ b/docs/src/docPages/examples/node-view.md @@ -0,0 +1,3 @@ +# Node View + + diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index eb10635a..eccb2b37 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -130,6 +130,7 @@ export default class ExtensionManager { getPos, decorations, HTMLAttributes, + extension, }) } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d2a8727a..a8ed6fee 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -90,6 +90,7 @@ export type NodeViewRendererProps = { getPos: (() => number) | boolean, decorations: Decoration[], HTMLAttributes: { [key: string]: any }, + extension: Node, } export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView diff --git a/packages/vue/src/VueRenderer.ts b/packages/vue/src/VueRenderer.ts index 60ba426c..e4e489ce 100644 --- a/packages/vue/src/VueRenderer.ts +++ b/packages/vue/src/VueRenderer.ts @@ -1,5 +1,9 @@ -import { NodeViewRendererProps } from '@tiptap/core' +import { Node, NodeViewRendererProps } from '@tiptap/core' import { NodeView } from 'prosemirror-view' + +import { + Node as ProseMirrorNode, +} from 'prosemirror-model' import Vue from 'vue' import { VueConstructor } from 'vue/types/umd' @@ -7,12 +11,19 @@ class VueNodeView implements NodeView { vm!: Vue + extension!: Node + + node!: ProseMirrorNode + constructor(component: Vue | VueConstructor, props: NodeViewRendererProps) { // eslint-disable-next-line const { node, editor, getPos } = props // eslint-disable-next-line const { view } = editor + this.extension = props.extension + this.node = props.node + this.mount(component) } @@ -33,7 +44,17 @@ class VueNodeView implements NodeView { return this.vm.$refs.content as Element } - stopEvent() { + stopEvent(event: Event): boolean { + const isDraggable = this.node.type.spec.draggable + const isCopy = event.type === 'copy' + const isPaste = event.type === 'paste' + const isCut = event.type === 'cut' + const isDrag = event.type.startsWith('drag') || event.type === 'drop' + + if ((isDraggable && isDrag) || isCopy || isPaste || isCut) { + return false + } + return true } From 0860170684d7a527a719bb0dc8be3af131411632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 19 Nov 2020 09:38:05 +0100 Subject: [PATCH 02/28] improve node views --- .../src/demos/Examples/NodeView/Component.vue | 30 +++++++++++- docs/src/demos/Examples/NodeView/Test.ts | 4 +- docs/src/demos/Examples/NodeView/index.vue | 11 +++-- packages/vue/src/VueRenderer.ts | 49 +++++++++++++++++-- 4 files changed, 84 insertions(+), 10 deletions(-) diff --git a/docs/src/demos/Examples/NodeView/Component.vue b/docs/src/demos/Examples/NodeView/Component.vue index 0d99908c..7c7f4f7b 100644 --- a/docs/src/demos/Examples/NodeView/Component.vue +++ b/docs/src/demos/Examples/NodeView/Component.vue @@ -1,12 +1,38 @@ diff --git a/docs/src/demos/Examples/NodeView/Test.ts b/docs/src/demos/Examples/NodeView/Test.ts index 13fae1f8..33c790ee 100644 --- a/docs/src/demos/Examples/NodeView/Test.ts +++ b/docs/src/demos/Examples/NodeView/Test.ts @@ -7,6 +7,8 @@ export default Node.create({ group: 'block', + content: 'inline*', + draggable: true, selectable: false, @@ -20,7 +22,7 @@ export default Node.create({ }, renderHTML() { - return ['div', { 'data-type': 'test' }] + return ['div', { 'data-type': 'test' }, 0] }, addNodeView() { diff --git a/docs/src/demos/Examples/NodeView/index.vue b/docs/src/demos/Examples/NodeView/index.vue index 9103e856..c83fb0b3 100644 --- a/docs/src/demos/Examples/NodeView/index.vue +++ b/docs/src/demos/Examples/NodeView/index.vue @@ -34,10 +34,15 @@ export default { Test, ], content: ` -

test

-
-

test

+

paragraph

+
+ text +
+

paragraph

`, + onUpdate: () => { + console.log(this.editor.getHTML()) + }, }) }, diff --git a/packages/vue/src/VueRenderer.ts b/packages/vue/src/VueRenderer.ts index e4e489ce..ffe710bd 100644 --- a/packages/vue/src/VueRenderer.ts +++ b/packages/vue/src/VueRenderer.ts @@ -1,4 +1,4 @@ -import { Node, NodeViewRendererProps } from '@tiptap/core' +import { Editor, Node, NodeViewRendererProps } from '@tiptap/core' import { NodeView } from 'prosemirror-view' import { @@ -6,21 +6,29 @@ import { } from 'prosemirror-model' import Vue from 'vue' import { VueConstructor } from 'vue/types/umd' +// import Inner from './components/Inner.vue' + +// const Inner = Vue.extend() class VueNodeView implements NodeView { vm!: Vue + editor: Editor + extension!: Node node!: ProseMirrorNode + id!: string + constructor(component: Vue | VueConstructor, props: NodeViewRendererProps) { // eslint-disable-next-line const { node, editor, getPos } = props // eslint-disable-next-line const { view } = editor + this.editor = props.editor this.extension = props.extension this.node = props.node @@ -28,11 +36,40 @@ class VueNodeView implements NodeView { } mount(component: Vue | VueConstructor) { - const Component = Vue.extend(component) + this.id = `id_${Math.random().toString(36).replace('0.', '')}` + + const Inner = Vue.extend({ + functional: true, + render: createElement => { + return createElement( + 'div', { + style: { + whiteSpace: 'pre-wrap', + }, + attrs: { + id: this.id, + }, + }, + ) + }, + }) + + const Component = Vue + .extend(component) + .extend({ + components: { + Inner, + }, + }) + + const props = { + editor: this.editor, + inner: Inner, + } this.vm = new Component({ // parent: this.parent, - // propsData: props, + propsData: props, }).$mount() } @@ -41,7 +78,11 @@ class VueNodeView implements NodeView { } get contentDOM() { - return this.vm.$refs.content as Element + if (this.vm.$el.id === this.id) { + return this.vm.$el + } + + return this.vm.$el.querySelector(`#${this.id}`) } stopEvent(event: Event): boolean { From 2d2ffb60ef261d7f709b9b8ea1537d60c8435300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Thu, 19 Nov 2020 10:36:06 +0100 Subject: [PATCH 03/28] use functional component --- docs/src/demos/Examples/NodeView/Component.vue | 3 ++- packages/vue/src/VueRenderer.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/src/demos/Examples/NodeView/Component.vue b/docs/src/demos/Examples/NodeView/Component.vue index 7c7f4f7b..dcc31796 100644 --- a/docs/src/demos/Examples/NodeView/Component.vue +++ b/docs/src/demos/Examples/NodeView/Component.vue @@ -7,7 +7,8 @@ --> - + + - - diff --git a/docs/src/demos/Examples/NodeView/Test.ts b/docs/src/demos/Examples/NodeView/Test.ts index 981386cf..5d4b7911 100644 --- a/docs/src/demos/Examples/NodeView/Test.ts +++ b/docs/src/demos/Examples/NodeView/Test.ts @@ -13,8 +13,6 @@ export default Node.create({ selectable: false, - // atom: true, - addAttributes() { return { checked: { diff --git a/packages/vue/src/VueRenderer.ts b/packages/vue/src/VueRenderer.ts index dbca0f82..2d132f5c 100644 --- a/packages/vue/src/VueRenderer.ts +++ b/packages/vue/src/VueRenderer.ts @@ -6,9 +6,6 @@ import { } from 'prosemirror-model' import Vue from 'vue' import { VueConstructor } from 'vue/types/umd' -// import Inner from './components/Inner.vue' - -// const Inner = Vue.extend() class VueNodeView implements NodeView { @@ -31,16 +28,16 @@ class VueNodeView implements NodeView { this.extension = props.extension this.node = props.node this.getPos = props.getPos + this.createUniqueId() this.mount(component) } - mount(component: Vue | VueConstructor) { - this.id = `id_${Math.random().toString(36).replace('0.', '')}` + createUniqueId() { + this.id = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}` + } - const { id } = this - - const Inner = Vue.extend({ - // functional: true, + createNodeViewWrapper() { + return Vue.extend({ inheritAttrs: false, props: { as: { @@ -48,38 +45,69 @@ class VueNodeView implements NodeView { default: 'div', }, }, - render(createElement, context) { + render(createElement) { + return createElement( + this.as, { + style: { + whiteSpace: 'normal', + }, + }, + this.$slots.default, + ) + }, + }) + } + + createNodeViewContent() { + const { id } = this + + return Vue.extend({ + inheritAttrs: false, + props: { + as: { + type: String, + default: 'div', + }, + }, + render(createElement) { return createElement( - // context.props.as, { this.as, { style: { whiteSpace: 'pre-wrap', }, attrs: { id, - // contenteditable: true, + contenteditable: true, }, }, ) }, }) + } + + mount(component: Vue | VueConstructor) { + const NodeViewWrapper = this.createNodeViewWrapper() + const NodeViewContent = this.createNodeViewContent() const Component = Vue .extend(component) .extend({ components: { - Inner, + NodeViewWrapper, + NodeViewContent, }, }) const props = { editor: this.editor, - inner: Inner, + NodeViewWrapper, + NodeViewContent, node: this.node, - updateAttrs: (attrs: {}) => this.updateAttrs(attrs), + updateAttributes: (attrs: {}) => this.updateAttributes(attrs), } this.vm = new Component({ + // TODO: get parent component // parent: this.parent, propsData: props, }).$mount() @@ -98,8 +126,6 @@ class VueNodeView implements NodeView { } stopEvent(event: Event): boolean { - // console.log(event.type) - const isDraggable = this.node.type.spec.draggable const isCopy = event.type === 'copy' const isPaste = event.type === 'paste' @@ -139,7 +165,6 @@ class VueNodeView implements NodeView { this.node = node this.decorations = decorations - this.updateComponentProps() return true @@ -150,24 +175,17 @@ class VueNodeView implements NodeView { this.vm.$props.decorations = this.decorations } - updateAttrs(attrs: {}) { + updateAttributes(attributes: {}) { if (!this.editor.view.editable) { return } const { state } = this.editor.view - // const { type } = this.node const pos = this.getPos() - const newAttrs = { + const transaction = state.tr.setNodeMarkup(pos, undefined, { ...this.node.attrs, - ...attrs, - } - // const transaction = this.isMark - // ? state.tr - // .removeMark(pos.from, pos.to, type) - // .addMark(pos.from, pos.to, type.create(newAttrs)) - // : state.tr.setNodeMarkup(pos, null, newAttrs) - const transaction = state.tr.setNodeMarkup(pos, undefined, newAttrs) + ...attributes, + }) this.editor.view.dispatch(transaction) } diff --git a/packages/vue/src/components/EditorContent.ts b/packages/vue/src/components/EditorContent.ts index 2f74d5ff..bdb5b8dd 100644 --- a/packages/vue/src/components/EditorContent.ts +++ b/packages/vue/src/components/EditorContent.ts @@ -17,6 +17,7 @@ export default Vue.extend({ if (editor && editor.options.element) { this.$nextTick(() => { this.$el.appendChild(editor.options.element.firstChild) + console.log('append') // editor.setParentComponent(this) }) } From ae6630e6dcdb12a1e9efc9dd2e2b6bac3ee0d8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 24 Nov 2020 10:52:27 +0100 Subject: [PATCH 07/28] remove log --- packages/vue/src/components/EditorContent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vue/src/components/EditorContent.ts b/packages/vue/src/components/EditorContent.ts index bdb5b8dd..2f74d5ff 100644 --- a/packages/vue/src/components/EditorContent.ts +++ b/packages/vue/src/components/EditorContent.ts @@ -17,7 +17,6 @@ export default Vue.extend({ if (editor && editor.options.element) { this.$nextTick(() => { this.$el.appendChild(editor.options.element.firstChild) - console.log('append') // editor.setParentComponent(this) }) } From f4368f902156c82fffb28990f328767b4494525b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 24 Nov 2020 11:40:09 +0100 Subject: [PATCH 08/28] improve support for drag and drop --- .../src/demos/Examples/NodeView/Component.vue | 1 + packages/vue/src/VueRenderer.ts | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/src/demos/Examples/NodeView/Component.vue b/docs/src/demos/Examples/NodeView/Component.vue index e03e0490..386c68a6 100644 --- a/docs/src/demos/Examples/NodeView/Component.vue +++ b/docs/src/demos/Examples/NodeView/Component.vue @@ -6,6 +6,7 @@
checked: {{ node.attrs.checked }}
+
diff --git a/packages/vue/src/VueRenderer.ts b/packages/vue/src/VueRenderer.ts index 2d132f5c..5cdb96e2 100644 --- a/packages/vue/src/VueRenderer.ts +++ b/packages/vue/src/VueRenderer.ts @@ -23,6 +23,8 @@ class VueNodeView implements NodeView { getPos!: any + isDragging = false + constructor(component: Vue | VueConstructor, props: NodeViewRendererProps) { this.editor = props.editor this.extension = props.extension @@ -38,7 +40,6 @@ class VueNodeView implements NodeView { createNodeViewWrapper() { return Vue.extend({ - inheritAttrs: false, props: { as: { type: String, @@ -125,14 +126,32 @@ class VueNodeView implements NodeView { return this.vm.$el.querySelector(`#${this.id}`) } - stopEvent(event: Event): boolean { + stopEvent(event: Event) { const isDraggable = this.node.type.spec.draggable - const isCopy = event.type === 'copy' - const isPaste = event.type === 'paste' - const isCut = event.type === 'cut' - const isDrag = event.type.startsWith('drag') || event.type === 'drop' + const isCopyEvent = event.type === 'copy' + const isPasteEvent = event.type === 'paste' + const isCutEvent = event.type === 'cut' + const isDragEvent = event.type.startsWith('drag') || event.type === 'drop' - if ((isDraggable && isDrag) || isCopy || isPaste || isCut) { + if (isDragEvent && !this.isDragging) { + event.preventDefault() + } + + if (isDraggable && !this.isDragging && event.type === 'mousedown') { + const target = (event.target as HTMLElement) + const dragHandle = target.closest('[data-drag-handle]') + const isValidDragHandle = dragHandle + && (this.dom === dragHandle || this.dom.contains(dragHandle)) + + if (isValidDragHandle) { + this.isDragging = true + document.addEventListener('dragend', () => { + this.isDragging = false + }, { once: true }) + } + } + + if (this.isDragging || isCopyEvent || isPasteEvent || isCutEvent) { return false } From d93897958453583a3af5c2e6335453fd3f14d0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 24 Nov 2020 21:50:54 +0100 Subject: [PATCH 09/28] try to solve nested drag handles --- .../src/demos/Examples/NodeView/Component.vue | 2 +- docs/src/demos/Examples/NodeView/Test.ts | 5 ++- docs/src/demos/Examples/NodeView/index.vue | 5 ++- packages/vue/src/VueRenderer.ts | 43 ++++++++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/src/demos/Examples/NodeView/Component.vue b/docs/src/demos/Examples/NodeView/Component.vue index 386c68a6..14ff48f1 100644 --- a/docs/src/demos/Examples/NodeView/Component.vue +++ b/docs/src/demos/Examples/NodeView/Component.vue @@ -1,5 +1,5 @@