tiptap/docs/src/docPages/guide/node-views.md

272 lines
6.9 KiB
Markdown
Raw Normal View History

2021-03-09 18:55:14 +08:00
# Interactive node views
2020-11-06 23:25:46 +08:00
## toc
## Introduction
2021-03-09 18:55:14 +08:00
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.
2020-11-28 00:25:32 +08:00
<!-- ```js
2020-12-14 19:18:05 +08:00
import { Node } from '@tiptap/core'
2021-03-01 06:39:29 +08:00
import { VueNodeViewRenderer } from '@tiptap/vue-2'
2020-12-14 19:18:05 +08:00
import Component from './Component.vue'
export default Node.create({
addNodeView() {
return ({ editor, node, getPos, HTMLAttributes, decorations, extension }) => {
const dom = document.createElement('div')
dom.innerHTML = 'Im a node view'
return {
dom,
}
})
},
})
``` -->
2021-03-09 01:33:50 +08:00
<!--
2020-11-30 22:33:20 +08:00
## Different types of node views
### Simple
2020-11-27 21:58:28 +08:00
2020-11-28 00:25:01 +08:00
```html
<div class="Prosemirror" contenteditable="true">
<p>text</p>
<node-view>text</node-view>
<p>text</p>
</div>
```
#### Example: Task item
https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-task-item/src/task-item.ts#L74
2020-11-30 22:33:20 +08:00
### Without content
2020-11-27 21:58:28 +08:00
2020-11-28 00:25:01 +08:00
```html
<div class="Prosemirror" contenteditable="true">
<p>text</p>
<node-view contenteditable="false">text</node-view>
<p>text</p>
</div>
```
2021-01-09 01:50:37 +08:00
#### Example: Table of contents
2020-12-01 05:53:31 +08:00
<demo name="Guide/NodeViews/TableOfContents" />
2021-01-09 01:50:37 +08:00
#### Example: Drawing in the editor
2020-12-10 22:56:18 +08:00
<demo name="Examples/Drawing" />
2020-11-30 22:33:20 +08:00
### Advanced node views with content
2020-11-27 21:58:28 +08:00
2020-11-28 00:25:01 +08:00
```html
<div class="Prosemirror" contenteditable="true">
<p>text</p>
<node-view>
<div>
non-editable text
</div>
<div>
editable text
</div>
</node-view>
<p>text</p>
</div>
2021-03-09 01:33:50 +08:00
``` -->
<!-- #### Example: Drag handles
<demo name="Guide/NodeViews/DragHandle" /> -->
2020-11-28 00:25:01 +08:00
2021-03-09 01:33:50 +08:00
## Node views with Vue.js
2021-03-09 18:55:14 +08:00
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, before starting.
2020-11-06 23:25:46 +08:00
2021-03-09 01:33:50 +08:00
### Render a Vue component
2021-03-09 18:55:14 +08:00
Lets start rendering your first Vue component. Here is what you need to do:
1. [Create a new node](/guide/build-extensions)
2. Create a new 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:
2021-03-09 18:17:48 +08:00
```js
import { Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-2'
import Component from './Component.vue'
export default Node.create({
2021-03-09 18:55:14 +08:00
// configuration …
2021-03-09 18:17:48 +08:00
addNodeView() {
return VueNodeViewRenderer(Component)
},
})
```
2021-03-09 18:55:14 +08:00
There is a little bit of magic required to make this work. But dont worry, we provide a Vue component you can use to get started easily. Dont 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? Lets see that in action. Feel free to copy the example to get started.
2021-03-09 01:33:50 +08:00
<demo name="Guide/NodeViews/VueComponent" />
2020-11-06 23:25:46 +08:00
2021-03-09 01:33:50 +08:00
### Access node attributes
2021-03-09 18:17:48 +08:00
```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
<demo name="Guide/NodeViews/VueComponentContent" />
`content: 'block+'`
2021-03-09 01:33:50 +08:00
2021-03-09 18:17:48 +08:00
`atom: true`
2021-03-09 01:33:50 +08:00
<!-- ### Node
2020-12-14 19:18:05 +08:00
```js
import { Node } from '@tiptap/core'
2021-03-01 06:39:29 +08:00
import { VueNodeViewRenderer } from '@tiptap/vue-2'
2020-12-14 19:18:05 +08:00
import Component from './Component.vue'
export default Node.create({
addNodeView() {
2021-01-20 03:27:51 +08:00
return VueNodeViewRenderer(Component)
2020-12-14 19:18:05 +08:00
},
})
```
### Component
```html
<template>
<node-view-wrapper />
2020-12-14 19:18:05 +08:00
</template>
<script>
import { NodeViewWrapper } from '@tiptap/vue-2'
2020-12-14 19:18:05 +08:00
export default {
components: {
NodeViewWrapper,
},
2020-12-14 19:18:05 +08:00
props: {
editor: {
type: Object,
},
node: {
type: Object,
},
decorations: {
type: Array,
},
selected: {
type: Boolean,
},
extension: {
type: Object,
},
getPos: {
type: Function,
},
updateAttributes: {
type: Function,
},
},
}
</script>
```
### Component with Content
```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,
},
}
2021-03-09 01:33:50 +08:00
``` -->
2021-02-11 23:08:58 +08:00
## Reference
### dom: ?dom.Node
> The outer DOM node that represents the document node. When not given, the default strategy is used to create a DOM node.
### contentDOM: ?dom.Node
> The DOM node that should hold the node's content. Only meaningful if the node view also defines a dom property and if its node type is not a leaf node type. When this is present, ProseMirror will take care of rendering the node's children into it. When it is not present, the node view itself is responsible for rendering (or deciding not to render) its child nodes.
### update: ?fn(node: Node, decorations: [Decoration]) → bool
> When given, this will be called when the view is updating itself. It will be given a node (possibly of a different type), and an array of active decorations (which are automatically drawn, and the node view may ignore if it isn't interested in them), and should return true if it was able to update to that node, and false otherwise. If the node view has a contentDOM property (or no dom property), updating its child nodes will be handled by ProseMirror.
### selectNode: ?fn()
> Can be used to override the way the node's selected status (as a node selection) is displayed.
### deselectNode: ?fn()
> When defining a selectNode method, you should also provide a deselectNode method to remove the effect again.
### setSelection: ?fn(anchor: number, head: number, root: dom.Document)
> This will be called to handle setting the selection inside the node. The anchor and head positions are relative to the start of the node. By default, a DOM selection will be created between the DOM positions corresponding to those positions, but if you override it you can do something else.
### stopEvent: ?fn(event: dom.Event) → bool
> Can be used to prevent the editor view from trying to handle some or all DOM events that bubble up from the node view. Events for which this returns true are not handled by the editor.
### ignoreMutation: ?fn(dom.MutationRecord) → bool
> Called when a DOM mutation or a selection change happens within the view. When the change is a selection change, the record will have a type property of "selection" (which doesn't occur for native mutation records). Return false if the editor should re-read the selection or re-parse the range around the mutation, true if it can safely be ignored.
### destroy: ?fn()
> Called when the node view is removed from the editor or the whole editor is destroyed.