merge installation and the getting started guide
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
# Getting started
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
tiptap 2 is framework-agnostic and even works with plain JavaScript, if that’s your thing. We’re working on guides for all the different frameworks and workflows, but here is the general one. The following steps should help you to integrate tiptap in your JavaScript project.
|
||||
|
||||
## Alternative Guides
|
||||
* [Vue CLI](/guide/getting-started/vue)
|
||||
* [Nuxt.js](/guide/getting-started/nuxt)
|
||||
* [React](/guide/getting-started/react) (Draft)
|
||||
* [Svelte](/guide/getting-started/svelte) (Draft)
|
||||
* [Alpine.js](/guide/getting-started/alpine) (Draft)
|
||||
* [Livewire](/guide/getting-started/livewire) (Draft)
|
||||
|
||||
## Requirements
|
||||
* [Node](https://nodejs.org/en/download/) installed on your machine
|
||||
|
||||
## 1. Install the dependencies
|
||||
For the following example you’ll need the `@tiptap/core` (the actual editor) and the `@tiptap/starter-kit` which has everything to get started quickly, for example a few default extensions.
|
||||
|
||||
```bash
|
||||
# install with npm
|
||||
npm install @tiptap/core @tiptap/starter-kit
|
||||
|
||||
# install with Yarn
|
||||
yarn add @tiptap/core @tiptap/starter-kit
|
||||
```
|
||||
|
||||
## 2. Add a container
|
||||
You need a place somewhere in your app, where we should place tiptap. Place the following HTML there:
|
||||
|
||||
```html
|
||||
<div class="element"></div>
|
||||
```
|
||||
|
||||
## 3. Initialize the editor
|
||||
Now, let’s initialize the editor in JavaScript:
|
||||
|
||||
```js
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
|
||||
new Editor({
|
||||
element: document.querySelector('.element'),
|
||||
extensions: defaultExtensions(),
|
||||
content: '<p>Your content.</p>',
|
||||
})
|
||||
```
|
||||
|
||||
When you open the project in your browser, you should now see tiptap in action. Time to give yourself a pat on the back. Let’s start to configure your editor in the next step.
|
||||
@@ -1,112 +0,0 @@
|
||||
# Alpine.js
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [Alpine.js](https://github.com/alpinejs/alpine) project.
|
||||
|
||||
TODO
|
||||
|
||||
https://codesandbox.io/s/alpine-tiptap-2ro5e?file=/index.html:0-1419
|
||||
|
||||
index.html
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Parcel Sandbox</title>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.0/dist/alpine.min.js"
|
||||
defer
|
||||
></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div
|
||||
x-data="setupEditor('<p>My content goes here</p>')"
|
||||
x-init="() => init($refs.editor)"
|
||||
x-on:click.away="inFocus = false;"
|
||||
>
|
||||
<template x-if="editor">
|
||||
<nav class="space-x-1">
|
||||
<button
|
||||
class="bg-gray-200 rounded px-2 py-1"
|
||||
x-bind:class="{ 'bg-gray-600 text-white': editor.isActive('bold') }"
|
||||
@click="editor.chain().focus().toggleBold().run()"
|
||||
>
|
||||
Bold
|
||||
</button>
|
||||
<button
|
||||
class="bg-gray-200 rounded px-2 py-1"
|
||||
x-bind:class="{ 'bg-gray-600 text-white': editor.isActive('italic') }"
|
||||
@click="editor.chain().focus().toggleItalic().run()"
|
||||
>
|
||||
Italic
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
<div x-ref="editor"></div>
|
||||
<button
|
||||
class="bg-indigo-500 text-white rounded px-3 py-1"
|
||||
x-on:click="console.log(content)"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
(view console for editor output)
|
||||
</div>
|
||||
|
||||
<script src="src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
index.js
|
||||
|
||||
```js
|
||||
import { Editor as TipTap } from "@tiptap/core";
|
||||
import { defaultExtensions } from "@tiptap/starter-kit";
|
||||
|
||||
window.setupEditor = function (content) {
|
||||
return {
|
||||
content: content,
|
||||
inFocus: false,
|
||||
// updatedAt is to force Alpine to
|
||||
// rerender on selection change
|
||||
updatedAt: Date.now(),
|
||||
editor: null,
|
||||
|
||||
init(el) {
|
||||
let editor = new TipTap({
|
||||
element: el,
|
||||
extensions: defaultExtensions(),
|
||||
content: this.content,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: "prose-sm py-4 focus:outline-none"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
editor.on("update", () => {
|
||||
this.content = this.editor.getHTML();
|
||||
});
|
||||
|
||||
editor.on("focus", () => {
|
||||
this.inFocus = true;
|
||||
});
|
||||
|
||||
editor.on("selection", () => {
|
||||
this.updatedAt = Date.now();
|
||||
});
|
||||
|
||||
this.editor = editor;
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
# Livewire
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [Livewire](https://laravel-livewire.com/) project.
|
||||
|
||||
TODO
|
||||
|
||||
editor.blade.php
|
||||
|
||||
```html
|
||||
<!--
|
||||
In your livewire component you could add an
|
||||
autosave method to handle saving the content
|
||||
from the editor every 10 seconds if you wanted
|
||||
-->
|
||||
<x-editor
|
||||
wire:model="foo"
|
||||
wire:poll.10000ms="autosave"
|
||||
></x-editor>
|
||||
```
|
||||
|
||||
my-livewire-component.blade.php
|
||||
|
||||
```html
|
||||
<div
|
||||
x-data="setupEditor(
|
||||
@entangle($attributes->wire('model')).defer
|
||||
)"
|
||||
x-init="() => init($refs.editor)"
|
||||
x-on:click.away="inFocus = false;"
|
||||
wire:ignore
|
||||
{{ $attributes->whereDoesntStartWith('wire:model') }}
|
||||
>
|
||||
<div x-ref="editor"></div>
|
||||
</div>
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
# Next.js
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [Next.js](https://nextjs.org/) project.
|
||||
|
||||
TODO
|
||||
|
||||
<demo name="React" mode="react" />
|
||||
@@ -1,98 +0,0 @@
|
||||
# Nuxt.js
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [Nuxt.js](https://nuxtjs.org/) project.
|
||||
|
||||
## Requirements
|
||||
* [Node](https://nodejs.org/en/download/) installed on your machine
|
||||
* Experience with [Vue](https://vuejs.org/v2/guide/#Getting-Started)
|
||||
|
||||
## 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.
|
||||
|
||||
For the sake of this guide, let’s start with a fresh Nuxt.js project called `tiptap-example`. The following command sets up everything we need. It asks a lot of questions, but just use what floats your boat or use the defaults.
|
||||
|
||||
```bash
|
||||
# create a project
|
||||
npm init nuxt-app tiptap-example
|
||||
|
||||
# change directory
|
||||
cd tiptap-example
|
||||
```
|
||||
|
||||
## 2. Install the dependencies
|
||||
Okay, enough of the boring boilerplate work. Let’s finally install tiptap! For the following example you’ll need `@tiptap/core` (the actual editor) and the `@tiptap/vue-starter-kit` which has everything to get started quickly, for example a few default extensions and a basic Vue component.
|
||||
|
||||
```bash
|
||||
# install with npm
|
||||
npm install @tiptap/core @tiptap/vue-starter-kit
|
||||
|
||||
# install with Yarn
|
||||
yarn add @tiptap/core @tiptap/vue-starter-kit
|
||||
```
|
||||
|
||||
If you followed step 1 and 2, you can now start your project with `npm run serve` or `yarn serve`, and open [http://localhost:8080/](http://localhost:8080/) 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/components/Tiptap.vue`.
|
||||
|
||||
This is the fastest way to get tiptap up and running with Vue. 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
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
content: '<p>I’m running tiptap with Vue.js. 🎉</p>',
|
||||
extensions: defaultExtensions(),
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 4. Add it to your app
|
||||
Now, let’s replace the content of `pages/index.vue` with the following example code to use our new `Tiptap` component in our app.
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<client-only>
|
||||
<tiptap />
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Note that tiptap needs to run in the client, not on the server. It’s required to wrap the editor in a `<client-only>` tag.
|
||||
|
||||
[Read more](https://nuxtjs.org/api/components-client-only)
|
||||
|
||||
You should now see tiptap in your browser. You’ve successfully set up tiptap! Time to give yourself a pat on the back. Let’s start to configure your editor in the next step.
|
||||
|
||||
## 5. Use v-model (optional)
|
||||
You’re probably used to bind your data with `v-model` in forms, that’s also possible with tiptap. Here is a working example component, that you can integrate in your project:
|
||||
|
||||
<demo name="Guide/GettingStarted/VModel" />
|
||||
@@ -1,10 +0,0 @@
|
||||
# React
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [React](https://reactjs.org/) project.
|
||||
|
||||
TODO
|
||||
|
||||
<demo name="React" mode="react" />
|
||||
@@ -1,52 +0,0 @@
|
||||
# Svelte
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your Svelte project.
|
||||
|
||||
TODO
|
||||
|
||||
Svelte REPL: https://svelte.dev/repl/c839da77db2444e5b23a752266613639?version=3.31.2
|
||||
|
||||
App.svelte
|
||||
```html
|
||||
<script>
|
||||
import Editor from './Editor.svelte';
|
||||
</script>
|
||||
|
||||
<Editor />
|
||||
```
|
||||
|
||||
Editor.svelte
|
||||
```html
|
||||
<script type="module">
|
||||
import { onMount } from 'svelte'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||
|
||||
let element
|
||||
let editor
|
||||
|
||||
onMount(() => {
|
||||
editor = new Editor({
|
||||
element: element,
|
||||
extensions: defaultExtensions(),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
<button on:click={editor.chain().focus().toggleBold().run()} class:error={editor.isActive('bold')}>
|
||||
bold
|
||||
</button>
|
||||
<button on:click={editor.chain().focus().toggleItalic().run()} class:error={editor.isActive('italic')}>
|
||||
italic
|
||||
</button>
|
||||
<button on:click={editor.chain().focus().toggleStrike().run()} class:error={editor.isActive('strike')}>
|
||||
strike
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div bind:this={element} />
|
||||
```
|
||||
@@ -1,393 +0,0 @@
|
||||
# Vue.js 3
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The `@tiptap/vue` package is not yet ported to Vue 3. Meanwhile, you can find compatible version contributed by [@samwillis](https://github.com/samwillis) and [@areknawo](https://github.com/areknawo) here.
|
||||
|
||||
## EditorContent.ts
|
||||
https://github.com/ueberdosis/tiptap-next/issues/85#issuecomment-774520164
|
||||
|
||||
```ts
|
||||
import { defineComponent, h, ref, Teleport, onBeforeUpdate } from 'vue'
|
||||
import VueRenderer from '../VueRenderer'
|
||||
|
||||
function setupVueRenderers(){
|
||||
const vueRenderers = ref(([] as VueRenderer[]))
|
||||
const vueRendererEls = ref(new Map())
|
||||
const addVueRenderer = (vueRenderer: VueRenderer) => {
|
||||
vueRenderers.value.push(vueRenderer)
|
||||
}
|
||||
const deleteVueRenderer = (vueRenderer: VueRenderer) => {
|
||||
const index = vueRenderers.value.indexOf(vueRenderer)
|
||||
if (index > -1) {
|
||||
vueRenderers.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
onBeforeUpdate(() => {
|
||||
vueRendererEls.value = new Map()
|
||||
})
|
||||
return {
|
||||
vueRenderers,
|
||||
vueRendererEls,
|
||||
addVueRenderer,
|
||||
deleteVueRenderer,
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditorContent',
|
||||
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
...setupVueRenderers(),
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor && editor.options.element) {
|
||||
this.$nextTick(() => {
|
||||
this.$el.appendChild(editor.options.element)
|
||||
editor.createNodeViews()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const components = []
|
||||
for (const vueRenderer of this.vueRenderers) {
|
||||
components.push(h(
|
||||
Teleport,
|
||||
{ to: vueRenderer.element },
|
||||
h(
|
||||
vueRenderer.component,
|
||||
{
|
||||
ref: (el: any) => { this.vueRendererEls.set(vueRenderer, el) },
|
||||
...vueRenderer.props
|
||||
},
|
||||
)
|
||||
))
|
||||
}
|
||||
return h('div', { class: 'editor-content' }, components)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.editor.setOptions({
|
||||
element: this.$el,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## VueRenderer.ts
|
||||
|
||||
https://github.com/ueberdosis/tiptap-next/issues/85#issuecomment-774520164)
|
||||
|
||||
```ts
|
||||
import { ref, markRaw } from 'vue'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import EditorContent from './components/EditorContent'
|
||||
|
||||
export interface EditorReactiveProps {
|
||||
props: any
|
||||
editor?: Editor
|
||||
element?: Element
|
||||
}
|
||||
|
||||
export default class VueRenderer {
|
||||
private _element: Element
|
||||
private _component: any
|
||||
public props: any
|
||||
public editor: Editor
|
||||
|
||||
constructor(component: any, options: EditorReactiveProps) {
|
||||
this._component = markRaw(component)
|
||||
this.props = ref(options.props || {})
|
||||
if (options.editor) {
|
||||
this.editor = options.editor
|
||||
} else {
|
||||
this.editor = options.props.editor
|
||||
}
|
||||
if (options.element) {
|
||||
this._element = options.element
|
||||
} else {
|
||||
this._element = document.createElement('div')
|
||||
}
|
||||
this.editorContent.ctx.addVueRenderer(this)
|
||||
}
|
||||
|
||||
get editorContent(): typeof EditorContent {
|
||||
return (this.editor.options.element.parentElement as any).__vueParentComponent
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
}
|
||||
|
||||
get component() {
|
||||
return this._component
|
||||
}
|
||||
|
||||
get ref() {
|
||||
// This is the instance of the component,
|
||||
// you can call methods on the component from this
|
||||
return this.editorContent.ctx.vueRendererEls.get(this)
|
||||
}
|
||||
|
||||
updateProps(props: { [key: string]: any } = {}) {
|
||||
Object
|
||||
.entries(props)
|
||||
.forEach(([key, value]) => {
|
||||
this.props.value[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.editorContent.ctx.deleteVueRenderer(this)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## BubbleMenu.ts
|
||||
|
||||
https://github.com/ueberdosis/tiptap-next/issues/62#issuecomment-750914155
|
||||
|
||||
```ts
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
|
||||
interface BubbleMenuSettings {
|
||||
bottom: number
|
||||
isActive: boolean
|
||||
left: number
|
||||
top: number
|
||||
}
|
||||
interface BubbleMenuPluginOptions {
|
||||
editor: Editor
|
||||
element: HTMLElement
|
||||
keepInBounds: boolean
|
||||
onUpdate(menu: BubbleMenuSettings): void
|
||||
}
|
||||
type DOMRectSide = 'bottom' | 'left' | 'right' | 'top'
|
||||
|
||||
function textRange(node: Node, from?: number, to?: number) {
|
||||
const range = document.createRange()
|
||||
range.setEnd(
|
||||
node,
|
||||
typeof to === 'number' ? to : (node.nodeValue || ').length
|
||||
)
|
||||
range.setStart(node, from || 0)
|
||||
return range
|
||||
}
|
||||
|
||||
function singleRect(object: Range | Element, bias: number) {
|
||||
const rects = object.getClientRects()
|
||||
return !rects.length
|
||||
? object.getBoundingClientRect()
|
||||
: rects[bias < 0 ? 0 : rects.length - 1]
|
||||
}
|
||||
|
||||
function coordsAtPos(view: EditorView, pos: number, end = false) {
|
||||
const { node, offset } = view.domAtPos(pos) //view.docView.domFromPos(pos)
|
||||
let side: DOMRectSide | null = null
|
||||
let rect: DOMRect | null = null
|
||||
if (node.nodeType === 3) {
|
||||
const nodeValue = node.nodeValue || '
|
||||
if (end && offset < nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset - 1, offset), -1)
|
||||
side = 'right'
|
||||
} else if (offset < nodeValue.length) {
|
||||
rect = singleRect(textRange(node, offset, offset + 1), -1)
|
||||
side = 'left'
|
||||
}
|
||||
} else if (node.firstChild) {
|
||||
if (offset < node.childNodes.length) {
|
||||
const child = node.childNodes[offset]
|
||||
rect = singleRect(
|
||||
child.nodeType === 3 ? textRange(child) : (child as Element),
|
||||
-1
|
||||
)
|
||||
side = 'left'
|
||||
}
|
||||
if ((!rect || rect.top === rect.bottom) && offset) {
|
||||
const child = node.childNodes[offset - 1]
|
||||
rect = singleRect(
|
||||
child.nodeType === 3 ? textRange(child) : (child as Element),
|
||||
1
|
||||
)
|
||||
side = 'right'
|
||||
}
|
||||
} else {
|
||||
const element = node as Element
|
||||
rect = element.getBoundingClientRect()
|
||||
side = 'left'
|
||||
}
|
||||
|
||||
if (rect && side) {
|
||||
const x = rect[side]
|
||||
|
||||
return {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
left: x,
|
||||
right: x,
|
||||
}
|
||||
}
|
||||
return {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
}
|
||||
}
|
||||
|
||||
class Menu {
|
||||
public options: BubbleMenuPluginOptions
|
||||
public editorView: EditorView
|
||||
public isActive = false
|
||||
public left = 0
|
||||
public bottom = 0
|
||||
public top = 0
|
||||
public preventHide = false
|
||||
|
||||
constructor({
|
||||
options,
|
||||
editorView,
|
||||
}: {
|
||||
options: BubbleMenuPluginOptions
|
||||
editorView: EditorView
|
||||
}) {
|
||||
this.options = {
|
||||
...{
|
||||
element: null,
|
||||
keepInBounds: true,
|
||||
onUpdate: () => false,
|
||||
},
|
||||
...options,
|
||||
}
|
||||
this.editorView = editorView
|
||||
this.options.element.addEventListener('mousedown', this.mousedownHandler, {
|
||||
capture: true,
|
||||
})
|
||||
this.options.editor.on('focus', this.focusHandler)
|
||||
this.options.editor.on('blur', this.blurHandler)
|
||||
}
|
||||
|
||||
mousedownHandler = () => {
|
||||
this.preventHide = true
|
||||
}
|
||||
focusHandler = () => {
|
||||
this.update(this.options.editor.view)
|
||||
}
|
||||
blurHandler = ({ event }: { event: FocusEvent }) => {
|
||||
if (this.preventHide) {
|
||||
this.preventHide = false
|
||||
return
|
||||
}
|
||||
|
||||
this.hide(event)
|
||||
}
|
||||
|
||||
update(view: EditorView, lastState?: EditorState) {
|
||||
const { state } = view
|
||||
|
||||
if (view.composing) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
lastState &&
|
||||
lastState.doc.eq(state.doc) &&
|
||||
lastState.selection.eq(state.selection)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.selection.empty) {
|
||||
this.hide()
|
||||
return
|
||||
}
|
||||
|
||||
const { from, to } = state.selection
|
||||
const start = coordsAtPos(view, from)
|
||||
const end = coordsAtPos(view, to, true)
|
||||
const parent = this.options.element.offsetParent
|
||||
|
||||
if (!parent) {
|
||||
this.hide()
|
||||
return
|
||||
}
|
||||
|
||||
const box = parent.getBoundingClientRect()
|
||||
const el = this.options.element.getBoundingClientRect()
|
||||
const left = (start.left + end.left) / 2 - box.left
|
||||
|
||||
this.left = Math.round(
|
||||
this.options.keepInBounds
|
||||
? Math.min(box.width - el.width / 2, Math.max(left, el.width / 2))
|
||||
: left
|
||||
)
|
||||
this.bottom = Math.round(box.bottom - start.top)
|
||||
this.top = Math.round(end.bottom - box.top)
|
||||
this.isActive = true
|
||||
|
||||
this.sendUpdate()
|
||||
}
|
||||
|
||||
sendUpdate() {
|
||||
this.options.onUpdate({
|
||||
isActive: this.isActive,
|
||||
left: this.left,
|
||||
bottom: this.bottom,
|
||||
top: this.top,
|
||||
})
|
||||
}
|
||||
|
||||
hide(event?: FocusEvent) {
|
||||
if (
|
||||
event &&
|
||||
event.relatedTarget &&
|
||||
this.options.element.parentNode &&
|
||||
this.options.element.parentNode.contains(event.relatedTarget as Node)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isActive = false
|
||||
this.sendUpdate()
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.options.element.removeEventListener(
|
||||
'mousedown',
|
||||
this.mousedownHandler
|
||||
)
|
||||
this.options.editor.off('focus', this.focusHandler)
|
||||
this.options.editor.off('blur', this.blurHandler)
|
||||
}
|
||||
}
|
||||
|
||||
const BubbleMenu = (options: BubbleMenuPluginOptions) => {
|
||||
return new Plugin({
|
||||
key: new PluginKey('menu_bubble'),
|
||||
view(editorView) {
|
||||
return new Menu({ editorView, options })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export { BubbleMenu }
|
||||
```
|
||||
@@ -1,104 +0,0 @@
|
||||
# Vue.js 2
|
||||
|
||||
## toc
|
||||
|
||||
## Introduction
|
||||
The following guide describes how to integrate tiptap with your [Vue](https://vuejs.org/) CLI project.
|
||||
|
||||
## 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)
|
||||
|
||||
## 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.
|
||||
|
||||
For the sake of this guide, let’s start with a fresh Vue project called `tiptap-example`. The Vue CLI sets up everything we need, just select the default Vue 2 template.
|
||||
|
||||
```bash
|
||||
# create a project
|
||||
vue create tiptap-example
|
||||
|
||||
# change directory
|
||||
cd tiptap-example
|
||||
```
|
||||
|
||||
## 2. Install the dependencies
|
||||
Okay, enough of the boring boilerplate work. Let’s finally install tiptap! For the following example you’ll need `@tiptap/core` (the actual editor) and the `@tiptap/vue-starter-kit` which has everything to get started quickly, for example a few default extensions and a basic Vue component.
|
||||
|
||||
```bash
|
||||
# install with npm
|
||||
npm install @tiptap/core @tiptap/vue-starter-kit
|
||||
|
||||
# install with Yarn
|
||||
yarn add @tiptap/core @tiptap/vue-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:8080/](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 `components/Tiptap.vue`.
|
||||
|
||||
This is the fastest way to get tiptap up and running with Vue. 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
|
||||
<template>
|
||||
<editor-content :editor="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent, defaultExtensions } from '@tiptap/vue-starter-kit'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
content: '<p>I’m running tiptap with Vue.js. 🎉</p>',
|
||||
extensions: defaultExtensions(),
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 4. Add it to your app
|
||||
Now, let’s replace the content of `src/App.vue` with the following example code to use our new `Tiptap` component in our app.
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="app">
|
||||
<tiptap />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tiptap from './components/Tiptap.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Tiptap
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
You should now see tiptap in your browser. You’ve successfully set up tiptap! Time to give yourself a pat on the back. Let’s start to configure your editor in the next step.
|
||||
|
||||
## 5. Use v-model (optional)
|
||||
You’re probably used to bind your data with `v-model` in forms, that’s also possible with tiptap. Here is a working example component, that you can integrate in your project:
|
||||
|
||||
<demo name="Guide/GettingStarted/VModel" />
|
||||
Reference in New Issue
Block a user