Leaf.Collab (Leaf v0.6.0)

Copy Markdown View Source

Live editing, wired into a LiveView in one call.

Everything a shared document needs — placing edits that crossed on the wire, agreeing which version everyone is on, putting a session back in step after its socket drops, showing where other people's carets are — is here rather than in the host. It is not obvious code and it is not code worth writing twice; every part of it exists because something went visibly wrong without it.

Using it

def mount(%{"id" => id}, _session, socket) do
  {:ok,
   Leaf.Collab.join(socket,
     room: MyApp.Notes.room_name(id),
     editor_id: "note-editor",
     identity: %{name: socket.assigns.current_user.name}
   )}
end

and in the template:

<.leaf_editor
  id="note-editor"
  content={@leaf_collab.content}
  collaboration={@leaf_collab.collaboration}
/>

That is the whole integration. join/2 attaches a handle_info hook, so the host writes no message handling of its own.

What the hook consumes

The hook handles — and stops — {:leaf_operation, …}, {:leaf_awareness, …}, {:leaf_ready, …}, {:leaf_resync, …}, {:leaf_debug_state, …} and {:leaf_changed, …}. A collaborating LiveView therefore does not receive {:leaf_changed, …}: read the document from @leaf_collab.content, which is kept current on every edit, local or remote. Anything the hook does not recognise passes through to the host's own handle_info clauses untouched.

One collaborative editor per LiveView: join/2 owns the @leaf_collab assign and the hook name.

What the host still owns

Starting a room per document and supervising it — Leaf has no opinion about how many nodes you run or how you name processes. And saying where documents live, which is Leaf.Collab.Store.

Turning it off

Not calling join/2 costs nothing. The editor does no collaboration work unless it is asked to: no coordinates measured, no fingerprints taken, no selection listener attached. Somebody using Leaf for a comment box pays for none of this.

Summary

Functions

Join the document this room holds.

Everyone in the document, for a host that wants to list them.

Put the document back to its starting text, for everyone.

Functions

join(socket, opts)

Join the document this room holds.

Options:

  • :room — the room process, started and supervised by the host
  • :editor_id — the id given to leaf_editor
  • :identity%{name:, color:}, both optional. A host with signed-in users passes theirs so everyone sees a name rather than an identifier.
  • :awareness — show other people's carets and selections. Defaults true.
  • :debug — diagnostics. Defaults false; see Leaf.Collab.Log.

people(socket)

Everyone in the document, for a host that wants to list them.

reset(socket)

Put the document back to its starting text, for everyone.

Handles the part a host would forget: telling the other sessions. A reset that only changed the room left every other tab holding the old text, and the next reconciliation adopted it straight back — the reset undone by the people it was for.