Skip to content

Commit

Permalink
react docs
Browse files Browse the repository at this point in the history
  • Loading branch information
janthurau committed Jun 2, 2023
1 parent 89bce5e commit 4ad2134
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 13 deletions.
52 changes: 52 additions & 0 deletions demos/src/Tutorials/1-3-yjs_lexical/Lexical-React/Note.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import './styles.css'

import { TiptapCollabProvider } from '@hocuspocus/provider'
import { CollaborationPlugin } from '@lexical/react/LexicalCollaborationPlugin'
import { InitialConfigType, LexicalComposer } from '@lexical/react/LexicalComposer'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import React from 'react'
import * as Y from 'yjs'

import { TNote } from './types'

export default ({ note }: { note: TNote }) => {
const initialConfig: InitialConfigType = {
onError(error: Error): void {
throw error
},
namespace: 'myeditor',
editable: true,
}

return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable/>}
placeholder={<p>{note.defaultContent}</p>}
ErrorBoundary={LexicalErrorBoundary}
/>
<CollaborationPlugin
id={note.id}
key={note.id}
// @ts-ignore
providerFactory={(id, yjsDocMap) => {
const doc = new Y.Doc()

yjsDocMap.set(id, doc)

const provider = new TiptapCollabProvider({
name: 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,
})

return provider
}}
shouldBootstrap={true}
/>
</LexicalComposer>
)
}
Empty file.
26 changes: 26 additions & 0 deletions demos/src/Tutorials/1-3-yjs_lexical/Lexical-React/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import './styles.css'

import React from 'react'

import Note from './Note'
import { TNote } from './types'

const notes: TNote[] = [
{
id: 'note-1',
defaultContent: 'some random note text',
},
{
id: 'note-2',
defaultContent: 'some really random note text',
},
]

export default () => {

return (
<div>
{notes.map(note => <Note note={note} key={note.id}/>)}
</div>
)
}
3 changes: 3 additions & 0 deletions demos/src/Tutorials/1-3-yjs_lexical/Lexical-React/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
4 changes: 4 additions & 0 deletions demos/src/Tutorials/1-3-yjs_lexical/Lexical-React/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type TNote = {
id: string;
defaultContent: string;
};
2 changes: 1 addition & 1 deletion demos/src/Tutorials/1-4-collab/React/Note.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default ({ note }: { note: TNote }) => {
return () => {
provider.destroy()
}
}, [])
}, [note.id])

const editor = useEditor({
// make sure that you don't use `content` property anymore!
Expand Down
5 changes: 5 additions & 0 deletions demos/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import { defineConfig } from 'vite'
const getPackageDependencies = () => {
const paths: Array<{ find: string, replacement: any }> = []

paths.push({
find: 'yjs',
replacement: resolve('../node_modules/yjs/src/index.js'),
})

fg.sync('../packages/*', { onlyDirectories: true })
.map(name => name.replace('../packages/', ''))
.forEach(name => {
Expand Down
95 changes: 92 additions & 3 deletions docs/cloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,101 @@
tableOfContents: true
---

# Tiptap Collab
"Embed real-time collaboration into your app in under one minute, and everything is in sync." ([Live-Demo](/collab))- If that sounds interesting to you, we might have something :)

Tiptap Collab is our hosted solution of Hocuspocus for those who don't want to maintain their own deployment.
# Tiptap Collab

TODO: Maybe remove Cloud from hocuspocus docs, or keep the link but redirect to tiptap.dev/cloud?
Tiptap Collab is our hosted solution of Hocuspocus (The plug’n’play collaborative editing backend) making it a blast to add real-time collaboration to any app.

:::warning Pro Feature
To get started, you need a Tiptap Pro account ([sign up / login here](https://tiptap.dev/pro)).
:::

[![Cloud Dashboard](https://tiptap.dev/images/docs/server/cloud/dashboard.png)](https://tiptap.dev/images/docs/server/cloud/dashboard.png)

Note that you need `@hocuspocus/provider` [~v2.0.0](https://github.com/ueberdosis/hocuspocus/releases/tag/v2.0.0)


## Getting started

Tiptap Collab makes your app collaborative by syncing your Y.Doc across users using websockets. If you are already using yjs in your app, getting started is as simple as shown below.

If you are not, you might want to start in our [Tutorials](/tutorials) section.

```typescript
import { TiptapCollabProvider } from '@hocuspocus/provider'
import * as Y from 'yjs'

const provider = new TiptapCollabProvider({
appId: 'your_app_id', // get this at collab.tiptap.dev
name: 'your_document_name', // e.g. a uuid uuidv4();
token: 'your_JWT', // see "Authentication" below
doc: new Y.Doc() // pass your existing doc, or leave this out and use provider.document
});

// That's it! Your Y.Doc will now be synced to any other user currently connected
```

### Upgrade from self-hosted deployments

If you are upgrading from a self-hosted deployment, on the frontend you just need to replace `HocuspocusProvider` with the new `TiptapCollabProvider`. The API is the same, it's just a wrapper that handles hostnames / auth.

## Examples

##### replit / Sandbox: Fully functional prototype

[![Cloud Documents](https://tiptap.dev/images/docs/server/cloud/tiptapcollab-demo.png)](https://tiptap.dev/images/docs/server/cloud/tiptapcollab-demo.png)

We have created a simple client / server setup using replit, which you can review and fork here:

[Github](https://github.com/janthurau/TiptapCollab) or [Replit (Live-Demo)](https://replit.com/@ueberdosis/TiptapCollab?v=1)

The example load multiple documents over the same websocket (multiplexing), and shows how to realize per-document authentication using JWT.

##### Authentication

Authentication is done using JWT. You can see your secret in the admin interface and use it to generate tokens for your clients. If you want to generate a JWT and add some attributes for testing, you can use http://jwtbuilder.jamiekurtz.com/ . You can leave all fields default, just replace the "key" with the secret from your settings.

In Node.js, you can generate a JWT like this:

```typescript
import jsonwebtoken from 'jsonwebtoken'

const data = {
// use this list to limit the number of documents that can be accessed by this client.
// empty array means no access at all
// not sending this property means access to all documents
// we are supporting a wildcard at the end of the string (only there)
allowedDocumentNames: ['document-1', 'document-2', 'my-user-uuid/*', 'my-organization-uuid/*']
}

const jwt = jsonwebtoken.sign(data, 'your_secret')
// this JWT should be sent in the `token` field of the provider. Never expose 'your_secret' to a frontend!
```

#### Getting the JSON document

If you want to access the JSON representation (we're currently exporting the `default` fragment of the YDoc), you can add a webhook in the admin interface. We are calling it when storing to our database, so it's debounced by 2 seconds (max 10 seconds).

All requests contain a header `X-Hocuspocus-Signature-256` which signs the entire message using 'your_secret' (find it in the settings). The payload looks like this:

```json
{
"appName": '', // name of your app
"name": '', // name of the document
"time": // current time as ISOString (new Date()).toISOString())
"tiptapData": {}, // JSON output from Tiptap (see https://tiptap.dev/guide/output#option-1-json): TiptapTransformer.fromYdoc()
"ydocState"?: {}, // optionally contains the entire yDoc as base64. Contact us to enable this property!
"clientsCount": 100 // number of currently connected clients
}
```

### Screenshots

[![Cloud Documents](https://tiptap.dev/images/docs/server/cloud/documents.png)](https://tiptap.dev/images/docs/server/cloud/documents.png)

[![Cloud Settings](https://tiptap.dev/images/docs/server/cloud/settings.png)](https://tiptap.dev/images/docs/server/cloud/settings.png)

### Need anything else?

Contact us on [Discord](https://tiptap.dev/discord) or send an email to [humans@tiptap.dev](mailto:humans@tiptap.dev).
2 changes: 1 addition & 1 deletion docs/tutorials.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
tableOfContents: true
---

# Introduction
# Tutorials
8 changes: 0 additions & 8 deletions docs/tutorials/get-started-with-tiptap-collab.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

**Welcome** to the first of a series of tutorials about collaboration in Tiptap (or Lexical, Quill, Slate, and others that have a [Yjs editor binding](https://docs.yjs.dev/ecosystem/editor-bindings)) using Tiptap Collab. This series will start covering the basics, and expand to more specific use cases in the next posts. For today, we’ll start moving from a simple textarea box to a fully collaborative editor instance.

_For simplicity, I'm always referring to Tiptap from here, but you can switch each code sample to Lexical, Quill or Slate._

Imagine that you are building a simple sticky note app, where a user can create notes.

So let's say you have a few textareas. Depending on your framework (Vue, React, ..), the code probably looks similar to this:
Expand All @@ -21,9 +19,6 @@ You begin by importing the necessary Tiptap components and creating a new editor
```bash
npm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit
# for React: npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
# for Lexical: npm install lexical @lexical/react
# for Quill: npm install quill
# for Slate: npm install slate slate-react react react-dom
```

<tiptap-demo name="Tutorials/1-2-tiptap"></tiptap-demo>
Expand All @@ -39,9 +34,6 @@ To add the Collaboration extension to your editor instance, you first need to in

```bash
npm install @tiptap/extension-collaboration yjs
# for Lexical: npm install yjs
# for Quill: npm install y-quill yjs
# for Slate: npm install @slate-yjs/react yjs
```

Then, you can import the `Collaboration` extension and add it to your editor extensions:
Expand Down

0 comments on commit 4ad2134

Please sign in to comment.