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

The rows a page of the blog is made of, read once for the request and handed to everything that
draws them: the head (`MishkaBlog.Plugins.PageSeo`), the frame (`MishkaBlog.Plugins.PageFrame`)
and the page's blocks.

## Read once, for everything that draws them

A post's page was drawn by the head, the frame, `blog-post-detail`, `blog-post-comments` and
`blog-related-posts`, and each of them read the post the `:slug` capture names on its own; a
category's, a tag's, a blogger's and a team's the same, and the blog's front page read its
featured posts twice and counted its posts twice. `read/1` reads them once, in `handle_params/3`,
through the `runtime:page:source` event (`MishkaBlog.Plugins.PageSource`,
`MishkaCmsCore.Runtime.PageSource`), and every reader is handed what it read: the two plugins as
`source`, the blocks as `page_source`.

What is read, in ONE read of the record and what that read loads with it:

  * a post (`"blog:post"`) — with its text as the site draws it, what it opens with, its readers'
    rating, how many approved comments it has, the categories it is filed under, its tags, its
    authors with their teams, and its other editions;
  * a category (`"blog:category"`) and a tag (`"blog:tag"`) — with what it opens with, how many of
    its posts its own page lists (`listed_post_count`), and its other editions; then the page of
    those posts its list draws first, with what each card is drawn from;
  * a blogger (`"blog:author"`) — with their teams; a team (`"blog:team"`) — with its members and
    how many posts each has;
  * the blog's front page (`"blog:home"`, the Posts place, `MishkaBlog.Provision.places/0`) — the
    featured posts its strip draws, which its list leaves out, and the page of the other posts its
    list draws first, each with what its card is drawn from, and how many posts there are in its
    language, which its search box says.

The page of a list is the page the address names (`?page=`), of `MishkaBlog.Provision.list_page/0`
posts, newest first, as the provisioned list reads it; nothing is read for it while the reader's
search (`?q=`), or on the front page a tag (`?tag=`), narrows the list. The strip's posts and the
list's are drawn from what one read loads for both of them.

A post's, a category's and a tag's other editions are the rows of its group of translations,
joined by `language_group_id` (the master's own id, which the master itself may not carry), each
with its language, its slug and its title or name, and nothing else: what the frame offers and
the head's `hreflang` links name. A blogger and a team are one row in every language, and have
none.

## As the visitor

Every row is read as the visitor, `current_user`, through the public actions and the policies
the blocks read with (`MishkaBlog.Resources.Preparations.OpenToActor`, `ReaderLocks`), and every
relationship and aggregate loaded with it is read through the public read of what it reaches,
as the same visitor: a draft, a post locked to permissions they do not hold by its own lock or by
a category it is filed under, a locked category, a locked tag, and a record whose page on the
site is closed to them are not among them, and are not counted.

A record the visitor's read does not find is `nil` here. Its lock, which the head hands
`MishkaCmsWeb.RuntimeLive` to refuse them with, is read by the head alone, as the system, and
nothing else of that read reaches the visitor.

## 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, the kind and the slug 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;
anything else it reads for itself, so a block an author 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 (`MishkaBlog.KitTest`), so it reads a source by
its shape, which is therefore part of what the kit relies on:

  * `rows_of` — `"mishka_blog"`, which a block asks before anything else;
  * `site_id`, `visitor`, `language`, `kind`, `slug` — whom and for what it was read;
  * `record` — the row, every field a block draws of it and the relationships above loaded;
  * `featured` — on the front page, `%{limit: limit, rows: posts}`, the newest featured posts;
  * `total` — on the front page, how many posts the visitor may open in its language;
  * `listed` — on the front page, a category's and a tag's, `%{per_page: n, page: p, rows: posts}`,
    the page of posts the list draws, newest first: of the category or the tag, and on the front
    page of the posts other than `featured`'s.

## Another page of the same record

A live patch that stays on the same record — another page of a list — reads nothing of it again:
the event hands back the source the address before read for the same visitor (`held`).

# `t`

```elixir
@type t() :: %MishkaBlog.PageSource{
  featured: %{limit: pos_integer(), rows: [struct()]} | nil,
  kind: String.t(),
  language: String.t() | nil,
  listed:
    %{per_page: pos_integer(), page: pos_integer(), rows: [struct()]} | nil,
  record: struct() | nil,
  rows_of: String.t(),
  site_id: String.t(),
  slug: String.t() | nil,
  total: non_neg_integer() | nil,
  visitor: term()
}
```

# `for_page?`

```elixir
@spec for_page?(t(), map()) :: boolean()
```

Whether `source` was read for the page `request` asks for: the same site, language, kind and
slug. The head is handed no visitor to ask the rest of, and takes the source core hands it for
the request when this holds.

# `of`

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

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

# `read`

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

The rows of the blog page `request` asks for, read as its `current_user`; `nil` for a page that is
not one of the blog's, or that names no row.

---

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