Editor
Selection formatting, project-configured references, inline media and / commands with canonical JSON and portable derived Markdown; no fixed toolbar.
When to use
- Record descriptions, documents, and comment composers.
- Configure reference sources once with EditorProvider; editor sources extend the provider and replace sources with the same id for a project.
- Use value JSON for saved records; defaultMarkdown loads legacy records. onChange emits both; servers re-derive Markdown from validated JSON.
- Paste, drop or pick inline images with onUploadImage; paste YouTube, Vimeo or Loom URLs on their own line, or use /video and onUploadVideo.
- Upload callbacks receive file and { onProgress } and return { src, displaySrc?, alt?, width?, height? } or null. src is a durable token-free path; displaySrc is a session-only preview.
- Private media uses resolveMediaSrc on EditorProvider, Editor or EditorReadOnly; JSON and Markdown keep the storage path. MCP consumers resolve paths through their own storage.
- Import from @revido-llc/kit/editor and install its optional TipTap 3.30.5 peers.
Consider an alternative
- Single-line names (TextField or EditableTitle).
- Editing the JSON and Markdown independently: JSON preserves reference identities, media dimensions, marks and anchors; Markdown serves integrations, search, email and diffs.
- Persisting private signed URLs or local previews: upload callbacks return a durable src, with an optional signed displaySrc for immediate preview.
Import
import { Editor } from '@revido-llc/kit/editor';Keyboard interaction
- ⌘B / ⌘I / ⌘U / ⌘⇧X — formatting
- ⌘K — inline link field
- ⌘E — code
- ⌥F10 — focus the format bar
- / after whitespace or at a block start — insert a block; unmatched commands close the menu
- @ — search configured sources; # / [[ — dedicated triggers when configured
- Arrow keys / Tab / Enter / Escape — navigate reference groups, choose or dismiss suggestions
- Composer Enter — send; Shift+Enter — new line
- Resize handle ↑ / ↓ — 16px; Home / End — minimum / maximum
Props
Props documented by the Kit. Refer to the TypeScript types for the complete interface, including inherited props.
Related exports
EditorComposer, EditorReadOnly, EditorProvider
Examples
Descriptions and documents
import { Editor } from "@revido-llc/kit/editor";
<Editor aria-label="Description" defaultMarkdown={legacyMarkdown} onChange={({ json, markdown }) => save({ json, markdown })} />References
One @ search groups People, Docs, Issues and Projects. Each source controls its search row, inline chip and link target.
import { Editor, type ReferenceSource } from "@revido-llc/kit/editor";
const usersSource: ReferenceSource = {
id: "user", label: "People",
search: (query, { signal }) => api.searchPeople(query, { signal }),
renderItem: (item) => <PersonRow person={item} />,
renderChip: (item) => <PersonChip name={item.label} />,
href: (item) => `/people/${item.id}`,
};
<Editor aria-label="Description" sources={[usersSource, docsSource, issuesSource, projectsSource]} />Per-project setup
Share one registry across a project's document and comment editors. Dedicated # and [[ triggers search Issues and Docs; @ searches People and Projects.
import { Editor, EditorComposer, EditorProvider } from "@revido-llc/kit/editor";
const projectSources = [
usersSource,
{ ...docsSource, trigger: "[[" },
{ ...issuesSource, trigger: "#" },
projectsSource,
];
<EditorProvider sources={projectSources}>
<Editor aria-label="Project document" value={document} />
<EditorComposer aria-label="Comment" value={draft} onSubmit={send} />
</EditorProvider>Inline images
Paste, drop or choose images with /image. This fixture uploader reports progress and resolves after 800ms; selection, alt text, caption and resize remain in the editor.
import { Editor } from "@revido-llc/kit/editor";
<Editor aria-label="Description" onUploadImage={async (file, { onProgress }) => {
const image = await storage.uploadImage(file, { onProgress });
return { src: image.url, alt: file.name, width: image.width, height: image.height };
}} />Private media
The document keeps a durable storage path. This demo denies access and shows the neutral placeholder; your app resolves authorized paths to signed display URLs.
import { Editor, EditorProvider } from "@revido-llc/kit/editor";
<EditorProvider resolveMediaSrc={(src, { kind, signal }) => storage.signAuthorizedMedia(src, { kind, signal })}>
<Editor aria-label="Description" onUploadImage={async (file, { onProgress }) => {
const media = await storage.uploadImage(file, { onProgress });
return { src: media.path, displaySrc: media.signedUrl, alt: file.name };
}} />
</EditorProvider>
// Signed display URLs stay in the session; JSON and Markdown retain media.path.Image upload failure and retry
The demo fails each file's first attempt. Its inline Retry reuses the file; Remove discards the placeholder.
import { Editor } from "@revido-llc/kit/editor";
<Editor aria-label="Description" onUploadImage={async (file, { onProgress }) => {
// Throw on failure; the editor keeps the file for Retry and Remove.
return storage.uploadImage(file, { onProgress });
}} />Video embeds and upload
Paste a YouTube, Vimeo or Loom URL on its own line for a privacy-friendly embed. /video accepts a URL or a file when onUploadVideo is configured.
import { Editor } from "@revido-llc/kit/editor";
<Editor aria-label="Description" onUploadVideo={async (file, { onProgress }) => {
const video = await storage.uploadVideo(file, { onProgress });
return { src: video.url, alt: file.name };
}} />
// JSON stores video nodes; derived Markdown uses a fenced video directive
// containing JSON attributes so agent edits round-trip hosted and uploaded videos.Comment composer
import { EditorComposer } from "@revido-llc/kit/editor";
<EditorComposer aria-label="Comment" sources={projectSources} value={draft} onChange={({ json }) => setDraft(json)} onSubmit={send} storageKey="comment-height" footer={{ left: <InternalOnly />, right: <Send /> }} />Compact record comment
Rest, focus, retained draft and dark states. One centred 20px line on a subtle sunken surface; the ghost send becomes primary for content, and focus reveals tools beneath it.
import { EditorComposer } from "@revido-llc/kit/editor";
<EditorComposer size="compact" aria-label="Comment" value={draft} onChange={({ json }) => setDraft(json)} onSubmit={send} sendAction={{ onPress: send, loading: sending }} storageKey="record-comment-height" footer={{ left: <InternalOnly /> }} />Read-only with identity links and anchors
import { EditorProvider, EditorReadOnly } from "@revido-llc/kit/editor";
<EditorProvider sources={projectSources}>
<EditorReadOnly value={record.description_json} markdown={record.description} />
</EditorProvider>Portable description
A literal Markdown seed composes without application callbacks or reference sources.
import { Editor } from "@revido-llc/kit/editor";
<Editor aria-label="Portable description" defaultMarkdown="Write the delivery plan here." />Portable composer
import { EditorComposer } from "@revido-llc/kit/editor";
<EditorComposer aria-label="Portable comment" defaultMarkdown="Please review the plan." />Portable read-only document
import { EditorReadOnly } from "@revido-llc/kit/editor";
<EditorReadOnly value={{ type: "doc", content: [{ type: "paragraph", attrs: { blockId: "summary" }, content: [{ type: "text", text: "Delivery is ready for review." }] }] }} />