Skip to content

Commit 6237576

Browse files
authored
Nachrichten: ein Feld, das Mitglieder und andere Netzwerke zugleich findet (#2254)
Die Nachrichtenliste nahm bisher nur eine Fediverse-Adresse entgegen, und Mitglieder erreichte man ausschließlich über deren Profil. Das sind zwei Wege für dieselbe Frage. Über der Liste steht jetzt ein Feld, das beide Welten durchsucht: Mitglieder nach Vorname, Nachname, beidem oder Handle, dazu die Konten anderer Netzwerke, die diese Installation ohnehin schon hält. Beides kostet nichts nach draußen. Eine Adresse, die hier niemand kennt, bekommt eine eigene Zeile "Diese Adresse nachschlagen" — das ist der einzige Teil, der das Haus verlässt, und deshalb ein Druck und nichts, was beim Tippen passiert. Die Namenssuche zieht in `Accounts.search_people/3`, wo sie hingehört; der Composer hatte seine eigene unter `Posts.search_users/3`, weil er sie zuerst brauchte. Neu ist dabei die umgekehrte Reihenfolge: "Petersen Jan" findet Jan Petersen, weil im deutschen Büro beide Reihenfolgen selbstverständlich sind. `Fediverse.search_accounts/2` ist die Typeahead-Hälfte neben der exakten Adresssuche. Was das Feld kann, steht als Satz darunter, nicht im Platzhalter: die zweite Hälfte ist nicht erratbar, und ohne Fediverse-Teilnahme gibt es sie nicht — dann sucht das Feld nur auf vutuv und sagt, wo der Schalter sitzt. Das `change`-Attribut am geteilten Adressformular ist wieder weg, es hatte mit diesem Umbau keinen Aufrufer mehr. Diesen Text hat ein KI-Agent in meinem Namen geschrieben. Ich weiß, dass das problematisch ist.
1 parent 30aa4fb commit 6237576

10 files changed

Lines changed: 1016 additions & 530 deletions

File tree

‎docs/architecture/messages.md‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,29 @@ the hourly outbound budget with public replies.
212212

213213
### Starting one, and finding the way back
214214

215-
Every other conversation here starts from somebody's page. An account nobody
216-
on this installation has ever heard of has no page, so `/messages` carries a
217-
**New message to another network** box: an address in, that account's
218-
conversation out. Resolving costs an outbound request and a slot of the
219-
member's hourly budget, so it is a submit they make on purpose — a native
220-
`<details>` (`data-keep-open`, or a ticking badge folds it shut over a
221-
half-typed address) around the shared `<.address_form>`, whose `change` event
222-
this page has to name for itself because `typing` already means the typing
223-
indicator here. Without Fediverse participation the box explains where the
224-
switch is instead of taking an address it could not send.
215+
Every other conversation here starts from somebody's page — a profile, an
216+
account page, a private answer under a post. `/messages` therefore carries a
217+
**finder** above the list, and it asks the member's real question ("who?")
218+
rather than ours ("on which server?"): one field, always on screen, that
219+
searches **both worlds at once**.
220+
221+
* **Members**, by first name, last name, both in either order, or a handle
222+
(`Vutuv.Accounts.search_people/3`, which is where the composer's old
223+
`Posts.search_users/3` question belongs; `SearchText.name_ilike/3` covers
224+
the pair, and the reversed pair is asked for separately, because in a
225+
German office "Petersen Jan" is as likely as "Jan Petersen").
226+
* **Accounts on other networks this installation already holds**
227+
(`Fediverse.search_accounts/2`) — rows we have because somebody follows
228+
them, answered them or was answered by them. No request to anybody.
229+
* **An address nobody here has met**, as one offered row: "Look this address
230+
up". That is the only part that leaves the building, so it is a press of
231+
its own, never something the page does while somebody types.
232+
233+
Two characters before anything is searched, `phx-debounce="250"` on the field,
234+
and both halves are named in one line under it, because neither is guessable
235+
from a placeholder. Without Fediverse participation the finder says so and
236+
searches vutuv alone — a field that took an address it could not send would
237+
have wasted the typing.
225238

226239
The two views link to each other **by anchor**, not merely to each other's
227240
page: `Fediverse.reply_anchor/1` for a note and `private_reply_anchor/1` for a

‎lib/vutuv/accounts.ex‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2790,6 +2790,54 @@ defmodule Vutuv.Accounts do
27902790
defp filter_flag(query, "spam"), do: where(query, [u], u.moderation_reason == "spam")
27912791
defp filter_flag(query, _all), do: query
27922792

2793+
@doc """
2794+
Person typeahead: activated members matching `term`, `me` excluded, ordered
2795+
by name. Returns `[]` below two characters, so one keystroke never runs a
2796+
`%like%` over the whole table.
2797+
2798+
Matches a **first name, a last name, both in either order, or a handle** —
2799+
`SearchText.name_ilike/3` covers "Jan", "Petersen" and "Jan Petersen", and
2800+
the reversed pair is asked for separately because somebody looking for a
2801+
colleague types the name the way they hold it in their head, which in a
2802+
German office is as often "Petersen Jan".
2803+
2804+
It lives here rather than in the context that first needed it (`Vutuv.Posts`
2805+
grew one for the composer's "Hide from…" sheet): finding a member by name is
2806+
an Accounts question, and the messages page asks exactly the same one.
2807+
"""
2808+
def search_people(%User{id: me_id}, term, limit \\ 8) when is_binary(term) do
2809+
term = String.trim(term)
2810+
2811+
if String.length(term) < 2 do
2812+
[]
2813+
else
2814+
like = contains(term)
2815+
reversed = reversed_name(term)
2816+
2817+
Repo.all(
2818+
from(u in User,
2819+
where: u.id != ^me_id,
2820+
where: account_confirmed_row(u),
2821+
where:
2822+
name_ilike(u.first_name, u.last_name, ^like) or ilike(u.username, ^like) or
2823+
name_ilike(u.first_name, u.last_name, ^reversed),
2824+
order_by: [u.first_name, u.last_name],
2825+
limit: ^limit
2826+
)
2827+
)
2828+
end
2829+
end
2830+
2831+
# "Petersen Jan" as "Jan Petersen", so the pair matches whichever way round
2832+
# it was typed. Anything that is not exactly two words searches for itself
2833+
# twice, which costs nothing and keeps the query one shape.
2834+
defp reversed_name(term) do
2835+
case String.split(term, ~r/\s+/, trim: true) do
2836+
[first, last] -> contains(last <> " " <> first)
2837+
_other -> contains(term)
2838+
end
2839+
end
2840+
27932841
defp search_members(query, nil), do: query
27942842

27952843
defp search_members(query, term) do

‎lib/vutuv/fediverse.ex‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,43 @@ defmodule Vutuv.Fediverse do
14821482
end
14831483
end
14841484

1485+
@doc """
1486+
Accounts on other networks this installation already holds, matching `term`
1487+
by display name, handle or address — the typeahead half of "write to
1488+
somebody out there", beside `remote_account_by_address/1`'s exact answer.
1489+
1490+
Nothing is asked of anybody: these are rows we have because somebody here
1491+
follows them, answered them or was answered by them. An address nobody here
1492+
has met is not found by this and is **resolved** instead, which costs a
1493+
request and a slot of the member's hourly budget and therefore stays a
1494+
deliberate act (`resolve_remote_account/2`).
1495+
1496+
Returns `[]` below two characters, like the member typeahead beside it.
1497+
"""
1498+
def search_accounts(term, limit \\ 6) when is_binary(term) do
1499+
term = String.trim(term)
1500+
1501+
if String.length(term) < 2 do
1502+
[]
1503+
else
1504+
base = from(a in RemoteAccount, order_by: [asc: a.handle, asc: a.id], limit: ^limit)
1505+
1506+
case browse_search_parts(term) do
1507+
{:handle_and_host, name, host} ->
1508+
Repo.all(where(base, [a], ilike(a.handle, ^name) and ilike(a.host, ^host)))
1509+
1510+
{:anywhere, like} ->
1511+
Repo.all(
1512+
where(
1513+
base,
1514+
[a],
1515+
ilike(a.name, ^like) or ilike(a.handle, ^like) or ilike(a.actor_uri, ^like)
1516+
)
1517+
)
1518+
end
1519+
end
1520+
end
1521+
14851522
@doc """
14861523
The stored remote accounts these actor URIs name, keyed by URI — one query
14871524
for a whole page. A URI nobody here stored is simply absent, so callers

‎lib/vutuv_web/components/fediverse_components.ex‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,6 @@ defmodule VutuvWeb.FediverseComponents do
6363
attr(:error, :any, default: nil, doc: "the reason the last submit came back with, or nil")
6464
attr(:event, :string, required: true, doc: "the phx-submit event the host handles")
6565

66-
attr(:change, :string,
67-
default: "typing",
68-
doc:
69-
"the phx-change event; a host that already answers `typing` for something else " <>
70-
"(the messages page's typing indicator) passes its own name instead"
71-
)
72-
7366
attr(:submit, :string, required: true, doc: "what the button says")
7467
attr(:variant, :string, default: "primary")
7568

@@ -96,7 +89,7 @@ defmodule VutuvWeb.FediverseComponents do
9689
<form
9790
id={"#{@id}-form"}
9891
phx-submit={@event}
99-
phx-change={@change}
92+
phx-change="typing"
10093
class={["flex flex-wrap items-end gap-3", @class]}
10194
>
10295
<div class="min-w-56 grow">

0 commit comments

Comments
 (0)