mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-14 05:52:47 +08:00
Fix editor destruction at the end of Vue transition (#5648)
* wip: destruction éditeur * fix: replacing DOM nodes at unmount * fix: event for useEditor destroy * chore: Generating changeset * chore: generating changeset * chore: delete duplicate changeset * revert: note tutorial * feat: add Vue transition example * fix: test for Vue transition * fix: components within editor * chore: remove useless ref
This commit is contained in:
parent
4efd2278a1
commit
364231a1bd
5
.changeset/five-melons-compete.md
Normal file
5
.changeset/five-melons-compete.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tiptap/vue-3": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix editor content being destroyed before transition end
|
21
demos/src/Examples/Transition/Vue/Component.vue
Normal file
21
demos/src/Examples/Transition/Vue/Component.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<NodeViewWrapper>
|
||||||
|
<label>Vue Component</label>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<button @click="increase">This button has been clicked {{ node.attrs.count }} times.</button>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
||||||
|
|
||||||
|
const props = defineProps(nodeViewProps)
|
||||||
|
|
||||||
|
function increase() {
|
||||||
|
props.updateAttributes({
|
||||||
|
count: props.node.attrs.count + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
36
demos/src/Examples/Transition/Vue/Extension.js
Normal file
36
demos/src/Examples/Transition/Vue/Extension.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
|
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
||||||
|
|
||||||
|
import Component from './Component.vue'
|
||||||
|
|
||||||
|
export default Node.create({
|
||||||
|
name: 'vueComponent',
|
||||||
|
|
||||||
|
group: 'block',
|
||||||
|
|
||||||
|
atom: true,
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
count: {
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: 'vue-component',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ['vue-component', mergeAttributes(HTMLAttributes)]
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
return VueNodeViewRenderer(Component)
|
||||||
|
},
|
||||||
|
})
|
0
demos/src/Examples/Transition/Vue/index.html
Normal file
0
demos/src/Examples/Transition/Vue/index.html
Normal file
29
demos/src/Examples/Transition/Vue/index.spec.js
Normal file
29
demos/src/Examples/Transition/Vue/index.spec.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
context('/src/Examples/Transition/Vue/', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/src/Examples/Transition/Vue/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not have an active tiptap instance but a button', () => {
|
||||||
|
cy.get('.tiptap').should('not.exist')
|
||||||
|
|
||||||
|
cy.get('button').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicking the button should show the editor', () => {
|
||||||
|
cy.get('button').click()
|
||||||
|
|
||||||
|
cy.get('.tiptap').should('exist')
|
||||||
|
cy.get('.tiptap').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clicking the button again should hide the editor', () => {
|
||||||
|
cy.get('button').click()
|
||||||
|
|
||||||
|
cy.get('.tiptap').should('exist')
|
||||||
|
cy.get('.tiptap').should('be.visible')
|
||||||
|
|
||||||
|
cy.get('button').click()
|
||||||
|
|
||||||
|
cy.get('.tiptap').should('not.exist')
|
||||||
|
})
|
||||||
|
})
|
66
demos/src/Examples/Transition/Vue/index.vue
Normal file
66
demos/src/Examples/Transition/Vue/index.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import VueComponent from './Extension.js'
|
||||||
|
import type { TNote } from './types.js'
|
||||||
|
|
||||||
|
const note = ref<TNote>({
|
||||||
|
id: 'note-1',
|
||||||
|
content: `
|
||||||
|
<p>Some random note text</p>
|
||||||
|
<vue-component count="0"></vue-component>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: note.value.content,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: 'textarea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
VueComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const showEditor = ref(false)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button type="button" @click="showEditor = !showEditor" style="margin-bottom: 1rem;">
|
||||||
|
{{ showEditor ? 'Hide editor' : 'Show editor' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="showEditor" class="tiptap-wrapper">
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap-wrapper {
|
||||||
|
background-color: var(--purple-light);
|
||||||
|
border: 2px solid var(--purple);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -59,6 +59,7 @@ export const EditorContent = defineComponent({
|
|||||||
|
|
||||||
editor.createNodeViews()
|
editor.createNodeViews()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -69,27 +70,8 @@ export const EditorContent = defineComponent({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy nodeviews before vue removes dom element
|
|
||||||
if (!editor.isDestroyed) {
|
|
||||||
editor.view.setProps({
|
|
||||||
nodeViews: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.contentComponent = null
|
editor.contentComponent = null
|
||||||
editor.appContext = null
|
editor.appContext = null
|
||||||
|
|
||||||
if (!editor.options.element.firstChild) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const newElement = document.createElement('div')
|
|
||||||
|
|
||||||
newElement.append(...editor.options.element.childNodes)
|
|
||||||
|
|
||||||
editor.setOptions({
|
|
||||||
element: newElement,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { rootEl }
|
return { rootEl }
|
||||||
|
Loading…
Reference in New Issue
Block a user