Text Editing
Reflect supports high-quality collaborative text editing via the popular Yjs (opens in a new tab) library. All editors and tools built on Yjs are supported, including CodeMirror (opens in a new tab), Monaco (opens in a new tab), TipTap (opens in a new tab), Quill (opens in a new tab), and many others.
When running on Reflect, Yjs gets some extra powers too. For example, you can easily add server-side validation to your Yjs docs with Reflect's mutators.
Get Started
These steps assume you already have a project with Reflect setup. To quickly create one see Scaffold a New Project.
Install
npm install yjs reflect-yjs
Instantiate Reflect
Create a Reflect instance as normal, but add the Yjs-specific mutators from reflect-yjs
to your own:
import { Reflect } from "@rocicorp/reflect";
import { Provider, mutators as yjsMutators } from "reflect-yjs";
import * as Y from "yjs";
import {mutators} from "./my-mutators.ts"
const r = new Reflect({
roomID,
userID,
mutators: {
...mutators,
...yjsMutators
}
});
Register Yjs mutators with your Reflect server
Just like your own mutators, the mutators from reflect-yjs
run on both the client and server.
import { mutators } from "../my-mutators.ts";
import { mutators as yjsMutators } from "reflect-yjs";
export default function makeOptions() {
return {
mutators: {
...mutators,
...yjsMutators
}
};
}
Create a Yjs document and bind it to Reflect
The Provider
from reflect-yjs
sends Yjs changes to and from the server using Reflect.
const yDoc = new Y.Doc();
const yProvider = new Provider(r, "mydoc", yDoc);
Use Yjs
At this point, Yjs is ready to go. You can use it directly, but more likely you will connect it to some text editor. For example to use it with TipTap (opens in a new tab), first install the TipTap libraries:
npm add @tiptap/react @tiptap/extension-document @tiptap/extension-paragraph \
@tiptap/extension-text @tiptap/extension-collaboration
Then create a TipTap editor and bind it to Yjs:
// ...
import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph";
import Text from "@tiptap/extension-text";
import Collaboration from "@tiptap/extension-collaboration";
import { EditorContent, useEditor } from "@tiptap/react";
// ...
function App() {
const editor = useEditor({
extensions: [
Document,
Paragraph,
Text,
Collaboration.configure({
document: yDoc,
}),
],
});
// Render app.
return (
<>
Type something:
<EditorContent
editor={editor}
style={{
display: "flex",
flexDirection: "column",
alignItems: "stretch",
width: 500,
border: "1px solid black",
}}
/>
</>
);
}
For a complete working example of the TipTap editor on Reflect, see Type. For examples using other editors, see the GitHub repo (opens in a new tab).
You can also consult the documentation for Yjs (opens in a new tab) itself.
Validation
You can apply server-side validation to Yjs documents, just like other Reflect data. In your Reflect server, override the default updateYJS
mutator to provide a validator:
import {regex} from 'badwords-list';
function makeOptions() {
return {
mutators: {
...yjsMutators,
updateYJS: updateYJS({
validator: doc => {
const text = doc.getText('monaco');
const string = text.toString();
let match: RegExpExecArray | null = null;
while ((match = regex.exec(string)) !== null) {
const badWordLength = match[0].length;
text.delete(match.index, badWordLength);
text.insert(match.index, '*'.repeat(badWordLength));
}
},
}),
},
};
}
Not seeing what you're looking for? We're planning to improve the Yjs server-side API. Find us on Discord (opens in a new tab) to let us know what you're missing.
Limits
YJs documents are limited to 1MB. This is not a fundamental limit, but a function of some details of our messaging infrastructure. If this is a problem for you let us know, it can be fixed.