Skip to main content

Crate lexical_yjs_html

Crate lexical_yjs_html 

Source
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.XmlText with a __type attribute: paragraph, heading (plus __tag), quote, code (plus __language), list and listitem, table, tablerow and tablecell, and the inline link and autolink.
  • Each text run is preceded by an embedded Y.Map with its metadata: a __type of text, code-highlight, or tab, and a __format bitmask.
  • A linebreak is a bare metadata map. A tab is a map followed by a "\t" run.
  • Decorator nodes are Y.XmlElements whose fields are plain attributes. horizontalrule is 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.
MarkRule
A custom mark (ProseMirror only): a wrapping tag with attributes read from the mark’s own value map.
Rules
TypeInfo
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§

AttrPart
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 — Deferred means callback nodes are present and need splicing — so this is an enum rather than a Result.
NodeRule
One node rule: markup as data, or a deferral to the caller.
Segment
One piece of renderer output. Html is finished markup; Deferred is 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_types lists the node’s element/block children by type, in document order — structural facts a callback can’t recover from attrs or 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 None when the root isn’t Lexical-shaped. Lexical marks every node with a __type attribute; a root whose children carry none — a ProseMirror document, say, whose blocks are plain <paragraph> elements — is a foreign schema, and render returns None for 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 (__type and 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.