Powered by AppSignal & Oban Pro

Coelho: your own schema

notebooks/schema.livemd

Coelho: your own schema

Mix.install([
  {:coelho, "~> 0.18"},
  {:kino, "~> 0.19"},
  {:phoenix_live_view, "~> 1.1"}
])

One schema, both halves

A schema is declared once, in Elixir. The server validates and renders with it; the browser builds its editor from the same declaration, exported as JSON. Add a node of your own — a callout — to the shipped schema:

schema =
  Coelho.Schema.extend(Coelho.Schema.default(),
    nodes: [
      callout: [
        group: "block",
        content: "inline*",
        attrs: [tone: [default: "info", validate: {:one_of, ~w(info warning)}]],
        render: {"aside", [{"class", "callout"}]}
      ]
    ]
  )

Map.keys(schema.nodes)

The validator is the schema

The schema says what a document may hold, and nothing else gets through:

callout = fn tone ->
  %{
    "type" => "doc",
    "content" => [
      %{
        "type" => "callout",
        "attrs" => %{"tone" => tone},
        "content" => [%{"type" => "text", "text" => "Mind the gap"}]
      }
    ]
  }
end

%{
  info: Coelho.validate(callout.("info"), schema),
  panic: Coelho.validate(callout.("panic"), schema),
  without_the_node: Coelho.validate(callout.("info"), Coelho.Schema.default())
}
{:ok, document} = Coelho.validate(callout.("warning"), schema)
Coelho.to_html(document, schema)

An editor for it

The editor knows the node because the schema says so: no JavaScript to write.

Coelho.Kino.new(schema: schema, value: document)

Less, for one field

restrict/2 narrows a schema to what one field may hold — a comment that takes paragraphs, bold and italic, and nothing else. The toolbar follows.

comment =
  Coelho.Schema.restrict(Coelho.Schema.default(),
    nodes: [:doc, :paragraph, :text],
    marks: [:bold, :italic]
  )

Coelho.Kino.new(schema: comment, placeholder: "A comment: bold and italic only")