Merge branch 'docs/node-views' into main
# Conflicts: # yarn.lock
This commit is contained in:
69
docs/src/demos/Guide/NodeViews/VueComponent/Component.vue
Normal file
69
docs/src/demos/Guide/NodeViews/VueComponent/Component.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<node-view-wrapper class="vue-component">
|
||||
<span class="label">Vue Component</span>
|
||||
|
||||
<div class="content">
|
||||
<button @click="increase">
|
||||
This button has been clicked {{ node.attrs.count }} times.
|
||||
</button>
|
||||
</div>
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper } from '@tiptap/vue-2'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
},
|
||||
|
||||
props: {
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
increase() {
|
||||
this.updateAttributes({
|
||||
count: this.node.attrs.count + 1,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-component {
|
||||
border: 1px solid #adb5bd;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 1rem;
|
||||
background-color: #adb5bd;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 1px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
35
docs/src/demos/Guide/NodeViews/VueComponent/index.js
Normal file
35
docs/src/demos/Guide/NodeViews/VueComponent/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
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)
|
||||
},
|
||||
})
|
||||
63
docs/src/demos/Guide/NodeViews/VueComponent/index.vue
Normal file
63
docs/src/demos/Guide/NodeViews/VueComponent/index.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
import VueComponent from './index.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
VueComponent,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is still the text editor you’re used to, but enriched with node views.
|
||||
</p>
|
||||
<vue-component count="0"></vue-component>
|
||||
<p>
|
||||
Did you see that? That’s a Vue component. We are really living in the future.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.output {
|
||||
background-color: #0D0D0D;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
font-family: monospace;
|
||||
font-size: 1.1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<node-view-wrapper class="vue-component">
|
||||
<span class="label">Vue Component</span>
|
||||
|
||||
<node-view-content class="content" />
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-component {
|
||||
border: 1px solid #adb5bd;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 1rem;
|
||||
background-color: #adb5bd;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 1px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 2.5rem 1rem 1rem;
|
||||
padding: 1rem;
|
||||
border: 1px dashed #adb5bd;
|
||||
}
|
||||
</style>
|
||||
35
docs/src/demos/Guide/NodeViews/VueComponentContent/index.js
Normal file
35
docs/src/demos/Guide/NodeViews/VueComponentContent/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
name: 'vueComponent',
|
||||
|
||||
group: 'block',
|
||||
|
||||
content: 'inline*',
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
count: {
|
||||
default: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'vue-component',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['vue-component', mergeAttributes(HTMLAttributes)]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return VueNodeViewRenderer(Component)
|
||||
},
|
||||
})
|
||||
65
docs/src/demos/Guide/NodeViews/VueComponentContent/index.vue
Normal file
65
docs/src/demos/Guide/NodeViews/VueComponentContent/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
import VueComponent from './index.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
VueComponent,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is still the text editor you’re used to, but enriched with node views.
|
||||
</p>
|
||||
<vue-component>
|
||||
<p>This is editable.</p>
|
||||
</vue-component>
|
||||
<p>
|
||||
Did you see that? That’s a Vue component. We are really living in the future.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.output {
|
||||
background-color: #0D0D0D;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
font-family: monospace;
|
||||
font-size: 1.1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,14 @@
|
||||
# Complex node views
|
||||
# Interactive node views
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
Node views are the best thing since sliced bread, at least if you’re a fan of customization (and bread). Node views enable you to add literally anything to a node. If you can write it in JavaScript, you can use it in your editor.
|
||||
|
||||
<!-- ```js
|
||||
import { Node } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
addNodeView() {
|
||||
return ({ editor, node, getPos, HTMLAttributes, decorations, extension }) => {
|
||||
const dom = document.createElement('div')
|
||||
|
||||
dom.innerHTML = 'I’m a node view'
|
||||
|
||||
return {
|
||||
dom,
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
``` -->
|
||||
Node views are the best thing since sliced bread, at least if you are a fan of customization (and bread). With node views you can add interactive nodes to your editor content. That can literally be everything. If you can write it in JavaScript, you can use it in your editor.
|
||||
|
||||
## Different types of node views
|
||||
TODO
|
||||
|
||||
### Simple
|
||||
|
||||
### Editable content
|
||||
```html
|
||||
<div class="Prosemirror" contenteditable="true">
|
||||
<p>text</p>
|
||||
@@ -37,12 +17,7 @@ export default Node.create({
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Example: Task item
|
||||
|
||||
https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-task-item/src/task-item.ts#L74
|
||||
|
||||
### Without content
|
||||
|
||||
### Non-editable content
|
||||
```html
|
||||
<div class="Prosemirror" contenteditable="true">
|
||||
<p>text</p>
|
||||
@@ -51,14 +26,7 @@ https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-task-item
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Example: Table of contents
|
||||
<demo name="Guide/NodeViews/TableOfContents" />
|
||||
|
||||
#### Example: Drawing in the editor
|
||||
<demo name="Examples/Drawing" />
|
||||
|
||||
### Advanced node views with content
|
||||
|
||||
### Mixed content
|
||||
```html
|
||||
<div class="Prosemirror" contenteditable="true">
|
||||
<p>text</p>
|
||||
@@ -74,12 +42,8 @@ https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-task-item
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Example: Drag handles
|
||||
<demo name="Guide/NodeViews/DragHandle" />
|
||||
|
||||
## Render Vue components
|
||||
|
||||
### Node
|
||||
## Node views with JavaScript
|
||||
TODO
|
||||
|
||||
```js
|
||||
import { Node } from '@tiptap/core'
|
||||
@@ -87,13 +51,119 @@ import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
addNodeView() {
|
||||
return ({ editor, node, getPos, HTMLAttributes, decorations, extension }) => {
|
||||
const dom = document.createElement('div')
|
||||
|
||||
dom.innerHTML = 'Hello, I’m a node view!'
|
||||
|
||||
return {
|
||||
dom,
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Node views with Vue
|
||||
Using Vanilla JavaScript can feel complex, if you are used to work in Vue. Good news: You can even use regular Vue components in your node views. There is just a little bit you need to know, but let’s go through this one by one.
|
||||
|
||||
### Render a Vue component
|
||||
Here is what you need to do to render Vue components inside your text editor:
|
||||
|
||||
1. [Create a node extension](/guide/build-extensions)
|
||||
2. Create a Vue component
|
||||
3. Pass that component to the provided `VueNodeViewRenderer`
|
||||
4. Register it with `addNodeView()`
|
||||
5. [Configure tiptap to use your new node](/guide/configuration)
|
||||
|
||||
This is how your node could look like:
|
||||
|
||||
```js
|
||||
import { Node } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
// configuration …
|
||||
|
||||
addNodeView() {
|
||||
return VueNodeViewRenderer(Component)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Component
|
||||
There is a little bit of magic required to make this work. But don’t worry, we provide a Vue component you can use to get started easily. Don’t forget to add it to your custom Vue component, like shown below:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<node-view-wrapper>
|
||||
Vue Component
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
```
|
||||
|
||||
Got it? Let’s see that in action. Feel free to copy the example to get started.
|
||||
|
||||
<demo name="Guide/NodeViews/VueComponent" />
|
||||
|
||||
### Access node attributes
|
||||
|
||||
```js
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
```js
|
||||
this.node.attrs.count
|
||||
```
|
||||
|
||||
### Update node attributes
|
||||
|
||||
```js
|
||||
props: {
|
||||
updateAttributes: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
```js
|
||||
this.updateAttributes({
|
||||
count: this.node.attrs.count + 1,
|
||||
})
|
||||
```
|
||||
|
||||
### Adding a content editable
|
||||
|
||||
```html
|
||||
<template>
|
||||
<node-view-wrapper class="dom">
|
||||
<node-view-content class="content-dom" />
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<demo name="Guide/NodeViews/VueComponentContent" />
|
||||
|
||||
`content: 'block+'`
|
||||
|
||||
### All available props
|
||||
|
||||
```html
|
||||
<template>
|
||||
@@ -141,26 +211,35 @@ export default {
|
||||
</script>
|
||||
```
|
||||
|
||||
### Component with Content
|
||||
## Node views with React
|
||||
TODO
|
||||
|
||||
```html
|
||||
<template>
|
||||
<node-view-wrapper class="dom">
|
||||
<node-view-content class="content-dom" />
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
## Rendered content
|
||||
|
||||
<script>
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'
|
||||
```js
|
||||
parseHTML() {
|
||||
return [{
|
||||
tag: 'vue-component',
|
||||
}]
|
||||
},
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
},
|
||||
}
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['vue-component', mergeAttributes(HTMLAttributes)]
|
||||
},
|
||||
```
|
||||
|
||||
## Examples
|
||||
TODO
|
||||
|
||||
<!-- ### Drag handles
|
||||
<demo name="Guide/NodeViews/DragHandle" />
|
||||
|
||||
### Table of contents
|
||||
<demo name="Guide/NodeViews/TableOfContents" />
|
||||
|
||||
### Drawing in the editor
|
||||
<demo name="Examples/Drawing" /> -->
|
||||
|
||||
## Reference
|
||||
|
||||
### dom: ?dom.Node
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
link: /guide/extend-extensions
|
||||
- title: Build extensions
|
||||
link: /guide/build-extensions
|
||||
- title: Complex node views
|
||||
- title: Interactive node views
|
||||
link: /guide/node-views
|
||||
type: draft
|
||||
- title: Working with TypeScript
|
||||
|
||||
Reference in New Issue
Block a user