From d091ce8bac668b46ae4268aeded2ec9611707499 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:59:09 -0400 Subject: [PATCH] Admin ring on comments/channel + pending-submissions dots on Admin tab & Submissions subtab - adminBadge.js: add applyAdminRing(el, id) for surfaces that already place the star on a specific element and just need the ring. - comments: ring + star on admin authors' avatars; round the comment avatar box so the ring reads as a circle. - channel page: ring the inner rounded avatar box (star already on the wrap). - Notification dots: red dot on the profile modal's Admin nav tab and on the Submissions subtab button when there are pending submissions, so admins know where to look. profileModalController.setSubmissionsUnreadIndicator toggles both (Admin nav dot gated on the tab being visible); driven from applyProfileNotificationDot off app._submissionsPending. Co-Authored-By: Claude Opus 4.8 --- components/profile-modal.html | 12 ++++++++- css/tailwind.source.css | 26 +++++++++++++++++++ js/channelProfile.js | 5 +++- js/ui/adminBadge.js | 18 +++++++++++++ js/ui/applicationBootstrap.js | 10 +++++++ .../video-modal/commentNodeFactory.js | 10 +++---- js/ui/profileModalController.js | 26 +++++++++++++++++++ 7 files changed, 100 insertions(+), 7 deletions(-) diff --git a/components/profile-modal.html b/components/profile-modal.html index ba70415b..2a4249a5 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -423,6 +423,11 @@ /> Admin (Moderation) + diff --git a/css/tailwind.source.css b/css/tailwind.source.css index 36437bb4..c63f0540 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -2764,6 +2764,8 @@ position: relative; inline-size: var(--comment-avatar-size); block-size: var(--comment-avatar-size); + /* rounded so the admin ring (box-shadow) reads as a circle */ + border-radius: var(--radius-full); } .comment-thread__avatar img { @@ -3561,6 +3563,30 @@ display: block; } + /* Pending-submissions dot on the Admin pane's "Submissions" subtab. The + subtabs sit in a horizontal row, so this dot rides the top-right corner + (not the left edge like the vertical nav dots). */ + .admin-subtab-has-dot { + position: relative; + } + + .admin-subtab-dot { + position: absolute; + top: calc(-1 * var(--space-3xs)); + right: calc(-1 * var(--space-3xs)); + width: var(--space-sm); + height: var(--space-sm); + border-radius: var(--radius-full); + border: var(--border-width-hairline) solid var(--surface-panel); + background-color: var(--status-danger); + display: none; + pointer-events: none; + } + + .admin-subtab-dot.is-visible { + display: block; + } + .profile-modal__panes { display: flex; flex: 1 1 auto; diff --git a/js/channelProfile.js b/js/channelProfile.js index e5bb0f87..187277f7 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -10,7 +10,7 @@ import { subscriptions } from "./subscriptions.js"; import { attachHealthBadges } from "./gridHealth.js"; import { attachUrlHealthBadges } from "./urlHealthObserver.js"; import { accessControl } from "./accessControl.js"; -import { applyAdminStar } from "./ui/adminBadge.js"; +import { applyAdminStar, applyAdminRing } from "./ui/adminBadge.js"; import { getApplication } from "./applicationContext.js"; import { escapeHTML } from "./utils/domUtils.js"; import { formatShortNpub } from "./utils/formatters.js"; @@ -5161,6 +5161,9 @@ function applyChannelProfileMetadata({ const blurPubkey = pubkey || currentChannelHex || ""; applyAdminStar(document.getElementById("channelAvatarWrap"), blurPubkey); + // Ring the inner rounded avatar box (the wrap is a non-clipped positioning + // shell; the box-shadow ring belongs on the circular element itself). + applyAdminRing(avatarEl?.parentElement, blurPubkey); updateChannelModerationVisuals({ bannerEl, avatarEl, diff --git a/js/ui/adminBadge.js b/js/ui/adminBadge.js index 2221e6e0..a5f5ab2a 100644 --- a/js/ui/adminBadge.js +++ b/js/ui/adminBadge.js @@ -90,6 +90,23 @@ export function applyAdminStar( return setAdminStar(container, isAdminActor(pubkeyOrNpub), { doc, label }); } +/** + * Toggle just the gold ring on a circular element (for surfaces that already + * place the star on a specific element and only need the ring added). The + * element should be circular (border-radius) for the ring to read right. + * @param {Element|null} el + * @param {string} pubkeyOrNpub + * @returns {boolean} whether the ring is applied + */ +export function applyAdminRing(el, pubkeyOrNpub) { + if (!el || !el.classList) { + return false; + } + const isAdmin = isAdminActor(pubkeyOrNpub); + el.classList.toggle(ADMIN_RING_CLASS, isAdmin); + return isAdmin; +} + /** * Decorate a circular avatar element with BOTH the admin ring and the corner * star. `avatarEl` is the circular element (an or a clip container). The @@ -138,5 +155,6 @@ export default { ADMIN_WRAP_CLASS, isAdminActor, applyAdminStar, + applyAdminRing, decorateAdminAvatar, }; diff --git a/js/ui/applicationBootstrap.js b/js/ui/applicationBootstrap.js index c3b05a67..ee0f2c52 100644 --- a/js/ui/applicationBootstrap.js +++ b/js/ui/applicationBootstrap.js @@ -879,6 +879,16 @@ export default class ApplicationBootstrap { ) { app.profileController.setMessagesUnreadIndicator(app._dmUnread); } + // Pending-submissions dots on the Admin tab + Submissions subtab so an + // admin knows exactly where to look. + if ( + app.profileController && + typeof app.profileController.setSubmissionsUnreadIndicator === "function" + ) { + app.profileController.setSubmissionsUnreadIndicator( + app._submissionsPending, + ); + } }; app.refreshUnreadDmIndicator = async ({ reason = "" } = {}) => { diff --git a/js/ui/components/video-modal/commentNodeFactory.js b/js/ui/components/video-modal/commentNodeFactory.js index 600a9439..318dd75e 100644 --- a/js/ui/components/video-modal/commentNodeFactory.js +++ b/js/ui/components/video-modal/commentNodeFactory.js @@ -1,7 +1,7 @@ import { UI_FEEDBACK_DELAY_MS } from "../../../constants.js"; import { formatAbsoluteTimestamp, formatTimeAgo } from "../../../utils/formatters.js"; import { registerCommentAvatarFailure as registerCommentAvatarFailureUtil } from "./utils/commentAvatar.js"; -import { applyAdminStar } from "../../adminBadge.js"; +import { applyAdminStar, applyAdminRing } from "../../adminBadge.js"; export class CommentNodeFactory { constructor({ document, window, logger, dispatch, DEFAULT_PROFILE_AVATAR }) { @@ -46,11 +46,11 @@ export class CommentNodeFactory { const avatarLabel = this.document.createElement("span"); avatarWrapper.appendChild(avatarLabel); - // Gold admin star for comments authored by a bitvid admin. + // Gold admin ring + star for comments authored by a bitvid admin. if (typeof event.pubkey === "string" && event.pubkey.trim()) { - applyAdminStar(avatarWrapper, event.pubkey.trim(), { - doc: this.document, - }); + const authorKey = event.pubkey.trim(); + applyAdminRing(avatarWrapper, authorKey); + applyAdminStar(avatarWrapper, authorKey, { doc: this.document }); } const content = this.document.createElement("div"); diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index 01be1cfe..00b7d8db 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -721,6 +721,12 @@ export class ProfileModalController { this.navButtons.admin = document.getElementById("profileNavAdmin") || null; this.navButtons.safety = document.getElementById("profileNavSafety") || null; + // Pending-submissions dots: on the Admin nav tab and the Submissions subtab. + this.adminNavUnreadDot = + document.getElementById("profileNavAdminUnreadDot") || null; + this.adminSubmissionsDot = + document.getElementById("adminSubtabSubmissionsDot") || null; + this.profileEditBtn = document.getElementById("profileEditBtn") || null; this.profileEditBackBtn = document.getElementById("profileEditBackBtn") || null; @@ -1353,6 +1359,26 @@ export class ProfileModalController { ); } + // Pending-submissions dots for admins: on the Admin nav tab (gated on that tab + // being visible) and on the Submissions subtab button inside the admin pane. + setSubmissionsUnreadIndicator(visible) { + const show = Boolean(visible); + const adminButton = this.navButtons.admin; + const adminNavVisible = + adminButton instanceof HTMLElement && + !adminButton.classList.contains("hidden") && + !adminButton.hasAttribute("hidden"); + if (this.adminNavUnreadDot instanceof HTMLElement) { + this.adminNavUnreadDot.classList.toggle( + "is-visible", + show && adminNavVisible, + ); + } + if (this.adminSubmissionsDot instanceof HTMLElement) { + this.adminSubmissionsDot.classList.toggle("is-visible", show); + } + } +