# `Coelho`
[🔗](https://github.com/nseaSeb/coelho/blob/main/lib/coelho.ex#L1)

Structured rich text for Elixir.

Coelho stores a rich text document as a validated tree — the same shape
ProseMirror produces — rather than as a blob of HTML. A schema, written
once in Elixir, says which nodes and marks exist; validating a document
against it *is* the sanitisation step, and rendering it is a pure function
the application can override node by node.

This module is the convenience surface over the three that do the work:

  * `Coelho.Schema` — declaring the schema and exporting it to the browser
  * `Coelho.Document` — validating, normalising, extracting plain text
  * `Coelho.Render` — turning a validated document into HTML

Every function here defaults to `Coelho.Schema.default/0`; applications
with their own schema call the underlying modules directly.

    iex> document = %{
    ...>   "type" => "doc",
    ...>   "content" => [
    ...>     %{"type" => "paragraph", "content" => [%{"type" => "text", "text" => "hello"}]}
    ...>   ]
    ...> }
    iex> {:ok, document} = Coelho.validate(document)
    iex> Coelho.to_html(document)
    "<p>hello</p>"

# `blank?`

```elixir
@spec blank?(term(), Coelho.Schema.t()) :: boolean()
```

Whether a document would put anything on the page.

What to ask before rendering a block at all. See
`Coelho.Document.blank?/2` — in particular for why
`text_length(document) == 0` is not the same question.

# `canonical`

```elixir
@spec canonical(term()) :: binary()
```

A byte-for-byte stable serialisation of a validated document.

# `empty`

```elixir
@spec empty(Coelho.Schema.t()) :: map()
```

The empty document of a schema.

The child is derived from the top node's content expression rather than
assumed to be a paragraph, so a schema that calls its block node something
else still gets a document its own `validate/2` accepts.

# `from_html`

```elixir
@spec from_html(String.t(), Coelho.Schema.t(), keyword()) ::
  {:ok, map(), [Coelho.HTML.warning()]} | {:error, term()}
```

Converts existing HTML into a validated document, and says what it left
behind.

The migration path for content already stored as HTML. Requires the
optional `:floki` dependency; see `Coelho.HTML.from_html/2` for what the
import does with markup the schema does not know, and for the shape of the
warnings.

# `hash`

```elixir
@spec hash(term(), :sha256 | :sha512 | :sha384 | :sha224 | :sha) :: String.t() | nil
```

The hex digest of a validated document, or `nil` when it holds nothing.

# `migrate`

```elixir
@spec migrate(
  map(),
  keyword()
) :: {:ok, map()} | {:error, String.t()}
```

Moves a document from one schema version to the next.

A schema that declares a `:version` stamps it on every document it
validates, and refuses a document stamped with another — which is the
whole point: when a node is renamed or an attribute retired, there is
otherwise no way to tell a document written under the old vocabulary from
one that is simply wrong.

    Coelho.migrate(document, from: 1, to: 2, with: &MyApp.RichText.v1_to_v2/1)

`:with` takes the document and returns the rewritten one. Crossing more
than one version at a time takes a map of the step to run *into* each
version:

    Coelho.migrate(document, from: 1, to: 3, with: %{2 => &v1_to_v2/1, 3 => &v2_to_v3/1})

The result is stamped with `:to` and is **not** validated: run it through
`validate/2` with the new schema, which is where a migration that missed
something says so.

# `reduce`

```elixir
@spec reduce(
  map(),
  Coelho.Schema.t(),
  Coelho.Render.callbacks(),
  Coelho.Render.opts()
) :: term()
```

Folds a document into any term at all, for a target that is not HTML.

See `Coelho.Render.reduce/4`.

# `sanitize`

```elixir
@spec sanitize(term(), Coelho.Schema.t(), keyword()) :: map()
```

Turns any term into a document the schema accepts, without failing.

The counterpart of `validate/2` for the way out: see
`Coelho.Document.sanitize/3` for what it removes, why a stored document
needs it at all, and what `limits: [max_text_length: :infinity]` is for.

# `text_length`

```elixir
@spec text_length(term()) :: non_neg_integer()
```

The number of characters a writer typed, counted the way the editor counts.

# `to_html`

```elixir
@spec to_html(map()) :: String.t()
```

Renders a validated document to HTML.

The schema may be left out, in which case the render options can be passed
straight as the second argument.

# `to_html`

```elixir
@spec to_html(map(), Coelho.Schema.t() | Coelho.Render.opts()) :: String.t()
```

# `to_html`

```elixir
@spec to_html(map(), Coelho.Schema.t(), Coelho.Render.opts()) :: String.t()
```

# `to_inline_html`

```elixir
@spec to_inline_html(map()) :: String.t()
```

Renders a document where only inline elements are legal.

For a banner, a map bubble, a card excerpt — anywhere the document goes
inside a `<p>` or a `<span>` and `to_html/3` would have the browser close
the enclosing paragraph out from under it. See
`Coelho.Render.to_inline_html/3`, which carries the whole reason.

# `to_inline_html`

```elixir
@spec to_inline_html(map(), Coelho.Schema.t() | Coelho.Render.opts()) :: String.t()
```

# `to_inline_html`

```elixir
@spec to_inline_html(map(), Coelho.Schema.t(), Coelho.Render.opts()) :: String.t()
```

# `to_safe_html`

```elixir
@spec to_safe_html(map()) :: {:safe, iodata()}
```

Renders a validated document as `{:safe, iodata}`, for a template.

The form that needs no `raw/1` — see `Coelho.Render.to_safe_html/3`.

# `to_safe_html`

```elixir
@spec to_safe_html(map(), Coelho.Schema.t() | Coelho.Render.opts()) ::
  {:safe, iodata()}
```

# `to_safe_html`

```elixir
@spec to_safe_html(map(), Coelho.Schema.t(), Coelho.Render.opts()) ::
  {:safe, iodata()}
```

# `to_safe_inline_html`

```elixir
@spec to_safe_inline_html(map()) :: {:safe, iodata()}
```

`to_inline_html/3` in the shape a template will not escape again.

# `to_safe_inline_html`

```elixir
@spec to_safe_inline_html(map(), Coelho.Schema.t() | Coelho.Render.opts()) ::
  {:safe, iodata()}
```

# `to_safe_inline_html`

```elixir
@spec to_safe_inline_html(map(), Coelho.Schema.t(), Coelho.Render.opts()) ::
  {:safe, iodata()}
```

# `to_text`

```elixir
@spec to_text(map(), Coelho.Schema.t()) :: String.t()
```

Extracts the plain text of a document, for full text search.

# `validate`

```elixir
@spec validate(term(), Coelho.Schema.t()) ::
  {:ok, map()} | {:error, [Coelho.Document.Error.t()]}
```

Validates and normalises a document against a schema.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
