# `MishkaCmsCore.Runtime.PageBuilder.Mentions`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/page_builder/mentions.ex#L5)

Naming somebody in a note: `@who` in the body, and the notification that follows it.

## The handle is the whole address

`MishkaCmsCore.Accounts.User` has no display name — `email` is the only thing a person is called —
so `@sara@example.com` is how Sara is named. Nothing stores a nickname; the handle is derived on
every read and cannot drift from the account.

The whole address rather than its local part, because a local part is not a name: this CMS is
multi-site, and `master@one.test` and `master@two.test` are two different people who would both
answer to `@master`. An address is the only handle that names exactly one account, and a mention
that reaches the wrong person is worse than a mention that is long.

A bare local part is still UNDERSTOOD — notes written before this, and anyone who types `@sara` by
hand, still reach every Sara the writer can see. It is simply not what the picker writes.

## Who can be named, and who the writer is

A TENANT may name their own site's people and nobody else. Not another site's — that would leak
one customer's user list into another's editor — and not the masters, whose addresses belong to
the platform rather than to any customer. The activity feed draws the same line, and
`MishkaCmsCoreWeb.Templates.ReviewComments` draws it again on the byline and on a mention, where a
master reads as "Master" (`master_mentions/2`).

A MASTER may name anybody the document can reach: the site's people and the other masters. There
is nobody a master may not see, so there is nothing to hide from them.

The document matters too, not just the writer: a note on something SHARED has no site of its own,
so a master's candidates there are the other masters.

## Where each piece runs

`segments/1` and `handles/1` are pure, so the card can highlight a mention and the notifier can
find one without either calling the other. `candidates/2` is the autocomplete's one query.
`notify/2` is called from `MishkaCmsCore.Runtime.Resources.Changes.NotifyMentions`, after the row
exists — a mention that is delivered before the note is saved points at nothing.

# `segment`

```elixir
@type segment() :: {:text, String.t()} | {:mention, String.t()}
```

A body cut into the parts a card draws: plain runs and the people named in them.

# `candidates`

```elixir
@spec candidates(map(), String.t() | nil) :: [
  %{id: String.t(), handle: String.t(), local: String.t(), domain: String.t()}
]
```

Who `query` could mean, for the autocomplete — at most 6, nearest match first.

A blank query offers the first few people rather than nothing, so pressing `@` is useful before
typing anything. Matching is a prefix of the whole address, so `@mas` narrows to the masters and
`@master@two` narrows those to one site — which is the point of naming people by address.

`scope[:actor]` decides how far the list reaches; see "Who can be named" above.

# `handles`

```elixir
@spec handles(String.t() | nil) :: [String.t()]
```

The handles a body names, downcased and without repeats.

Case is dropped because an address is matched case-insensitively everywhere else in this codebase
— `Sara@x.test` and `sara@x.test` are one person, and being notified twice for writing both is not
a feature.

# `mask_masters`

```elixir
@spec mask_masters(String.t() | nil, MapSet.t()) :: String.t() | nil
```

`body` with every mention in `masked` written as `@Master`. Everything else is kept as written.

    iex> alias MishkaCmsCore.Runtime.PageBuilder.Mentions
    iex> Mentions.mask_masters("ping @Boss@platform.test, thanks", MapSet.new(["boss@platform.test"]))
    "ping @Master, thanks"

# `master_handles`

```elixir
@spec master_handles([String.t() | nil], String.t() | nil) :: MapSet.t()
```

The handles in `bodies` that name a master and nobody on `site_id`, downcased.

A whole address names a master when a master holds it. A bare local part does when a master's
address starts with it and no account of `site_id` does. One read for every body; with no site,
nothing.

# `master_mentions`

```elixir
@spec master_mentions([map()], map() | nil) :: [[String.t()]]
```

For each of `notes`, the handles its body names that `reader` may not see: a master's, when
`reader` is a site user. A master reads every handle. Backs `PageComment.master_mentions`.

# `notification_type`

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

The notification type, and the key its per-site and per-user toggles hang off.

Public because `MishkaCmsCore.Application` registers the two Settings under it and
`MishkaCmsCore.Notify.Delivery` checks them by the same name.

# `notify`

```elixir
@spec notify(map(), map()) :: :ok
```

Tell the people a note names that it names them.

Nothing is delivered to the author: being notified of your own writing is noise, and it is the one
mention that is certain to be read already. Nothing is delivered for a handle that matches nobody
either — a typo is not an audience.

Returns `:ok` whatever happens. A note that saved is saved; a notification centre that is down
must not turn that into a failed write in front of the person who typed it.

# `segments`

```elixir
@spec segments(String.t() | nil) :: [segment()]
```

A note's body, cut into the runs a card draws.

Splitting on whitespace rather than scanning for `@` is what keeps a quoted address out of it:
`write to sara@example.com` is one chunk that does not START with `@`, so it stays text, while
`@sara@example.com` is a chunk that does. Trailing punctuation rides along as text, so
`@sara@example.com,` still names her.

    iex> alias MishkaCmsCore.Runtime.PageBuilder.Mentions
    iex> Mentions.segments("ping @sara@example.com, thanks")
    [{:text, "ping "}, {:mention, "sara@example.com"}, {:text, ", thanks"}]

---

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