diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 5aeb252a9..badf9cd8d 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -64,7 +64,7 @@ export class Editor extends EventEmitter { constructor(options: Partial = {}) { super() - this.options = { ...this.options, ...options } + this.setOptions(options) this.createExtensionManager() this.createCommandManager() this.createSchema() @@ -121,6 +121,10 @@ export class Editor extends EventEmitter { */ public setOptions(options: Partial = {}): void { this.options = { ...this.options, ...options } + } + + public setEditable(editable: boolean): void { + this.setOptions({ editable }) if (this.view && this.state && !this.isDestroyed) { this.view.updateState(this.state) @@ -212,7 +216,14 @@ export class Editor extends EventEmitter { // `editor.view` is not yet available at this time. // Therefore we will add all plugins and node views directly afterwards. const newState = this.state.reconfigure({ - plugins: this.extensionManager.plugins, + plugins: [ + new Plugin({ + view: () => ({ + update: () => this.emit('viewUpdate'), + }), + }), + ...this.extensionManager.plugins, + ], }) this.view.updateState(newState) @@ -410,7 +421,7 @@ export class Editor extends EventEmitter { /** * Check if the editor is already destroyed. */ - private get isDestroyed(): boolean { + public get isDestroyed(): boolean { // @ts-ignore return !this.view?.docView } diff --git a/packages/vue-3/src/NodeViewContent.ts b/packages/vue-3/src/NodeViewContent.ts new file mode 100644 index 000000000..eee3c8003 --- /dev/null +++ b/packages/vue-3/src/NodeViewContent.ts @@ -0,0 +1,25 @@ +import { h, defineComponent } from 'vue' + +export const NodeViewContent = defineComponent({ + props: { + as: { + type: String, + default: 'div', + }, + }, + + inject: ['editable'], + + render() { + return h( + this.as, { + style: { + whiteSpace: 'pre-wrap', + }, + 'data-node-view-content': '', + // @ts-ignore (https://github.com/vuejs/vue-next/issues/3031) + contenteditable: this.editable.value, + }, + ) + }, +}) diff --git a/packages/vue-3/src/NodeViewWrapper.ts b/packages/vue-3/src/NodeViewWrapper.ts new file mode 100644 index 000000000..2be7e271e --- /dev/null +++ b/packages/vue-3/src/NodeViewWrapper.ts @@ -0,0 +1,26 @@ +import { h, defineComponent } from 'vue' + +export const NodeViewWrapper = defineComponent({ + props: { + as: { + type: String, + default: 'div', + }, + }, + + inject: ['onDragStart'], + + render() { + return h( + this.as, { + style: { + whiteSpace: 'normal', + }, + 'data-node-view-wrapper': '', + // @ts-ignore + onDragStart: this.onDragStart, + }, + this.$slots.default?.(), + ) + }, +}) diff --git a/packages/vue-3/src/VueNodeViewRenderer.ts b/packages/vue-3/src/VueNodeViewRenderer.ts index 6469a1e94..d9a639294 100644 --- a/packages/vue-3/src/VueNodeViewRenderer.ts +++ b/packages/vue-3/src/VueNodeViewRenderer.ts @@ -4,8 +4,8 @@ import { NodeViewRendererProps, } from '@tiptap/core' import { - h, - markRaw, + ref, + provide, Component, defineComponent, } from 'vue' @@ -32,8 +32,6 @@ class VueNodeView implements NodeView { decorations!: Decoration[] - id!: string - getPos!: any isDragging = false @@ -49,40 +47,10 @@ class VueNodeView implements NodeView { this.extension = props.extension this.node = props.node this.getPos = props.getPos - this.createUniqueId() this.mount(component) } - createUniqueId() { - this.id = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}` - } - - createNodeViewWrapper() { - const { handleDragStart } = this - const dragstart = handleDragStart.bind(this) - - return markRaw(defineComponent({ - props: { - as: { - type: String, - default: 'div', - }, - }, - render() { - return h( - this.as, { - style: { - whiteSpace: 'normal', - }, - onDragStart: dragstart, - }, - this.$slots.default?.(), - ) - }, - })) - } - - handleDragStart(event: DragEvent) { + onDragStart(event: DragEvent) { const { view } = this.editor const target = (event.target as HTMLElement) @@ -99,39 +67,8 @@ class VueNodeView implements NodeView { view.dispatch(transaction) } - createNodeViewContent() { - const { id } = this - const { isEditable } = this.editor - - return markRaw(defineComponent({ - inheritAttrs: false, - props: { - as: { - type: String, - default: 'div', - }, - }, - render() { - return h( - this.as, { - style: { - whiteSpace: 'pre-wrap', - }, - id, - contenteditable: isEditable, - }, - ) - }, - })) - } - mount(component: Component) { - const NodeViewWrapper = this.createNodeViewWrapper() - const NodeViewContent = this.createNodeViewContent() - const props = { - NodeViewWrapper, - NodeViewContent, editor: this.editor, node: this.node, decorations: this.decorations, @@ -141,12 +78,21 @@ class VueNodeView implements NodeView { updateAttributes: (attributes = {}) => this.updateAttributes(attributes), } + const onDragStart = this.onDragStart.bind(this) + const isEditable = ref(this.editor.isEditable) + + this.editor.on('viewUpdate', () => { + isEditable.value = this.editor.isEditable + }) + const extendedComponent = defineComponent({ extends: { ...component }, props: Object.keys(props), - components: { - NodeViewWrapper, - NodeViewContent, + setup() { + provide('onDragStart', onDragStart) + provide('editable', isEditable) + + return (component as any).setup?.() }, }) @@ -161,11 +107,15 @@ class VueNodeView implements NodeView { } get contentDOM() { - if (this.dom.id === this.id) { - return this.dom + const hasContent = !this.node.type.isAtom + + if (!hasContent) { + return null } - return this.dom.querySelector(`#${this.id}`) + const contentElement = this.dom.querySelector('[data-node-view-content]') + + return contentElement || this.dom } stopEvent(event: Event) { diff --git a/packages/vue-3/src/index.ts b/packages/vue-3/src/index.ts index 718398d2c..cf5e27183 100644 --- a/packages/vue-3/src/index.ts +++ b/packages/vue-3/src/index.ts @@ -4,3 +4,5 @@ export * from './EditorContent' export * from './useEditor' export * from './VueRenderer' export * from './VueNodeViewRenderer' +export * from './NodeViewWrapper' +export * from './NodeViewContent'