# `MishkaDocument.PageSource`
[🔗](https://github.com/mishka-group/mishka_document/blob/v0.0.1/lib/mishka_document/page_source.ex#L5)

The rows a documentation page is made of: the document its address names, its published
versions, the version a reader is shown and that version's approved pages, the page the address
names with its text, and that page's notes.

## Read once, for everything that draws them

A document's front page and each of its pages are drawn by the head
(`MishkaDocument.Plugins.PageSeo`), the frame (`MishkaDocument.Plugins.PageFrame`) and the page's
own blocks, `doc-sidebar`, `doc-section`, `doc-toc` and the bar's `doc-switcher`. Each of them
read the same document, the same versions and the same pages on its own. `read/1` reads them once,
in `handle_params/3`, through the `runtime:page:source` event (`MishkaDocument.Plugins.PageSource`,
`MishkaCmsCore.Runtime.PageSource`), and every one of them is handed what it read: the two plugins
as `source`, the blocks as `page_source`.

Three reads, each bringing what is joined to it: the document, with its versions and its other
editions and theirs (relationships loaded in the same read); the pages of all of those versions
the page's readers look in, in one read; and the page the address names, with its text and its
notes.

A request is the map core hands any of the three plugins: the `site_id`, the page and its `path`,
the `params` (the path's captures and the query), the `language` of the response, the
`current_user` and, on the event, `held`.

## As the visitor

Every row is read as the visitor, `current_user`, through the actions and policies the blocks read
with (`MishkaDocument.Resources.Preparations.OpenToActor`): `:public_list` of the document, of its
versions, of the pages of the versions shown, and of the page and its notes. What the visitor may
not open is not among them: a locked document, a locked page or one under a locked page, a page
whose own page on the site is closed to them, a draft, a version that is not published.

THE SYSTEM READS ONE ROW, AND ONLY WHEN THE VISITOR'S READ FOUND NO DOCUMENT: the document and its
versions, as the blocks have always read them (`open?` says which read found it). Two things need
it, and neither shows the visitor anything their own lock does not let them see:

  * the lock. A document the visitor may not open is no row at all when read as them, and the head
    would hand `MishkaCmsWeb.RuntimeLive` no permissions to refuse them with. The head takes the
    lock alone from it, and the address answers 404;
  * a page whose own lock opens for the visitor inside a document whose lock does not. Its pages
    are still read as the visitor, so only a page they may open is found, and the document is drawn
    around that page as the blocks draw it.

## Keyed to its visitor, and to what it was read for

A source names the site, the visitor (`MishkaCmsCore.Runtime.PageSource.visitor_id/1`), the
language and the document it was read for. A block takes nothing from one read for another site or
another visitor, and takes a row only for exactly the read it would make itself: the same document
named the same way, the versions of that document, the pages of that version, that page's text and
notes. Anything else it reads for itself, so a block an admin pinned to another document, or placed
on a page of their own, reads as it always did.

## What a block reads of it

A block's code calls nothing of this extension's (`MishkaDocument.KitTest`), so it reads a source
by its shape, which is therefore part of what the kit relies on:

  * `rows_of` — `"mishka_document"`, which a block asks before anything else;
  * `site_id`, `visitor`, `language` — whom and for what it was read;
  * `tried` — the documents the address named, `{:path, address}` or `{:slug, slug}`, in the order
    they were looked for, and `found_by` the one that answered, or `nil`;
  * `workspace` — that document; `versions` — its published versions, newest first;
  * `version` — the one shown; `pages` — its approved pages, in their order;
    `pages_by_version` — the approved pages of every version of the document, and on a page of
    it of the version each other edition is on, by the version's id, each in their order;
  * `body` — the page the address names, every field, with `rendered_content`; `notes` — its
    approved notes, in their order, with their `content`.

## Another page of the same document

A live patch to another page, or to another version, reads only what changed: the event hands back
the source the address before read for the same visitor (`held`), and the document, its versions
and, in the same version, its pages are taken from it.

# `named`

```elixir
@type named() :: {:path, String.t()} | {:slug, String.t()}
```

# `t`

```elixir
@type t() :: %MishkaDocument.PageSource{
  body: struct() | nil,
  editions: [struct()],
  found_by: named() | nil,
  language: String.t() | nil,
  lock: [String.t()],
  notes: [struct()],
  open?: boolean(),
  page: struct() | nil,
  pages: [struct()],
  pages_by_version: %{optional(String.t()) =&gt; [struct()]},
  rows_of: String.t(),
  site_id: String.t(),
  tried: [named()],
  version: struct() | nil,
  versions: [struct()],
  visitor: term(),
  workspace: struct() | nil
}
```

# `of`

```elixir
@spec of(map()) :: t() | nil
```

The source the page read for `request`, when it was handed one for this site and visitor; else
the rows read here, once (`read/1`). For the plugins on the frame and the head, which are handed
core's `source`.

# `pages`

```elixir
@spec pages() :: Ash.Query.t()
```

A version's pages, without their text, for `Ash.load/3` or a read of the pages of several.

# `read`

```elixir
@spec read(map()) :: t() | nil
```

The rows of the documentation page `request` asks for, read as its `current_user`; `nil` for a
request that names no document, on no site. A document that is not there, or not shown, is a
source with no `workspace`: every reader handed it answers as its own read of it would.

# `shown`

```elixir
@spec shown([struct()], String.t() | nil, String.t()) :: {struct(), [struct()]} | nil
```

The version a reader is shown among a document's published `versions`
(`MishkaDocument.Address.version_shown/2`) and its approved pages as a tree — what a page's
address is followed through (`MishkaDocument.Address.page_at/2`), read for that version alone,
as the system, as `workspace/3` reads; `nil` when there is none.

# `versions`

```elixir
@spec versions() :: Ash.Query.t()
```

A document's published versions, without their pages, for `workspace/3` to load.

# `workspace`

```elixir
@spec workspace(map(), [atom()], term()) :: struct() | nil
```

The document whose own address the page is at, else the one with no address of its own the
`workspace` capture names, in the page's language, on the page's site; `nil` when the request
names none or none is shown. Only `fields` of it are read, and `loads` loaded.

READ AS THE SYSTEM, for the head of a caller that is no visitor: an editor's preview and a share
card (`MishkaDocument.Plugins.PageSeo`). A page a visitor reads is answered from `read/1`.

A slug names only a document with no address of its own: one that has an address is read there
and nowhere else, so `/docs/<its slug>` names nothing.

---

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