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.
Summary
Functions
Who query could mean, for the autocomplete — at most 6, nearest match first.
The handles a body names, downcased and without repeats.
body with every mention in masked written as @Master. Everything else is kept as written.
The handles in bodies that name a master and nobody on site_id, downcased.
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.
The notification type, and the key its per-site and per-user toggles hang off.
Tell the people a note names that it names them.
A note's body, cut into the runs a card draws.
Types
Functions
@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.
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.
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"
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.
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.
@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.
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.
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"}]