Expand description
HTML for Lexical documents, rendered from the Yjs structure the editor syncs. No Node process and no headless editor. A server that holds the document bytes produces the markup itself.
This renders core Lexical: paragraphs, headings, quotes, code blocks,
lists and list items, tables, horizontal rules, links, and the whole
text-format model. Lexxy’s additions live in the Ruby layer, as the
Y::Lexxy renderer’s rule set: its own node types (attachments,
galleries, early_escape_code, horizontal_divider) and its decorations
of core nodes (the figure around a table, header-cell styling, the
nested-list-item class). That rule set uses the same extension API
applications use. Y::Lexical is the core base class. The byte-parity
guarantee is held there too: the Ruby fixture tests and the
headless-Chrome end-to-end run pin Y::Lexxy#to_html against a real
editor’s serialized value. The tests in this crate pin core output as
regression goldens. Stock Lexical has no canonical serializer to capture
from.
Prior art: ueberdosis/tiptap-php renders ProseMirror JSON to HTML in
plain PHP, outside any JavaScript runtime. This crate does the same job
from the collaborative (Yjs) structure.
How Lexical stores a document, checked against bytes captured from a live editor:
- Blocks are
Y.XmlTextwith a__typeattribute:paragraph,heading(plus__tag),quote,code(plus__language),listandlistitem,table,tablerowandtablecell, and the inlinelinkandautolink. - Each text run is preceded by an embedded
Y.Mapwith its metadata: a__typeoftext,code-highlight, ortab, and a__formatbitmask. - A
linebreakis a bare metadata map. Atabis a map followed by a"\t"run. - Decorator nodes are
Y.XmlElements whose fields are plain attributes.horizontalruleis handled here. Application and Lexxy decorators come in through rules.
Text formatting follows the export pipeline Lexxy runs: Lexical’s
$generateHtmlFromNodes, then sanitize. That output is the only
formatting truth that can be pinned from outside. The inner tag is
strong for bold, or em for italic without bold. The outer tag is
code, mark, sub, or sup. An <i> wraps only when bold and italic
combine, because the em slot is taken. <s> and <u> wrap whenever
present. <span>s are unwrapped, so unformatted text is bare. A run’s
__style (highlight colors) survives on the createDOM tag, filtered to
color and background-color. On a plain run, or one with only strike or
underline, it goes away with the span. The case-transform format bits
never render; their text-transform style is outside the sanitize
whitelist.
A node type with no rule still renders its text and child blocks, just unwrapped.
Custom nodes register rules by __type (see yjs-html-core). A rule is
consulted before the built-in arms, so it can extend the schema or
replace a built-in. Declarative rules render here. Callback rules emit
Segment::Deferred for the caller to fill in after the render returns.
Structs§
- Emitter
- Builds segmented output. Renderers append markup through this instead of a
bare
String; frames capture sub-output (a deferred node’s children, or a “did this render anything?” probe) without string sentinels. - Mark
Rule - A custom mark (ProseMirror only): a wrapping tag with attributes read from the mark’s own value map.
- Rules
- Type
Info - What a document walk observed about one node type — the facts behind
Y::Lexical#node_types/Y::ProseMirror#node_types, the discovery aid for writing rules against a real document.
Enums§
- Attr
Part - A piece of an attribute value or text template: a literal, or a reference to one of the node’s stored attributes.
- Content
- What goes inside a custom node’s element.
- Flattened
- What flattening produced. Both variants are normal outcomes —
Deferredmeans callback nodes are present and need splicing — so this is an enum rather than aResult. - Node
Rule - One node rule: markup as data, or a deferral to the caller.
- Segment
- One piece of renderer output.
Htmlis finished markup;Deferredis a callback node whose markup the caller supplies after the render, carrying everything needed to produce it. Content nests, so callback nodes inside callback nodes resolve depth-first.child_typeslists the node’s element/block children by type, in document order — structural facts a callback can’t recover fromattrsor the rendered content (an image count, whether a list item holds a nested list).
Functions§
- any_
attr_ string - A stored attribute as a string: strings pass through; numbers print JS-style; bools as true/false. Anything else is None.
- collect_
node_ types - Walk the document and record what each node type actually looks like —
the discovery aid behind
Y::Lexical#node_types. It records facts: counts, attribute names (minus__type, which is the key), child types, and whether text runs were seen. - flatten
- Join the segments when every one is finished markup, so the common no-callback path stays a single string and the splicing layer can be skipped; hand the segments back untouched when callback nodes are present.
- is_
builtin - The node types a Lexical renderer’s built-in arms cover (core Lexical; inline and decorator types included). Everything else needs a rule.
- render
- Rule-free rendering to a plain string — the simplest way to use this crate standalone, and the fixture-parity surface the tests pin. With no callback rules, segments always flatten.
- render_
segments - Render a Lexical/Lexxy-shaped XML root, or
Nonewhen the root isn’t Lexical-shaped. Lexical marks every node with a__typeattribute; a root whose children carry none — a ProseMirror document, say, whose blocks are plain<paragraph>elements — is a foreign schema, and render returnsNonefor it rather than a lossy guess. - resolve_
parts - Resolve a lit/ref template against a node’s attributes.
None(attribute or text skipped) when the resolved value is empty — matching how the built-in renderers omit absent attributes. - type_
map_ json - Serialize the observations, annotating each type with what already
handles it (
"rule","builtin", or null — the ones a rule author needs to cover). - xml_
attrs_ json - A node’s stored attributes as a JSON object, for callback rules. Keys as
stored (
__typeand friends keep their prefix); values via yrs’s own JSON encoding. - xml_
ref_ attr - An attribute reference on a node: rules say
:kind; Lexical stores its own props as__kind— try the raw name first, then prefixed. (ProseMirror stores attrs bare, so the fallback never fires there.)
Type Aliases§
- TypeMap
- Per-type observations, ordered for stable output.