Files
bitvid/js/ui/dm/MessageBubble.js
T
thePR0M3TH3ANandClaude Opus 4.8 37edbd18d2 Admin ring + star across avatars (identity, switcher, cards, player, DMs)
Add a reusable decorateAdminAvatar(avatarEl, id) that applies BOTH the gold
ring (box-shadow, not clipped by the avatar's overflow) and the corner star
(hung off a non-clipped .admin-avatar-wrap it ensures around the avatar).
Idempotent; works on attached or freshly-built detached avatars.

Wired onto:
- profile modal "Acting as" identity pill
- account-switcher saved-profile avatars
- video card creator avatars
- video player creator avatar
- DM conversation list + message bubbles (upgraded from star-only to ring+star)

adminBadge.js: split the star decision from its DOM (setAdminStar) so the
ring and star share one isAdminActor check; added ADMIN_RING_CLASS /
ADMIN_WRAP_CLASS. tests: decorateAdminAvatar (wrap/ring/star, idempotency,
non-admin, detached).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-07 14:24:16 -04:00

89 lines
2.4 KiB
JavaScript

import { decorateAdminAvatar, isAdminActor } from "../adminBadge.js";
function createElement(doc, tag, className, text) {
const element = doc.createElement(tag);
if (className) {
element.className = className;
}
if (typeof text === "string") {
element.textContent = text;
}
return element;
}
export function MessageBubble({
document: doc,
message = {},
variant = "incoming",
avatarSrc = "",
adminId = "",
} = {}) {
if (!doc) {
throw new Error("MessageBubble requires a document reference.");
}
const {
body = "",
timestamp = "",
status = "sent",
} = message;
const row = createElement(doc, "div", "dm-message-row");
row.classList.add(`dm-message-row--${variant}`);
const avatar = createElement(doc, "img", "dm-message-avatar");
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,
// decorate it (ring + wrapped corner star). Non-admin bubbles keep the plain
// <img> so existing layout is untouched.
let avatarNode = avatar;
if (adminId && isAdminActor(adminId)) {
avatarNode = decorateAdminAvatar(avatar, adminId, { doc });
}
const bubble = createElement(doc, "div", "dm-message-bubble");
bubble.classList.add(`dm-message-bubble--${variant}`);
bubble.dataset.status = status;
const content = createElement(doc, "div", "dm-message-bubble__content", body);
bubble.appendChild(content);
const meta = createElement(doc, "div", "dm-message-bubble__meta");
if (timestamp) {
// Added 'text-muted' class for theme-respecting color (usually handles light/dark text)
const timeSpan = createElement(doc, "span", "dm-message-bubble__time text-muted", timestamp);
meta.appendChild(timeSpan);
}
if (variant === "outgoing") {
const statusLabel = createElement(
doc,
"span",
"dm-message-bubble__status",
status === "failed"
? "Failed to send"
: status === "pending"
? "Pending…"
: status === "sending"
? "Sending…"
: "Sent",
);
statusLabel.dataset.status = status;
meta.appendChild(statusLabel);
}
bubble.appendChild(meta);
if (variant === "outgoing") {
row.appendChild(bubble);
row.appendChild(avatarNode);
} else {
row.appendChild(avatarNode);
row.appendChild(bubble);
}
return row;
}