To Kotoba 0.8
Kotoba 0.8 keeps uploads with Storehouse, and shared editors run on Issho 0.4. An editor with no uploads and no sharing needs nothing.
What changes
| 0.7 | 0.8 |
|---|---|
{:issho, "~> 0.3"} | {:issho, "~> 0.4.2"}, for a shared editor |
Kotoba kept the files (Kotoba.Storage.Local) | Storehouse keeps them ({:storehouse, "~> 0.2"}), for an editor with uploads |
config :kotoba, Kotoba.Storage.Local, root: ... | config :storehouse, Storehouse.Local, root: ..., url_prefix: "/uploads/kotoba" |
config :kotoba, storage: MyApp.Store, @behaviour Kotoba.Storage | config :storehouse, adapter: MyApp.Store, @behaviour Storehouse.Adapter (the same callbacks) |
Kotoba.Storage.Local.Plug | Storehouse.Local.Plug |
Kotoba.Storage's key/2 | Storehouse.key/2 |
The old names still work, with deprecation warnings, and are removed in Kotoba 1.0.
Steps
- Update
{:kotoba, "~> 0.8"}. For a shared editor,{:issho, "~> 0.4.2"}. For uploads, add{:storehouse, "~> 0.2"}: without it, an editor with uploads says what is missing. - Move the store's config to Storehouse, with the same
root, andurl_prefix: "/uploads/kotoba"(the old default, unless you gave another), so that the URLs in your documents stay valid. - Replace the plug and
key/2, as in the table.
Step 1 is the only one that is necessary: the rest works as it did, and the warnings say what to change. The Uploads guide, "From Kotoba 0.7", has the details.
To Kotoba 0.7
Kotoba 0.7 runs on Issho 0.3, which makes agents move as people do and following someone the room's, across every block of a page. An editor that is not shared needs nothing.
What changes
| 0.6 | 0.7 |
|---|---|
{:issho, "~> 0.2"} | {:issho, "~> 0.3"} |
| an agent's edits appeared at once | it goes to the text, selects as a drag does, holds a moment, and types at a person's pace (config :kotoba, Kotoba.Issho, pace: 25, travel: true), when someone sees it (Issho.Motion) |
| no following | the editor's people list follows on a click, and the editor follows the one followed in the room to their caret |
collab={Kotoba.Collab.token(MyApp.Issho, id, user: user, role: :write)} | issho={[instance: MyApp.Issho, document: id, can: :write]} user={user}: the keys of every component (Issho.Join) |
token/3's role:, people:, follow:, undo:, socket:, max_channels: | the same in issho, with can: for role: |
Kotoba.Collab.document, Kotoba.Collab.transact | Kotoba.Issho.document/1, Kotoba.Issho.change/4 (the old names still work, deprecated) |
| a change's author, a key | a key, or your actor (author: current_user) |
Steps
Update
{:kotoba, "~> 0.7"}and{:issho, "~> 0.3"}.For changes that appear at once, as in 0.6:
config :kotoba, Kotoba.Issho, pace: 0, travel: false(or Issho'sconfig :issho, Issho.Motion, travel: falsefor every component).Join with
issho. Where a LiveView signed credentials, give the editor its options instead:-collab = Kotoba.Collab.token(MyApp.Issho, document.id, user: user, role: :write) -<.kotoba field={@form[:body]} id="body" collab={@collab} /> +<.kotoba field={@form[:body]} id="body" + issho={[instance: MyApp.Issho, document: @document.id, can: :write]} + user={@current_user} />collabraises with what to write instead, and an unknownisshokey raises, so nothing is left behind silently.Rename
Kotoba.Collab.documentandtransact/4toKotoba.Issho.document/1andchange/4, in your policy and your server code.On a page of several blocks, add Issho's pointers (
Issho.Components.pointers/1): an agent's pointer goes from block to block, the page follows the one followed, and a person's motion is the app's to say (motionon the pointers).
To Kotoba 0.6
Kotoba 0.6 moves shared editing onto Issho, collaboration for Phoenix, which Hakushi's canvases were built on, and onto which Masu 0.4 moves its grids' people. An editor that is not shared works exactly as before: the editor, its content, forms, rendering, uploads, prompts and custom nodes need nothing.
This guide is for an app with shared editors (collab on <.kotoba>).
Why
Kotoba 0.5 brought its own collaboration: a document supervisor, a channel, a socket, credentials and their renewal, cursors and people. That served one editor of one document on a page, and it was all Kotoba's:
- A page has one set of people. A page with two editors, or an
editor beside a grid or a canvas, showed a list of people for each, a
connection for each, and a color of each for one person. In an Issho
room the page has one connection, one list (
Issho.Components.people/1), one color for each person in every part of the page, and a person's cursor goes from one editor to the next ("notebook:7/cell-1"and"notebook:7/cell-2"are in one room). - One policy for the page. Your channel's
authorize/3was Kotoba's own. Issho's policy (Issho.Policy) says who may join, read, write and lock in a room, for every component of it, on join and again as they work, and renews the credentials it granted only while it still lets the person in: your LiveView handles nokotoba:collab_tokenevent. - Agents. In an Issho room an agent is someone: it reads the
document, selects text (the people see its selection), types a few
characters at a time, formats, links, copies and pastes, as a person
does with the editor, and its changes merge with everyone's
(
Kotoba.Issho.tools/0). It reaches your app however you like: MCP (Issho.MCP), your app's own model (Issho.Tools), a job. - Undo is the page's.
undo: :pagetakes back the person's last change on the page, in whichever editor or component they made it. - The page as a live space. Issho draws each person's pointer over
the whole page (
Issho.Components.pointers/1), and an agent at work in a document at its editor. - What comes next is Issho's. Following someone from page to page, a live canvas or grid inside a text, rich text inside a canvas's node, a document of another app or another cluster: Issho's roadmap, which a shared editor gets as it comes, rather than each library building its own.
Kotoba keeps what is Kotoba's: the document, its operations and merge rules, its policy of nodes and features, the editor, the offline drafts and the form flow. Issho is the document's authority, the room's people and their cursors, and the connection.
What changes
| 0.5 | 0.6 |
|---|---|
{Kotoba.Collab.Supervisor, name:, store:, policy:} | {Issho, name: MyApp.Issho, pubsub:, endpoint:, policy: MyApp.Collaboration}, whose document/2 gives Kotoba.Collab.document(store:, policy:) |
your channel's authorize(socket, document_id, action) | your policy's authorize(identity, room, action) (identity.key is the user's id) |
use Kotoba.Collab.Channel and channel "kotoba:*" at /kotoba/socket | use Issho.Channel and channel "issho:*" at /issho/socket |
Kotoba.Collab.token(socket, id, opts) | Kotoba.Collab.token(MyApp.Issho, id, opts) |
handle_event("kotoba:collab_token", ...) | none: on_mount Issho.Live renews the grant, asking your policy |
Kotoba.Collab.content(MyApp.CollabSupervisor, ...), transact/4 | the same, with MyApp.Issho |
an import map with kotoba/collab | kotoba/collab and issho |
Kotoba.Collab.Store is unchanged. Documents it saved before open as
they are, with their epoch, revision, history and receipts, and are saved
in Issho's form with their next change. The editors' browsers keep their
offline drafts.
Steps
Add Issho to your dependencies:
{:kotoba, "~> 0.6"}, {:issho, "~> 0.2"}Replace the supervisor with Issho, in your supervision tree after the PubSub:
-{Kotoba.Collab.Supervisor, - name: MyApp.CollabSupervisor, - store: {MyApp.CollabStore, MyApp.Repo}, - policy: [features: ~w(bold italic links lists)]} +{Issho, + name: MyApp.Issho, + pubsub: MyApp.PubSub, + endpoint: MyAppWeb.Endpoint, + policy: MyApp.Collaboration}Move your channel's authorization into a policy, which says what each document is too:
defmodule MyApp.Collaboration do @behaviour Issho.Policy @impl true def authorize(%Issho.Identity{key: user_id}, room, action) do if MyApp.Documents.allowed?(user_id, room, action), do: :ok, else: {:error, :forbidden} end @impl true def document(_identity, _document_id) do Kotoba.Collab.document( store: {MyApp.CollabStore, MyApp.Repo}, policy: [features: ~w(bold italic links lists)] ) end endThe policy is asked about the room: the document's id, or the part of it before the first
"/"("notebook:7"for"notebook:7/cell-1"). Its actions are:join,:read,:writeand:lock.Replace the channel and the socket:
defmodule MyAppWeb.CollabChannel do - use Kotoba.Collab.Channel, supervisor: MyApp.CollabSupervisor - def authorize(socket, document_id, action), do: ... + use Issho.Channel, instance: MyApp.Issho end defmodule MyAppWeb.CollabSocket do use Phoenix.Socket - channel "kotoba:*", MyAppWeb.CollabChannel + channel "issho:*", MyAppWeb.CollabChannel def connect(_params, socket, _connect_info), do: {:ok, socket} def id(_socket), do: nil end # In the endpoint: -socket "/kotoba/socket", MyAppWeb.CollabSocket, +socket "/issho/socket", MyAppWeb.CollabSocket, websocket: [max_frame_size: 1_000_000]Sign credentials with the instance, add Issho's hook, and remove the renewal event:
+on_mount Issho.Live -collab = Kotoba.Collab.token(socket, document.id, user: user, role: :write) +collab = Kotoba.Collab.token(MyApp.Issho, document.id, user: user, role: :write) -def handle_event("kotoba:collab_token", params, socket), do: ...and publish with it:
-Kotoba.Collab.content(MyApp.CollabSupervisor, document.id, revision) +Kotoba.Collab.content(MyApp.Issho, document.id, revision)Map
isshoin an import map, next tokotoba/collab; with esbuild it is found indeps/, asphoenixis.
Then
- On a page of several editors, put their documents in one room
(
"notebook:7/cell-1","notebook:7/cell-2"), list its people once withIssho.Components.people/1, and give the editorspeople: false. - Show the pointers of the page's people:
Issho.Components.pointers/1. - Choose undo:
undo: :pagefor the person's last change on the page. - Give agents the editor's tools:
Kotoba.Issho.tools/0, with Issho's room tools (see Collaboration).