# `MishkaDocumentWeb.AdminActions`
[🔗](https://github.com/mishka-group/mishka_document/blob/v0.0.1/lib/mishka_document_web/admin_actions.ex#L5)

The buttons plugins add to a document's admin screen — the Sections screen of a workspace,
`/admin/dashboard/document/sections/:workspace_version_id` — through the
`document:workspace:admin_actions` mishka_installer event. Each button opens the plugin's own
LiveComponent in the screen's modal, one at a time.

## The event

The screen fires it through `actions/2`, which calls `MishkaInstaller.Event.Hook.call/3`, once it
has loaded its document and again when another version is chosen — only for a reader who may
manage that workspace (`admin:dashboard:document:workspace`); anyone else is shown no button and
no plugin is asked. Each plugin is handed:

    %{
      site_id: "…",
      workspace: %{id: "…", slug: "chelekom", path: "/chelekom/docs", name: "Chelekom"},
      version: %{id: "…", version_number: "0.0.9"},
      actor: %MishkaCmsCore.Accounts.User{},
      actions: []
    }

`version` is `nil` for a workspace with no version, and `path` is `nil` for a document read at
its default address. The plugins are asked in the screen's process, so a plugin answers from what
it has at hand and leaves slow work to its component. A plugin appends its actions to `actions`
and replies with the map:

    use MishkaInstaller.Event.Hook, event: "document:workspace:admin_actions"

    def call(%{workspace: %{slug: "handbook"}, actions: actions} = state),
      do: {:reply, %{state | actions: actions ++ [action()]}}

    def call(state), do: {:reply, state}

## An action

    %{
      id: "handbook-export",
      label: gettext("Export"),
      icon: "hero-arrow-down-tray",
      component: MyPlugin.ExportPanel,
      assigns: %{format: :pdf}
    }

  * `:id` — lower-case letters, digits, `-` and `_`, starting with a letter or a digit; the
    first action with an id is kept.
  * `:label` — the button's words, already in the reader's language.
  * `:icon` — a Heroicon class name (`hero-…`), or `nil`.
  * `:component` — a `Phoenix.LiveComponent` module.
  * `:assigns` — a map with atom keys, handed to the component.

An action of any other shape is left out. An event that cannot run shows no button.

## The component

Rendered inside the modal, which the screen opens only for a reader who may manage the workspace,
with the action's `:assigns` and these, which it cannot override:
`:id` (`component_id/1`), `:site_id`, `:workspace`, `:version` and `:current_user`. While its
modal is open, each message the screen does not handle itself is handed to it as
`update(%{info: message}, socket)`: a component that subscribes to a PubSub topic hears it
there. Closing the modal removes it.

# `action`

```elixir
@type action() :: %{
  id: String.t(),
  label: String.t(),
  icon: String.t() | nil,
  component: module(),
  assigns: map()
}
```

One button on the screen and the component its modal draws.

# `context`

```elixir
@type context() :: %{
  site_id: String.t() | nil,
  workspace: %{
    id: String.t(),
    slug: String.t() | nil,
    path: String.t() | nil,
    name: String.t() | nil
  },
  version: %{id: String.t(), version_number: String.t() | nil} | nil
}
```

What the screen tells a plugin, and its component, about the document on screen.

# `actions`

```elixir
@spec actions(context(), map() | nil) :: [action()]
```

The actions the plugins add for `context` (`context/2`) and `actor`, in the order the plugins
answered; `[]` when the event cannot run.

# `assigns`

```elixir
@spec assigns(action(), context(), map()) :: map()
```

The assigns `action`'s component is rendered with, besides its module and `component_id/1`: the
action's own, then the screen's `context` and `current_user` over them.

# `component_id`

```elixir
@spec component_id(action()) :: String.t()
```

The id the screen renders `action`'s component under, and sends its updates to.

# `context`

```elixir
@spec context(map(), map() | nil) :: context()
```

What the screen tells a plugin about `workspace` and its `version` (`nil` for none): its site,
and the fields of each named in the moduledoc.

# `event`

```elixir
@spec event() :: String.t()
```

The event's name.

---

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