From 9ca726bc99e9e024eecce731f748d825cc422036 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:24:53 -0400 Subject: [PATCH] Admin star on DM message bubbles from admin peers Extend the admin star to per-message avatars: incoming bubbles from an admin peer now show the gold star. MessageBubble gains an opt-in adminId and wraps its avatar img in a non-clipped shell only when the author is an admin (non-admin bubbles keep the bare ). MessageThread passes the peer pubkey for incoming bubbles; outgoing bubbles are the current user, whose admin status is already shown on the profile button. Co-Authored-By: Claude Opus 4.8 --- css/tailwind.source.css | 8 ++++++++ js/ui/dm/MessageBubble.js | 18 ++++++++++++++++-- js/ui/dm/MessageThread.js | 3 +++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/css/tailwind.source.css b/css/tailwind.source.css index 45f2c793..4f34dda1 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -5896,6 +5896,14 @@ border: var(--border-width-hairline) solid var(--border-subtle); } + /* Non-clipped relative shell used only when a message avatar needs an admin + star; the plain .dm-message-avatar img has no wrapper otherwise. */ + .dm-message-avatar-wrap { + position: relative; + display: inline-flex; + flex-shrink: 0; + } + .dm-message-bubble { max-width: 75%; display: flex; diff --git a/js/ui/dm/MessageBubble.js b/js/ui/dm/MessageBubble.js index 7276a796..53155bbf 100644 --- a/js/ui/dm/MessageBubble.js +++ b/js/ui/dm/MessageBubble.js @@ -1,3 +1,5 @@ +import { applyAdminStar, isAdminActor } from "../adminBadge.js"; + function createElement(doc, tag, className, text) { const element = doc.createElement(tag); if (className) { @@ -14,6 +16,7 @@ export function MessageBubble({ message = {}, variant = "incoming", avatarSrc = "", + adminId = "", } = {}) { if (!doc) { throw new Error("MessageBubble requires a document reference."); @@ -32,6 +35,17 @@ export function MessageBubble({ avatar.src = avatarSrc || "assets/svg/default-profile.svg"; avatar.alt = variant === "outgoing" ? "Me" : "Sender"; + // The message avatar is a bare circular ; when its author is an admin, + // wrap it in a non-clipped relative shell so the star can sit in the corner. + // Non-admin bubbles keep the plain so existing layout is untouched. + let avatarNode = avatar; + if (adminId && isAdminActor(adminId)) { + const wrap = createElement(doc, "span", "dm-message-avatar-wrap"); + wrap.appendChild(avatar); + applyAdminStar(wrap, adminId, { doc }); + avatarNode = wrap; + } + const bubble = createElement(doc, "div", "dm-message-bubble"); bubble.classList.add(`dm-message-bubble--${variant}`); bubble.dataset.status = status; @@ -67,9 +81,9 @@ export function MessageBubble({ if (variant === "outgoing") { row.appendChild(bubble); - row.appendChild(avatar); + row.appendChild(avatarNode); } else { - row.appendChild(avatar); + row.appendChild(avatarNode); row.appendChild(bubble); } diff --git a/js/ui/dm/MessageThread.js b/js/ui/dm/MessageThread.js index e03f734d..e1fdd3de 100644 --- a/js/ui/dm/MessageThread.js +++ b/js/ui/dm/MessageThread.js @@ -137,6 +137,9 @@ export function MessageThread({ message, variant: isOutgoing ? "outgoing" : "incoming", avatarSrc: isOutgoing ? currentUserAvatarUrl : (contact.avatarSrc || ""), + // Star incoming bubbles from an admin peer; the current user's own admin + // status is already shown on the profile button. + adminId: isOutgoing ? "" : (contact.pubkey || contact.id || ""), }); timeline.appendChild(bubble); });