Skip to content

Getting started

Rich text editor

@junco/editor is a TipTap editor with Junco's look: a formatting toolbar, a / command menu and markdown shortcuts. Give it a Y.Doc and it's collaborative.

Use it#

npm i @junco/editor @junco/react @junco/tailwind yjs
On its own
import { Editor } from "@junco/editor";

<Editor content="<p>Hello</p>" onUpdate={(editor) => save(editor.getJSON())} />

Markdown shortcuts work as you type: # for headings, - or 1. for lists, > for quotes, ``` for code, **bold**, _italic_. Type / on a line for the command menu; arrow keys move, Enter picks, Escape closes.

Edit together#

Pass a doc and everyone with the same document edits it live; pass a provider with an Awareness and their carets and selections show with their names. A Towhee room is both. Undo only undoes your own changes.

With Towhee and @junco/collab
import { createClient } from "@towhee/client";
import { Editor, editorText } from "@junco/editor";
import { CollabProvider, CommentsPanel, HistoryPanel, towheeAdapter } from "@junco/collab";

const client = createClient({ url: "wss://towhee.example.com", authEndpoint: "/api/towhee-token" });
const room = client.enter("doc:42");

// The room is the Y.Doc's home and holds everyone's carets (its awareness).
<Editor doc={room.doc} provider={room} />

// Comments and history for the same document; textOf turns on version compare.
const adapter = towheeAdapter({ room, inbox: client.inbox, textOf: (doc) => editorText(doc), restoreOptions: { types: { default: "xml" } } });
<CollabProvider adapter={adapter}>
  <CommentsPanel />
  <HistoryPanel />
</CollabProvider>

See it running in the collaborative docs editor: comments, an inbox, version history with compare, and a share dialog around one shared document.

Slash commands#

The defaults are text, three headings, bulleted and numbered lists, quote, code block and divider. Add your own:

import { Editor, defaultSlashCommands, type SlashCommand } from "@junco/editor";

const callout: SlashCommand = {
  id: "callout",
  label: "Callout",
  keywords: "note tip",
  run: (editor) => editor.chain().focus().setBlockquote().insertContent("Note: ").run(),
};

<Editor slashCommands={[...defaultSlashCommands, callout]} />

Props#

PropWhat it does
contentStarting HTML or TipTap JSON, when not collaborating
docA Y.Doc to edit together; the document is then the content
fieldThe XmlFragment in doc this editor owns (default "default")
providerAnything with an awareness (a Towhee room, y-websocket, Hocuspocus) for shared carets
userYour name and color on your caret. Towhee sets these from the token
placeholderShown on an empty document
editableFalse for read-only (hides the toolbar and menu)
toolbarShow the formatting toolbar (default true)
slashCommandsCommands for the / menu; [] turns it off
extensionsMore TipTap extensions
onReady, onUpdateGet the TipTap editor, to read or change content
labelAccessible name of the editing area (default "Document")
className, classNamesroot, toolbar and content slots

Also exported: Toolbar and SlashMenu for your own TipTap setup, defaultSlashCommands, and editorText(doc, field), which turns a document into plain text (for version compare or search).

Accessibility

The editing area is a labeled multi-line textbox, the toolbar is a toolbar with pressed states and shortcuts in its tooltips, and the / menu is a listbox the editor points at with aria-activedescendant. Other people's cursors show their name in a neutral tag with a colored border, so text contrast holds for any color.