mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-26 05:39:16 +08:00
Merge branch 'main' into feature/extension-code-block-lowlight
# Conflicts: # docs/src/docPages/api/nodes/code-block-lowlight.md # docs/src/links.yaml
This commit is contained in:
commit
704e01de6a
@ -4,7 +4,7 @@
|
||||
<div class="demo" v-else>
|
||||
<template v-if="mainFile">
|
||||
<demo-frame class="demo__preview" v-bind="props" />
|
||||
<div class="demo__source" v-if="showSource">
|
||||
<div class="demo__source" v-if="!hideSource">
|
||||
<div class="demo__scroller" v-if="showFileNames">
|
||||
<div class="demo__tabs">
|
||||
<button
|
||||
|
@ -17,9 +17,9 @@ export default {
|
||||
default: null,
|
||||
},
|
||||
|
||||
showSource: {
|
||||
hideSource: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
@ -38,7 +38,7 @@ export default {
|
||||
name: this.name,
|
||||
inline: this.inline,
|
||||
highlight: this.highlight,
|
||||
showSource: this.showSource,
|
||||
hideSource: this.hideSource,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
<demo
|
||||
:name="selectedItem.name"
|
||||
:key="selectedItem.title"
|
||||
:hide-source="hideSource"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -29,6 +30,10 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
hideSource: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -15,7 +15,10 @@ export default () => {
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Hey, try to select some text here. There will popup a menu for selecting some inline styles. Remember: you have full control about content and styling of this menu.
|
||||
Try to select <em>this text</em> to see what we call the bubble menu.
|
||||
</p>
|
||||
<p>
|
||||
Neat, isn’t it? Add an empty paragraph to see the floating menu.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
@ -27,19 +30,19 @@ export default () => {
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
className={editor.isActive('bold') ? 'is-active' : ''}
|
||||
>
|
||||
bold
|
||||
Bold
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
className={editor.isActive('italic') ? 'is-active' : ''}
|
||||
>
|
||||
italic
|
||||
Italic
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
className={editor.isActive('strike') ? 'is-active' : ''}
|
||||
>
|
||||
strike
|
||||
Strike
|
||||
</button>
|
||||
</BubbleMenu>}
|
||||
|
||||
@ -48,19 +51,19 @@ export default () => {
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
|
||||
>
|
||||
h1
|
||||
H1
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
|
||||
>
|
||||
h2
|
||||
H2
|
||||
</button>
|
||||
<button
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
className={editor.isActive('bulletList') ? 'is-active' : ''}
|
||||
>
|
||||
bullet list
|
||||
Bullet List
|
||||
</button>
|
||||
</FloatingMenu>}
|
||||
|
||||
|
@ -2,25 +2,25 @@
|
||||
<div style="position: relative">
|
||||
<bubble-menu class="bubble-menu" :editor="editor" v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
|
||||
bold
|
||||
Bold
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
|
||||
italic
|
||||
Italic
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
|
||||
strike
|
||||
Strike
|
||||
</button>
|
||||
</bubble-menu>
|
||||
|
||||
<floating-menu class="floating-menu" :editor="editor" v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
|
||||
h1
|
||||
H1
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
|
||||
h2
|
||||
H2
|
||||
</button>
|
||||
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
|
||||
bullet list
|
||||
Bullet List
|
||||
</button>
|
||||
</floating-menu>
|
||||
|
||||
@ -57,7 +57,10 @@ export default {
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
Hey, try to select some text here. You’ll see a formatting menu pop up. And as always, you are in full control about content and styling of this menu.
|
||||
Try to select <em>this text</em> to see what we call the bubble menu.
|
||||
</p>
|
||||
<p>
|
||||
Neat, isn’t it? Add an empty paragraph to see the floating menu.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
|
@ -10,8 +10,9 @@ export default () => {
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is an example of a medium-like editor. Enter a new line and some buttons will appear.
|
||||
This is an example of a Medium-like editor. Enter a new line and some buttons will appear.
|
||||
</p>
|
||||
<p></p>
|
||||
`,
|
||||
})
|
||||
|
||||
|
@ -38,8 +38,9 @@ export default {
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is an example of a medium-like editor. Enter a new line and some buttons will appear.
|
||||
This is an example of a Medium-like editor. Enter a new line and some buttons will appear.
|
||||
</p>
|
||||
<p></p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
@ -3,49 +3,50 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Option 1: Browser + server-side
|
||||
import { generateHTML } from '@tiptap/html'
|
||||
// Option 2: Browser-only (lightweight)
|
||||
// import { generateHTML } from '@tiptap/core'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Bold from '@tiptap/extension-bold'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
output: '',
|
||||
json: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Example ',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'bold',
|
||||
},
|
||||
],
|
||||
text: 'Text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
const json = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Example ',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'bold',
|
||||
},
|
||||
],
|
||||
text: 'Text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.output = generateHTML(this.json, [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
])
|
||||
export default {
|
||||
computed: {
|
||||
output() {
|
||||
return generateHTML(json, [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Bold,
|
||||
// other extensions …
|
||||
])
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
77
docs/src/demos/Guide/NodeViews/JavaScript/Extension.js
Normal file
77
docs/src/demos/Guide/NodeViews/JavaScript/Extension.js
Normal file
@ -0,0 +1,77 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
export default Node.create({
|
||||
name: 'nodeView',
|
||||
|
||||
group: 'block',
|
||||
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
count: {
|
||||
default: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'node-view',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['node-view', mergeAttributes(HTMLAttributes)]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ({ editor, node, getPos }) => {
|
||||
const { view } = editor
|
||||
|
||||
// Markup
|
||||
/*
|
||||
<div class="node-view">
|
||||
<span class="label">Node view</span>
|
||||
|
||||
<div class="content">
|
||||
<button>
|
||||
This button has been clicked ${node.attrs.count} times.
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
|
||||
const dom = document.createElement('div')
|
||||
dom.classList.add('node-view')
|
||||
|
||||
const label = document.createElement('span')
|
||||
label.classList.add('label')
|
||||
label.innerHTML = 'Node view'
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.classList.add('content')
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = `This button has been clicked ${node.attrs.count} times.`
|
||||
button.addEventListener('click', () => {
|
||||
if (typeof getPos === 'function') {
|
||||
view.dispatch(view.state.tr.setNodeMarkup(getPos(), undefined, {
|
||||
count: node.attrs.count + 1,
|
||||
}))
|
||||
|
||||
editor.commands.focus()
|
||||
}
|
||||
})
|
||||
content.append(button)
|
||||
|
||||
dom.append(label, content)
|
||||
|
||||
return {
|
||||
dom,
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
@ -5,7 +5,7 @@
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
import VueComponent from './index.js'
|
||||
import NodeView from './Extension.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -22,13 +22,13 @@ export default {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
VueComponent,
|
||||
NodeView,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is still the text editor you’re used to, but enriched with node views.
|
||||
</p>
|
||||
<node-view count="0"></node-view>
|
||||
<node-view></node-view>
|
||||
<p>
|
||||
Did you see that? That’s a JavaScript node view. We are really living in the future.
|
||||
</p>
|
||||
@ -52,7 +52,8 @@ export default {
|
||||
|
||||
::v-deep {
|
||||
.node-view {
|
||||
border: 1px solid #adb5bd;
|
||||
background: #FAF594;
|
||||
border: 3px solid #0D0D0D;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
@ -60,7 +61,7 @@ export default {
|
||||
|
||||
.label {
|
||||
margin-left: 1rem;
|
||||
background-color: #adb5bd;
|
||||
background-color: #0D0D0D;
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 1px;
|
||||
font-weight: bold;
|
||||
@ -73,7 +74,8 @@ export default {
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 2.5rem 1rem 1rem;
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -5,15 +5,7 @@ export default Node.create({
|
||||
|
||||
group: 'block',
|
||||
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
count: {
|
||||
default: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
content: 'inline*',
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
@ -29,32 +21,32 @@ export default Node.create({
|
||||
|
||||
addNodeView() {
|
||||
return () => {
|
||||
// Markup
|
||||
/*
|
||||
<div class="node-view">
|
||||
<span class="label">Node view</span>
|
||||
|
||||
<div class="content"></div>
|
||||
</div>
|
||||
*/
|
||||
|
||||
const dom = document.createElement('div')
|
||||
dom.classList.add('node-view')
|
||||
|
||||
const label = document.createElement('span')
|
||||
label.classList.add('label')
|
||||
label.innerHTML = 'Node View'
|
||||
label.innerHTML = 'Node view'
|
||||
label.contentEditable = false
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.classList.add('content')
|
||||
content.innerHTML = 'I’m rendered with JavaScript.'
|
||||
|
||||
dom.append(label, content)
|
||||
|
||||
return {
|
||||
dom,
|
||||
contentDOM: content,
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// <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>
|
85
docs/src/demos/Guide/NodeViews/JavaScriptContent/index.vue
Normal file
85
docs/src/demos/Guide/NodeViews/JavaScriptContent/index.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
import NodeView from './Extension.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
...defaultExtensions(),
|
||||
NodeView,
|
||||
],
|
||||
content: `
|
||||
<p>
|
||||
This is still the text editor you’re used to, but enriched with node views.
|
||||
</p>
|
||||
<node-view>
|
||||
<p>This is editable.</p>
|
||||
</node-view>
|
||||
<p>
|
||||
Did you see that? That’s a JavaScript node view. We are really living in the future.
|
||||
</p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Basic editor styles */
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.node-view {
|
||||
background: #FAF594;
|
||||
border: 3px solid #0D0D0D;
|
||||
border-radius: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 1rem;
|
||||
background-color: #0D0D0D;
|
||||
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: 0.5rem;
|
||||
border: 2px dashed #0D0D0D20;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -31,6 +31,26 @@ In the example above two different commands are executed at once. When a user cl
|
||||
|
||||
All chained commands are kind of queued up. They are combined to one single transaction. That means, the content is only updated once, also the `update` event is only triggered once.
|
||||
|
||||
#### Chaining inside custom commands
|
||||
When chaining a command, the transaction is held back. If you want to chain commands inside your custom commands, you’ll need to use said transaction and add to it. Here is how you would do that:
|
||||
|
||||
```js
|
||||
addCommands() {
|
||||
return {
|
||||
insertTimecode: attributes => ({ chain }) => {
|
||||
// Doesn’t work:
|
||||
// return editor.chain() …
|
||||
|
||||
// Does work:
|
||||
return chain()
|
||||
.insertNode('timecode', attributes)
|
||||
.insertText(' ')
|
||||
.run()
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inline commands
|
||||
In some cases, it’s helpful to put some more logic in a command. That’s why you can execute commands in commands. I know, that sounds crazy, but let’s look at an example:
|
||||
|
||||
@ -132,6 +152,7 @@ Have a look at all of the core commands listed below. They should give you a goo
|
||||
| --------------- | ------------------------------------------------ |
|
||||
| .clearContent() | Clear the whole document. |
|
||||
| .insertHTML() | Insert a string of HTML at the current position. |
|
||||
| .insertNode() | Insert a node at the current position. |
|
||||
| .insertText() | Insert a string of text at the current position. |
|
||||
| .setContent() | Replace the whole document with new content. |
|
||||
|
||||
@ -207,9 +228,26 @@ this.editor
|
||||
.createParagraphNear()
|
||||
.insertHTML('<p></p>')
|
||||
.run()
|
||||
``` -->
|
||||
```
|
||||
|
||||
## Add your own commands
|
||||
Add a custom command to insert a node.
|
||||
```js
|
||||
addCommands() {
|
||||
return {
|
||||
insertTimecode: attributes => ({ chain }) => {
|
||||
return chain()
|
||||
.focus()
|
||||
.insertNode('timecode', attributes)
|
||||
.insertText(' ')
|
||||
.run();
|
||||
},
|
||||
}
|
||||
},
|
||||
```
|
||||
-->
|
||||
|
||||
## Add custom commands
|
||||
All extensions can add additional commands (and most do), check out the specific [documentation for the provided nodes](/api/nodes), [marks](/api/marks), and [extensions](/api/extensions) to learn more about those.
|
||||
|
||||
Of course, you can [add your custom extensions](/guide/build-extensions) with custom commands aswell.
|
||||
|
||||
|
@ -23,3 +23,4 @@ ProseMirror has its own vocabulary and you’ll stumble upon all those words now
|
||||
| Node | Adds blocks, like heading, paragraph. |
|
||||
| Mark | Adds inline formatting, for example bold or italic. |
|
||||
| Command | Execute an action inside the editor, that somehow changes the state. |
|
||||
| Decoration | Styling on top of the document, for example to highlight mistakes. |
|
||||
|
@ -5,8 +5,30 @@
|
||||
## Introduction
|
||||
This class is a central building block of tiptap. It does most of the heavy lifting of creating a working [ProseMirror](https://ProseMirror.net/) editor such as creating the [`EditorView`](https://ProseMirror.net/docs/ref/#view.EditorView), setting the initial [`EditorState`](https://ProseMirror.net/docs/ref/#state.Editor_State) and so on.
|
||||
|
||||
## List of available settings
|
||||
Check out the API documentation to see [all available options](/api/editor/).
|
||||
## Methods
|
||||
The editor instance will provide a bunch of public methods. They’ll help you to work with the editor.
|
||||
|
||||
Don’t confuse methods with [commands](/api/commands). Commands are used to change the state of editor (content, selection, and so on) and only return `true` or `false`. Methods are regular functions and can return anything.
|
||||
|
||||
| Method | Parameters | Description |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `can()` | - | Check if a command or a command chain can be executed. Without executing it. |
|
||||
| `chain()` | - | Create a command chain to call multiple commands at once. |
|
||||
| `createDocument()` | `content` EditorContent<br>`parseOptions` | Creates a ProseMirror document. |
|
||||
| `destroy()` | – | Stops the editor instance and unbinds all events. |
|
||||
| `getHTML()` | – | Returns the current content as HTML. |
|
||||
| `getJSON()` | – | Returns the current content as JSON. |
|
||||
| `getMarkAttributes()` | `name` Name of the mark | Get attributes of the currently selected mark. |
|
||||
| `getNodeAttributes()` | `name` Name of the node | Get attributes of the currently selected node. |
|
||||
| `isActive()` | `name` Name of the node or mark<br>`attrs` Attributes of the node or mark | Returns if the currently selected node or mark is active. |
|
||||
| `isEditable()` | - | Returns whether the editor is editable. |
|
||||
| `isEmpty()` | - | Check if there is no content. |
|
||||
| `getCharacterCount()` | - | Get the number of characters for the current document. |
|
||||
| `registerPlugin()` | `plugin` A ProseMirror plugin<br>`handlePlugins` Control how to merge the plugin into the existing plugins. | Register a ProseMirror plugin. |
|
||||
| `setOptions()` | `options` A list of options | Update editor options. |
|
||||
| `unregisterPlugin()` | `name` The plugins name | Unregister a ProseMirror plugin. |
|
||||
|
||||
## Settings
|
||||
|
||||
### Element
|
||||
The `element` specifies the HTML element the editor will be binded too. The following code will integrate tiptap with an element with the `.element` class:
|
||||
@ -147,37 +169,28 @@ new Editor({
|
||||
})
|
||||
```
|
||||
|
||||
<!--
|
||||
| Setting | Type | Default | Description |
|
||||
| ------------------ | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `autofocus` | `Boolean` | `false` | Focus the editor on init. |
|
||||
| `content` | `Object|String` | `null` | The editor state object used by Prosemirror. You can also pass HTML to the `content` slot. When used both, the `content` slot will be ignored. |
|
||||
| `editable` | `Boolean` | `true` | When set to `false` the editor is read-only. |
|
||||
| ~~`editorProps`~~ | ~~`Object`~~ | ~~`{}`~~ | ~~A list of [Prosemirror editorProps](https://prosemirror.net/docs/ref/#view.EditorProps).~~ |
|
||||
| `element` | `Element` | `false` | Focus the editor on init. |
|
||||
| `extensions` | `Array` | `[]` | A list of extensions you would like to use. Can be [`Nodes`](/api/nodes), [`Marks`](/api/marks) or [`Extensions`](/api/extensions). |
|
||||
| `injectCSS` | `Boolean` | `true` | When set to `false` tiptap won’t load [the default ProseMirror CSS](https://github.com/ueberdosis/tiptap-next/tree/main/packages/core/src/style.ts). |
|
||||
| ~~`parseOptions`~~ | ~~`Object`~~ | ~~`{}`~~ | ~~A list of [Prosemirror parseOptions](https://prosemirror.net/docs/ref/#model.ParseOptions).~~ | -->
|
||||
### Editor props
|
||||
For advanced use cases, you can pass `editorProps` which will be handled by [ProseMirror](https://prosemirror.net/docs/ref/#view.EditorProps). Here is an example how you can pass a few [Tailwind](https://tailwindcss.com/) classes to the editor container, but there is a lot more you can do.
|
||||
|
||||
## List of available methods
|
||||
An editor instance will provide the following public methods. They’ll help you to work with the editor.
|
||||
```js
|
||||
new Editor({
|
||||
// Learn more: https://prosemirror.net/docs/ref/#view.EditorProps
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none',
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Don’t confuse methods with [commands](/api/commands), which are used to change the state of editor (content, selection, and so on) and only return `true` or `false`.
|
||||
### Parse options
|
||||
Passed content is parsed by ProseMirror. To hook into the parsing, you can pass `parseOptions` which are then handled by [ProseMirror](https://prosemirror.net/docs/ref/#model.ParseOptions).
|
||||
|
||||
| Method | Parameters | Description |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `can()` | - | Check if a command or a command chain can be executed. Without executing it. |
|
||||
| `chain()` | - | Create a command chain to call multiple commands at once. |
|
||||
| `createDocument()` | `content` EditorContent<br>`parseOptions` | Creates a ProseMirror document. |
|
||||
| `destroy()` | – | Stops the editor instance and unbinds all events. |
|
||||
| `getHTML()` | – | Returns the current content as HTML. |
|
||||
| `getJSON()` | – | Returns the current content as JSON. |
|
||||
| `getMarkAttributes()` | `name` Name of the mark | Get attributes of the currently selected mark. |
|
||||
| `getNodeAttributes()` | `name` Name of the node | Get attributes of the currently selected node. |
|
||||
| `isActive()` | `name` Name of the node or mark<br>`attrs` Attributes of the node or mark | Returns if the currently selected node or mark is active. |
|
||||
| `isEditable()` | - | Returns whether the editor is editable. |
|
||||
| `isEmpty()` | - | Check if there is no content. |
|
||||
| `getCharacterCount()` | - | Get the number of characters for the current document. |
|
||||
| `registerPlugin()` | `plugin` A ProseMirror plugin<br>`handlePlugins` Control how to merge the plugin into the existing plugins. | Register a ProseMirror plugin. |
|
||||
| `setOptions()` | `options` A list of options | Update editor options. |
|
||||
| `unregisterPlugin()` | `name` The plugins name | Unregister a ProseMirror plugin. |
|
||||
```js
|
||||
new Editor({
|
||||
// Learn more: https://prosemirror.net/docs/ref/#model.ParseOptions
|
||||
parseOptions: {
|
||||
preserveWhitespace: 'full',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
@ -41,5 +41,5 @@ This extension requires the [`Collaboration`](/api/extensions/collaboration) ext
|
||||
The content of this editor is shared with other users.
|
||||
:::
|
||||
|
||||
<demo name="Extensions/CollaborationCursor" :show-source="false" />
|
||||
<demo name="Extensions/CollaborationCursor" hide-source />
|
||||
<demo name="Extensions/CollaborationCursor" highlight="11,39-45" />
|
||||
|
@ -48,5 +48,5 @@ yarn add @tiptap/extension-collaboration yjs y-websocket
|
||||
:::warning Public
|
||||
The content of this editor is shared with other users.
|
||||
:::
|
||||
<demo name="Extensions/Collaboration" :show-source="false" />
|
||||
<demo name="Extensions/Collaboration" hide-source />
|
||||
<demo name="Extensions/Collaboration" highlight="10,27-28,35-37,44" />
|
||||
|
@ -7,7 +7,7 @@ With the CodeBlock extension you can add fenced code blocks to your documents. I
|
||||
Type <code>``` </code> (three backticks and a space) or <code>∼∼∼ </code> (three tildes and a space) and a code block is instantly added for you. You can even specify the language, try writing <code>```css </code>. That should add a `language-css` class to the `<code>`-tag.
|
||||
|
||||
::: warning Restrictions
|
||||
The CodeBlock extension doesn’t come with styling and has no syntax highlighting built-in. It’s on our roadmap though.
|
||||
The CodeBlock extension doesn’t come with styling and has no syntax highlighting built-in. It’s [on our roadmap](/api/nodes/code-block-lowlight) though.
|
||||
:::
|
||||
|
||||
## Installation
|
||||
|
@ -8,4 +8,4 @@ The utility helps rendering JSON content as HTML without an editor instance, for
|
||||
[packages/html/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/html/)
|
||||
|
||||
## Usage
|
||||
<demo name="Guide/Content/GenerateHTML" highlight="6,43-48"/>
|
||||
<demo name="Guide/Content/GenerateHTML" highlight="6-7,42-48"/>
|
||||
|
@ -40,7 +40,7 @@ editor.commands.setContent({
|
||||
|
||||
Here is an interactive example where you can see that in action:
|
||||
|
||||
<demo name="Guide/Content/ExportJSON" :show-source="false"/>
|
||||
<demo name="Guide/Content/ExportJSON" hide-source/>
|
||||
|
||||
### Option 2: HTML
|
||||
HTML can be easily rendered in other places, for example in emails and it’s wildly used, so it’s probably easier to switch the editor at some point. Anyway, every editor instance provides a method to get HTML from the current document:
|
||||
@ -64,7 +64,7 @@ editor.commands.setContent(`<p>Example Text</p>`)
|
||||
|
||||
Use this interactive example to fiddle around:
|
||||
|
||||
<demo name="Guide/Content/ExportHTML" :show-source="false"/>
|
||||
<demo name="Guide/Content/ExportHTML" hide-source/>
|
||||
|
||||
### Option 3: Y.js
|
||||
Our editor has amazing support for Y.js, which is amazing to add [realtime collaboration, offline editing, or syncing between devices](/guide/collaborative-editing).
|
||||
@ -110,18 +110,14 @@ const editor = new Editor({
|
||||
### Option 1: Read-only instance of tiptap
|
||||
To render the saved content, set the editor to read-only. That’s how you can achieve the exact same rendering as it’s in the editor, without duplicating your CSS and other code.
|
||||
|
||||
<demo name="Guide/Content/ReadOnly" highlight="3-6,22,28,41-47" />
|
||||
<demo name="Guide/Content/ReadOnly" highlight="3-6,22,28,42-46" />
|
||||
|
||||
### Option 2: Generate HTML from ProseMirror JSON
|
||||
If you need to render the content on the server side, for example to generate the HTML for a blog post which has been written in tiptap, you’ll probably want to do just that without an actual editor instance.
|
||||
|
||||
That’s what the `generateHTML()` is for. It’s a helper function which renders HTML without an actual editor instance.
|
||||
|
||||
:::info Browser-only rendering
|
||||
Import a lightweight implementation of `generateHTML()` from `@tiptap/core` if you’re using the function in a browser context only.
|
||||
:::
|
||||
|
||||
<demo name="Guide/Content/GenerateHTML" highlight="6,43-48"/>
|
||||
<demo name="Guide/Content/GenerateHTML" highlight="6-7,42-48"/>
|
||||
|
||||
## Migration
|
||||
If you’re migrating existing content to tiptap we would recommend to get your existing output to HTML. That’s probably the best format to get your initial content into tiptap, because ProseMirror ensures there is nothing wrong with it. Even if there are some tags or attributes that aren’t allowed (based on your configuration), tiptap just throws them away quietly.
|
||||
@ -131,7 +127,4 @@ We’re about to go through a few cases to help with that, for example we provid
|
||||
[Share your experiences with us!](mailto:humans@tiptap.dev) We’d like to add more information here.
|
||||
|
||||
## Security
|
||||
There’s no reason to use on or the other because of security concerns.
|
||||
|
||||
### Validation
|
||||
Always validate user input sent to an API. Attackers don’t need to use tiptap to send malicious HTML or JSON to an API endpoint.
|
||||
There’s no reason to use on or the other because of security concerns. If someone wants to send malicious content to your server, it doesn’t matter if it’s JSON or HTML. You should always validate user input.
|
||||
|
115
docs/src/docPages/guide/menus.md
Normal file
115
docs/src/docPages/guide/menus.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Create menus
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
tiptap comes very raw, but that’s a good thing. You have full control (and when we say full, we mean full) about the appearance of it. That also means you can (and have to) build a menu on your own. You can start with a few buttons, and we help you to wire everything up.
|
||||
|
||||
## Menus
|
||||
The editor provides a fluent API to trigger commands and add active states. You can use any markup you like. To make the positioning of line menus easier, we provide a few utilities and components. Let’s go through the most typical use cases one by one.
|
||||
|
||||
### Fixed menu
|
||||
A fixed menu, for example on top of the editor, can be anything. We don’t provide such menu. Just add a `<div>` with a few `<button>`s. How those buttons can trigger [commands](/api/commands) is [explained below](#actions).
|
||||
|
||||
### Bubble menu
|
||||
The [bubble menu](/api/extensions/bubble-menu) appears when selecting text. Markup and styling is totally up to you.
|
||||
|
||||
<demos
|
||||
:items="{
|
||||
Vue: 'Extensions/BubbleMenu/Vue',
|
||||
React: 'Extensions/BubbleMenu/React',
|
||||
}"
|
||||
hide-source
|
||||
/>
|
||||
|
||||
It’ll get an absolute position near the text selection. Make sure to add `position: relative` to the wrapper.
|
||||
|
||||
### Floating menu
|
||||
The [floating menu](/api/extensions/floating-menu) appears in empty lines. Markup and styling is totally up to you.
|
||||
|
||||
<demos
|
||||
:items="{
|
||||
Vue: 'Extensions/FloatingMenu/Vue',
|
||||
React: 'Extensions/FloatingMenu/React',
|
||||
}"
|
||||
hide-source
|
||||
/>
|
||||
|
||||
## Buttons
|
||||
Okay, you’ve got your menu. But how do you wire things up?
|
||||
### Commands
|
||||
Let’s assume you’ve got the editor running already and you want to add your first button. You’ll need a `<button>` HTML tag with a click handler. Depending on your setup, that can look like the following example:
|
||||
|
||||
```html
|
||||
<button onclick="editor.chain().toggleBold().focus().run()">
|
||||
Bold
|
||||
</button>
|
||||
```
|
||||
|
||||
Oh, that’s a long command, right? Actually, it’s a [chain of commands](/api/commands#chain-commands), so let’s go through this one by one:
|
||||
|
||||
```js
|
||||
editor.chain().toggleBold().focus().run()
|
||||
```
|
||||
|
||||
1. `editor` should be a tiptap instance,
|
||||
2. `chain()` is used to tell the editor you want to execute multiple commands,
|
||||
3. `focus()` sets the focus back to the editor,
|
||||
4. `toggleBold()` marks the selected text bold, or removes the bold mark from the text selection if it’s already applied and
|
||||
5. `run()` will execute the chain.
|
||||
|
||||
In other words: This will be a typical **Bold** button for your text editor.
|
||||
|
||||
Which commands are available depends on what extensions you have registered with the editor. Most extensions come with a `set…()`, `unset…()` and `toggle…()` command. Read the extension documentation to see what’s actually available or just surf through your code editor’s autocomplete.
|
||||
|
||||
### Keep the focus
|
||||
You have seen the `focus()` command in the above example already. When you click on the button, the browser focuses that DOM element and the editor loses focus. It’s likely you want to add `focus()` to all your menu buttons, so the writing flow of your users isn’t interrupted.
|
||||
|
||||
### The active state
|
||||
The editor provides an `isActive()` method to check if something is applied to the selected text already. In Vue.js you can toggle a CSS class with help of that function like that:
|
||||
|
||||
```html
|
||||
<button :class="{ 'is-active': editor.isActive('bold') }" @click="editor.chain().toggleBold().focus().run()">
|
||||
Bold
|
||||
</button>
|
||||
```
|
||||
|
||||
This toggles the `.is-active` class accordingly and works for nodes and marks. You can even check for specific attributes, here is an example with the [`Highlight`](/api/marks/highlight) mark, that ignores different attributes:
|
||||
|
||||
```js
|
||||
editor.isActive('highlight')
|
||||
```
|
||||
|
||||
And an example that compares the given attribute(s):
|
||||
|
||||
```js
|
||||
editor.isActive('highlight', { color: '#ffa8a8' })
|
||||
```
|
||||
|
||||
You can even ignore nodes and marks, but check for the attributes only. Here is an example with the [`TextAlign`](/api/extensions/text-align) extension:
|
||||
|
||||
```js
|
||||
editor.isActive({ textAlign: 'right' })
|
||||
```
|
||||
|
||||
If your selection spans multiple nodes or marks, or only part of the selection has a mark, `isActive()` will return `false` and indicate nothing is active. This is how it is supposed to be, because it allows people to apply a new node or mark to that selection right-away.
|
||||
|
||||
## User experience
|
||||
When designing a great user experience you should consider a few things.
|
||||
|
||||
### Accessibility
|
||||
* Make sure users can navigate the menu with their keyboard
|
||||
* Use proper [title attributes](https://developer.mozilla.org/de/docs/Web/HTML/Global_attributes/title)
|
||||
* Use proper [aria attributes](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics)
|
||||
* List available keyboard shortcuts
|
||||
|
||||
:::warning Incomplete
|
||||
This section needs some work. Do you know what else needs to be taken into account when building an editor menu? Let us know on [GitHub](https://github.com/ueberdosis/tiptap-next) or send us an email to [humans@tiptap.dev](mailto:humans@tiptap.dev)!
|
||||
:::
|
||||
|
||||
### Icons
|
||||
Most editor menus use icons for their buttons. In some of our demos, we use the open-source icon set [Remix Icon](https://remixicon.com/), that’s free to use. But it’s totally up to you what you use. Here are a few icon sets you can consider:
|
||||
|
||||
* [Remix Icon](https://remixicon.com/#editor)
|
||||
* [Font Awesome](https://fontawesome.com/icons?c=editors)
|
||||
* [UI icons](https://www.ibm.com/design/language/iconography/ui-icons/library/)
|
@ -3,13 +3,23 @@
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
TODO
|
||||
Node views enable you to fully customize your nodes. We are collecting a few different examples here. Feel free to copy them and start building on them.
|
||||
|
||||
Keep in mind that those are just examples to get you started, not officially supported extensions. We don’t have tests for them, and don’t plan to maintain them with the same attention as we do with official extensions.
|
||||
|
||||
## Drag handles
|
||||
Drag handles aren’t that easy to add. We are still on the lookout what’s the best way to add them. Official support will come at some point, but there’s no timeline yet.
|
||||
|
||||
<demo name="Guide/NodeViews/DragHandle" />
|
||||
|
||||
## Table of contents
|
||||
This one loops through the editor content, gives all headings an ID and renders a Table of Contents with Vue.
|
||||
|
||||
<demo name="Guide/NodeViews/TableOfContents" />
|
||||
|
||||
## Drawing in the editor
|
||||
The drawing example shows a SVG that enables you to draw inside the editor.
|
||||
|
||||
<demo name="Examples/Drawing" />
|
||||
|
||||
It’s not working very well with the Collaboration extension. It’s sending all data on every change, which can get pretty huge with Y.js. If you plan to use those two in combination, you need to improve it or your WebSocket backend will melt.
|
||||
|
@ -3,15 +3,25 @@
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
TODO
|
||||
Using frameworks like Vue or React can feel too complex, if you’re used to work without those two. Good news: You can use plain JavaScript 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 node view with JavaScript
|
||||
Here is what you need to do to render a node view inside your editor:
|
||||
|
||||
1. [Create a node extension](/guide/build-extensions)
|
||||
2. Register a new node view with `addNodeView()`
|
||||
3. Write your render function
|
||||
4. [Configure tiptap to use your new node extension](/guide/configuration)
|
||||
|
||||
This is how your node extension could look like:
|
||||
|
||||
## Code snippet
|
||||
```js
|
||||
import { Node } from '@tiptap/core'
|
||||
import { VueNodeViewRenderer } from '@tiptap/vue-2'
|
||||
import Component from './Component.vue'
|
||||
|
||||
export default Node.create({
|
||||
// configuration …
|
||||
|
||||
addNodeView() {
|
||||
return ({ editor, node, getPos, HTMLAttributes, decorations, extension }) => {
|
||||
const dom = document.createElement('div')
|
||||
@ -26,17 +36,88 @@ export default Node.create({
|
||||
})
|
||||
```
|
||||
|
||||
## Render markup
|
||||
Got it? Let’s see it in action. Feel free to copy the below example to get started.
|
||||
|
||||
<demo name="Guide/NodeViews/JavaScript" />
|
||||
|
||||
That node view even interacts with the editor. Time to see how that is wired up.
|
||||
|
||||
## Access node attributes
|
||||
TODO
|
||||
The editor passes a few helpful things to your render function. One of them is the the `node` prop. This one enables you to access node attributes in your node view. Let’s say you have [added an attribute](/guide/extend-extensions#attributes) named `count` to your node extension. You could access the attribute like this:
|
||||
|
||||
```js
|
||||
addNodeView() {
|
||||
return ({ node }) => {
|
||||
console.log(node.attrs.count)
|
||||
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Update node attributes
|
||||
TODO
|
||||
You can even update node attributes from your node view, with the help of the `getPos` prop passed to your render function. Dispatch a new transaction with an object of the updated attributes:
|
||||
|
||||
```js
|
||||
addNodeView() {
|
||||
return ({ editor, node, getPos }) => {
|
||||
const { view } = editor
|
||||
|
||||
// Create a button …
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = `This button has been clicked ${node.attrs.count} times.`
|
||||
|
||||
// … and when it’s clicked …
|
||||
button.addEventListener('click', () => {
|
||||
if (typeof getPos === 'function') {
|
||||
// … dispatch a transaction, for the current position in the document …
|
||||
view.dispatch(view.state.tr.setNodeMarkup(getPos(), undefined, {
|
||||
count: node.attrs.count + 1,
|
||||
}))
|
||||
|
||||
// … and set the focus back to the editor.
|
||||
editor.commands.focus()
|
||||
}
|
||||
})
|
||||
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Does seem a little bit too complex? Consider using [React](/guide/node-views/react) or [Vue](/guide/node-views/vue), if you have one of those in your project anyway. It get’s a little bit easier with those two.
|
||||
|
||||
## Adding a content editable
|
||||
TODO
|
||||
To add editable content to your node view, you need to pass a `contentDOM`, a container element for the content. Here is a simplified version of a node view with non-editable and editable text content:
|
||||
|
||||
```js
|
||||
// Create a container for the node view
|
||||
const dom = document.createElement('div')
|
||||
|
||||
// Give other elements containing text `contentEditable = false`
|
||||
const label = document.createElement('span')
|
||||
label.innerHTML = 'Node view'
|
||||
label.contentEditable = false
|
||||
|
||||
// Create a container for the content
|
||||
const content = document.createElement('div')
|
||||
|
||||
// Append all elements to the node view container
|
||||
dom.append(label, content)
|
||||
|
||||
return {
|
||||
// Pass the node view container …
|
||||
dom,
|
||||
// … and the content container:
|
||||
contentDOM: content,
|
||||
}
|
||||
```
|
||||
|
||||
Got it? You’re free to do anything you like, as long as you return a container for the node view and another one for the content. Here is the above example in action:
|
||||
|
||||
<demo name="Guide/NodeViews/JavaScriptContent" />
|
||||
|
||||
Keep in mind that this content is rendered by tiptap. That means you need to tell what kind of content is allowed, for example with `content: 'inline*'` in your node extension (that’s what we use in the above example).
|
||||
|
||||
|
||||
## All available props
|
||||
TODO
|
||||
|
@ -3,8 +3,106 @@
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
TODO
|
||||
Using plain JavaScript can feel complex if you are used to work in React. Good news: You can use regular React components in your node views, too. There is just a little bit you need to know, but let’s go through this one by one.
|
||||
|
||||
## Render a React component
|
||||
Here is what you need to do to render React components inside your editor:
|
||||
|
||||
1. [Create a node extension](/guide/build-extensions)
|
||||
2. Create a React component
|
||||
3. Pass that component to the provided `ReactNodeViewRenderer`
|
||||
4. Register it with `addNodeView()`
|
||||
5. [Configure tiptap to use your new node extension](/guide/configuration)
|
||||
|
||||
This is how your node extension could look like:
|
||||
|
||||
```js
|
||||
import { Node } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||
import Component from './Component.jsx'
|
||||
|
||||
export default Node.create({
|
||||
// configuration …
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(Component)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
There is a little bit of magic required to make this work. But don’t worry, we provide a wrapper component you can use to get started easily. Don’t forget to add it to your custom React component, like shown below:
|
||||
|
||||
```html
|
||||
<NodeViewWrapper className="react-component">
|
||||
React Component
|
||||
</NodeViewWrapper>
|
||||
```
|
||||
|
||||
Got it? Let’s see it in action. Feel free to copy the below example to get started.
|
||||
|
||||
<demo name="Guide/NodeViews/ReactComponent" />
|
||||
|
||||
That component doesn’t interact with the editor, though. Time to wire it up.
|
||||
|
||||
## Access node attributes
|
||||
The `ReactNodeViewRenderer` which you use in your node extension, passes a few very helpful props to your custom React component. One of them is the `node` prop. Let’s say you have [added an attribute](/guide/extend-extensions#attributes) named `count` to your node extension (like we did in the above example) you could access it like this:
|
||||
|
||||
```js
|
||||
props.node.attrs.count
|
||||
```
|
||||
|
||||
## Update node attributes
|
||||
You can even update node attributes from your node, with the help of the `updateAttributes` prop passed to your component. Pass an object with updated attributes to the `updateAttributes` prop:
|
||||
|
||||
```js
|
||||
export default props => {
|
||||
const increase = () => {
|
||||
props.updateAttributes({
|
||||
count: props.node.attrs.count + 1,
|
||||
})
|
||||
}
|
||||
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
And yes, all of that is reactive, too. A pretty seemless communication, isn’t it?
|
||||
|
||||
## Adding a content editable
|
||||
There is another component called `NodeViewContent` which helps you adding editable content to your node view. Here is an example:
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<NodeViewWrapper className="react-component-with-content">
|
||||
<span className="label" contenteditable="false">React Component</span>
|
||||
|
||||
<NodeViewContent className="content" />
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You don’t need to add those `className` attributes, feel free to remove them or pass other class names. Try it out in the following example:
|
||||
|
||||
<demo name="Guide/NodeViews/ReactComponentContent" />
|
||||
|
||||
Keep in mind that this content is rendered by tiptap. That means you need to tell what kind of content is allowed, for example with `content: 'inline*'` in your node extension (that’s what we use in the above example).
|
||||
|
||||
The `NodeViewWrapper` and `NodeViewContent` components render a `<div>` HTML tag (`<span>` for inline nodes), but you can change that. For example `<NodeViewContent as="p">` should render a paragraph. One limitation though: That tag must not change during runtime.
|
||||
|
||||
## All available props
|
||||
Here is the full list of what props you can expect:
|
||||
|
||||
| Prop | Description |
|
||||
| ------------------ | -------------------------------------------------------- |
|
||||
| `editor` | The editor instance |
|
||||
| `node` | The current node |
|
||||
| `decorations` | An array of decorations |
|
||||
| `selected` | `true` when the cursor is inside the node view |
|
||||
| `extension` | Access to the node extension, for example to get options |
|
||||
| `getPos` | Get the document position of the current node |
|
||||
| `updateAttributes` | Update attributes of the current node |
|
||||
|
@ -3,10 +3,10 @@
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
Using Vanilla JavaScript can feel complex if you are used to work in Vue. Good news: You can use regular Vue components in your node views, too. There is just a little bit you need to know, but let’s go through this one by one.
|
||||
Using plain JavaScript can feel complex if you are used to work in Vue. Good news: You can use regular Vue components in your node views, too. 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:
|
||||
Here is what you need to do to render Vue components inside your editor:
|
||||
|
||||
1. [Create a node extension](/guide/build-extensions)
|
||||
2. Create a Vue component
|
||||
@ -44,10 +44,10 @@ Got it? Let’s see it in action. Feel free to copy the below example to get sta
|
||||
|
||||
<demo name="Guide/NodeViews/VueComponent" />
|
||||
|
||||
That component doesn’t interactive with the editor, though. Time to connect it to the editor output.
|
||||
That component doesn’t interact with the editor, though. Time to wire it up.
|
||||
|
||||
## Access node attributes
|
||||
The `VueNodeViewRenderer` which you use in your node extension, passes a few very helpful props to your custom view component. One of them is the `node` prop. Add this snippet to your Vue component to directly access the node:
|
||||
The `VueNodeViewRenderer` which you use in your node extension, passes a few very helpful props to your custom Vue component. One of them is the `node` prop. Add this snippet to your Vue component to directly access the node:
|
||||
|
||||
```js
|
||||
props: {
|
||||
@ -58,7 +58,7 @@ props: {
|
||||
},
|
||||
```
|
||||
|
||||
That makes it super easy to access node attributes in your Vue component. Let’s say you have [added an attribute](/guide/extend-extensions#attributes) named `count` to your node extension (like we did in the above example) you could access it like this:
|
||||
That enables you to access node attributes in your Vue component. Let’s say you have [added an attribute](/guide/extend-extensions#attributes) named `count` to your node extension (like we did in the above example) you could access it like this:
|
||||
|
||||
```js
|
||||
this.node.attrs.count
|
||||
|
@ -1,72 +0,0 @@
|
||||
# Create a toolbar
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
tiptap comes very raw, but that’s a good thing. You have full control (and when we say full, we mean full) about the appearance of it. That also means you have to build the editor toolbar on your own. Don’t worry though, you can start with a few buttons and we help you with everything else.
|
||||
|
||||
## Commands
|
||||
Let’s assume you’ve got the editor running already and you want to add your first button. You’ll need a `<button>` HTML tag, and add a click handler. Depending on your setup, that can look like the following Vue.js example:
|
||||
|
||||
```html
|
||||
<button @click="editor.chain().toggleBold().focus().run()">
|
||||
Bold
|
||||
</button>
|
||||
```
|
||||
|
||||
Oh, that’s a long command, right? Actually, it’s a [chain of commands](/api/commands#chain-commands), so let’s go through this one by one:
|
||||
|
||||
```js
|
||||
editor.chain().toggleBold().focus().run()
|
||||
```
|
||||
|
||||
1. `editor` should be a tiptap instance,
|
||||
2. `chain()` is used to tell the editor you want to execute multiple commands,
|
||||
3. `focus()` sets the focus back to the editor,
|
||||
4. `toggleBold()` marks the selected text bold, or removes the bold mark from the text selection if it’s already applied and
|
||||
5. `run()` will execute the chain.
|
||||
|
||||
In other words: This will be the typical **Bold** button for your text editor.
|
||||
|
||||
Which commands are available depends on what extensions you’ve registered with the editor. Most of the extensions come with a `set…()`, `unset…()` and `toggle…()` command. Read the extension documentation to see what’s actually available or just surf through your code editor’s autocomplete.
|
||||
|
||||
## Keep the focus
|
||||
You’ve seen the `focus()` command in the above example already. When you click on the button, the browser focuses that DOM element and the editor loses focus. It’s likely you want to add `focus()` to all your toolbar buttons, so the writing flow of your users isn’t interrupted.
|
||||
|
||||
## The active state
|
||||
The editor provides an `isActive()` method to check if something is applied to the selected text already. In Vue.js you can toggle a CSS class with help of that function like that:
|
||||
|
||||
```html
|
||||
<button :class="{ 'is-active': editor.isActive('bold') }" @click="editor.chain().toggleBold().focus().run()">
|
||||
Bold
|
||||
</button>
|
||||
```
|
||||
|
||||
This toggles the `.is-active` class accordingly. This works for nodes, and marks. You can even check for specific attributes, here is an example with the [`Highlight`](/api/marks/highlight) mark, that ignores different attributes:
|
||||
|
||||
```js
|
||||
editor.isActive('highlight')
|
||||
```
|
||||
|
||||
And an example that compares the given attribute(s):
|
||||
|
||||
```js
|
||||
editor.isActive('highlight', { color: '#ffa8a8' })
|
||||
```
|
||||
|
||||
You can even ignore nodes and marks, but check for the attributes only. Here is an example with the [`TextAlign`](/api/extensions/text-align) extension:
|
||||
|
||||
```js
|
||||
editor.isActive({ textAlign: 'right' })
|
||||
```
|
||||
|
||||
If your selection spans multiple nodes or marks, or only part of the selection has a mark, `isActive()` will return `false` and indicate nothing is active. That is how it is supposed to be, because it allows people to apply a new node or mark to that selection right-away.
|
||||
|
||||
## Icons
|
||||
Most editor toolbars use icons for their buttons. In some of our demos, we use the open-source icon set [Remix Icon](https://remixicon.com/), that’s free to use. But it’s totally up to you what you use. Here are a few icon sets you can consider:
|
||||
|
||||
* [Remix Icon](https://remixicon.com/#editor)
|
||||
* [Font Awesome](https://fontawesome.com/icons?c=editors)
|
||||
* [UI icons](https://www.ibm.com/design/language/iconography/ui-icons/library/)
|
||||
|
||||
Also, we’re working on providing a configurable interface for tiptap. If you think that’s a great idea, [become a sponsor](/sponsor) to show us your support. ♥
|
@ -49,7 +49,7 @@ import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
new Editor({
|
||||
element: document.querySelector('.element'),
|
||||
extensions: defaultExtensions(),
|
||||
content: '<p>Your content.</p>',
|
||||
content: '<p>Hello World!</p>',
|
||||
})
|
||||
```
|
||||
|
||||
|
@ -14,7 +14,7 @@ For testing purposes or demos, use our [Skypack](https://www.skypack.dev/) CDN b
|
||||
const editor = new Editor({
|
||||
element: document.querySelector('.element'),
|
||||
extensions: defaultExtensions(),
|
||||
content: '<p>Your content.</p>',
|
||||
content: '<p>Hello World!</p>',
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
@ -3,22 +3,46 @@
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your Svelte project.
|
||||
The following guide describes how to integrate tiptap with your [SvelteKit](https://kit.svelte.dev/) project.
|
||||
|
||||
TODO
|
||||
## Take a shortcut: Svelte REPL with tiptap
|
||||
If you just want to jump into it right-away, here is a [Svelte REPL with tiptap](https://svelte.dev/repl/798f1b81b9184780aca18d9a005487d2?version=3.31.2) installed.
|
||||
|
||||
Svelte REPL: https://svelte.dev/repl/3651789dcfcb40de80b1ba36263d4bbd?version=3.31.2
|
||||
## Requirements
|
||||
* [Node](https://nodejs.org/en/download/) installed on your machine
|
||||
* Experience with [Svelte](https://vuejs.org/v2/guide/#Getting-Started)
|
||||
|
||||
App.svelte
|
||||
```html
|
||||
<script>
|
||||
import Editor from './Editor.svelte';
|
||||
</script>
|
||||
## 1. Create a project (optional)
|
||||
If you already have an existing SvelteKit project, that’s fine too. Just skip this step and proceed with the next step.
|
||||
|
||||
<Editor />
|
||||
For the sake of this guide, let’s start with a fresh SvelteKit project called `tiptap-example`. The following commands set up everything we need. It asks a lot of questions, but just use what floats your boat or use the defaults.
|
||||
|
||||
```bash
|
||||
mkdir tiptap-example
|
||||
cd tiptap-example
|
||||
npm init svelte@next
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Editor.svelte
|
||||
## 2. Install the dependencies
|
||||
Okay, enough of the boring boilerplate work. Let’s finally install tiptap! For the following example you’ll need the `@tiptap/core` package, with a few components, and `@tiptap/starter-kit` which has the most common extensions to get started quickly.
|
||||
|
||||
```bash
|
||||
# install with npm
|
||||
npm install @tiptap/core @tiptap/starter-kit
|
||||
|
||||
# install with Yarn
|
||||
yarn add @tiptap/core @tiptap/starter-kit
|
||||
```
|
||||
|
||||
If you followed step 1 and 2, you can now start your project with `npm run dev` or `yarn dev`, and open [http://localhost:3000/](http://localhost:3000/) in your favorite browser. This might be different, if you’re working with an existing project.
|
||||
|
||||
## 3. Create a new component
|
||||
To actually start using tiptap, you’ll need to add a new component to your app. Let’s call it `Tiptap` and put the following example code in `src/lib/Tiptap.svelte`.
|
||||
|
||||
This is the fastest way to get tiptap up and running with SvelteKit. It will give you a very basic version of tiptap, without any buttons. No worries, you will be able to add more functionality soon.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
@ -32,7 +56,7 @@ Editor.svelte
|
||||
editor = new Editor({
|
||||
element: element,
|
||||
extensions: defaultExtensions(),
|
||||
content: '<p>Hello <strong>Svelte</strong>!<p>',
|
||||
content: '<p>Hello World! 🌍️ </p>',
|
||||
onTransaction: () => {
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor
|
||||
@ -41,19 +65,27 @@ Editor.svelte
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
editor.destroy()
|
||||
if (editor) {
|
||||
editor.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
<button on:click={() => editor.chain().focus().toggleBold().run()} class:active={editor.isActive('bold')}>
|
||||
Bold
|
||||
<button
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 1}).run()}
|
||||
class:active={editor.isActive('heading', { level: 1 })}
|
||||
>
|
||||
H1
|
||||
</button>
|
||||
<button on:click={() => editor.chain().focus().toggleItalic().run()} class:active={editor.isActive('italic')}>
|
||||
Italic
|
||||
<button
|
||||
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
class:active={editor.isActive('heading', { level: 2 })}
|
||||
>
|
||||
H2
|
||||
</button>
|
||||
<button on:click={() => editor.chain().focus().toggleStrike().run()} class:active={editor.isActive('strike')}>
|
||||
Strike
|
||||
<button on:click={() => editor.chain().focus().setParagraph().run()} class:active={editor.isActive('paragraph')}>
|
||||
P
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@ -66,3 +98,18 @@ Editor.svelte
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 4. Add it to your app
|
||||
Now, let’s replace the content of `src/routes/index.svelte` with the following example code to use our new `Tiptap` component in our app.
|
||||
|
||||
```html
|
||||
<script>
|
||||
import Tiptap from '$lib/Tiptap.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Tiptap />
|
||||
</main>
|
||||
```
|
||||
|
||||
You should now see tiptap in your browser. You’ve successfully set up tiptap! Time to give yourself a pat on the back.
|
||||
|
@ -8,7 +8,7 @@ The following guide describes how to integrate tiptap with your [Vue](https://vu
|
||||
## Requirements
|
||||
* [Node](https://nodejs.org/en/download/) installed on your machine
|
||||
* [Vue CLI](https://cli.vuejs.org/) installed on your machine
|
||||
* Experience with [Vue](https://vuejs.org/v2/guide/#Getting-Started)
|
||||
* Experience with [Vue](https://v3.vuejs.org/guide/introduction.html)
|
||||
|
||||
## 1. Create a project (optional)
|
||||
If you already have an existing Vue project, that’s fine too. Just skip this step and proceed with the next step.
|
||||
|
@ -13,7 +13,7 @@ tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a
|
||||
Create exactly the rich text editor you want out of customizable building blocks. tiptap comes with sensible defaults, a lot of extensions and a friendly API to customize every aspect. It’s backed by a welcoming community, open-source, and free.
|
||||
|
||||
## Example
|
||||
<demo name="Examples/CollaborativeEditing" :show-source="false" inline />
|
||||
<demo name="Examples/CollaborativeEditing" hide-source inline />
|
||||
|
||||
## Features
|
||||
**Headless.** We don’t tell you what a menu should look like or where it should be rendered in the DOM. That’s why tiptap is headless and comes without any CSS. You are in full control over markup, styling and behaviour.
|
||||
|
@ -143,6 +143,9 @@ All new extensions come with specific commands to set, unset and toggle styles.
|
||||
| ~~`.underline()`~~ | `.toggleUnderline()` |
|
||||
| … | … |
|
||||
|
||||
### MenuBar, BubbleMenu and FloatingMenu
|
||||
Read the dedicated [guide on creating menus](/guide/menus) to migrate your menus.
|
||||
|
||||
### Commands can be chained now
|
||||
Most commands can be combined to one call now. That’s shorter than separate function calls in most cases. Here is an example to make the selected text bold:
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
skip: true
|
||||
- title: React
|
||||
link: /installation/react
|
||||
type: new
|
||||
skip: true
|
||||
- title: Vue 3
|
||||
link: /installation/vue3
|
||||
@ -21,8 +20,11 @@
|
||||
skip: true
|
||||
- title: Svelte
|
||||
link: /installation/svelte
|
||||
type: draft
|
||||
type: new
|
||||
skip: true
|
||||
# - title: CodeSandbox
|
||||
# link: /installation/codesandbox
|
||||
# skip: true
|
||||
- title: Alpine.js
|
||||
link: /installation/alpine
|
||||
type: draft
|
||||
@ -31,9 +33,6 @@
|
||||
link: /installation/livewire
|
||||
type: draft
|
||||
skip: true
|
||||
- title: CodeSandbox
|
||||
link: /installation/codesandbox
|
||||
skip: true
|
||||
- title: Upgrade guide
|
||||
link: /overview/upgrade-guide
|
||||
- title: Become a sponsor
|
||||
@ -82,8 +81,9 @@
|
||||
items:
|
||||
- title: Configure the editor
|
||||
link: /guide/configuration
|
||||
- title: Create a toolbar
|
||||
link: /guide/toolbar
|
||||
- title: Create menus
|
||||
link: /guide/menus
|
||||
type: new
|
||||
- title: Custom styling
|
||||
link: /guide/styling
|
||||
- title: Accessibility
|
||||
@ -103,10 +103,10 @@
|
||||
items:
|
||||
- title: With JavaScript
|
||||
link: /guide/node-views/js
|
||||
type: draft
|
||||
type: new
|
||||
- title: With React
|
||||
link: /guide/node-views/react
|
||||
type: draft
|
||||
type: new
|
||||
- title: With Vue
|
||||
link: /guide/node-views/vue
|
||||
- title: A few examples
|
||||
@ -133,6 +133,7 @@
|
||||
link: /api/nodes/code-block
|
||||
- title: CodeBlockLowlight
|
||||
link: /api/nodes/code-block-lowlight
|
||||
type: draft
|
||||
- title: Document
|
||||
link: /api/nodes/document
|
||||
- title: Emoji
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/core@2.0.0-beta.13...@tiptap/core@2.0.0-beta.14) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/core@2.0.0-beta.12...@tiptap/core@2.0.0-beta.13) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.12](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/core@2.0.0-beta.11...@tiptap/core@2.0.0-beta.12) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/core
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/core",
|
||||
"description": "headless rich text editor",
|
||||
"version": "2.0.0-beta.12",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
|
@ -94,6 +94,7 @@ export class NodeView<Component, Editor extends CoreEditor = CoreEditor> impleme
|
||||
}
|
||||
|
||||
const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName)
|
||||
|| target.isContentEditable
|
||||
|
||||
// any input event within node views should be ignored by ProseMirror
|
||||
if (isInput) {
|
||||
|
31
packages/core/src/commands/insertNode.ts
Normal file
31
packages/core/src/commands/insertNode.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { NodeType } from 'prosemirror-model'
|
||||
import getNodeType from '../helpers/getNodeType'
|
||||
import { Command, RawCommands, AnyObject } from '../types'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands {
|
||||
insertNode: {
|
||||
/**
|
||||
* Insert a node at the current position.
|
||||
*/
|
||||
insertNode: (typeOrName: string | NodeType, attributes?: AnyObject) => Command,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const insertNode: RawCommands['insertNode'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
|
||||
const { selection } = tr
|
||||
const type = getNodeType(typeOrName, state.schema)
|
||||
|
||||
if (!type) {
|
||||
return false
|
||||
}
|
||||
|
||||
const node = type.create(attributes)
|
||||
|
||||
if (dispatch) {
|
||||
tr.insert(selection.anchor, node)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -12,6 +12,7 @@ import * as extendMarkRange from '../commands/extendMarkRange'
|
||||
import * as first from '../commands/first'
|
||||
import * as focus from '../commands/focus'
|
||||
import * as insertHTML from '../commands/insertHTML'
|
||||
import * as insertNode from '../commands/insertNode'
|
||||
import * as insertText from '../commands/insertText'
|
||||
import * as joinBackward from '../commands/joinBackward'
|
||||
import * as joinForward from '../commands/joinForward'
|
||||
@ -58,6 +59,7 @@ export { extendMarkRange }
|
||||
export { first }
|
||||
export { focus }
|
||||
export { insertHTML }
|
||||
export { insertNode }
|
||||
export { insertText }
|
||||
export { joinBackward }
|
||||
export { joinForward }
|
||||
@ -109,6 +111,7 @@ export const Commands = Extension.create({
|
||||
...first,
|
||||
...focus,
|
||||
...insertHTML,
|
||||
...insertNode,
|
||||
...insertText,
|
||||
...joinBackward,
|
||||
...joinForward,
|
||||
|
@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.2](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/extension-code-block@2.0.0-beta.1...@tiptap/extension-code-block@2.0.0-beta.2) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/extension-code-block
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.1](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/extension-code-block@2.0.0-alpha.11...@tiptap/extension-code-block@2.0.0-beta.1) (2021-03-05)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/extension-code-block
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/extension-code-block",
|
||||
"description": "code block extension for tiptap",
|
||||
"version": "2.0.0-beta.1",
|
||||
"version": "2.0.0-beta.2",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/extension-mention@2.0.0-beta.13...@tiptap/extension-mention@2.0.0-beta.14) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/extension-mention
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/extension-mention@2.0.0-beta.12...@tiptap/extension-mention@2.0.0-beta.13) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/extension-mention
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.12](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/extension-mention@2.0.0-beta.11...@tiptap/extension-mention@2.0.0-beta.12) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/extension-mention
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/extension-mention",
|
||||
"description": "mention extension for tiptap",
|
||||
"version": "2.0.0-beta.12",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
@ -25,6 +25,6 @@
|
||||
"@tiptap/core": "^2.0.0-beta.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/suggestion": "^2.0.0-beta.12"
|
||||
"@tiptap/suggestion": "^2.0.0-beta.14"
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/html@2.0.0-beta.13...@tiptap/html@2.0.0-beta.14) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/html@2.0.0-beta.12...@tiptap/html@2.0.0-beta.13) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.12](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/html@2.0.0-beta.11...@tiptap/html@2.0.0-beta.12) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/html
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/html",
|
||||
"description": "utility package to render tiptap JSON as HTML",
|
||||
"version": "2.0.0-beta.12",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
@ -22,7 +22,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.0.0-beta.12",
|
||||
"@tiptap/core": "^2.0.0-beta.14",
|
||||
"hostic-dom": "^0.8.6",
|
||||
"prosemirror-model": "^1.13.3"
|
||||
}
|
||||
|
@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/react@2.0.0-beta.13...@tiptap/react@2.0.0-beta.14) (2021-04-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* render wrapper element for inline node views as span, fix [#242](https://github.com/ueberdosis/tiptap-next/issues/242) ([bdb5d72](https://github.com/ueberdosis/tiptap-next/commit/bdb5d724956c0c757e29be38fb2c9dd85d8fd36b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/react@2.0.0-beta.12...@tiptap/react@2.0.0-beta.13) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/react
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/react",
|
||||
"description": "React components for tiptap",
|
||||
"version": "2.0.0-beta.13",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
|
@ -66,6 +66,9 @@ class ReactNodeView extends NodeView<React.FunctionComponent, Editor> {
|
||||
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
||||
editor: this.editor,
|
||||
props,
|
||||
as: this.node.isInline
|
||||
? 'span'
|
||||
: 'div',
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,9 @@ function isClassComponent(Component: any) {
|
||||
}
|
||||
|
||||
export interface ReactRendererOptions {
|
||||
as?: string,
|
||||
editor: Editor,
|
||||
props?: AnyObject,
|
||||
as?: string,
|
||||
}
|
||||
|
||||
export class ReactRenderer {
|
||||
@ -31,12 +31,12 @@ export class ReactRenderer {
|
||||
|
||||
ref: React.Component | null = null
|
||||
|
||||
constructor(component: React.Component | React.FunctionComponent, { props = {}, editor }: ReactRendererOptions) {
|
||||
constructor(component: React.Component | React.FunctionComponent, { editor, props = {}, as = 'div' }: ReactRendererOptions) {
|
||||
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString()
|
||||
this.component = component
|
||||
this.editor = editor
|
||||
this.props = props
|
||||
this.element = document.createElement('div')
|
||||
this.element = document.createElement(as)
|
||||
this.element.classList.add('react-renderer')
|
||||
this.render()
|
||||
}
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.9](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/starter-kit@2.0.0-beta.8...@tiptap/starter-kit@2.0.0-beta.9) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/starter-kit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.8](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/starter-kit@2.0.0-beta.7...@tiptap/starter-kit@2.0.0-beta.8) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/starter-kit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.7](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/starter-kit@2.0.0-beta.6...@tiptap/starter-kit@2.0.0-beta.7) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/starter-kit
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/starter-kit",
|
||||
"description": "starter kit for tiptap",
|
||||
"version": "2.0.0-beta.7",
|
||||
"version": "2.0.0-beta.9",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
@ -22,12 +22,12 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.0.0-beta.12",
|
||||
"@tiptap/core": "^2.0.0-beta.14",
|
||||
"@tiptap/extension-blockquote": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-bold": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-bullet-list": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-code": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-code-block": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-code-block": "^2.0.0-beta.2",
|
||||
"@tiptap/extension-document": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-dropcursor": "^2.0.0-beta.1",
|
||||
"@tiptap/extension-gapcursor": "^2.0.0-beta.4",
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/suggestion@2.0.0-beta.13...@tiptap/suggestion@2.0.0-beta.14) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/suggestion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/suggestion@2.0.0-beta.12...@tiptap/suggestion@2.0.0-beta.13) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/suggestion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.12](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/suggestion@2.0.0-beta.11...@tiptap/suggestion@2.0.0-beta.12) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/suggestion
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/suggestion",
|
||||
"description": "suggestion plugin for tiptap",
|
||||
"version": "2.0.0-beta.12",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
@ -22,7 +22,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.0.0-beta.12",
|
||||
"@tiptap/core": "^2.0.0-beta.14",
|
||||
"prosemirror-model": "^1.13.3",
|
||||
"prosemirror-state": "^1.3.4",
|
||||
"prosemirror-view": "^1.18.2"
|
||||
|
@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-2@2.0.0-beta.13...@tiptap/vue-2@2.0.0-beta.14) (2021-04-03)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-2@2.0.0-beta.12...@tiptap/vue-2@2.0.0-beta.13) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-2
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/vue-2",
|
||||
"description": "Vue components for tiptap",
|
||||
"version": "2.0.0-beta.13",
|
||||
"version": "2.0.0-beta.14",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
|
@ -8,11 +8,13 @@ export const NodeViewWrapper = Vue.extend({
|
||||
},
|
||||
},
|
||||
|
||||
inject: ['onDragStart'],
|
||||
inject: ['onDragStart', 'decorationClasses'],
|
||||
|
||||
render(createElement) {
|
||||
return createElement(
|
||||
this.as, {
|
||||
// @ts-ignore
|
||||
class: this.decorationClasses.value,
|
||||
style: {
|
||||
whiteSpace: 'normal',
|
||||
},
|
||||
|
@ -51,6 +51,10 @@ class VueNodeView extends NodeView<(Vue | VueConstructor), Editor> {
|
||||
|
||||
renderer!: VueRenderer
|
||||
|
||||
decorationClasses!: {
|
||||
value: string
|
||||
}
|
||||
|
||||
mount() {
|
||||
const props: NodeViewProps = {
|
||||
editor: this.editor,
|
||||
@ -71,14 +75,19 @@ class VueNodeView extends NodeView<(Vue | VueConstructor), Editor> {
|
||||
isEditable.value = this.editor.isEditable
|
||||
})
|
||||
|
||||
this.decorationClasses = Vue.observable({
|
||||
value: this.getDecorationClasses(),
|
||||
})
|
||||
|
||||
const Component = Vue
|
||||
.extend(this.component)
|
||||
.extend({
|
||||
props: Object.keys(props),
|
||||
provide() {
|
||||
provide: () => {
|
||||
return {
|
||||
onDragStart,
|
||||
isEditable,
|
||||
decorationClasses: this.decorationClasses,
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -122,6 +131,7 @@ class VueNodeView extends NodeView<(Vue | VueConstructor), Editor> {
|
||||
|
||||
this.node = node
|
||||
this.decorations = decorations
|
||||
this.decorationClasses.value = this.getDecorationClasses()
|
||||
this.renderer.updateProps({ node, decorations })
|
||||
|
||||
return true
|
||||
@ -139,6 +149,14 @@ class VueNodeView extends NodeView<(Vue | VueConstructor), Editor> {
|
||||
})
|
||||
}
|
||||
|
||||
getDecorationClasses() {
|
||||
return this.decorations
|
||||
// @ts-ignore
|
||||
.map(item => item.type.attrs.class)
|
||||
.flat()
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.renderer.destroy()
|
||||
}
|
||||
|
@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.15](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-3@2.0.0-beta.14...@tiptap/vue-3@2.0.0-beta.15) (2021-04-03)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-3
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.14](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-3@2.0.0-beta.13...@tiptap/vue-3@2.0.0-beta.14) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-3
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/vue-3",
|
||||
"description": "Vue components for tiptap",
|
||||
"version": "2.0.0-beta.14",
|
||||
"version": "2.0.0-beta.15",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
|
@ -42,6 +42,6 @@ export const BubbleMenu = defineComponent({
|
||||
editor.unregisterPlugin(BubbleMenuPluginKey)
|
||||
})
|
||||
|
||||
return () => h('div', { ref: root, style: { visibility: 'hidden' } }, slots.default?.())
|
||||
return () => h('div', { ref: root }, slots.default?.())
|
||||
},
|
||||
})
|
||||
|
@ -36,6 +36,6 @@ export const FloatingMenu = defineComponent({
|
||||
editor.unregisterPlugin(FloatingMenuPluginKey)
|
||||
})
|
||||
|
||||
return () => h('div', { ref: root, style: { visibility: 'hidden' } }, slots.default?.())
|
||||
return () => h('div', { ref: root }, slots.default?.())
|
||||
},
|
||||
})
|
||||
|
@ -8,11 +8,13 @@ export const NodeViewWrapper = defineComponent({
|
||||
},
|
||||
},
|
||||
|
||||
inject: ['onDragStart'],
|
||||
inject: ['onDragStart', 'decorationClasses'],
|
||||
|
||||
render() {
|
||||
return h(
|
||||
this.as, {
|
||||
// @ts-ignore
|
||||
class: this.decorationClasses.value,
|
||||
style: {
|
||||
whiteSpace: 'normal',
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from '@tiptap/core'
|
||||
import {
|
||||
ref,
|
||||
Ref,
|
||||
provide,
|
||||
PropType,
|
||||
Component,
|
||||
@ -56,6 +57,8 @@ class VueNodeView extends NodeView<Component, Editor> {
|
||||
|
||||
renderer!: VueRenderer
|
||||
|
||||
decorationClasses!: Ref<string>
|
||||
|
||||
mount() {
|
||||
const props: NodeViewProps = {
|
||||
editor: this.editor,
|
||||
@ -74,12 +77,15 @@ class VueNodeView extends NodeView<Component, Editor> {
|
||||
isEditable.value = this.editor.isEditable
|
||||
})
|
||||
|
||||
this.decorationClasses = ref(this.getDecorationClasses())
|
||||
|
||||
const extendedComponent = defineComponent({
|
||||
extends: { ...this.component },
|
||||
props: Object.keys(props),
|
||||
setup: () => {
|
||||
provide('onDragStart', onDragStart)
|
||||
provide('isEditable', isEditable)
|
||||
provide('decorationClasses', this.decorationClasses)
|
||||
|
||||
return (this.component as any).setup?.(props)
|
||||
},
|
||||
@ -124,6 +130,7 @@ class VueNodeView extends NodeView<Component, Editor> {
|
||||
|
||||
this.node = node
|
||||
this.decorations = decorations
|
||||
this.decorationClasses.value = this.getDecorationClasses()
|
||||
this.renderer.updateProps({ node, decorations })
|
||||
|
||||
return true
|
||||
@ -141,6 +148,14 @@ class VueNodeView extends NodeView<Component, Editor> {
|
||||
})
|
||||
}
|
||||
|
||||
getDecorationClasses() {
|
||||
return this.decorations
|
||||
// @ts-ignore
|
||||
.map(item => item.type.attrs.class)
|
||||
.flat()
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.renderer.destroy()
|
||||
}
|
||||
|
@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-beta.13](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-starter-kit@2.0.0-beta.12...@tiptap/vue-starter-kit@2.0.0-beta.13) (2021-04-04)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-starter-kit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.12](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-starter-kit@2.0.0-beta.11...@tiptap/vue-starter-kit@2.0.0-beta.12) (2021-04-02)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-starter-kit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-beta.11](https://github.com/ueberdosis/tiptap-next/compare/@tiptap/vue-starter-kit@2.0.0-beta.10...@tiptap/vue-starter-kit@2.0.0-beta.11) (2021-04-01)
|
||||
|
||||
**Note:** Version bump only for package @tiptap/vue-starter-kit
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@tiptap/vue-starter-kit",
|
||||
"description": "Vue starter kit for tiptap",
|
||||
"version": "2.0.0-beta.11",
|
||||
"version": "2.0.0-beta.13",
|
||||
"homepage": "https://tiptap.dev",
|
||||
"keywords": [
|
||||
"tiptap",
|
||||
@ -22,7 +22,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.7",
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.9",
|
||||
"@tiptap/vue": "^2.0.0-beta.5"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user