tiptap/README.md

537 lines
20 KiB
Markdown
Raw Normal View History

2020-07-14 19:03:03 +08:00
> This repository has been migrated to a new organization. Read more: https://github.com/ueberdosis/tiptap/issues/759
2020-07-13 23:10:50 +08:00
2018-08-21 03:58:59 +08:00
# tiptap
2018-08-24 15:09:32 +08:00
A renderless and extendable rich-text editor for [Vue.js](https://github.com/vuejs/vue)
2018-08-21 04:49:24 +08:00
2020-07-20 17:47:06 +08:00
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ueberdosis/tiptap)
2020-07-14 19:30:37 +08:00
[![Version](https://img.shields.io/npm/v/tiptap.svg?label=version)](https://www.npmjs.com/package/tiptap)
[![Downloads](https://img.shields.io/npm/dm/tiptap.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
[![License](https://img.shields.io/npm/l/tiptap.svg)](https://www.npmjs.com/package/tiptap)
[![Filesize](https://img.badgesize.io/https://unpkg.com/tiptap/dist/tiptap.min.js?compression=gzip&label=size&colorB=000000)](https://www.npmjs.com/package/tiptap)
2020-07-14 19:26:31 +08:00
[![Build Status](https://github.com/ueberdosis/tiptap/workflows/ci/badge.svg)](https://github.com/ueberdosis/tiptap/actions)
2020-07-14 19:30:37 +08:00
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
2018-08-25 21:27:41 +08:00
## Why I built tiptap
2018-10-02 21:03:26 +08:00
I was looking for a text editor for [Vue.js](https://github.com/vuejs/vue) and found some solutions that didn't really satisfy me. The editor should be easy to extend and not based on old dependencies such as jQuery. For React there is already a great editor called [Slate.js](https://github.com/ianstormtaylor/slate), which impresses with its modularity. I came across [Prosemirror](https://github.com/prosemirror) and decided to build on it. Prosemirror is a toolkit for building rich-text editors that are already in use at many well-known companies such as *Atlassian* or *New York Times*.
2018-08-24 15:09:32 +08:00
### What does `renderless` mean?
2018-08-28 14:39:33 +08:00
2020-07-14 19:00:50 +08:00
With renderless components you'll have (almost) full control over markup and styling. I don't want to tell you what a menu should look like or where it should be rendered in the DOM. That's all up to you. There is also a [good article about renderless components](https://adamwathan.me/renderless-components-in-vuejs/) by Adam Wathan.
2018-08-28 14:39:33 +08:00
### How is the data stored under the hood?
2018-10-02 21:03:26 +08:00
You can save your data as a raw `HTML` string or can get a `JSON`-serializable representation of your document. And of course, you can pass these two types back to the editor.
2018-08-28 14:39:33 +08:00
2020-07-14 19:06:52 +08:00
## 💖 Sponsor the development
2020-07-14 19:07:30 +08:00
Are you using tiptap in production? We need your sponsorship to maintain, update and develop tiptap. [Become a Sponsor now!](https://github.com/sponsors/ueberdosis)
2020-07-14 19:06:52 +08:00
2018-08-24 15:13:31 +08:00
## Examples
2020-07-13 22:43:55 +08:00
To check out some live examples, visit [tiptap.dev](https://tiptap.dev/).
2018-08-21 16:11:17 +08:00
## Installation
```
npm install tiptap
```
2018-08-25 21:27:41 +08:00
or
```
yarn add tiptap
```
## Basic Setup
```vue
<template>
2018-11-13 05:49:54 +08:00
<editor-content :editor="editor" />
</template>
<script>
// Import the editor
2018-11-13 05:49:54 +08:00
import { Editor, EditorContent } from 'tiptap'
export default {
components: {
2018-11-13 05:49:54 +08:00
EditorContent,
},
data() {
return {
2018-11-13 05:54:30 +08:00
editor: null,
2018-11-13 05:49:54 +08:00
}
},
2018-11-13 05:54:30 +08:00
mounted() {
this.editor = new Editor({
2018-11-14 23:05:45 +08:00
content: '<p>This is just a boring paragraph</p>',
2018-11-14 23:04:19 +08:00
})
2018-11-13 05:54:30 +08:00
},
2018-11-13 05:49:54 +08:00
beforeDestroy() {
this.editor.destroy()
},
}
</script>
```
2018-11-14 05:24:53 +08:00
## Editor Properties
2018-08-23 14:33:21 +08:00
2018-08-24 15:35:59 +08:00
| **Property** | **Type** | **Default** | **Description** |
| --- | :---: | :---: | --- |
2018-11-15 08:05:59 +08:00
| `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. |
2019-01-29 16:31:26 +08:00
| `editorProps` | `Object` | `{}` | A list of [Prosemirror editorProps](https://prosemirror.net/docs/ref/#view.EditorProps). |
2018-08-24 16:11:00 +08:00
| `editable` | `Boolean` | `true` | When set to `false` the editor is read-only. |
2019-01-02 05:29:08 +08:00
| `autoFocus` | `Boolean` | `false` | Focus the editor on init. |
2018-08-24 16:11:00 +08:00
| `extensions` | `Array` | `[]` | A list of extensions used, by the editor. This can be `Nodes`, `Marks` or `Plugins`. |
2018-11-23 04:56:19 +08:00
| `useBuiltInExtensions` | `Boolean` | `true` | By default tiptap adds a `Doc`, `Paragraph` and `Text` node to the Prosemirror schema. |
2018-12-20 15:21:21 +08:00
| `dropCursor` | `Object` | `{}` | Config for `prosemirror-dropcursor`. |
2019-04-16 16:29:03 +08:00
| `parseOptions` | `Object` | `{}` | A list of [Prosemirror parseOptions](https://prosemirror.net/docs/ref/#model.ParseOptions). |
2018-11-14 05:24:53 +08:00
| `onInit` | `Function` | `undefined` | This will return an Object with the current `state` and `view` of Prosemirror on init. |
| `onFocus` | `Function` | `undefined` | This will return an Object with the `event` and current `state` and `view` of Prosemirror on focus. |
| `onBlur` | `Function` | `undefined` | This will return an Object with the `event` and current `state` and `view` of Prosemirror on blur. |
2019-01-02 05:31:29 +08:00
| `onUpdate` | `Function` | `undefined` | This will return an Object with the current `state` of Prosemirror, a `getJSON()` and `getHTML()` function and the `transaction` on every change. |
2018-08-23 14:33:21 +08:00
2019-02-02 02:03:49 +08:00
## Editor Methods
| **Method** | **Arguments**| **Description** |
| --- | :---: | --- |
| `setContent` | `content, emitUpdate, parseOptions` | Replace the current content. You can pass an HTML string or a JSON document. `emitUpdate` defaults to `false`. `parseOptions` defaults to those provided in constructor. |
2019-02-02 02:03:49 +08:00
| `clearContent` | `emitUpdate` | Clears the current content. `emitUpdate` defaults to `false`. |
| `setOptions` | `options` | Overwrites the current editor properties. |
| `registerPlugin` | `plugin`, `handlePlugins` | Register a Prosemirror plugin. You can pass a function `handlePlugins` with parameters `(plugin, oldPlugins)` to define an order in which `newPlugins` will be called. `handlePlugins` defaults to pushing `plugin` to front of `oldPlugins`. |
2019-02-02 02:03:49 +08:00
| `getJSON` | | Get the current content as JSON. |
| `getHTML` | | Get the current content as HTML. |
2019-02-13 05:22:56 +08:00
| `focus` | | Focus the editor. |
| `blur` | | Blur the editor. |
| `destroy` | | Destroy the editor. |
2019-02-02 02:03:49 +08:00
2018-11-13 05:49:54 +08:00
## Components
2018-08-26 21:51:35 +08:00
| **Name** | **Description** |
| --- | --- |
2018-11-13 05:49:54 +08:00
| `<editor-content />` | Here the content will be rendered. |
| `<editor-menu-bar />` | Here a menu bar will be rendered. |
| `<editor-menu-bubble />` | Here a menu bubble will be rendered. |
| `<editor-floating-menu />` | Here a floating menu will be rendered. |
2018-08-26 21:51:35 +08:00
2018-11-13 05:49:54 +08:00
### EditorMenuBar
2018-08-26 21:51:35 +08:00
2018-11-13 05:49:54 +08:00
The `<editor-menu-bar />` component is renderless and will receive some properties through a scoped slot.
2018-08-26 21:51:35 +08:00
| **Property** | **Type** | **Description** |
| --- | :---: | --- |
2018-11-13 05:49:54 +08:00
| `commands` | `Array` | A list of all commands. |
2018-11-14 23:41:44 +08:00
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
2018-11-23 21:14:15 +08:00
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
2020-04-11 19:02:19 +08:00
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
2018-08-26 21:51:35 +08:00
| `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. |
2018-11-13 05:49:54 +08:00
#### Example
```vue
<template>
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
<div>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.bold() }" @click="commands.bold">
2018-11-13 05:49:54 +08:00
Bold
</button>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.heading({ level: 2 }) }" @click="commands.heading({ level: 2 })">
H2
</button>
2018-11-13 05:49:54 +08:00
</div>
</editor-menu-bar>
</template>
```
### EditorMenuBubble
The `<editor-menu-bubble />` component is renderless and will receive some properties through a scoped slot.
| **Property** | **Type** | **Description** |
| --- | :---: | --- |
| `commands` | `Array` | A list of all commands. |
2018-11-14 23:41:44 +08:00
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
2018-11-23 21:14:15 +08:00
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
2020-04-11 19:02:19 +08:00
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
2018-11-13 05:49:54 +08:00
| `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. |
| `menu` | `Object` | An object for positioning your menu. |
#### Example
```vue
<template>
<editor-menu-bubble :editor="editor" v-slot="{ commands, isActive, menu }">
2018-11-13 05:49:54 +08:00
<div
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.bold() }" @click="commands.bold">
2018-11-13 05:49:54 +08:00
Bold
</button>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.heading({ level: 2 }) }" @click="commands.heading({ level: 2 })">
H2
</button>
2018-11-13 05:49:54 +08:00
</div>
</editor-menu-bubble>
</template>
```
### EditorFloatingMenu
The `<editor-floating-menu />` component is renderless and will receive some properties through a scoped slot.
| **Property** | **Type** | **Description** |
| --- | :---: | --- |
| `commands` | `Array` | A list of all commands. |
2018-11-14 23:41:44 +08:00
| `isActive` | `Object` | An object of functions to check if your selected text is a node or mark. `isActive.{node|mark}(attrs)` |
2018-11-23 21:14:15 +08:00
| `getMarkAttrs` | `Function` | A function to get all mark attributes of your selection. |
2020-04-11 19:02:19 +08:00
| `getNodeAttrs` | `Function` | A function to get all node attributes of your selection. |
2018-11-13 05:49:54 +08:00
| `focused` | `Boolean` | Whether the editor is focused. |
| `focus` | `Function` | A function to focus the editor. |
| `menu` | `Object` | An object for positioning your menu. |
#### Example
```vue
<template>
<editor-floating-menu :editor="editor" v-slot="{ commands, isActive, menu }">
2018-11-13 05:49:54 +08:00
<div
:class="{ 'is-active': menu.isActive }"
:style="`top: ${menu.top}px`"
>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.bold() }" @click="commands.bold">
2018-11-13 05:49:54 +08:00
Bold
</button>
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.heading({ level: 2 }) }" @click="commands.heading({ level: 2 })">
H2
</button>
2018-11-13 05:49:54 +08:00
</div>
</editor-floating-menu>
</template>
```
2018-08-23 14:33:21 +08:00
## Extensions
2018-10-02 21:03:26 +08:00
By default, the editor will only support paragraphs. Other nodes and marks are available as **extensions**. There is a package called `tiptap-extensions` with the most basic nodes, marks, and plugins.
### Available Extensions
```vue
<template>
2018-11-13 05:49:54 +08:00
<div>
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
2018-11-14 23:41:44 +08:00
<button :class="{ 'is-active': isActive.bold() }" @click="commands.bold">
2018-11-13 05:49:54 +08:00
Bold
</button>
</editor-menu-bar>
<editor-content :editor="editor" />
</div>
</template>
<script>
2018-11-13 05:49:54 +08:00
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import {
2018-11-13 05:49:54 +08:00
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
export default {
components: {
2018-11-13 05:49:54 +08:00
EditorMenuBar,
EditorContent,
},
data() {
return {
2018-11-13 05:49:54 +08:00
editor: new Editor({
extensions: [
new Blockquote(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
2018-11-14 05:32:24 +08:00
new BulletList(),
2018-11-13 05:49:54 +08:00
new OrderedList(),
2018-11-14 05:32:24 +08:00
new ListItem(),
2018-11-13 05:49:54 +08:00
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h1>Yay Headlines!</h1>
<p>All these <strong>cool tags</strong> are working now.</p>
`,
}),
}
},
2018-11-13 05:49:54 +08:00
beforeDestroy() {
this.editor.destroy()
},
}
</script>
```
### Create Custom Extensions
2018-08-27 00:47:08 +08:00
The most powerful feature of tiptap is that you can create your own extensions. There are 3 types of extensions.
2018-08-24 15:35:59 +08:00
| **Type** | **Description** |
| --- | --- |
2018-08-24 16:11:00 +08:00
| `Extension` | The most basic type. It's useful to register some [Prosemirror plugins](https://prosemirror.net/docs/guide/) or some input rules. |
2018-10-02 21:03:26 +08:00
| `Node` | Add a custom node. Nodes are block elements like a headline or a paragraph. |
2018-08-24 16:11:00 +08:00
| `Mark` | Add a custom mark. Marks are used to add extra styling or other information to inline content like a strong tag or links. |
### Extension Class
| **Method** | **Type** | **Default** | **Description** |
| --- | :---: | :---: | --- |
2018-08-24 16:11:00 +08:00
| `get name()` | `String` | `null` | Define a name for your extension. |
| `get defaultOptions()` | `Object` | `{}` | Define some default options. The options are available as `this.$options`. |
| `get plugins()` | `Array` | `[]` | Define a list of [Prosemirror plugins](https://prosemirror.net/docs/guide/). |
| `keys({ schema })` | `Object` | `null` | Define some keybindings. |
2018-11-14 05:24:53 +08:00
| `commands({ schema, attrs })` | `Object` | `null` | Define a command. |
| `inputRules({ schema })` | `Array` | `[]` | Define a list of input rules. |
2018-11-26 18:19:06 +08:00
| `pasteRules({ schema })` | `Array` | `[]` | Define a list of paste rules. |
| `get update()` | `Function` | `undefined` | Called when options of extension are changed via `editor.extensions.options` |
2018-08-24 16:11:00 +08:00
### Node|Mark Class
| **Method** | **Type** | **Default** | **Description** |
| --- | :---: | :---: | --- |
2018-08-24 16:11:00 +08:00
| `get name()` | `String` | `null` | Define a name for your node or mark. |
| `get defaultOptions()` | `Object` | `{}` | Define some default options. The options are available as `this.$options`. |
| `get schema()` | `Object` | `null` | Define a [schema](https://prosemirror.net/docs/guide/#schema). |
| `get view()` | `Object` | `null` | Define a node view as a vue component. |
| `keys({ type, schema })` | `Object` | `null` | Define some keybindings. |
2018-11-14 05:24:53 +08:00
| `commands({ type, schema, attrs })` | `Object` | `null` | Define a command. For example this is used for menus to convert to this node or mark. |
2018-08-24 16:12:40 +08:00
| `inputRules({ type, schema })` | `Array` | `[]` | Define a list of input rules. |
2018-11-26 18:19:06 +08:00
| `pasteRules({ type, schema })` | `Array` | `[]` | Define a list of paste rules. |
2018-08-24 16:11:00 +08:00
| `get plugins()` | `Array` | `[]` | Define a list of [Prosemirror plugins](https://prosemirror.net/docs/guide/). |
2018-08-24 15:35:59 +08:00
2018-08-25 22:39:33 +08:00
### Create a Node
2018-08-24 22:52:42 +08:00
Let's take a look at a real example. This is basically how the default `blockquote` node from [`tiptap-extensions`](https://www.npmjs.com/package/tiptap-extensions) looks like.
```js
import { Node } from 'tiptap'
import { wrappingInputRule, setBlockType, toggleWrap } from 'tiptap-commands'
2018-08-24 22:52:42 +08:00
export default class BlockquoteNode extends Node {
2018-08-24 22:52:42 +08:00
// choose a unique name
get name() {
return 'blockquote'
}
2018-08-24 22:52:42 +08:00
// the prosemirror schema object
2018-08-25 21:34:14 +08:00
// take a look at https://prosemirror.net/docs/guide/#schema for a detailed explanation
2018-08-24 22:52:42 +08:00
get schema() {
return {
content: 'block+',
group: 'block',
defining: true,
draggable: false,
2018-08-25 21:34:14 +08:00
// define how the editor will detect your node from pasted HTML
// every blockquote tag will be converted to this blockquote node
2018-08-24 22:52:42 +08:00
parseDOM: [
{ tag: 'blockquote' },
],
// this is how this node will be rendered
2018-08-25 21:34:14 +08:00
// in this case a blockquote tag with a class called `awesome-blockquote` will be rendered
// the '0' stands for its text content inside
2018-08-24 22:52:42 +08:00
toDOM: () => ['blockquote', { class: 'awesome-blockquote' }, 0],
}
}
2018-08-24 22:52:42 +08:00
// this command will be called from menus to add a blockquote
2018-08-25 21:34:14 +08:00
// `type` is the prosemirror schema object for this blockquote
// `schema` is a collection of all registered nodes and marks
2018-11-13 05:49:54 +08:00
commands({ type, schema }) {
return () => toggleWrap(type)
2018-08-24 22:52:42 +08:00
}
2018-08-24 22:52:42 +08:00
// here you can register some shortcuts
2018-08-25 21:34:14 +08:00
// in this case you can create a blockquote with `ctrl` + `>`
2018-08-24 22:52:42 +08:00
keys({ type }) {
return {
'Ctrl->': toggleWrap(type),
2018-08-24 22:52:42 +08:00
}
}
2018-08-25 21:34:14 +08:00
// a blockquote will be created when you are on a new line and type `>` followed by a space
2018-08-24 22:52:42 +08:00
inputRules({ type }) {
return [
wrappingInputRule(/^\s*>\s$/, type),
]
}
}
```
2018-08-25 22:39:33 +08:00
### Create a Node as a Vue Component
2018-08-24 22:52:42 +08:00
2020-07-13 23:08:17 +08:00
The real power of the nodes comes in combination with Vue components. Let us build an iframe node, where you can change its URL (this can also be found in our [examples](https://github.com/ueberdosis/tiptap/tree/master/examples/Components/Routes/Embeds)).
2018-08-25 22:39:33 +08:00
```js
import { Node } from 'tiptap'
export default class IframeNode extends Node {
get name() {
return 'iframe'
}
get schema() {
return {
// here you have to specify all values that can be stored in this node
attrs: {
src: {
default: null,
},
},
group: 'block',
selectable: false,
// parseDOM and toDOM is still required to make copy and paste work
parseDOM: [{
tag: 'iframe',
getAttrs: dom => ({
src: dom.getAttribute('src'),
}),
}],
toDOM: node => ['iframe', {
src: node.attrs.src,
frameborder: 0,
allowfullscreen: 'true',
}],
}
}
2018-08-25 22:39:33 +08:00
// return a vue component
// this can be an object or an imported component
get view() {
return {
// there are some props available
// `node` is a Prosemirror Node Object
// `updateAttrs` is a function to update attributes defined in `schema`
2019-05-22 03:22:15 +08:00
// `view` is the ProseMirror view instance
// `options` is an array of your extension options
2020-02-13 16:34:33 +08:00
// `selected` is a boolean which is true when selected
// `editor` is a reference to the TipTap editor instance
// `getPos` is a function to retrieve the start position of the node
// `decorations` is an array of decorations around the node
2019-05-22 03:22:15 +08:00
props: ['node', 'updateAttrs', 'view'],
2018-11-14 23:41:44 +08:00
computed: {
src: {
get() {
return this.node.attrs.src
},
set(src) {
// we cannot update `src` itself because `this.node.attrs` is immutable
this.updateAttrs({
src,
})
},
2018-08-25 22:39:33 +08:00
},
},
template: `
<div class="iframe">
2018-11-14 23:41:44 +08:00
<iframe class="iframe__embed" :src="src"></iframe>
2019-05-22 03:22:15 +08:00
<input class="iframe__input" type="text" v-model="src" v-if="view.editable" />
2018-08-25 22:39:33 +08:00
</div>
`,
}
}
}
```
2018-08-23 14:33:21 +08:00
#### NodeView Prop Types
| **Prop** | **Type** | **Description** |
| --- | :---: | --- |
| `node` | `Object` | The Prosemirror node object. Common use case is to get `node.attrs` using a getter on a computed property. |
| `updateAttrs` | `Function` | A function to update `node.attrs` defined in `schema`. Common use case is as setter on a computed property. |
| `view` | `Object` | The Prosemirror editor view instance. |
| `options` | `Array` | An array of your extension options. |
| `getPos` | `Function` | A function that returns the anchored position of the node. |
| `selected` | `Boolean` | A boolean that is set when the node is or is not selected. Common use case is using `watch` to see when the view is selected/unselected to do something, such focus an `<input>` or refocus the editor. |
## Editor Methods
2018-09-01 15:35:39 +08:00
## Development Setup
2018-10-02 21:03:26 +08:00
Currently, only Yarn is supported for development because of a feature called workspaces we are using here.
2018-09-01 15:35:39 +08:00
``` bash
2018-10-02 21:03:26 +08:00
# install dependencies
2018-09-01 15:35:39 +08:00
yarn install
# serve examples at localhost:3000
yarn start
# build dist files for packages
yarn build:packages
# build dist files for examples
yarn build:examples
```
## Contribute using the online one-click setup
You can use Gitpod(a free online VS Code-like IDE) for contributing. With a single click, it will launch a workspace and automatically:
- clone the `tiptap` repo.
- install the dependencies.
- run `yarn run start`.
So that anyone interested in contributing can start straight away.
2020-07-20 17:48:11 +08:00
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ueberdosis/tiptap/)
2018-08-21 16:11:17 +08:00
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
## Credits
- [Philipp Kühn](https://github.com/philippkuehn)
2020-07-14 19:02:45 +08:00
- [Hans Pagel](https://github.com/hanspagel)
- [Christoph Flathmann](https://github.com/Chrissi2812)
- [Erick Wilder](https://github.com/erickwilder)
2019-05-07 13:59:41 +08:00
- [Marius Tolzmann](https://github.com/mariux)
2018-08-21 16:11:17 +08:00
- [All Contributors](../../contributors)
2020-03-27 17:23:26 +08:00
## Related projects
2018-12-14 22:49:37 +08:00
2020-07-14 19:02:45 +08:00
- [html-to-prosemirror](https://github.com/ueberdosis/html-to-prosemirror) by @hanspagel
- [prosemirror-to-html](https://github.com/ueberdosis/prosemirror-to-html) by @hanspagel
2020-03-27 17:23:26 +08:00
- [tiptap-svelte](https://github.com/andrewjk/tiptap-svelte) by @andrewjk
2018-12-14 22:49:37 +08:00
- [Laravel Nova Tiptap Editor Field](https://github.com/manogi/nova-tiptap) by @manogi
2019-05-25 08:41:07 +08:00
- [WYSIWYG editor for Vuetify](https://github.com/iliyaZelenko/tiptap-vuetify) by @iliyaZelenko
- [Quasar Tiptap Demo](https://github.com/kfields/quasar-tiptap-demo) @kfields
- [Python Library that converts tiptap JSON](https://github.com/scrolltech/tiptapy) @scrolltech
2020-03-14 22:41:01 +08:00
- [WYSIWYG editor for Element UI](https://github.com/Leecason/element-tiptap) by @Leecason
- [WYSIWYG editor for Quasar Framework](https://github.com/donotebase/quasar-tiptap) by @mekery
2018-12-14 22:49:37 +08:00
2019-05-23 01:51:29 +08:00
## Love our work?
[Sponsor us](https://github.com/sponsors/ueberdosis) ❤️
2019-05-23 01:51:29 +08:00
2018-08-21 16:11:17 +08:00
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.