CoTexts
Jazz provides two CoValue types for collaborative text editing, collectively referred to as "CoText" values:
co.plainText()for simple text editing without formattingco.richText()for rich text with HTML-based formatting (extendsco.plainText())
Both types enable real-time collaborative editing of text content while maintaining consistency across multiple users.
Note: If you're looking for a quick way to add rich text editing to your app, check out our prosemirror plugin.
constconst note: CoPlainTextnote =import coco.plainText().function plainText(): co.PlainText export plainTextcreate("Meeting notes", {PlainTextSchema.create(text: string, options?: { owner: Account | Group; } | Account | Group): CoPlainText (+1 overload)owner: Group | Accountowner:me }); // Update the textconst me: Account | ({ readonly [x: string]: any; } & Account)const note: CoPlainTextnote.CoPlainText.$jazz: CoTextJazzApi<CoPlainText>$jazz.CoTextJazzApi<CoPlainText>.applyDiff(other: string): voidApply text, modifying the text in place. Calculates the diff and applies it to the CoValue.applyDiff("Meeting notes for Tuesday");var console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const note: CoPlainTextnote.CoPlainText.toString(): stringReturns a string representation of a string.toString()); // "Meeting notes for Tuesday"
For a full example of CoTexts in action, see our Richtext example app, which shows plain text and rich text editing.
co.plainText() vs z.string()
While z.string() is perfect for simple text fields, co.plainText() is the right choice when you need:
- Frequent text edits that aren't just replacing the whole field
- Fine-grained control over text edits (inserting, deleting at specific positions)
- Multiple users editing the same text simultaneously
- Character-by-character collaboration
- Efficient merging of concurrent changes
Both support real-time updates, but co.plainText() provides specialized tools for collaborative editing scenarios.
Creating CoText Values
CoText values are typically used as fields in your schemas:
constProfile =const Profile: co.Profile<{ name: z.z.ZodString; bio: co.PlainText; description: co.RichText; }>import coco.profile({profile<{ name: z.z.ZodString; bio: co.PlainText; description: co.RichText; }>(shape?: ({ name: z.z.ZodString; bio: co.PlainText; description: co.RichText; } & Partial<DefaultProfileShape>) | undefined): co.Profile<...> export profilename: z.z.ZodString & z.z.core.$ZodString<string>name:import zz.string(),function string(params?: string | z.z.core.$ZodStringParams): z.z.ZodString (+1 overload) export stringbio: co.PlainTextbio:import coco.plainText(), // Plain text fieldfunction plainText(): co.PlainText export plainTextdescription: co.RichTextdescription:import coco.richText(), // Rich text with formatting });function richText(): co.RichText export richText
Create a CoText value with a simple string:
// Create plaintext with default ownership (current user) constconst note: CoPlainTextnote =import coco.plainText().function plainText(): co.PlainText export plainTextcreate("Meeting notes", {PlainTextSchema.create(text: string, options?: { owner: Account | Group; } | Account | Group): CoPlainText (+1 overload)owner: Group | Accountowner:me }); // Create rich text with HTML content constconst me: Account | ({ readonly [x: string]: any; } & Account)const document: CoRichTextdocument =import coco.richText().function richText(): co.RichText export richTextcreate("<p>Project <strong>overview</strong></p>", {RichTextSchema.create(text: string, options?: { owner: Account | Group; } | Account | Group): CoRichText (+1 overload)owner: Group | Accountowner:me } );const me: Account | ({ readonly [x: string]: any; } & Account)
Ownership
Like other CoValues, you can specify ownership when creating CoTexts.
// Create with shared ownership constconst teamGroup: GroupteamGroup =class GroupGroup.create();Group.create<Group>(this: CoValueClass<Group>, options?: { owner: Account; } | Account): Groupconst teamGroup: GroupteamGroup.Group.addMember(member: Account, role: AccountRole): void (+3 overloads)addMember(colleagueAccount, "writer"); constconst colleagueAccount: Account | ({ readonly [x: string]: any; } & Account)const teamNote: CoPlainTextteamNote =import coco.plainText().function plainText(): co.PlainText export plainTextcreate("Team updates", {PlainTextSchema.create(text: string, options?: { owner: Group; } | Group): CoPlainText (+1 overload)owner: Groupowner:const teamGroup: GroupteamGroup });
See Groups as permission scopes for more information on how to use groups to control access to CoText values.
Reading Text
CoText values work similarly to JavaScript strings:
// Get the text contentvar console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const note: CoPlainTextnote.CoPlainText.toString(): stringReturns a string representation of a string.toString()); // "Meeting notes"var console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(`${const note: CoPlainTextnote}`); // "Meeting notes" // Check the text lengthvar console: ConsoleThe `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```console.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(const note: CoPlainTextnote.CoPlainText.length: numberReturns the length of a String object.length); // 14
When using CoTexts in JSX, you can read the text directly:
<> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{const note: CoPlainTextnote.CoPlainText.toString(): stringReturns a string representation of a string.toString()}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{const note: CoPlainTextnote}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p> </>
Making Edits
Insert and delete text with intuitive methods:
// Insert text at a specific positionconst note: CoPlainTextnote.CoPlainText.insertBefore(idx: number, text: string): voidinsertBefore(8, "weekly "); // "Meeting weekly notes" // Insert after a positionconst note: CoPlainTextnote.CoPlainText.insertAfter(idx: number, text: string): voidinsertAfter(21, " for Monday"); // "Meeting weekly notes for Monday" // Delete a range of textconst note: CoPlainTextnote.deleteRange({CoPlainText.deleteRange(range: { from: number; to: number; }): voidfrom: numberfrom: 8,to: numberto: 15 }); // "Meeting notes for Monday" // Apply a diff to update the entire textconst note: CoPlainTextnote.CoPlainText.$jazz: CoTextJazzApi<CoPlainText>$jazz.CoTextJazzApi<CoPlainText>.applyDiff(other: string): voidApply text, modifying the text in place. Calculates the diff and applies it to the CoValue.applyDiff("Team meeting notes for Tuesday");
Applying Diffs
Use applyDiff to efficiently update text with minimal changes:
// Original text: "Team status update" constconst minutes: CoPlainTextminutes =import coco.plainText().function plainText(): co.PlainText export plainTextcreate("Team status update", {PlainTextSchema.create(text: string, options?: { owner: Account | Group; } | Account | Group): CoPlainText (+1 overload)owner: Group | Accountowner:me }); // Replace the entire text with a new versionconst me: Account | ({ readonly [x: string]: any; } & Account)const minutes: CoPlainTextminutes.CoPlainText.$jazz: CoTextJazzApi<CoPlainText>$jazz.CoTextJazzApi<CoPlainText>.applyDiff(other: string): voidApply text, modifying the text in place. Calculates the diff and applies it to the CoValue.applyDiff("Weekly team status update for Project X"); // Make partial changes letlet text: stringtext =const minutes: CoPlainTextminutes.CoPlainText.toString(): stringReturns a string representation of a string.toString();let text: stringtext =let text: stringtext.String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)Replaces text in a string, using a regular expression or search string.replace("Weekly", "Monday");const minutes: CoPlainTextminutes.CoPlainText.$jazz: CoTextJazzApi<CoPlainText>$jazz.CoTextJazzApi<CoPlainText>.applyDiff(other: string): voidApply text, modifying the text in place. Calculates the diff and applies it to the CoValue.applyDiff(let text: stringtext); // Efficiently updates only what changed
Perfect for handling user input in form controls:
functionTextEditor({function TextEditor({ textId }: { textId: string; }): React.JSX.Element | null | undefinedtextId: stringtextId }: {textId: stringtextId: string }) { constconst note: CoPlainText | null | undefinednote =useCoState<co.PlainText, true>(Schema: co.PlainText, id: string | undefined, options?: { resolve?: RefsToResolve<CoPlainText, 10, []> | undefined; unstable_branch?: BranchDefinition; } | undefined): CoPlainText | ... 1 more ... | undefinedReact hook for subscribing to CoValues and handling loading states. This hook provides a convenient way to subscribe to CoValues and automatically handles the subscription lifecycle (subscribe on mount, unsubscribe on unmount). It also supports deep loading of nested CoValues through resolve queries.useCoState(import coco.plainText(),function plainText(): co.PlainText export plainTexttextId: stringtextId); return (const note: CoPlainText | null | undefinednote && <React.JSX.IntrinsicElements.textarea: React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>textareaReact.TextareaHTMLAttributes<HTMLTextAreaElement>.value?: string | number | readonly string[] | undefinedvalue={const note: CoPlainTextnote.CoPlainText.toString(): stringReturns a string representation of a string.toString()}React.TextareaHTMLAttributes<HTMLTextAreaElement>.onChange?: React.ChangeEventHandler<HTMLTextAreaElement> | undefinedonChange={(e: React.ChangeEvent<HTMLTextAreaElement>e) => { // Efficiently update only what the user changedconst note: CoPlainTextnote.CoPlainText.$jazz: CoTextJazzApi<CoPlainText>$jazz.CoTextJazzApi<CoPlainText>.applyDiff(other: string): voidApply text, modifying the text in place. Calculates the diff and applies it to the CoValue.applyDiff(e: React.ChangeEvent<HTMLTextAreaElement>e.React.ChangeEvent<HTMLTextAreaElement>.target: EventTarget & HTMLTextAreaElementtarget.HTMLTextAreaElement.value: stringRetrieves or sets the text in the entry field of the textArea element.value); }} /> ); }
Using Rich Text with ProseMirror
Jazz provides a dedicated plugin for integrating co.richText() with the popular ProseMirror editor that enables bidirectional synchronization between your co.richText() instances and ProseMirror editors.
ProseMirror Plugin Features
- Bidirectional Sync: Changes in the editor automatically update the
co.richText()and vice versa - Real-time Collaboration: Multiple users can edit the same document simultaneously
- HTML Conversion: Automatically converts between HTML (used by
co.richText()) and ProseMirror's document model
Installation
pnpm add prosemirror-view \ prosemirror-state \ prosemirror-schema-basic
Integration
For use with React:
// RichTextEditor.tsx import {function createJazzPlugin(coRichText: CoRichText | undefined, config?: JazzPluginConfig): Plugin<{ coRichText: CoRichText | undefined; }>Creates a ProseMirror plugin that synchronizes a CoRichText instance with a ProseMirror editor. This plugin enables bidirectional synchronization between a CoRichText instance and a ProseMirror editor. It handles: - Initializing the editor with CoRichText content - Updating the editor when CoRichText changes - Updating CoRichText when the editor changes - Managing the editor view lifecyclecreateJazzPlugin } from "jazz-tools/prosemirror"; import {function exampleSetup(options: { schema: Schema; mapKeys?: { [key: string]: string | false; }; menuBar?: boolean; history?: boolean; floatingMenu?: boolean; menuContent?: MenuElement[][]; }): (Plugin<any> | Plugin<{ transform: Transaction; from: number; to: number; text: string; } | null>)[]Create an array of plugins pre-configured for the given schema. The resulting array will include the following plugins: Input rules for smart quotes and creating the block types in the schema using markdown conventions (say `"> "` to create a blockquote) A keymap that defines keys to create and manipulate the nodes in the schema A keymap binding the default keys provided by the prosemirror-commands module The undo history plugin The drop cursor plugin The gap cursor plugin A custom plugin that adds a `menuContent` prop for the prosemirror-menu wrapper, and a CSS class that enables the additional styling defined in `style/style.css` in this package Probably only useful for quickly setting up a passable editor—you'll need more control over your settings in most real-world situations.exampleSetup } from "prosemirror-example-setup"; import {const schema: Schema<"blockquote" | "image" | "text" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "hard_break", "link" | "code" | "em" | "strong">This schema roughly corresponds to the document schema used by [CommonMark](http://commonmark.org/), minus the list elements, which are defined in the [`prosemirror-schema-list`](https://prosemirror.net/docs/ref/#schema-list) module. To reuse elements from this schema, extend or read from its `spec.nodes` and `spec.marks` [properties](https://prosemirror.net/docs/ref/#model.Schema.spec).schema } from "prosemirror-schema-basic"; import {class EditorStateThe state of a ProseMirror editor is represented by an object of this type. A state is a persistent data structure—it isn't updated, but rather a new state value is computed from an old one using the [`apply`](https://prosemirror.net/docs/ref/#state.EditorState.apply) method. A state holds a number of built-in fields, and plugins can [define](https://prosemirror.net/docs/ref/#state.PluginSpec.state) additional fields.EditorState } from "prosemirror-state"; import {class EditorViewAn editor view manages the DOM structure that represents an editable document. Its state and behavior are determined by its [props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).EditorView } from "prosemirror-view"; functionfunction RichTextEditor(): React.JSX.Element | nullRichTextEditor() { const {me } =const me: CoMapLikeLoaded<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefineduseAccount<co.Account<{ profile: co.Profile<{ bio: co.RichText; }>; root: co.Map<{}, unknown, Account | Group>; }>, { profile: true; }>(AccountSchema?: co.Account<{ profile: co.Profile<{ bio: co.RichText; }>; root: co.Map<...>; }> | undefined, options?: { ...; } | undefined): { ...; }React hook for accessing the current user's account and authentication state. This hook provides access to the current user's account profile and root data, along with authentication utilities. It automatically handles subscription to the user's account data and provides a logout function.useAccount(JazzAccount, {const JazzAccount: co.Account<{ profile: co.Profile<{ bio: co.RichText; }>; root: co.Map<{}, unknown, Account | Group>; }>resolve?: RefsToResolve<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, 10, []> | undefinedResolve query to specify which nested CoValues to load from the accountresolve: {profile: true } }); constprofile?: RefsToResolve<{ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profile, 10, [...]> | undefinedconst editorRef: React.RefObject<HTMLDivElement | null>editorRef =useRef<HTMLDivElement>(initialValue: HTMLDivElement | null): React.RefObject<HTMLDivElement | null> (+2 overloads)`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument (`initialValue`). The returned object will persist for the full lifetime of the component. Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useRef<HTMLDivElement>(null); constconst viewRef: React.RefObject<EditorView | null>viewRef =useRef<EditorView | null>(initialValue: EditorView | null): React.RefObject<EditorView | null> (+2 overloads)`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument (`initialValue`). The returned object will persist for the full lifetime of the component. Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.useRef<class EditorViewAn editor view manages the DOM structure that represents an editable document. Its state and behavior are determined by its [props](https://prosemirror.net/docs/ref/#view.DirectEditorProps).EditorView | null>(null);function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect(() => { if (!me?.const me: CoMapLikeLoaded<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefinedprofile.Account.profile: { readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profilebio: CoRichText | null | undefinedbio || !const editorRef: React.RefObject<HTMLDivElement | null>editorRef.React.RefObject<HTMLDivElement | null>.current: HTMLDivElement | nullThe current value of the ref.current) return; // Create the Jazz plugin for ProseMirror // Providing a co.richText() instance to the plugin to automatically sync changes constjazzPlugin =const jazzPlugin: Plugin<{ coRichText: CoRichText | undefined; }>function createJazzPlugin(coRichText: CoRichText | undefined, config?: JazzPluginConfig): Plugin<{ coRichText: CoRichText | undefined; }>Creates a ProseMirror plugin that synchronizes a CoRichText instance with a ProseMirror editor. This plugin enables bidirectional synchronization between a CoRichText instance and a ProseMirror editor. It handles: - Initializing the editor with CoRichText content - Updating the editor when CoRichText changes - Updating CoRichText when the editor changes - Managing the editor view lifecyclecreateJazzPlugin(me.const me: CoMapLikeLoaded<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, { ...; }, 10, []>profile.Account.profile: { readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profilebio: CoRichTextbio); // Set up ProseMirror with the Jazz plugin if (!const viewRef: React.RefObject<EditorView | null>viewRef.React.RefObject<EditorView | null>.current: EditorView | nullThe current value of the ref.current) {const viewRef: React.RefObject<EditorView | null>viewRef.React.RefObject<EditorView | null>.current: EditorView | nullThe current value of the ref.current = newnew EditorView(place: null | Node | ((editor: HTMLElement) => void) | { mount: HTMLElement; }, props: DirectEditorProps): EditorViewCreate a view. `place` may be a DOM node that the editor should be appended to, a function that will place it into the document, or an object whose `mount` property holds the node to use as the document container. If it is `null`, the editor will not be added to the document.EditorView(const editorRef: React.RefObject<HTMLDivElement | null>editorRef.React.RefObject<HTMLDivElement | null>.current: HTMLDivElementThe current value of the ref.current, {DirectEditorProps.state: EditorStateThe current state of the editor.state:class EditorStateThe state of a ProseMirror editor is represented by an object of this type. A state is a persistent data structure—it isn't updated, but rather a new state value is computed from an old one using the [`apply`](https://prosemirror.net/docs/ref/#state.EditorState.apply) method. A state holds a number of built-in fields, and plugins can [define](https://prosemirror.net/docs/ref/#state.PluginSpec.state) additional fields.EditorState.EditorState.create(config: EditorStateConfig): EditorStateCreate a new state.create({EditorStateConfig.schema?: Schema<any, any> | undefinedThe schema to use (only relevant if no `doc` is specified).schema,EditorStateConfig.plugins?: readonly Plugin<any>[] | undefinedThe plugins that should be active in this state.plugins: [ ...function exampleSetup(options: { schema: Schema; mapKeys?: { [key: string]: string | false; }; menuBar?: boolean; history?: boolean; floatingMenu?: boolean; menuContent?: MenuElement[][]; }): (Plugin<any> | Plugin<{ transform: Transaction; from: number; to: number; text: string; } | null>)[]Create an array of plugins pre-configured for the given schema. The resulting array will include the following plugins: Input rules for smart quotes and creating the block types in the schema using markdown conventions (say `"> "` to create a blockquote) A keymap that defines keys to create and manipulate the nodes in the schema A keymap binding the default keys provided by the prosemirror-commands module The undo history plugin The drop cursor plugin The gap cursor plugin A custom plugin that adds a `menuContent` prop for the prosemirror-menu wrapper, and a CSS class that enables the additional styling defined in `style/style.css` in this package Probably only useful for quickly setting up a passable editor—you'll need more control over your settings in most real-world situations.exampleSetup({schema: Schema<any, any>The schema to generate key bindings and menu items for.schema }),jazzPlugin, ], }), }); } return () => { if (const jazzPlugin: Plugin<{ coRichText: CoRichText | undefined; }>const viewRef: React.RefObject<EditorView | null>viewRef.React.RefObject<EditorView | null>.current: EditorView | nullThe current value of the ref.current) {const viewRef: React.RefObject<EditorView | null>viewRef.React.RefObject<EditorView | null>.current: EditorViewThe current value of the ref.current.EditorView.destroy(): voidRemoves the editor from the DOM and destroys all [node views](https://prosemirror.net/docs/ref/#view.NodeView).destroy();const viewRef: React.RefObject<EditorView | null>viewRef.React.RefObject<EditorView | null>.current: EditorView | nullThe current value of the ref.current = null; } }; }, [me?.const me: CoMapLikeLoaded<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefinedprofile.Account.profile: { readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap & Profilebio: CoRichText | null | undefinedbio?.CoPlainText.$jazz: CoTextJazzApi<CoRichText>$jazz.CoValueJazzApi<V extends CoValue>.id: string | undefinedid]); if (!me) return null; return ( <const me: CoMapLikeLoaded<{ readonly profile: ({ readonly bio: CoRichText | null; readonly name: string; readonly inbox: string | undefined; readonly inboxInvite: string | undefined; } & CoMap) | null; readonly root: ({} & CoMap) | null; } & Account, { ...; }, 10, []> | null | undefinedReact.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>divReact.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="border rounded-sm"> <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>divReact.RefAttributes<HTMLDivElement>.ref?: React.Ref<HTMLDivElement> | undefinedAllows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref).ref={const editorRef: React.RefObject<HTMLDivElement | null>editorRef}React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="p-2" /> </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); }