Render Quillon documents to HTML.
Rendering is one-way: an AST goes in, a string of HTML comes out. Text and attribute values are escaped, so a document holding untrusted text is safe to embed in a page.
iex> doc = Quillon.document([Quillon.paragraph("Hello")])
iex> Quillon.HTML.to_html(doc)
~s(<div class="quillon"><p>Hello</p></div>)Layout and styling tokens
Block nodes carry constrained tokens (align: :center, spacing: :lg) rather
than CSS. By default those become data-* attributes, so the output is
framework-agnostic and any stylesheet can target them:
iex> Quillon.HTML.to_html(Quillon.paragraph("Hi", align: :center))
~s(<p data-align="center">Hi</p>)To map tokens onto a design system, pass :classes - either a module
implementing Quillon.HTML.Classes or a two-argument function. Quillon ships
Quillon.HTML.Tailwind:
iex> Quillon.HTML.to_html(Quillon.paragraph("Hi", align: :center),
...> classes: Quillon.HTML.Tailwind)
~s(<p class="leading-relaxed text-center">Hi</p>)A mapper receives every token as class(property, value) and the node itself
as class(:node, type). Returning nil emits nothing. When a mapper is given
the data-* attributes are dropped - the design system owns presentation.
Custom node types
A node type the renderer does not know about still renders. It becomes a
<span> when it holds only inline content and a <div> otherwise, tagged
with the type so styling and round-tripping have something to hold on to:
iex> line = {:line, %{page: 2}, [Quillon.text("Hello")]}
iex> Quillon.HTML.to_html({:paragraph, %{}, [line]})
~s(<p><span data-node="line">Hello</span></p>)The same applies to marks, which render as <span data-mark="...">.
Cursors
Pass :cursors to draw other people's carets into the page. Each entry is a map
holding a Quillon.Selection and whatever identifies its owner:
iex> doc = Quillon.document([Quillon.paragraph("Hello")])
iex> Quillon.HTML.to_html(doc,
...> cursors: [%{label: "alice", selection: Quillon.Selection.cursor([0], 2)}])
~s(<div class="quillon"><p>He<span data-cursor data-cursor-label="alice"></span>llo</p></div>)A collapsed selection becomes an empty <span data-cursor> at the caret. An
expanded one also wraps what it covers in <span data-cursor-range>, as does a
node selection around its node. Position them with CSS; the renderer emits the
hooks and takes no view on how a caret looks.
Cursors are never part of the document. They are passed in per render, so the same document draws differently for each viewer.
Summary
Types
A token-to-class mapper: a module implementing Quillon.HTML.Classes, or a
function taking the property and its value.
Someone's caret: a Quillon.Selection plus whatever identifies its owner. The
:id, :label and :color keys are optional and become data-cursor-*
attributes.
Rendering options
Functions
Escape a string for use in HTML text or attribute values.
Render a node and its subtree to an HTML string.
Render a node to iodata.
Types
A token-to-class mapper: a module implementing Quillon.HTML.Classes, or a
function taking the property and its value.
@type cursor() :: %{ :selection => Quillon.Selection.t(), optional(:id) => String.t(), optional(:label) => String.t(), optional(:color) => String.t() }
Someone's caret: a Quillon.Selection plus whatever identifies its owner. The
:id, :label and :color keys are optional and become data-cursor-*
attributes.
Rendering options
Functions
Escape a string for use in HTML text or attribute values.
Examples
iex> Quillon.HTML.escape(~s(<a href="x">Tom & Jerry</a>))
"<a href="x">Tom & Jerry</a>"
Render a node and its subtree to an HTML string.
Examples
iex> Quillon.HTML.to_html(Quillon.heading(2, "Title"))
~s(<h2>Title</h2>)
iex> Quillon.HTML.to_html(Quillon.text("Bold", [:bold]))
~s(<strong>Bold</strong>)
iex> Quillon.HTML.to_html(Quillon.text("5 > 3 & <b>"))
~s(5 > 3 & <b>)
Render a node to iodata.
Use this when the result is written straight to a socket or file - it skips building an intermediate binary.
iex> Quillon.HTML.to_iodata(Quillon.paragraph("Hi")) |> IO.iodata_to_binary()
~s(<p>Hi</p>)