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);
+ }
+ }
+