ProsemirrorEx
Elixir port of the ProseMirror document model and transform libraries. Enables server-side ProseMirror document manipulation with full wire-format JSON compatibility.
Features
- Document Model (
ProsemirrorEx.Model) — complete port of prosemirror-model - Transforms (
ProsemirrorEx.Transform) — complete port of prosemirror-transform - Authority Server (
ProsemirrorEx.Authority) — collaborative editing server with version tracking and step history - Wire-format compatible — JSON serialization is identical to the JS libraries
- Arbitrary schemas — define any ProseMirror schema, not just the basic one
- Convenience schemas —
ProsemirrorEx.Schema.BasicandProsemirrorEx.Schema.Listfor common prose + list setups
Convenience schemas
alias ProsemirrorEx.Schema.{Basic, List}
alias ProsemirrorEx.Model.Schema
schema =
Schema.new(%{
"nodes" => Basic.nodes() |> List.add_list_nodes("paragraph block*", "block"),
"marks" => Basic.marks()
})
# or Basic.schema() without lists
Run the interactive demo: mix run examples/schema_demo.exs
Installation
Add prosemirror_ex to your list of dependencies in mix.exs:
def deps do
[
{:prosemirror_ex, "~> 0.3.0"}
]
end
Quick Start
alias ProsemirrorEx.Model.{Schema, Node}
alias ProsemirrorEx.Transform.Transform
# Define a schema
schema = Schema.new(%{
"nodes" => [
{"doc", %{"content" => "block+"}},
{"paragraph", %{"content" => "inline*", "group" => "block"}},
{"text", %{"group" => "inline"}}
],
"marks" => [
{"em", %{}},
{"strong", %{}}
]
})
# Create a document
doc = Schema.node(schema, "doc", nil, [
Schema.node(schema, "paragraph", nil, [
Schema.text(schema, "Hello world")
])
])
# Transform it
tr = Transform.new(doc)
|> Transform.add_mark(1, 6, Schema.mark(schema, "strong"))
new_doc = tr.doc
# doc(paragraph(strong("Hello"), " world"))
# Serialize to JSON (compatible with JS ProseMirror)
json = Node.to_json(new_doc)
# Deserialize from JSON
restored = Node.from_json(schema, json)
Authority Server (Collaborative Editing)
Implements the central authority from the ProseMirror collab guide: version tracking, step acceptance, and catch-up history. Built on Model + Transform only — client packages (prosemirror-collab, prosemirror-state, prosemirror-view, etc.) stay in the browser.
alias ProsemirrorEx.Authority
# Start an authority with a schema and initial document
# Optionally bound step history (official demo uses MAX_STEP_HISTORY)
auth = Authority.new(schema, doc, max_history: 10_000)
# Receive steps from clients
{:ok, auth} = Authority.receive_steps(auth, "client-1", 0, steps)
# Reject stale versions
{:error, :version_mismatch} = Authority.receive_steps(auth, "client-2", 0, other_steps)
# Get steps for catch-up
{:ok, missed_steps, client_ids} = Authority.steps_since(auth, 0)
# If history was trimmed past the client's version:
# {:error, :history_unavailable} — reload via Authority.doc/1
ProsemirrorEx.Authority.Server wraps this in a GenServer with JSON boundaries and subscribe/1 notifications ({:authority_update, payload}), the Elixir equivalent of the guide's onNewSteps callbacks.
License
MIT