# `Kotoba.Issho`
[🔗](https://github.com/hattoriai/kotoba/blob/v0.8.0/lib/kotoba/issho.ex#L91)

Kotoba's binding for [Issho](https://hex.pm/packages/issho): a shared
editor's document is an Issho document, its engine Kotoba's semantic
operations (`Kotoba.Collab.Document`), and a person's place their
selection in it.

Name it as a document's engine through `document/1`, in your policy;
change a document from your own code with `change/4`; give agents
`tools/0`. A place is a selection (`Kotoba.Issho.Place`). A document's
changes are events of its room (`events/2`), and a Kotoba editor writes
Issho's comments (`read_comment/1`).

# `change`

```elixir
@spec change(atom(), String.t(), list() | map(), keyword()) ::
  {:ok, map()} | {:error, term()}
```

Changes a shared document from your own code (a job, an import),
through the same authority, checks and store as the editors' changes:
operations (`Kotoba.Collab.Document`), or a transaction map (with its
`"id"` and `"base_revision"`) to retry one idempotently.

Options: `:author`, who made it, as the document's hook and history
know them: an actor (`Issho.Actor`, your user or an agent) or a key
(`"server"` unless given, a service); `:id`, the transaction's.

# `comment_field`

```elixir
@spec comment_field(map(), String.t(), [map()]) :: Phoenix.HTML.FormField.t()
```

The field of a comment box (`read_comment/1`), from what the `:input`
slot of `Issho.Components.comments/1` gives: the comment's words as a
document, a paragraph for each line, each `"@"` and name of someone
`comment_prompts/2` offers a mention again, so that an edit starts
from the comment as it is.

# `comment_prompts`

```elixir
@spec comment_prompts(String.t(), [map()]) :: list()
```

The `@` prompt of a comment box (`read_comment/1`): the room's people,
those here first, and `mentionable` (`[%{key: "user:42", name: "Ada"}]`,
as `Issho.Components.comments/1` has it), filtered as the person
types. A mention's id is the person's key. The same list for the
editor's `prompts` and for `Kotoba.Live.handle_prompt/3`, in the
LiveView that is in the room.

# `document`

```elixir
@spec document(keyword()) :: {:ok, map()}
```

What a shared editor's document is, for your policy's `document/2`
(`Issho.Policy`), the same wherever it opens first.

    def document(_identity, "page:" <> _ = id),
      do: Kotoba.Issho.document(store: {MyApp.CollabStore, MyApp.Repo})

Options: `:store` (required), your `Kotoba.Collab.Store` and its
context; `:policy`, the document's checks (`:features`, `:nodes`,
`:validate`), as every accepted transaction must pass them; `:events`,
`false` for a document whose changes make no events of the room (see
`events/2`).

# `events`

```elixir
@spec events([map()], map()) :: [{String.t(), map()}]
```

The events of a change of a shared document (the spec's `:events`,
see `Issho.Documents.open/4`), which `document/1` gives: once the
change is saved, they are events of the document's room, with the
document and the change's author, for agents (`wait_for_events`) and
for LiveViews (`Issho.Live.subscribe/3`'s `:events`).

  * `"text.block_added"`: `%{block: id, type: type, n: n}`, a block
    that was not there: a new one, or one that came back (an undo).
  * `"text.block_removed"`: `%{block: id, type: type}`, a block that
    went.
  * `"text.changed"`: `%{block: id, type: type, n: n}`, a block whose
    text changed, and that was there before and after. A change of
    format only is not one.

A block is a paragraph, a heading, a quote, a list item or a code
block. `id` is its node's id, which stays the same as the text
changes; `n` is its number, as `read_text` counts them (from 1), after
the change; `type` is what it is, as `read_text` says it
(`"paragraph"`, `"h2"`, `"bullet item"`). The text is not in the
event, as the room's people may not all read each of its documents.

    Issho.Live.subscribe("notebook:7", :self, events: ["text.*"])

    def handle_info({Issho.Live, "event", _room, %Issho.Event{name: "text.changed"} = event}, socket) do
      ...
    end

    # The room's other messages, which :self gets too.
    def handle_info({Issho.Live, _event, _key, _payload}, socket), do: {:noreply, socket}

An app with events of its own gives both:

    {:ok, spec} = Kotoba.Issho.document(store: {MyApp.CollabStore, MyApp.Repo})
    {:ok, %{spec | events: &(Kotoba.Issho.events(&1, &2) ++ MyApp.Notebooks.events(&1, &2))}}

# `place`

The place module of this binding, or `nil`.

# `read_comment`

```elixir
@spec read_comment(map()) :: %{body: String.t(), mentions: [String.t()]}
```

A Kotoba editor as the comment box of Issho's comments
(`Issho.Components.comments/1`): its `read`, for the comment's words
and whom they mention from the box's form params. With
`comment_prompts/2` and `comment_field/3`:

    <Issho.Components.comments id="comments" room={@room} mentionable={@mentionable} read={&Kotoba.Issho.read_comment/1}>
      <:input :let={input}>
        <.kotoba
          field={Kotoba.Issho.comment_field(input, @room, @mentionable)}
          id={input.id}
          label="Comment"
          placeholder={input.placeholder}
          features={[:mentions]}
          prompts={Kotoba.Issho.comment_prompts(@room, @mentionable)}
        />
      </:input>
    </Issho.Components.comments>

    def handle_event("kotoba:prompt", params, socket) do
      prompts = Kotoba.Issho.comment_prompts(socket.assigns.room, socket.assigns.mentionable)
      {:noreply, Kotoba.Live.handle_prompt(socket, params, prompts)}
    end

A comment keeps its words as text: a mention of the `@` prompt is
`"@"` and the person's name in it, as Issho's own comment box writes
it, and its key is in the comment's mentions, so the person hears of
it (`"comment.created"`, `Issho.Inbox`). Other mentions are their
label. Give the box only what text keeps, `[:mentions]`: bold or a
link's address would be lost.

# `suggestions`

```elixir
@spec suggestions(Phoenix.LiveView.Socket.t(), term(), map() | keyword(), keyword()) ::
  Phoenix.LiveView.Socket.t()
```

Shows Issho's streams (`Issho.Streams`) as suggestions in this
LiveView's shared editors: an answer, a draft, an agent's proposal,
written by a process of your app or an agent, for this person (or the
room), at a place of an editor's document. Each shows in its editor as
a suggestion (see the [Suggestions](suggestions.md) guide), after the
block of its place, as it comes; a page that mounts while it is
written picks it up where it got to. Accept puts it in the document,
as the person's change; Accept, Reject and Stop close the stream
(`"accepted"`, `"rejected"`, `"stopped"`), and a page that goes away
does not. A stream closed elsewhere (accepted in another tab) goes.

    on_mount Issho.Live

    def mount(_params, _session, socket) do
      issho = [instance: MyApp.Issho, document: "notebook:7/intro"]
      socket = assign(socket, issho: issho, user: socket.assigns.current_user)
      {:ok, Kotoba.Issho.suggestions(socket, socket.assigns.user, %{"intro_editor" => issho})}
    end

    # <.kotoba field={@form[:intro]} id="intro_editor" issho={@issho} user={@user} assist={true} />

`editors` maps each editor's id to its `issho` options (the
`:instance` and the `:document`). The editors need `assist` (`true`
for no menu), for the person's answers to reach the LiveView. The
LiveView joins each document's room (`Issho.Live.join/4`, with
`on_mount Issho.Live`), and handles the streams' messages and the
editors' `kotoba:suggestion` events itself (`attach_hook/4`).

A stream's place is `%{"doc" => document, "at" => at}`, `at` a node
id (`%{"node" => id}`, a block's or a text's) or a Kotoba selection
(`Kotoba.Issho.Place`): the suggestion goes after its block. An
editor shows one at a time; the others wait their turn. A stream's
label is its `meta`'s `"label"`, else its kind.

Options:

  * `:kinds` - the kinds of stream that are suggestions (all by default).
  * `:display` - `:panel` (the default) or `:inline`, as
    `Kotoba.Live.stream_start/4`.
  * `:pass` - `true` gives your `handle_info/2` the messages of the
    room's other streams; by default they stop here.

With `Kotoba.Issho.tools/0`, an agent writes one with `suggest_text`.
Without Issho, `Kotoba.Live.stream_resume/5` and the Suggestions
guide's recipe do the same with a process of your app.

# `tools`

```elixir
@spec tools() :: [module()]
```

Kotoba's tools for agents (`Issho.Tool`): an agent reads a shared
document, selects text (the people see its selection), types (they see
it written), replaces, deletes, formats, links, adds and moves
blocks, copies and pastes, as a person does in the editor, and its
changes merge with theirs; or it suggests text that the person takes
or leaves (`suggest_text`, see `suggestions/4`). Give them to your
app's model (`Issho.Tools`), or serve them to MCP clients
(`Issho.MCP`), with Issho's room tools:

    tools = Issho.Tools.room() ++ Kotoba.Issho.tools()

A tool names its document by id (`"notebook:7/intro"`); the room is the
part before the `"/"`. Text is quoted, not counted: `select_text` with
the words, then `link_text` with no text links the selection.

An agent moves as a person does, so that the people follow it: its
caret goes to where it works, it selects as a drag does and holds a
moment before it changes the selection, and it types at a person's
pace. Only where it goes is sent; each browser draws the way there.
For changes that appear at once:

    config :kotoba, Kotoba.Issho, pace: 0, travel: false

`pace`: the milliseconds it takes to type a character (25; a long text
is typed in at most 8 seconds). `travel`: whether it goes to where it
works and drags (true).

---

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