mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #1408 from PR0M3TH3AN/codex/add-unread-message-indicator-in-profile-button
Add unread DM badge indicator
This commit is contained in:
9 files changed
+216
-9
No files matched your search
@@ -148,11 +148,16 @@
|
||||
<button
|
||||
id="profileNavMessages"
|
||||
type="button"
|
||||
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
|
||||
class="btn-ghost focus-ring relative w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
|
||||
aria-controls="profilePaneMessages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Messages
|
||||
<span
|
||||
id="profileMessagesUnreadDot"
|
||||
class="profile-nav__unread-dot"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</button>
|
||||
<button
|
||||
id="profileNavHistory"
|
||||
|
||||
+72
-2
@@ -235,6 +235,23 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
#profileUnreadDot {
|
||||
position: absolute;
|
||||
top: var(--space-2xs);
|
||||
right: var(--space-2xs);
|
||||
width: var(--space-sm);
|
||||
height: var(--space-sm);
|
||||
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);
|
||||
@@ -2803,6 +2820,7 @@
|
||||
min-block-size: 100%;
|
||||
max-block-size: none;
|
||||
overflow: hidden;
|
||||
--modal-sheet-inline-max: var(--profile-modal-inline-max);
|
||||
--profile-modal-scale: 0.9;
|
||||
--profile-modal-gap: calc(
|
||||
(var(--space-md) - var(--space-3xs)) * var(--profile-modal-scale)
|
||||
@@ -2991,6 +3009,23 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.profile-nav__unread-dot {
|
||||
position: absolute;
|
||||
top: var(--space-3xs);
|
||||
right: var(--space-3xs);
|
||||
width: var(--space-sm);
|
||||
height: var(--space-sm);
|
||||
border-radius: 999px;
|
||||
border: var(--border-width-hairline) solid var(--surface-panel);
|
||||
background-color: var(--status-danger);
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.profile-nav__unread-dot.is-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.profile-modal__panes {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
@@ -4989,6 +5024,12 @@
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.profile-modal .dm-app-shell {
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.dm-app-shell__sidebar,
|
||||
.dm-app-shell__main {
|
||||
border-radius: var(--radius-lg);
|
||||
@@ -4997,6 +5038,12 @@
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.profile-modal .dm-app-shell__sidebar,
|
||||
.profile-modal .dm-app-shell__main {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.dm-app-shell__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -5018,6 +5065,11 @@
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
.profile-modal .dm-app-shell__main,
|
||||
.profile-modal .dm-app-shell__sidebar {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dm-conversation-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -5669,8 +5721,8 @@
|
||||
}
|
||||
|
||||
.dm-app-shell__sidebar {
|
||||
flex: 0 0 var(--size-18rem);
|
||||
max-width: var(--size-20rem);
|
||||
flex: 0 0 var(--size-18rem);
|
||||
max-width: var(--size-20rem);
|
||||
height: calc(100vh - var(--space-lg) * 2);
|
||||
}
|
||||
|
||||
@@ -5684,6 +5736,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: theme("screens.md")) {
|
||||
.profile-modal .dm-app-shell {
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.profile-modal .dm-app-shell__sidebar {
|
||||
flex: 0 0 var(--size-18rem);
|
||||
max-width: var(--size-20rem);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.profile-modal .dm-app-shell__main {
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: theme("screens.lg")) {
|
||||
.dm-app-shell__sidebar {
|
||||
flex-basis: var(--size-20rem);
|
||||
|
||||
@@ -322,6 +322,7 @@
|
||||
--size-28rem: 28rem;
|
||||
--size-34rem: 34rem;
|
||||
--size-45rem: 45rem;
|
||||
--size-70rem: 70rem;
|
||||
|
||||
/* Border radii */
|
||||
--radius-sm: 0.25rem;
|
||||
@@ -343,6 +344,7 @@
|
||||
--modal-shell-max-height: 90vh;
|
||||
--modal-sheet-max-height: 92vh;
|
||||
--modal-body-max-height: calc(92vh - 4rem);
|
||||
--profile-modal-inline-max: var(--size-70rem);
|
||||
--player-modal-module-scroll-min-height: var(--size-14rem);
|
||||
--player-modal-module-scroll-max-height: var(--size-28rem);
|
||||
--modal-pane-max-height: 48vh;
|
||||
|
||||
+2
-1
@@ -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>
|
||||
|
||||
@@ -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,11 @@ class Application {
|
||||
}
|
||||
|
||||
this.applyLoggedOutUiState();
|
||||
if (typeof this.refreshUnreadDmIndicator === "function") {
|
||||
void this.refreshUnreadDmIndicator({ reason: "auth-logout" });
|
||||
} else if (this.appChromeController?.setUnreadDmIndicator) {
|
||||
this.appChromeController.setUnreadDmIndicator(false);
|
||||
}
|
||||
|
||||
const logoutView = getHashViewName();
|
||||
if (
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,88 @@ export default class ApplicationBootstrap {
|
||||
};
|
||||
|
||||
app.videosMap = app.nostrService.getVideosMap();
|
||||
app.unsubscribeFromNostrService = app.nostrService.on(
|
||||
"subscription:changed",
|
||||
({ subscription }) => {
|
||||
app.refreshUnreadDmIndicator = async ({ reason = "" } = {}) => {
|
||||
const applyIndicatorState = (visible) => {
|
||||
if (
|
||||
app.appChromeController &&
|
||||
typeof app.appChromeController.setUnreadDmIndicator === "function"
|
||||
) {
|
||||
app.appChromeController.setUnreadDmIndicator(visible);
|
||||
}
|
||||
|
||||
if (
|
||||
app.profileController &&
|
||||
typeof app.profileController.setMessagesUnreadIndicator === "function"
|
||||
) {
|
||||
app.profileController.setMessagesUnreadIndicator(visible);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof app.isUserLoggedIn === "function" && !app.isUserLoggedIn()) {
|
||||
applyIndicatorState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!app.nostrService ||
|
||||
typeof app.nostrService.listDirectMessageConversationSummaries !==
|
||||
"function"
|
||||
) {
|
||||
applyIndicatorState(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;
|
||||
applyIndicatorState(totalUnseen > 0);
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to refresh DM unread indicator",
|
||||
reason,
|
||||
error,
|
||||
);
|
||||
applyIndicatorState(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 (typeof app.refreshUnreadDmIndicator === "function") {
|
||||
void app.refreshUnreadDmIndicator({ reason: "dm-cleared" });
|
||||
}
|
||||
}),
|
||||
);
|
||||
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 +1003,7 @@ export default class ApplicationBootstrap {
|
||||
elements: {
|
||||
logoutButton: app.logoutButton,
|
||||
profileButton: app.profileButton,
|
||||
profileUnreadDot: app.profileUnreadDot,
|
||||
uploadButton: app.uploadButton,
|
||||
loginButton: app.loginButton,
|
||||
closeLoginModalButton: app.closeLoginModalBtn,
|
||||
|
||||
@@ -1297,6 +1297,7 @@ export class ProfileModalController {
|
||||
this.profileMessagesRelayStatus = null;
|
||||
this.profileMessagesReadReceiptsToggle = null;
|
||||
this.profileMessagesTypingToggle = null;
|
||||
this.profileMessagesUnreadDot = null;
|
||||
this.dmAppShellContainer = null;
|
||||
this.dmAppShell = null;
|
||||
this.profileLinkPreviewAutoToggle = null;
|
||||
@@ -1615,6 +1616,8 @@ export class ProfileModalController {
|
||||
document.getElementById("profileMessagesReadReceiptsToggle") || null;
|
||||
this.profileMessagesTypingToggle =
|
||||
document.getElementById("profileMessagesTypingToggle") || null;
|
||||
this.profileMessagesUnreadDot =
|
||||
document.getElementById("profileMessagesUnreadDot") || null;
|
||||
this.dmAppShellContainer =
|
||||
document.getElementById("dmAppShellMount") || null;
|
||||
this.profileLinkPreviewAutoToggle =
|
||||
@@ -2758,6 +2761,23 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
|
||||
setMessagesUnreadIndicator(visible) {
|
||||
if (!(this.profileMessagesUnreadDot instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = this.navButtons.messages;
|
||||
const isVisible =
|
||||
button instanceof HTMLElement &&
|
||||
!button.classList.contains("hidden") &&
|
||||
!button.hasAttribute("hidden");
|
||||
|
||||
this.profileMessagesUnreadDot.classList.toggle(
|
||||
"is-visible",
|
||||
Boolean(visible) && isVisible,
|
||||
);
|
||||
}
|
||||
|
||||
updateMessageComposerState() {
|
||||
const input = this.profileMessageInput;
|
||||
const button = this.profileMessageSendButton;
|
||||
@@ -12593,6 +12613,7 @@ export class ProfileModalController {
|
||||
this.clearHashtagInputs();
|
||||
this.setHashtagStatus("", "muted");
|
||||
this.handleActiveDmIdentityChanged(null);
|
||||
this.setMessagesUnreadIndicator(false);
|
||||
this.populateDmRelayPreferences();
|
||||
this.setDmRelayPreferencesStatus("");
|
||||
|
||||
|
||||
Reference in new issue
Block a user