contact messages: unified overlay, corner ✉️ icon for everyone
The admin inbox leaves the short-lived /_m page and joins the visitor panel as one overlay (ContactOverlay shell over paskia's shared blur backdrop, centered dialog, full-screen on mobile): the corner ✉️ icon opens the visitor thread for visitors and the wider sidebar inbox for admins, while {contact:} trigger buttons always open the visitor side so admins can test both. The icon highlights unread state without extra connections: visitors via localStorage seen/last-admin timestamps, admins via the contact_unread count added to the /_api/settings probe, both kept live by the bundle once loaded. MessagesView owns its admin socket only while the overlay is open.
This commit is contained in:
1 parent
9a2435f03a
commit
afa6b967e6
16 files changed
+451
-427
No files matched your search
+38
-24
@@ -1,25 +1,30 @@
|
||||
# Contact messages
|
||||
|
||||
Visitor→admin messaging built into the site: a `{contact: Label}` directive
|
||||
in article Markdown renders a button that opens a chat-style panel; messages
|
||||
are stored in the kanta database and answered from the admin inbox on `/_m`.
|
||||
Visitors are anonymous — a localStorage session token identifies their
|
||||
thread — and everything realtime rides the two existing WebSockets; there is
|
||||
no REST polling anywhere.
|
||||
in article Markdown renders a button that opens the messaging overlay, and
|
||||
the corner ✉️ icon (top right, for everyone) opens it too — the visitor's
|
||||
own thread for visitors, the admin inbox for editors. Messages are stored
|
||||
in the kanta database. Visitors are anonymous — a localStorage session
|
||||
token identifies their thread — and everything realtime rides the two
|
||||
existing WebSockets; there is no REST polling anywhere.
|
||||
|
||||
- `pagerite/messages.py` — the feature's core: the strict message renderer,
|
||||
thread store ops, wire payloads, both socket protocols, the `/_m` inbox
|
||||
page route and the `/_contact/files` upload route.
|
||||
thread store ops, wire payloads, both socket protocols, the admin unread
|
||||
count and the `/_contact/files` upload route.
|
||||
- `pagerite/data.py` — `Data.contact` (`ContactThread`/`ContactMessage`).
|
||||
- `pagerite/tracking.py` — the sockets the protocol rides (see below).
|
||||
- `pagerite/views.py` — the `{contact:}` directive (`_contact_tag`), the
|
||||
`/_m` page render and the on-demand bundles' asset config.
|
||||
- `frontend/src/contact-main.js` + `ContactPanel.vue` — the visitor panel,
|
||||
imported on demand by `pagerite.js` on trigger clicks.
|
||||
- `pagerite/api.py` — `/_api/settings` carries `contact_unread` (the admin
|
||||
icon badge seed — the probe runs on every page load, no polling).
|
||||
- `pagerite/views.py` — the `{contact:}` directive (`_contact_tag`) and the
|
||||
on-demand bundle's asset config.
|
||||
- `frontend/src/contact-main.js` + `ContactOverlay.vue` — the overlay entry
|
||||
and shell (paskia's shared blur backdrop, centered dialog, full-screen on
|
||||
mobile), imported on demand by `pagerite.js` on icon/trigger clicks.
|
||||
- `frontend/src/ContactPanel.vue` — the visitor thread content.
|
||||
- `frontend/src/MessagesView.vue` — the admin inbox content (thread list,
|
||||
replies, read marking); connects its admin-gated socket only while open.
|
||||
- `frontend/src/Composer.vue` + `richtext.js` — the shared composer and its
|
||||
DOM→Markdown serializer.
|
||||
- `frontend/src/messages-main.js` + `MessagesView.vue` — the admin inbox
|
||||
page at `/_m` (own pens-row icon, own socket lifecycle).
|
||||
|
||||
## Trigger
|
||||
|
||||
@@ -38,8 +43,11 @@ The token is `crypto.randomUUID()` in localStorage
|
||||
(`pagerite-contact-token`), generated on first use; the server stores only
|
||||
its blake3 hash as the thread key. Pages are shared/cached renders, so the
|
||||
thread UI is entirely client-side. A per-browser "seen" timestamp
|
||||
(`pagerite-contact-seen`) drives the unread dot on trigger buttons when an
|
||||
admin reply arrives while the panel is closed.
|
||||
(`pagerite-contact-seen`) and the newest admin reply's timestamp
|
||||
(`pagerite-contact-lastadmin`) drive the unread dot on trigger buttons and
|
||||
the corner ✉️ icon when an admin reply arrives while the panel is closed —
|
||||
including on a fresh page load before any socket activity (pagerite.js
|
||||
reads the pair directly; the bundle keeps it live once loaded).
|
||||
|
||||
The panel rides the public `/_ws` activity socket via a bridge in
|
||||
`pagerite.js` (`register`/`send`, sharing its queue and reconnect pacing —
|
||||
@@ -78,18 +86,24 @@ solves and retries once. Challenges are a per-connection local in the
|
||||
|
||||
## Admin side
|
||||
|
||||
Admins watch the inbox on its own page, **`/_m`** (✉️ icon in the pens
|
||||
row), served by the existing `/_api/ws/analytics` socket (admin-gated like
|
||||
all of `/_api`). Analytics payloads keep their type-less snapshot shape;
|
||||
messaging frames always carry a `type` field, and that is how each client
|
||||
picks out its own frames (the analytics viewer ignores them). On connect
|
||||
the server sends `{type: "threads", threads: [...]}` (100 most
|
||||
recently active); new messages and replies push single-thread updates.
|
||||
Admin→server frames: `{type: "reply", id, text}` and `{type: "read", id}`.
|
||||
Editors open the inbox from the same ✉️ corner icon (the trigger buttons
|
||||
always open the visitor side, so both can be tested side by side): the
|
||||
same overlay, wider to fit the thread list, served by the existing
|
||||
`/_api/ws/analytics` socket (admin-gated like all of `/_api`) which
|
||||
`MessagesView` connects only while the overlay is open. Analytics payloads
|
||||
keep their type-less snapshot shape; messaging frames always carry a
|
||||
`type` field, and that is how each client picks out its own frames (the
|
||||
analytics viewer ignores them). On connect the server sends
|
||||
`{type: "threads", threads: [...]}` (100 most recently active); new
|
||||
messages and replies push single-thread updates. Admin→server frames:
|
||||
`{type: "reply", id, text}` and `{type: "read", id}`.
|
||||
Replies store the SSO `remote-user` name for audit but are labeled with
|
||||
the currently configured **site name** in the wire payload (substituted at
|
||||
read time) and mark the thread read; `unread` on a thread means its last
|
||||
message is the visitor's and newer than the admin's last read.
|
||||
message is the visitor's and newer than the admin's last read. The icon's
|
||||
badge (a count of unanswered threads) is seeded by the `/_api/settings`
|
||||
probe's `contact_unread` on every page load and kept live by the inbox
|
||||
while it is open.
|
||||
|
||||
## Message format and the XSS model
|
||||
|
||||
|
||||
Reference in new issue
Block a user