# `MishkaCmsBuilder.Mentions`
[🔗](https://github.com/mishka-group/mishka_cms_builder/blob/v0.0.1-alpha.2/lib/mishka_cms_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
`MishkaCmsBuilderWeb.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 `MishkaCmsBuilder.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, the people this document is
about 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. The writer is never
offered: naming yourself notifies nobody (`notify/2`), so the row would only push someone else off
the list.

## Who comes first

The list is cut at 6, so its order decides who can be picked without typing. Alphabetical
alone put whoever's address sorts first at the top of every page's list, whatever the page. So
the people this document involves lead, each group in address order:

  1. who is editing it with the writer right now — `scope[:editing]`, the user ids of the session's
     other editors (`MishkaCmsBuilder.Presence.others/2`);
  2. who has written a note on it (`scope[:section]` and `scope[:document_id]`);
  3. everybody else.

# `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 MishkaCmsBuilder.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 the editor's `settings:register` plugin
(`MishkaCmsBuilder.Plugins.Settings`) 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 MishkaCmsBuilder.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*
