Add unread DM badge to profile button

This commit is contained in:
thePR0M3TH3AN committed 2026-01-19 14:57:10 -05:00
1 parent 52a1b886f1
commit 225c6a1674
6 files changed
+121 -6

No files matched your search

+17
View File
@@ -235,6 +235,23 @@
display: block;
}
#profileUnreadDot {
position: absolute;
top: var(--space-3xs);
right: var(--space-3xs);
width: calc(var(--space-2xs) + var(--space-3xs));
height: calc(var(--space-2xs) + var(--space-3xs));
border-radius: 999px;
border: var(--border-width-hairline) solid var(--color-page);
background-color: var(--status-danger);
display: none;
pointer-events: none;
}
#profileUnreadDot.is-visible {
display: block;
}
.header-icon-toggle[data-theme-state="light"]:hover,
.header-icon-toggle[data-theme-state="light"]:focus-visible {
color: var(--color-text);
+2 -1
View File
@@ -290,7 +290,7 @@
<!-- Profile Button (hidden by default) -->
<button
id="profileButton"
class="btn-ghost focus-ring hidden h-12 w-12 rounded-full p-0"
class="btn-ghost focus-ring relative hidden h-12 w-12 rounded-full p-0"
>
<div class="h-10 w-10 overflow-hidden rounded-full">
<img
@@ -300,6 +300,7 @@
class="h-full w-full object-cover"
/>
</div>
<span id="profileUnreadDot" aria-hidden="true"></span>
</button>
</div>
</div>
+6
View File
@@ -4429,6 +4429,9 @@ class Application {
this.applyAuthenticatedUiState();
this.commentController?.refreshAuthState?.();
if (typeof this.refreshUnreadDmIndicator === "function") {
void this.refreshUnreadDmIndicator({ reason: "auth-login" });
}
const currentView = getHashViewName();
const normalizedView =
@@ -4681,6 +4684,9 @@ class Application {
}
this.applyLoggedOutUiState();
if (this.appChromeController?.setUnreadDmIndicator) {
this.appChromeController.setUnreadDmIndicator(false);
}
const logoutView = getHashViewName();
if (
+7 -1
View File
@@ -763,10 +763,16 @@ export class NostrService {
}
async acknowledgeRenderedDirectMessages(conversationId, renderedUntil) {
return this.dmNotificationManager.acknowledgeRenderedMessages({
const conversation = await this.dmNotificationManager.acknowledgeRenderedMessages({
conversationId,
renderedUntil,
});
if (conversation) {
this.emit("directMessages:conversationUpdated", {
conversation,
});
}
return conversation;
}
getDirectMessageUnseenCount(conversationId) {
+16
View File
@@ -18,6 +18,7 @@ export default class AppChromeController {
this.elements = {
logoutButton: this.normalizeElement(elements.logoutButton),
profileButton: this.normalizeElement(elements.profileButton),
profileUnreadDot: this.normalizeElement(elements.profileUnreadDot),
uploadButton: this.normalizeElement(elements.uploadButton),
loginButton: this.normalizeElement(elements.loginButton),
closeLoginModalButton: this.normalizeElement(
@@ -206,6 +207,21 @@ export default class AppChromeController {
}
}
setUnreadDmIndicator(visible) {
const dot = this.normalizeElement(this.elements.profileUnreadDot);
if (!dot) {
return;
}
const profileButton = this.normalizeElement(this.elements.profileButton);
const isProfileVisible =
profileButton &&
!profileButton.classList.contains("hidden") &&
!profileButton.hasAttribute("hidden");
dot.classList.toggle("is-visible", Boolean(visible) && isProfileVisible);
}
bindUploadButton() {
const button = this.normalizeElement(this.elements.uploadButton);
if (this.boundElements.uploadButton === button) {
+73 -4
View File
@@ -409,6 +409,7 @@ export default class ApplicationBootstrap {
app.profileButton = doc?.getElementById("profileButton") || null;
app.profileAvatar = doc?.getElementById("profileAvatar") || null;
app.profileUnreadDot = doc?.getElementById("profileUnreadDot") || null;
const modalManager = new ModalManager({
app,
@@ -685,12 +686,79 @@ export default class ApplicationBootstrap {
};
app.videosMap = app.nostrService.getVideosMap();
app.unsubscribeFromNostrService = app.nostrService.on(
"subscription:changed",
({ subscription }) => {
app.refreshUnreadDmIndicator = async ({ reason = "" } = {}) => {
if (
!app.appChromeController ||
typeof app.appChromeController.setUnreadDmIndicator !== "function"
) {
return;
}
if (typeof app.isUserLoggedIn === "function" && !app.isUserLoggedIn()) {
app.appChromeController.setUnreadDmIndicator(false);
return;
}
if (
!app.nostrService ||
typeof app.nostrService.listDirectMessageConversationSummaries !==
"function"
) {
app.appChromeController.setUnreadDmIndicator(false);
return;
}
try {
const summaries =
await app.nostrService.listDirectMessageConversationSummaries();
const totalUnseen = Array.isArray(summaries)
? summaries.reduce((sum, summary) => {
const count = Number(summary?.unseen_count);
return sum + (Number.isFinite(count) ? count : 0);
}, 0)
: 0;
app.appChromeController.setUnreadDmIndicator(totalUnseen > 0);
} catch (error) {
devLogger.warn(
"[Application] Failed to refresh DM unread indicator",
reason,
error,
);
app.appChromeController.setUnreadDmIndicator(false);
}
};
const nostrUnsubscribes = [];
nostrUnsubscribes.push(
app.nostrService.on("subscription:changed", ({ subscription }) => {
app.videoSubscription = subscription || null;
},
}),
);
nostrUnsubscribes.push(
app.nostrService.on("directMessages:notification", () => {
void app.refreshUnreadDmIndicator({ reason: "dm-notification" });
}),
);
nostrUnsubscribes.push(
app.nostrService.on("directMessages:conversationUpdated", () => {
void app.refreshUnreadDmIndicator({ reason: "dm-conversation-update" });
}),
);
nostrUnsubscribes.push(
app.nostrService.on("directMessages:cleared", () => {
if (app.appChromeController) {
app.appChromeController.setUnreadDmIndicator(false);
}
}),
);
app.unsubscribeFromNostrService = () => {
while (nostrUnsubscribes.length) {
const unsubscribe = nostrUnsubscribes.pop();
if (typeof unsubscribe === "function") {
unsubscribe();
}
}
};
app.boundNwcSettingsToastHandler = null;
Object.defineProperty(app, "savedProfiles", {
configurable: false,
@@ -926,6 +994,7 @@ export default class ApplicationBootstrap {
elements: {
logoutButton: app.logoutButton,
profileButton: app.profileButton,
profileUnreadDot: app.profileUnreadDot,
uploadButton: app.uploadButton,
loginButton: app.loginButton,
closeLoginModalButton: app.closeLoginModalBtn,