@davaux/rich-text
v0.9.0
Published
ProseMirror-backed rich text editor for Davaux, stored and loaded as OML
Downloads
10
Readme
@davaux/rich-text
ProseMirror-backed rich text editor for Davaux. Content is stored and loaded as OML — the same serializable JSX-tree format used everywhere else in Davaux — so there's no separate markdown/HTML column and no separate parser for rich text fields.
Installation
npm install @davaux/rich-textSetup
1. Register the plugin
Add richTextPlugin() to davaux.config.ts. This registers the editor's reactive components for client-side hydration:
// davaux.config.ts
import { defineConfig } from 'davaux/config'
import { richTextPlugin } from '@davaux/rich-text/plugin'
export default defineConfig({
plugins: [richTextPlugin()],
})2. Import the stylesheet
// src/routes/_layout.tsx
import '@davaux/ui/prose.css'
import '@davaux/rich-text/styles.css'@davaux/rich-text renders its content with the dv-prose class, so @davaux/ui's prose.css must be present too (typography for headings, lists, blockquotes, etc.).
Basic usage
import { RichTextEditor } from '@davaux/rich-text'
import type { OmlNode } from 'davaux/oml'
function ArticleForm({ initialBody }: { initialBody: OmlNode }) {
return (
<RichTextEditor
value={initialBody}
onChange={(oml) => console.log('updated OML:', oml)}
/>
)
}value is the current OML content (or null for an empty document); onChange fires with the updated OML tree on every edit.
Using it as a form field
Pass name to render a hidden <input> that stays in sync with the current OML as JSON — this drops the editor straight into a native <form method="post">, no client-side fetch/submit wiring required:
<form method="post">
<RichTextEditor name="body" value={initialBody} placeholder="Write your post…" />
<button type="submit">Publish</button>
</form>On the server, parse the submitted field back into OML before storing it. Store the raw JSON string as-is (e.g. field.body) rather than re-serializing — parseRichText (see below) expects that same string shape when reading it back, including its plain-text fallback for pre-migration content:
import { parseOml } from 'davaux/oml'
const body = parseOml(JSON.parse(field.body)) // OmlNodeHeadless mode
Pass hideToolbar to render the editing surface without the built-in toolbar — useful if you want to build custom controls against the same docToOml/omlToDoc conversion:
<RichTextEditor value={initialBody} hideToolbar />Rendering stored content (no editing)
Use RichTextReader to display stored content anywhere without pulling in ProseMirror. It takes the raw stored string directly — the same JSON that RichTextEditor's hidden input produces — and handles the plain-text fallback for content stored before a rich-text migration:
import { RichTextReader } from '@davaux/rich-text'
<RichTextReader value={article.body} />Pass options={{ links: false }} to strip link marks down to plain text — useful when the reader itself sits inside an outer link (e.g. a feed card linking to the full article), where a nested <a> would produce invalid, unpredictably-clickable nested anchors:
<a href={`/articles/${article.slug}`}>
<RichTextReader value={article.excerptBody} options={{ links: false }} />
</a>If you already have a parsed OmlNode rather than the stored string (e.g. straight from RichTextEditor's onChange), use renderToHtml from davaux/oml directly instead — it's the same renderer RichTextReader and RichTextEditor use internally for their initial paint:
import { renderToHtml } from 'davaux/oml'
<div class="dv-prose" dangerouslySetInnerHTML={{ __html: renderToHtml(oml) }} />Parsing helpers
parseRichText and richTextExcerpt work on the raw stored string, applying the same plain-text fallback as RichTextReader:
import { parseRichText, richTextExcerpt } from '@davaux/rich-text'
const body = parseRichText(article.body) // OmlNode, ready for renderToHtml or omlToDoc
const preview = richTextExcerpt(article.body) // plain-text string, for feed previews or search indexingrichTextExcerpt is also a cheap way to check whether a stored body is empty, since it flattens to '' for an empty document.
Supported formatting
- Blocks: paragraph, heading (h1–h6), blockquote, bullet list, numbered list, code block, horizontal rule, image
- Marks: bold, italic, underline, strikethrough, inline code, link
Marks nest as regular OML elements (e.g. bold text is a <strong> element wrapping a #text node), so no OML schema changes are needed to store rich text alongside any other Davaux content.
Converters
docToOml and omlToDoc are exported directly if you need to work with the ProseMirror document or schema yourself:
import { docToOml, omlToDoc, schema } from '@davaux/rich-text'
const doc = omlToDoc(storedOml, schema)
const oml = docToOml(doc)