@tiptap/vue-3 2.0.0-beta.21 → 2.0.0-beta.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Editor.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
2
1
  import { Editor as CoreEditor, EditorOptions } from '@tiptap/core'
2
+ import { EditorState, Plugin, PluginKey } from '@tiptap/pm/state'
3
3
  import {
4
- markRaw,
5
- Ref,
6
- customRef,
7
4
  ComponentInternalInstance,
8
5
  ComponentPublicInstance,
6
+ customRef,
7
+ markRaw,
9
8
  reactive,
9
+ Ref,
10
10
  } from 'vue'
11
+
11
12
  import { VueRenderer } from './VueRenderer'
12
13
 
13
14
  function useDebouncedRef<T>(value: T) {
@@ -33,12 +34,14 @@ function useDebouncedRef<T>(value: T) {
33
34
  }
34
35
 
35
36
  export type ContentComponent = ComponentInternalInstance & {
36
- ctx: ComponentPublicInstance,
37
+ ctx: ComponentPublicInstance
37
38
  }
38
39
 
39
40
  export class Editor extends CoreEditor {
40
41
  private reactiveState: Ref<EditorState>
41
42
 
43
+ private reactiveExtensionStorage: Ref<Record<string, any>>
44
+
42
45
  public vueRenderers = reactive<Map<string, VueRenderer>>(new Map())
43
46
 
44
47
  public contentComponent: ContentComponent | null = null
@@ -47,24 +50,31 @@ export class Editor extends CoreEditor {
47
50
  super(options)
48
51
 
49
52
  this.reactiveState = useDebouncedRef(this.view.state)
53
+ this.reactiveExtensionStorage = useDebouncedRef(this.extensionStorage)
50
54
 
51
55
  this.on('transaction', () => {
52
56
  this.reactiveState.value = this.view.state
57
+ this.reactiveExtensionStorage.value = this.extensionStorage
53
58
  })
54
59
 
55
- return markRaw(this)
60
+ return markRaw(this) // eslint-disable-line
56
61
  }
57
62
 
58
63
  get state() {
59
- return this.reactiveState
60
- ? this.reactiveState.value
61
- : this.view.state
64
+ return this.reactiveState ? this.reactiveState.value : this.view.state
65
+ }
66
+
67
+ get storage() {
68
+ return this.reactiveExtensionStorage ? this.reactiveExtensionStorage.value : super.storage
62
69
  }
63
70
 
64
71
  /**
65
72
  * Register a ProseMirror plugin.
66
73
  */
67
- public registerPlugin(plugin: Plugin, handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[]): void {
74
+ public registerPlugin(
75
+ plugin: Plugin,
76
+ handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[],
77
+ ): void {
68
78
  super.registerPlugin(plugin, handlePlugins)
69
79
  this.reactiveState.value = this.view.state
70
80
  }
@@ -1,17 +1,18 @@
1
1
  import {
2
+ DefineComponent,
3
+ defineComponent,
4
+ getCurrentInstance,
2
5
  h,
3
- ref,
6
+ nextTick,
7
+ onBeforeUnmount,
8
+ PropType,
4
9
  Ref,
5
- unref,
10
+ ref,
6
11
  Teleport,
7
- PropType,
8
- defineComponent,
9
- DefineComponent,
12
+ unref,
10
13
  watchEffect,
11
- nextTick,
12
- onBeforeUnmount,
13
- getCurrentInstance,
14
14
  } from 'vue'
15
+
15
16
  import { Editor } from './Editor'
16
17
 
17
18
  export const EditorContent = defineComponent({
@@ -39,7 +40,7 @@ export const EditorContent = defineComponent({
39
40
 
40
41
  const element = unref(rootEl.value)
41
42
 
42
- rootEl.value.appendChild(editor.options.element.firstChild)
43
+ rootEl.value.append(...editor.options.element.childNodes)
43
44
 
44
45
  // @ts-ignore
45
46
  editor.contentComponent = instance.ctx._
@@ -56,6 +57,10 @@ export const EditorContent = defineComponent({
56
57
  onBeforeUnmount(() => {
57
58
  const editor = props.editor
58
59
 
60
+ if (!editor) {
61
+ return
62
+ }
63
+
59
64
  // destroy nodeviews before vue removes dom element
60
65
  if (!editor.isDestroyed) {
61
66
  editor.view.setProps({
@@ -71,7 +76,7 @@ export const EditorContent = defineComponent({
71
76
 
72
77
  const newElement = document.createElement('div')
73
78
 
74
- newElement.appendChild(editor.options.element.firstChild)
79
+ newElement.append(...editor.options.element.childNodes)
75
80
 
76
81
  editor.setOptions({
77
82
  element: newElement,
@@ -1,39 +1,64 @@
1
+ import { FloatingMenuPlugin, FloatingMenuPluginProps } from '@tiptap/extension-floating-menu'
1
2
  import {
3
+ defineComponent,
2
4
  h,
3
- ref,
4
- PropType,
5
- onMounted,
6
5
  onBeforeUnmount,
7
- defineComponent,
6
+ onMounted,
7
+ PropType,
8
+ ref,
8
9
  } from 'vue'
9
- import {
10
- FloatingMenuPlugin,
11
- FloatingMenuPluginKey,
12
- FloatingMenuPluginProps,
13
- } from '@tiptap/extension-floating-menu'
14
10
 
15
11
  export const FloatingMenu = defineComponent({
16
12
  name: 'FloatingMenu',
17
13
 
18
14
  props: {
15
+ pluginKey: {
16
+ // TODO: TypeScript breaks :(
17
+ // type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
18
+ type: null,
19
+ default: 'floatingMenu',
20
+ },
21
+
19
22
  editor: {
20
23
  type: Object as PropType<FloatingMenuPluginProps['editor']>,
21
24
  required: true,
22
25
  },
26
+
27
+ tippyOptions: {
28
+ type: Object as PropType<FloatingMenuPluginProps['tippyOptions']>,
29
+ default: () => ({}),
30
+ },
31
+
32
+ shouldShow: {
33
+ type: Function as PropType<Exclude<Required<FloatingMenuPluginProps>['shouldShow'], null>>,
34
+ default: null,
35
+ },
23
36
  },
24
37
 
25
- setup({ editor }, { slots }) {
38
+ setup(props, { slots }) {
26
39
  const root = ref<HTMLElement | null>(null)
27
40
 
28
41
  onMounted(() => {
42
+ const {
43
+ pluginKey,
44
+ editor,
45
+ tippyOptions,
46
+ shouldShow,
47
+ } = props
48
+
29
49
  editor.registerPlugin(FloatingMenuPlugin({
50
+ pluginKey,
30
51
  editor,
31
52
  element: root.value as HTMLElement,
53
+ tippyOptions,
54
+ shouldShow,
32
55
  }))
33
56
  })
34
57
 
35
58
  onBeforeUnmount(() => {
36
- editor.unregisterPlugin(FloatingMenuPluginKey)
59
+ const { pluginKey, editor } = props
60
+
61
+ editor.unregisterPlugin(pluginKey)
37
62
  })
38
63
 
39
64
  return () => h('div', { ref: root }, slots.default?.())
@@ -1,4 +1,4 @@
1
- import { h, defineComponent } from 'vue'
1
+ import { defineComponent, h } from 'vue'
2
2
 
3
3
  export const NodeViewContent = defineComponent({
4
4
  props: {
@@ -9,13 +9,11 @@ export const NodeViewContent = defineComponent({
9
9
  },
10
10
 
11
11
  render() {
12
- return h(
13
- this.as, {
14
- style: {
15
- whiteSpace: 'pre-wrap',
16
- },
17
- 'data-node-view-content': '',
12
+ return h(this.as, {
13
+ style: {
14
+ whiteSpace: 'pre-wrap',
18
15
  },
19
- )
16
+ 'data-node-view-content': '',
17
+ })
20
18
  },
21
19
  })
@@ -1,4 +1,4 @@
1
- import { h, defineComponent } from 'vue'
1
+ import { defineComponent, h } from 'vue'
2
2
 
3
3
  export const NodeViewWrapper = defineComponent({
4
4
  props: {
@@ -12,15 +12,16 @@ export const NodeViewWrapper = defineComponent({
12
12
 
13
13
  render() {
14
14
  return h(
15
- this.as, {
15
+ this.as,
16
+ {
16
17
  // @ts-ignore
17
- class: this.decorationClasses.value,
18
+ class: this.decorationClasses,
18
19
  style: {
19
20
  whiteSpace: 'normal',
20
21
  },
21
22
  'data-node-view-wrapper': '',
22
23
  // @ts-ignore (https://github.com/vuejs/vue-next/issues/3031)
23
- onDragStart: this.onDragStart,
24
+ onDragstart: this.onDragStart,
24
25
  },
25
26
  this.$slots.default?.(),
26
27
  )
@@ -2,59 +2,71 @@ import {
2
2
  NodeView,
3
3
  NodeViewProps,
4
4
  NodeViewRenderer,
5
+ NodeViewRendererOptions,
5
6
  NodeViewRendererProps,
6
7
  } from '@tiptap/core'
8
+ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
9
+ import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
7
10
  import {
8
- ref,
9
- Ref,
10
- provide,
11
- PropType,
12
11
  Component,
13
12
  defineComponent,
13
+ PropType,
14
+ provide,
15
+ Ref,
16
+ ref,
14
17
  } from 'vue'
15
- import { Decoration, NodeView as ProseMirrorNodeView } from 'prosemirror-view'
16
- import { Node as ProseMirrorNode } from 'prosemirror-model'
18
+
17
19
  import { Editor } from './Editor'
18
20
  import { VueRenderer } from './VueRenderer'
19
21
 
20
22
  export const nodeViewProps = {
21
23
  editor: {
22
24
  type: Object as PropType<NodeViewProps['editor']>,
23
- required: true,
25
+ required: true as const,
24
26
  },
25
27
  node: {
26
28
  type: Object as PropType<NodeViewProps['node']>,
27
- required: true,
29
+ required: true as const,
28
30
  },
29
31
  decorations: {
30
32
  type: Object as PropType<NodeViewProps['decorations']>,
31
- required: true,
33
+ required: true as const,
32
34
  },
33
35
  selected: {
34
36
  type: Boolean as PropType<NodeViewProps['selected']>,
35
- required: true,
37
+ required: true as const,
36
38
  },
37
39
  extension: {
38
40
  type: Object as PropType<NodeViewProps['extension']>,
39
- required: true,
41
+ required: true as const,
40
42
  },
41
43
  getPos: {
42
44
  type: Function as PropType<NodeViewProps['getPos']>,
43
- required: true,
45
+ required: true as const,
44
46
  },
45
47
  updateAttributes: {
46
48
  type: Function as PropType<NodeViewProps['updateAttributes']>,
47
- required: true,
49
+ required: true as const,
50
+ },
51
+ deleteNode: {
52
+ type: Function as PropType<NodeViewProps['deleteNode']>,
53
+ required: true as const,
48
54
  },
49
55
  }
50
56
 
51
- interface VueNodeViewRendererOptions {
52
- stopEvent: ((event: Event) => boolean) | null,
53
- update: ((node: ProseMirrorNode, decorations: Decoration[]) => boolean) | null,
57
+ export interface VueNodeViewRendererOptions extends NodeViewRendererOptions {
58
+ update:
59
+ | ((props: {
60
+ oldNode: ProseMirrorNode
61
+ oldDecorations: Decoration[]
62
+ newNode: ProseMirrorNode
63
+ newDecorations: Decoration[]
64
+ updateProps: () => void
65
+ }) => boolean)
66
+ | null
54
67
  }
55
68
 
56
- class VueNodeView extends NodeView<Component, Editor> {
57
-
69
+ class VueNodeView extends NodeView<Component, Editor, VueNodeViewRendererOptions> {
58
70
  renderer!: VueRenderer
59
71
 
60
72
  decorationClasses!: Ref<string>
@@ -68,6 +80,7 @@ class VueNodeView extends NodeView<Component, Editor> {
68
80
  extension: this.extension,
69
81
  getPos: () => this.getPos(),
70
82
  updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
83
+ deleteNode: () => this.deleteNode(),
71
84
  }
72
85
 
73
86
  const onDragStart = this.onDragStart.bind(this)
@@ -77,12 +90,23 @@ class VueNodeView extends NodeView<Component, Editor> {
77
90
  const extendedComponent = defineComponent({
78
91
  extends: { ...this.component },
79
92
  props: Object.keys(props),
80
- setup: () => {
93
+ template: (this.component as any).template,
94
+ setup: reactiveProps => {
81
95
  provide('onDragStart', onDragStart)
82
96
  provide('decorationClasses', this.decorationClasses)
83
97
 
84
- return (this.component as any).setup?.(props)
98
+ return (this.component as any).setup?.(reactiveProps, {
99
+ expose: () => undefined,
100
+ })
85
101
  },
102
+ // add support for scoped styles
103
+ // @ts-ignore
104
+ // eslint-disable-next-line
105
+ __scopeId: this.component.__scopeId,
106
+ // add support for CSS Modules
107
+ // @ts-ignore
108
+ // eslint-disable-next-line
109
+ __cssModules: this.component.__cssModules,
86
110
  })
87
111
 
88
112
  this.renderer = new VueRenderer(extendedComponent, {
@@ -96,7 +120,7 @@ class VueNodeView extends NodeView<Component, Editor> {
96
120
  throw Error('Please use the NodeViewWrapper component for your node view.')
97
121
  }
98
122
 
99
- return this.renderer.element
123
+ return this.renderer.element as HTMLElement
100
124
  }
101
125
 
102
126
  get contentDOM() {
@@ -106,12 +130,29 @@ class VueNodeView extends NodeView<Component, Editor> {
106
130
 
107
131
  const contentElement = this.dom.querySelector('[data-node-view-content]')
108
132
 
109
- return contentElement || this.dom
133
+ return (contentElement || this.dom) as HTMLElement | null
110
134
  }
111
135
 
112
136
  update(node: ProseMirrorNode, decorations: Decoration[]) {
137
+ const updateProps = (props?: Record<string, any>) => {
138
+ this.decorationClasses.value = this.getDecorationClasses()
139
+ this.renderer.updateProps(props)
140
+ }
141
+
113
142
  if (typeof this.options.update === 'function') {
114
- return this.options.update(node, decorations)
143
+ const oldNode = this.node
144
+ const oldDecorations = this.decorations
145
+
146
+ this.node = node
147
+ this.decorations = decorations
148
+
149
+ return this.options.update({
150
+ oldNode,
151
+ oldDecorations,
152
+ newNode: node,
153
+ newDecorations: decorations,
154
+ updateProps: () => updateProps({ node, decorations }),
155
+ })
115
156
  }
116
157
 
117
158
  if (node.type !== this.node.type) {
@@ -124,8 +165,8 @@ class VueNodeView extends NodeView<Component, Editor> {
124
165
 
125
166
  this.node = node
126
167
  this.decorations = decorations
127
- this.decorationClasses.value = this.getDecorationClasses()
128
- this.renderer.updateProps({ node, decorations })
168
+
169
+ updateProps({ node, decorations })
129
170
 
130
171
  return true
131
172
  }
@@ -143,20 +184,24 @@ class VueNodeView extends NodeView<Component, Editor> {
143
184
  }
144
185
 
145
186
  getDecorationClasses() {
146
- return this.decorations
147
- // @ts-ignore
148
- .map(item => item.type.attrs.class)
149
- .flat()
150
- .join(' ')
187
+ return (
188
+ this.decorations
189
+ // @ts-ignore
190
+ .map(item => item.type.attrs.class)
191
+ .flat()
192
+ .join(' ')
193
+ )
151
194
  }
152
195
 
153
196
  destroy() {
154
197
  this.renderer.destroy()
155
198
  }
156
-
157
199
  }
158
200
 
159
- export function VueNodeViewRenderer(component: Component, options?: Partial<VueNodeViewRendererOptions>): NodeViewRenderer {
201
+ export function VueNodeViewRenderer(
202
+ component: Component,
203
+ options?: Partial<VueNodeViewRendererOptions>,
204
+ ): NodeViewRenderer {
160
205
  return (props: NodeViewRendererProps) => {
161
206
  // try to get the parent component
162
207
  // this is important for vue devtools to show the component hierarchy correctly
@@ -165,6 +210,6 @@ export function VueNodeViewRenderer(component: Component, options?: Partial<VueN
165
210
  return {}
166
211
  }
167
212
 
168
- return new VueNodeView(component, props, options) as ProseMirrorNodeView
213
+ return new VueNodeView(component, props, options) as unknown as ProseMirrorNodeView
169
214
  }
170
215
  }
@@ -1,16 +1,17 @@
1
- import { reactive, markRaw, Component } from 'vue'
2
- import { AnyObject } from '@tiptap/core'
3
- import { Editor } from './Editor'
1
+ import { Editor } from '@tiptap/core'
2
+ import { Component, markRaw, reactive } from 'vue'
3
+
4
+ import { Editor as ExtendedEditor } from './Editor'
4
5
 
5
6
  export interface VueRendererOptions {
6
7
  editor: Editor,
7
- props?: AnyObject,
8
+ props?: Record<string, any>,
8
9
  }
9
10
 
10
11
  export class VueRenderer {
11
12
  id: string
12
13
 
13
- editor: Editor
14
+ editor: ExtendedEditor
14
15
 
15
16
  component: Component
16
17
 
@@ -18,11 +19,11 @@ export class VueRenderer {
18
19
 
19
20
  element: Element
20
21
 
21
- props: AnyObject
22
+ props: Record<string, any>
22
23
 
23
24
  constructor(component: Component, { props = {}, editor }: VueRendererOptions) {
24
25
  this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
25
- this.editor = editor
26
+ this.editor = editor as ExtendedEditor
26
27
  this.component = markRaw(component)
27
28
  this.teleportElement = document.createElement('div')
28
29
  this.element = this.teleportElement
@@ -41,10 +42,10 @@ export class VueRenderer {
41
42
  }
42
43
 
43
44
  get ref(): any {
44
- return this.editor.contentComponent?.ctx.$refs[this.id]
45
+ return this.editor.contentComponent?.refs[this.id]
45
46
  }
46
47
 
47
- updateProps(props: AnyObject = {}): void {
48
+ updateProps(props: Record<string, any> = {}): void {
48
49
  Object
49
50
  .entries(props)
50
51
  .forEach(([key, value]) => {
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- export * from '@tiptap/core'
2
1
  export * from './BubbleMenu'
3
2
  export { Editor } from './Editor'
4
3
  export * from './EditorContent'
5
4
  export * from './FloatingMenu'
5
+ export * from './NodeViewContent'
6
+ export * from './NodeViewWrapper'
6
7
  export * from './useEditor'
7
- export * from './VueRenderer'
8
8
  export * from './VueNodeViewRenderer'
9
- export * from './NodeViewWrapper'
10
- export * from './NodeViewContent'
9
+ export * from './VueRenderer'
10
+ export * from '@tiptap/core'
package/src/useEditor.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { onMounted, onBeforeUnmount, ref } from 'vue'
2
1
  import { EditorOptions } from '@tiptap/core'
2
+ import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
3
+
3
4
  import { Editor } from './Editor'
4
5
 
5
6
  export const useEditor = (options: Partial<EditorOptions> = {}) => {
6
- const editor = ref<Editor>()
7
+ const editor = shallowRef<Editor>()
7
8
 
8
9
  onMounted(() => {
9
10
  editor.value = new Editor(options)