Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix React Dependency Array creating new editor instances instead of reusing existing one #4453

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
50 changes: 50 additions & 0 deletions demos/src/Issues/2403/React/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import './styles.scss'

import Document from '@tiptap/extension-document'
import Heading from '@tiptap/extension-heading'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { EditorContent, useEditor } from '@tiptap/react'
import React, { useState } from 'react'

const TiptapEditor = ({ count, editorClass }) => {
const editor = useEditor({
extensions: [
Document,
Heading,
Paragraph,
Text,
],
editorProps: {
attributes: {
class: editorClass,
},
},
onUpdate: () => {
console.log(count)
},
}, [count, editorClass])

if (!editor) {
return null
}

return (
<div>
<EditorContent editor={editor} />
</div>
)
}

export default () => {
const [count, setCount] = useState(0)
const [editorClass, setEditorClass] = useState('my-editor')

return (
<>
<button onClick={() => setCount(count + 1)}>Inc</button>
<button onClick={() => setEditorClass('my-editor updated-editor')}>Update class</button>
<TiptapEditor editorClass={editorClass} count={count} />
</>
)
}
6 changes: 6 additions & 0 deletions demos/src/Issues/2403/React/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Basic editor styles */
.tiptap {
> * + * {
margin-top: 0.75em;
}
}
11 changes: 11 additions & 0 deletions docs/api/utilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Editor API Utility Overview

Welcome to the Editor API Utility section. Here, you'll discover essential tools to enhance your Tiptap experience:

- **Render JSON as HTML**: Learn to convert JSON content to HTML, even without an editor instance, simplifying content management.

- **Tiptap for PHP**: Explore PHP integration for Tiptap, enabling seamless content transformation and modification.

- **Suggestions**: Enhance your editor with suggestions like mentions and emojis, tailored to your needs.

Explore subpages for in-depth guidance and examples.
65 changes: 65 additions & 0 deletions docs/collaboration/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## Getting Started

### Installation

First you need to install `@hocuspocus/provider` at least in version `2.0.0`.

```bash
npm install @hocuspocus/provider
```

### Basic Usage

Tiptap Collab makes your application collaborative by synchronizing a Yjs document between connected users using websockets. If you're already using Yjs in your application, it's as easy as this:

```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
document: new Y.Doc() // pass your existing doc, or leave this out and use provider.document
});
```

### Upgrade From Hocuspocus

If you are upgrading from our self-hosted collaboration backend called Hocuspocus, all you need to do is replace `HocuspocusProvider` with the new `TiptapCollabProvider`. The API is the same, it's just a wrapper that handles the hostname to your Tiptap Collab app and authentication.

## Example

[![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 that you can review and fork here:

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

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

More tutorials can be found in our [Tutorials section](/tutorials).

## Authentication

Authentication is done using [JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token). There are many libraries available to generate a valid token.

### JWT Generation

To generate a JWT in the browser, you can use [http://jwtbuilder.jamiekurtz.com/](http://jwtbuilder.jamiekurtz.com/). You can leave all the fields as default, just replace the "Key" at the bottom with the secret from your [settings](https://collab.tiptap.dev/apps/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.
// An 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/*']
}

// This JWT should be sent in the `token` field of the provider. Never expose 'your_secret' to a frontend!
const jwt = jsonwebtoken.sign(data, 'your_secret')
9 changes: 9 additions & 0 deletions docs/collaboration/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Collaboration

Implementing real-time collaboration is quite hard. With Tiptap Collab we build a solution that does it in minutes. To see it in action check out our [live demo](https://tiptap.dev/editor).

Tiptap Collab is our managed cloud solution of [Hocuspocus](https://tiptap.dev/hocuspocus/introduction). It makes it a easy to add real-time collaboration to any application. If you already have an application using Tiptap Editor, it's even easier to add collaboration.

:::warning Pro Feature
To get started, you need a Tiptap Pro account. [Log in](https://tiptap.dev/login) or [sign up](https://tiptap.dev/register) for free.
:::
239 changes: 239 additions & 0 deletions docs/collaboration/management-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
---
tableOfContents: true
---

# Management API

In addition to the websocket protocol, each Tiptap Collab app comes with a REST API for managing
your documents. It's exposed directly from your Tiptap Collab app, so it's available at your custom
URL:

`https://YOUR_APP_ID.collab.tiptap.cloud/`

Authentication is done using an API secret which you can find in
the [settings](https://collab.tiptap.dev/) of your Tiptap Collab app. The secret must be sent as
an `Authorization` header.

If your document identifier contains a slash (`/`), just make sure to encode it as `%2F`, e.g.
using `encodeURIComponent`.

## Documents

### Create Document

```bash
POST /api/documents/:identifier
```

This call takes a binary Yjs update message (an existing Yjs document on your side must be encoded
using `Y.encodeStateAsUpdate`) and creates a document. This can be used to seed documents before a
user connects to the Tiptap Collab server.

This endpoint will return the HTTP status `204` if the document was created successfully, or `409`
if the document already exists. If you want to overwrite it, you must delete it first.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--data '@yjsUpdate.binary.txt'
```

### List Documents

```bash
GET /api/documents?take=100&skip=0
```

This call returns a list of all documents present on the servers storage. We're returning the first
100 by default, pass `take` or `skip` parameters to adjust this.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### Get Document

```bash
GET /api/documents/:identifier?format=:format&fragment=:fragment
```

This call exports the given document with all fragments in JSON format. We export either the current
in-memory version or the version read from the database. If the document is currently open on your
server, we will return the in-memory version.

`format` supports either `yjs` or `json`. Default: `json`

If you choose the `yjs` format, you'll get the binary Yjs update message created
with `Y.encodeStateAsUpdate`.

`fragment` can be an array (`fragment=a&fragment=b`) of or a single fragment that you want to
export. By default we'll export all fragments. Note that this is only taken into account when using
the `json` format, otherwise you'll always get the whole Yjs document.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

**Note:** When using axios, you need to specify `responseType: arraybuffer` in the options of the
request.

```typescript
import * as Y from 'yjs'

const ydoc = new Y.Doc()

const axiosResult = await axios.get('https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/somedoc?format=yjs', {
headers: {
'Authorization': 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
responseType: 'arraybuffer'
})

Y.applyUpdate(ydoc, axiosResult.data)
```

When using `node-fetch`, you need to use .arrayBuffer() and create a Buffer from it:

```typescript
import * as Y from 'yjs'

const ydoc = new Y.Doc()

const fetchResult = await fetch('https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/somedoc?format=yjs', {
headers: {
'Authorization': 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
})

Y.applyUpdate(ydoc, Buffer.from(await docUpdateAsBinaryResponse.arrayBuffer()))
```

### Update Document

```bash
PATCH /api/documents/:identifier
```

This call accepts a Yjs update message and will apply it on the existing document on the server.
This endpoint will return the HTTP status `204` if the document was updated successfully, `404` is
the document does not exist, or `422` if the payload is invalid or the update cannot be applied.

```bash
curl --location --request PATCH 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA' \
--data '@yjsUpdate.binary.txt'
```

### Delete Document

```bash
DELETE /api/documents/:identifier
```

This endpoint deletes a document from the server after closing any open connection to the document.

It returns either HTTP status `204` if the document was deleted successfully or `404` if the
document was not found.

If the endpoint returned `204`, but the document still exists, make sure that there is no user
re-creating the document from the provider.
We are closing all connections before deleting a document, but your error handling might re-create
the provider, and thus create the document again.

```bash
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/DOCUMENT_NAME' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### Duplicate Document

In order to copy a document, you can just use the GET endpoint and then create it again with the
POST endpoint, here's an example in typescript:

```typescript

const docUpdateAsBinaryResponse = await axios.get('https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/somedoc?format=yjs', {
headers: {
'Authorization': 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
responseType: 'arraybuffer',
})

await axios.post('https://YOUR_APP_ID.collab.tiptap.cloud/api/documents/somedoc-duplicated', docUpdateAsBinaryResponse.data, {
headers: {
'Authorization': 'YOUR_SECRET_FROM_SETTINGS_AREA',
},
})
```

## Settings

TiptapCollab has a few settings that can be configured at runtime (no restart needed):

| Key | Type | Editable | Description |
|-------------------------|--------|----------|------------------------------------------------------------------------------------------------------|
| secret | string | Yes | The secret used to sign JWT tokens ; auto-generated on first boot. |
| api_secret | string | Yes | The secret that is sent in API calls (as the `Authorization` header) ; auto-generated on first boot. |
| webhook_url | string | Yes | The webhook URL ; optional |
| authentication_disabled | string | Yes | Whether authentication is disabled. Must be 1 or 0 ; optional (default = 0) |
| name | string | Yes | The name of the instance ; optional |

### Create setting

If you want to create or overwrite settings, this is the API for it.

```bash
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### List settings

All current settings can be retrieved with the GET /api/admin/settings call.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### Get setting

If you want to get a setting value, this is the API for it.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### Update setting

If you want to create or overwrite settings, this is the API for it.

```bash
curl --location --request PUT 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```

### Delete setting

If you want to delete settings, this is the API for it.

```bash
curl --location --request DELETE 'https://YOUR_APP_ID.collab.tiptap.cloud/api/admin/settings/:key' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```


## Get server statistics

You can get a few server statistics using the /api/statistics endpoint. We currently export the
total number of documents, the maximum concurrent connections (last 30 days), the total number of
connections (last 30 days) and the lifetime connection count.
Not that the last two values are exported as string, as they are internally a BIGINT.

```bash
curl --location 'https://YOUR_APP_ID.collab.tiptap.cloud/api/statistics' \
--header 'Authorization: YOUR_SECRET_FROM_SETTINGS_AREA'
```
2 changes: 1 addition & 1 deletion docs/links.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@
- title: Configure
link: /collaboration/configure
- title: Document API
link: /collaboration/document-api
link: /collaboration/document-api
- title: Document Manipulation
link: /collaboration/backend-document-manipulation
type: beta
Expand Down
Loading
Loading