mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-01 10:22:25 +00:00
Show a small gold "verified admin" star in the corner opposite the red notification dot whenever a rendered profile belongs to a bitvid admin (the super admin or a current list editor). - adminBadge.js (new): isAdminActor(hex|npub) sync check against accessControl, and applyAdminStar(container, id) which adds/removes a single `.admin-star` in a non-clipped, positioned container. Idempotent. - CSS: `.admin-star` (top-left gold disc, mirrors the top-right dot), `.dm-avatar-wrap`, and position:relative on the comment avatar wrapper. - Profile button: app.refreshAdminStar() lights the logged-in user's star; re-runs on login/logout and on accessControl.onEditorsChange (so a late-loading editor list still resolves). - Comments: star on comment-author avatars (commentNodeFactory). - Channel page: wrap the clipped avatar and star the channel owner. - DMs: Avatar gains an opt-in adminId — only admin avatars get wrapped so non-admin rows are byte-identical; ContactRow passes the peer pubkey. - tests: admin-badge unit coverage (recognition, idempotency, removal). Co-Authored-By: Claude Opus 4.8 <[email protected]>
86 lines
2.7 KiB
JavaScript
86 lines
2.7 KiB
JavaScript
// js/ui/adminBadge.js
|
|
//
|
|
// Shared "this profile belongs to a bitvid admin" star badge. An admin is the
|
|
// super admin OR a current list editor (moderator). The star is rendered as a
|
|
// small gold marker in the corner opposite the red notification dot, so a
|
|
// profile can show both at once. Styling lives in the `.admin-star` CSS rules.
|
|
//
|
|
// `isAdminActor` is a synchronous check, so editors added to the admin lists
|
|
// only show the star once those lists have loaded from relays; the super admin
|
|
// (known from config) always resolves immediately. Surfaces that persist across
|
|
// a session (e.g. the profile button) should re-run applyAdminStar on
|
|
// accessControl.onEditorsChange so a late-loading editor list still lights up.
|
|
|
|
import { accessControl } from "../accessControl.js";
|
|
import { safeEncodeNpub } from "../utils/nostrHelpers.js";
|
|
|
|
export const ADMIN_STAR_CLASS = "admin-star";
|
|
|
|
/**
|
|
* Is the given actor (hex pubkey or npub) a bitvid admin?
|
|
* @param {string} pubkeyOrNpub
|
|
* @returns {boolean}
|
|
*/
|
|
export function isAdminActor(pubkeyOrNpub) {
|
|
const npub = safeEncodeNpub(pubkeyOrNpub);
|
|
if (!npub) {
|
|
return false;
|
|
}
|
|
try {
|
|
return Boolean(accessControl?.isAdminEditor?.(npub));
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function findDirectStar(container) {
|
|
for (const child of Array.from(container.children || [])) {
|
|
if (child.classList?.contains(ADMIN_STAR_CLASS)) {
|
|
return child;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Show or hide the admin star inside `container`. The container MUST be a
|
|
* position:relative, non-clipped element (the star is absolutely positioned in
|
|
* its corner) — for avatars that clip to a circle, pass a wrapper around the
|
|
* clipped element, not the clipped element itself.
|
|
*
|
|
* Idempotent: safe to call repeatedly; it reuses/removes its own star only.
|
|
*
|
|
* @param {Element|null} container
|
|
* @param {string} pubkeyOrNpub
|
|
* @param {{ doc?: Document, label?: string }} [options]
|
|
* @returns {boolean} whether a star is present after the call
|
|
*/
|
|
export function applyAdminStar(
|
|
container,
|
|
pubkeyOrNpub,
|
|
{ doc = typeof document !== "undefined" ? document : null, label = "bitvid admin" } = {},
|
|
) {
|
|
if (!container || typeof container.querySelector !== "function" || !doc) {
|
|
return false;
|
|
}
|
|
const show = isAdminActor(pubkeyOrNpub);
|
|
const existing = findDirectStar(container);
|
|
if (show) {
|
|
if (!existing) {
|
|
const star = doc.createElement("span");
|
|
star.className = ADMIN_STAR_CLASS;
|
|
star.setAttribute("aria-hidden", "true");
|
|
star.title = label;
|
|
star.textContent = "★";
|
|
container.appendChild(star);
|
|
}
|
|
return true;
|
|
}
|
|
if (existing) {
|
|
existing.remove();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export default { ADMIN_STAR_CLASS, isAdminActor, applyAdminStar };
|