TiptapCollab blog post series: first WIP

This commit is contained in:
Jan Thurau 2023-05-07 21:11:23 +02:00
parent 20359ee27d
commit 63a56639fc
No known key found for this signature in database
GPG Key ID: 60B3EB3A1C49AC04
17 changed files with 277 additions and 0 deletions

View 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>

View 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>

View File

@ -0,0 +1,4 @@
export type TNote = {
id: string;
content: string;
};

View 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>

View 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>

View File

@ -0,0 +1,4 @@
export type TNote = {
id: string;
content: string;
};

View 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>

View File

View 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>

View File

@ -0,0 +1,4 @@
export type TNote = {
id: string;
defaultContent: string;
};

View 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>

View 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>

View File

@ -0,0 +1,4 @@
export type TNote = {
id: string;
documentBase64: string;
};

64
docs/posts/1.md Normal file
View 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, well 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!