Custom nodes
Register measurable block types and inline atoms with a measure function and a renderer.
Ori is driven by a schema of measurable nodes, so you add custom block types and inline atoms without forking the engine. A node only declares how it measures and how it renders; layout, virtualization, caret and selection come for free.
1. Register nodes (core)
Text nodes are laid out by Pretext. Atomic blocks return their own height; inline atoms return their own width.
const editor = useEditor({
schema: {
blocks: {
divider: { type: "divider", text: false, measure: () => 33 },
image: {
type: "image",
text: false,
// height is a pure function of width + attrs — re-measured on resize
measure: ({ width, attrs }) => Math.round(width / Number(attrs.ratio)),
},
},
atoms: {
mention: {
type: "mention",
measure: ({ data, typography, measurer }) =>
measurer.measure(`@${data.label}`, resolveFont(typography, {})) + 14,
},
},
},
});2. Render them (react)
Renderers are keyed by type. Atomic blocks get a block renderer; inline atoms get an atom renderer drawn at the measured width so the caret stays aligned.
<NoteEditor
editor={editor}
blockRenderers={{
divider: () => <hr />,
image: ({ editor, block }) => (
<img src={String(editor.getBlockAttrs(block.id).src)} alt="" />
),
}}
atomRenderers={{
mention: ({ atom }) => <Chip label={(atom.data as { label: string }).label} />,
}}
/>How it works
- Atomic blocks (divider, image, embed) provide a height function of
width + attrs. When the width changes the controller re-measures, and the virtualizer updates around it. - Inline atoms (mention, equation) are Yjs embeds occupying one offset that participate in Pretext's line-breaking as fixed-width boxes — the caret steps over them, hit-testing snaps to the nearer edge, and you can type around them.
- The built-in
paragraph/heading/quote/codeare themselves just registered text nodes; your schema merges over them.
Inserting custom nodes
// atomic block after the current selection
editor.insertBlockAfterSelection("image", { type: "image", src, ratio: 16 / 9 });
// inline atom at the caret
editor.insertInlineAtom({ type: "mention", label: "Ada Lovelace" });The example app wires a / command palette for blocks and an @ autocomplete
for mentions on top of these primitives.