mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-27 14:59:27 +08:00
TiptapCollab blog post series: first WIP
This commit is contained in:
parent
20359ee27d
commit
63a56639fc
19
demos/src/Posts/1-1-textarea/Vue/Note.vue
Normal file
19
demos/src/Posts/1-1-textarea/Vue/Note.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { TNote } from './types'
|
||||
|
||||
const props = defineProps<{ note: TNote }>()
|
||||
|
||||
const modelValueProxy = ref('')
|
||||
|
||||
watch(props, () => modelValueProxy.value = props.note?.content, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea v-model="modelValueProxy"></textarea>
|
||||
</template>
|
0
demos/src/Posts/1-1-textarea/Vue/index.html
Normal file
0
demos/src/Posts/1-1-textarea/Vue/index.html
Normal file
17
demos/src/Posts/1-1-textarea/Vue/index.vue
Normal file
17
demos/src/Posts/1-1-textarea/Vue/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Note from './Note.vue'
|
||||
import { TNote } from './types'
|
||||
|
||||
const notes: TNote[] = [
|
||||
{ id: 'note-1', content: 'some random note text' },
|
||||
{ id: 'note-2', content: 'some really random note text' },
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="note in notes" :key="note.id">
|
||||
<Note :note="note"/>
|
||||
</div>
|
||||
</template>
|
4
demos/src/Posts/1-1-textarea/Vue/types.ts
Normal file
4
demos/src/Posts/1-1-textarea/Vue/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TNote = {
|
||||
id: string;
|
||||
content: string;
|
||||
};
|
26
demos/src/Posts/1-2-tiptap/Vue/Note.vue
Normal file
26
demos/src/Posts/1-2-tiptap/Vue/Note.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import type { TNote } from './types'
|
||||
|
||||
const props = defineProps<{note: TNote}>()
|
||||
|
||||
const modelValueProxy = ref('')
|
||||
|
||||
watch(props, () => modelValueProxy.value = props.note.content)
|
||||
|
||||
const editor = useEditor({
|
||||
content: props.note.content,
|
||||
extensions: [
|
||||
StarterKit,
|
||||
],
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<editor-content :editor="editor"></editor-content>
|
||||
</template>
|
0
demos/src/Posts/1-2-tiptap/Vue/index.html
Normal file
0
demos/src/Posts/1-2-tiptap/Vue/index.html
Normal file
17
demos/src/Posts/1-2-tiptap/Vue/index.vue
Normal file
17
demos/src/Posts/1-2-tiptap/Vue/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Note from './Note.vue'
|
||||
import { TNote } from './types'
|
||||
|
||||
const notes: TNote[] = [
|
||||
{ id: 'note-1', content: 'some random note text' },
|
||||
{ id: 'note-2', content: 'some really random note text' },
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="note in notes" :key="note.id">
|
||||
<Note :note="note"/>
|
||||
</div>
|
||||
</template>
|
4
demos/src/Posts/1-2-tiptap/Vue/types.ts
Normal file
4
demos/src/Posts/1-2-tiptap/Vue/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TNote = {
|
||||
id: string;
|
||||
content: string;
|
||||
};
|
30
demos/src/Posts/1-3-yjs/Vue/Note.vue
Normal file
30
demos/src/Posts/1-3-yjs/Vue/Note.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { Collaboration } from '@tiptap/extension-collaboration'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||
import * as Y from 'yjs'
|
||||
|
||||
import type { TNote } from './types'
|
||||
|
||||
const props = defineProps<{note: TNote}>()
|
||||
|
||||
const doc = new Y.Doc()
|
||||
|
||||
const editor = useEditor({
|
||||
content: props.note.defaultContent,
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
history: false, // important because history will now be handled by Y.js
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: doc,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<editor-content :editor="editor"></editor-content>
|
||||
</template>
|
0
demos/src/Posts/1-3-yjs/Vue/index.html
Normal file
0
demos/src/Posts/1-3-yjs/Vue/index.html
Normal file
17
demos/src/Posts/1-3-yjs/Vue/index.vue
Normal file
17
demos/src/Posts/1-3-yjs/Vue/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Note from './Note.vue'
|
||||
import { TNote } from './types'
|
||||
|
||||
const notes: TNote[] = [
|
||||
{ id: 'note-1', defaultContent: 'some random note text' },
|
||||
{ id: 'note-2', defaultContent: 'some really random note text' },
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="note in notes" :key="note.id">
|
||||
<Note :note="note"/>
|
||||
</div>
|
||||
</template>
|
4
demos/src/Posts/1-3-yjs/Vue/types.ts
Normal file
4
demos/src/Posts/1-3-yjs/Vue/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TNote = {
|
||||
id: string;
|
||||
defaultContent: string;
|
||||
};
|
54
demos/src/Posts/1-4-collab/Vue/Note.vue
Normal file
54
demos/src/Posts/1-4-collab/Vue/Note.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { TiptapCollabProvider } from '@hocuspocus/provider'
|
||||
import { Collaboration } from '@tiptap/extension-collaboration'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||
import { fromBase64 } from 'lib0/buffer'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import * as Y from 'yjs'
|
||||
|
||||
import type { TNote } from './types'
|
||||
|
||||
const props = defineProps<{ note: TNote }>()
|
||||
|
||||
let provider: TiptapCollabProvider | undefined
|
||||
|
||||
const createDocFromBase64 = (base64Update: string) => {
|
||||
const doc = new Y.Doc()
|
||||
|
||||
Y.applyUpdate(doc, fromBase64(base64Update))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
// usually, you'd just do `new Y.Doc()` here. We are doing some magic to make sure you can just switch to your APP and you have the same document
|
||||
const doc = createDocFromBase64(props.note.documentBase64)
|
||||
|
||||
onMounted(() => {
|
||||
provider = new TiptapCollabProvider({
|
||||
name: props.note.id, // any identifier - all connections sharing the same identifier will be synced
|
||||
appId: '7j9y6m10', // replace with YOUR_APP_ID
|
||||
token: 'notoken', // replace with your JWT
|
||||
document: doc,
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => provider?.destroy())
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
history: false, // important because history will now be handled by Y.js
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: doc,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<editor-content :editor="editor"></editor-content>
|
||||
</template>
|
0
demos/src/Posts/1-4-collab/Vue/index.html
Normal file
0
demos/src/Posts/1-4-collab/Vue/index.html
Normal file
17
demos/src/Posts/1-4-collab/Vue/index.vue
Normal file
17
demos/src/Posts/1-4-collab/Vue/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Note from './Note.vue'
|
||||
import { TNote } from './types'
|
||||
|
||||
const notes: TNote[] = [
|
||||
{ id: 'note-1', documentBase64: 'AgHaj462BgCE4If+hAIbEHJhbmRvbSBub3RlIHRleHQG4If+hAIABwEHZGVmYXVsdAMJcGFyYWdyYXBoBwDgh/6EAgAGBADgh/6EAgEFc29tZSCE4If+hAIGD3RvdGFsbHkgcmFuZG9tIIHgh/6EAhUChOCH/oQCFwRub3RlAeCH/oQCAQcV' },
|
||||
{ id: 'note-2', documentBase64: 'AgHiy6OpCACE4If+hAIbF3JlYWxseSByYW5kb20gbm90ZSB0ZXh0BuCH/oQCAAcBB2RlZmF1bHQDCXBhcmFncmFwaAcA4If+hAIABgQA4If+hAIBBXNvbWUghOCH/oQCBg90b3RhbGx5IHJhbmRvbSCB4If+hAIVAoTgh/6EAhcEbm90ZQHgh/6EAgEHFQ==' },
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="note in notes" :key="note.id">
|
||||
<Note :note="note"/>
|
||||
</div>
|
||||
</template>
|
4
demos/src/Posts/1-4-collab/Vue/types.ts
Normal file
4
demos/src/Posts/1-4-collab/Vue/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type TNote = {
|
||||
id: string;
|
||||
documentBase64: string;
|
||||
};
|
64
docs/posts/1.md
Normal file
64
docs/posts/1.md
Normal file
@ -0,0 +1,64 @@
|
||||
Hi there!
|
||||
|
||||
Welcome to the first post of a series of blog posts about collaboration in Tiptap using TiptapCollab. This series will start covering the basics, and cover more specific use-cases in the next posts. For today, we’ll start moving from a simple textarea box to a fully collaborative Tiptap editor instance.
|
||||
|
||||
Imagine that you are building a simple sticky note app, where a user can create notes. Just like Apple Notes, but with better collaboration.
|
||||
|
||||
So you have like a textarea and a button 'create new note'. Depending on your framework (Vue, React, ..), the code probably looks similar to this:
|
||||
(for simplicity, we haven't added the 'new note' logic here)
|
||||
|
||||
<tiptap-demo name="Posts/1-1-textarea"></tiptap-demo>
|
||||
|
||||
In order to incorporate the Tiptap editor instance for better collaboration and formatting options, you start by modifying your code to include Tiptap in the Note component.
|
||||
|
||||
You begin by importing the necessary Tiptap components and creating a new editor instance within the Note component.
|
||||
|
||||
```bash
|
||||
npm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit
|
||||
```
|
||||
|
||||
<tiptap-demo name="Posts/1-2-tiptap"></tiptap-demo>
|
||||
|
||||
Now your Note component has a fully functional Tiptap editor instance! The user can now format their text (see https://tiptap.dev/guide/menus on how to add a menu bar, in our example you can make text bold using cmd+b). But what about collaboration?
|
||||
|
||||
To enable collaboration, you need to add the Collaboration extension to your editor instance. This extension allows multiple users to edit the same document simultaneously, with changes being synced in real-time.
|
||||
|
||||
|
||||
To add the Collaboration extension to your editor instance, you first need to install the `@tiptap/extension-collaboration` package:
|
||||
|
||||
```bash
|
||||
npm install @tiptap/extension-collaboration @yjs/yjs
|
||||
```
|
||||
|
||||
Then, you can import the `Collaboration` extension and add it to your editor extensions:
|
||||
|
||||
<tiptap-demo name="Posts/1-3-yjs"></tiptap-demo>
|
||||
|
||||
ok, so what have we done?
|
||||
|
||||
We just added the collaboration extension as well as the technology behind it, Yjs. Basically instead of text we are passing the Y.Doc which basically takes care of merging changes. But so far, there is no collaboration...
|
||||
|
||||
To enable real-time collaboration, we need to connect Yjs with the HocuspocusProvider. The HocuspocusProvider is a package that provides a simple way to share Yjs documents across different clients. It sets up a Yjs room and connects all participants to that room.
|
||||
|
||||
To start using HocuspocusProvider, we need to create a new instance of the HocuspocusProvider class and pass it our Yjs document. We also need to provide a document name to connect all participants.
|
||||
|
||||
To get started, let's sign up for a Tiptap Pro account, which comes with a free licence for Tiptap Collab: https://tiptap.dev/pricing
|
||||
|
||||
After you signed up, go to tiptap.dev/pro and click "Join the Beta". Just follow the instructions and you'll be set up within a few minutes.
|
||||
|
||||
Your app ID is shown in the collab admin interface: https://collab.tiptap.dev/ - just copy that and also already get the JWT from the settings area. It's valid for two hours, so more than enough for our quick test. We'll cover generating JWTs using your secret later..
|
||||
|
||||
|
||||
Now, back to our application:
|
||||
|
||||
```bash
|
||||
npm install @hocuspocus/provider
|
||||
```
|
||||
|
||||
Let's now create the TiptapCollabProvider to finally get syncing:
|
||||
|
||||
<tiptap-demo name="Posts/1-4-collab"></tiptap-demo>
|
||||
|
||||
And that's it! With these changes, our Tiptap note-taking application is now fully collaborative. Notes will get synced to other users in real-time.
|
||||
|
||||
Of course, this is just the beginning of what is possible with TiptapCollab and Hocuspocus. In future articles, we'll explore more advanced use cases, such as permissions, presence indicators, and more. Stay tuned!
|
Loading…
Reference in New Issue
Block a user