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 <img>). 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 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-07 10:24:53 -04:00
1 parent 610935ac03
commit 9ca726bc99
3 files changed
+27 -2

No files matched your search

+8
View File
@@ -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;
+16 -2
View File
@@ -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 <img>; 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 <img> 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);
}
+3
View File
@@ -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);
});