mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-01 01:59:56 +00:00
fix: Fix notifications (#2132)
* add global service for unread notifications * update inbox counter when marking a message as read * adapt NotificationService for new auth parameter * refactor unread counter service * user service: refactor moderatesSomething * use behavioursubjects for unreadcounterservice * retry tests --------- Co-authored-by: SleeplessOne1917 <abias1122@gmail.com>
This commit is contained in:
parent
8a2cd127ee
commit
de8255fc9e
5 changed files with 163 additions and 113 deletions
|
@ -1,31 +1,30 @@
|
|||
import { myAuth, showAvatars } from "@utils/app";
|
||||
import { showAvatars } from "@utils/app";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import { numToSI, poll } from "@utils/helpers";
|
||||
import { numToSI } from "@utils/helpers";
|
||||
import { amAdmin, canCreateCommunity } from "@utils/roles";
|
||||
import { Component, createRef, linkEvent } from "inferno";
|
||||
import { NavLink } from "inferno-router";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { donateLemmyUrl } from "../../config";
|
||||
import {
|
||||
GetReportCountResponse,
|
||||
GetSiteResponse,
|
||||
GetUnreadCountResponse,
|
||||
GetUnreadRegistrationApplicationCountResponse,
|
||||
} from "lemmy-js-client";
|
||||
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
|
||||
import { I18NextService, UserService } from "../../services";
|
||||
import { HttpService, RequestState } from "../../services/HttpService";
|
||||
I18NextService,
|
||||
UserService,
|
||||
UnreadCounterService,
|
||||
} from "../../services";
|
||||
import { toast } from "../../toast";
|
||||
import { Icon } from "../common/icon";
|
||||
import { PictrsImage } from "../common/pictrs-image";
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
interface NavbarProps {
|
||||
siteRes?: GetSiteResponse;
|
||||
}
|
||||
|
||||
interface NavbarState {
|
||||
unreadInboxCountRes: RequestState<GetUnreadCountResponse>;
|
||||
unreadReportCountRes: RequestState<GetReportCountResponse>;
|
||||
unreadApplicationCountRes: RequestState<GetUnreadRegistrationApplicationCountResponse>;
|
||||
onSiteBanner?(url: string): any;
|
||||
unreadInboxCount: number;
|
||||
unreadReportCount: number;
|
||||
unreadApplicationCount: number;
|
||||
}
|
||||
|
||||
function handleCollapseClick(i: Navbar) {
|
||||
|
@ -44,13 +43,17 @@ function handleLogOut(i: Navbar) {
|
|||
}
|
||||
|
||||
export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||
state: NavbarState = {
|
||||
unreadInboxCountRes: { state: "empty" },
|
||||
unreadReportCountRes: { state: "empty" },
|
||||
unreadApplicationCountRes: { state: "empty" },
|
||||
};
|
||||
collapseButtonRef = createRef<HTMLButtonElement>();
|
||||
mobileMenuRef = createRef<HTMLDivElement>();
|
||||
unreadInboxCountSubscription: Subscription;
|
||||
unreadReportCountSubscription: Subscription;
|
||||
unreadApplicationCountSubscription: Subscription;
|
||||
|
||||
state: NavbarState = {
|
||||
unreadInboxCount: 0,
|
||||
unreadReportCount: 0,
|
||||
unreadApplicationCount: 0,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
@ -63,7 +66,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
if (isBrowser()) {
|
||||
// On the first load, check the unreads
|
||||
this.requestNotificationPermission();
|
||||
this.fetchUnreads();
|
||||
this.unreadInboxCountSubscription =
|
||||
UnreadCounterService.Instance.unreadInboxCountSubject.subscribe(
|
||||
unreadInboxCount => this.setState({ unreadInboxCount }),
|
||||
);
|
||||
this.unreadReportCountSubscription =
|
||||
UnreadCounterService.Instance.unreadReportCountSubject.subscribe(
|
||||
unreadReportCount => this.setState({ unreadReportCount }),
|
||||
);
|
||||
this.unreadApplicationCountSubscription =
|
||||
UnreadCounterService.Instance.unreadApplicationCountSubject.subscribe(
|
||||
unreadApplicationCount => this.setState({ unreadApplicationCount }),
|
||||
);
|
||||
this.requestNotificationPermission();
|
||||
|
||||
document.addEventListener("mouseup", this.handleOutsideMenuClick);
|
||||
|
@ -72,6 +86,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mouseup", this.handleOutsideMenuClick);
|
||||
this.unreadInboxCountSubscription.unsubscribe();
|
||||
this.unreadReportCountSubscription.unsubscribe();
|
||||
this.unreadApplicationCountSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
// TODO class active corresponding to current pages
|
||||
|
@ -103,34 +120,34 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
to="/inbox"
|
||||
className="p-1 nav-link border-0 nav-messages"
|
||||
title={I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.state.unreadApplicationCountRes.state),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="bell" />
|
||||
{this.unreadInboxCount > 0 && (
|
||||
{this.state.unreadInboxCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadInboxCount)}
|
||||
{numToSI(this.state.unreadInboxCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
{this.moderatesSomething && (
|
||||
{UserService.Instance.moderatesSomething && (
|
||||
<li className="nav-item nav-item-icon">
|
||||
<NavLink
|
||||
to="/reports"
|
||||
className="p-1 nav-link border-0"
|
||||
title={I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(this.state.unreadReportCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="shield" />
|
||||
{this.unreadReportCount > 0 && (
|
||||
{this.state.unreadReportCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadReportCount)}
|
||||
{numToSI(this.state.unreadReportCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -144,16 +161,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
title={I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
formattedCount: numToSI(this.unreadApplicationCount),
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
},
|
||||
)}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="clipboard" />
|
||||
{this.unreadApplicationCount > 0 && (
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadApplicationCount)}
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -268,46 +287,48 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
||||
to="/inbox"
|
||||
title={I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.unreadInboxCount),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="bell" />
|
||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||
{I18NextService.i18n.t("unread_messages", {
|
||||
count: Number(this.unreadInboxCount),
|
||||
formattedCount: numToSI(this.unreadInboxCount),
|
||||
count: Number(this.state.unreadInboxCount),
|
||||
formattedCount: numToSI(this.state.unreadInboxCount),
|
||||
})}
|
||||
</span>
|
||||
{this.unreadInboxCount > 0 && (
|
||||
{this.state.unreadInboxCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadInboxCount)}
|
||||
{numToSI(this.state.unreadInboxCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
{this.moderatesSomething && (
|
||||
{UserService.Instance.moderatesSomething && (
|
||||
<li id="navModeration" className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link d-inline-flex align-items-center d-md-inline-block"
|
||||
to="/reports"
|
||||
title={I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(this.state.unreadReportCount),
|
||||
})}
|
||||
onMouseUp={linkEvent(this, handleCollapseClick)}
|
||||
>
|
||||
<Icon icon="shield" />
|
||||
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
|
||||
{I18NextService.i18n.t("unread_reports", {
|
||||
count: Number(this.unreadReportCount),
|
||||
formattedCount: numToSI(this.unreadReportCount),
|
||||
count: Number(this.state.unreadReportCount),
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadReportCount,
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
{this.unreadReportCount > 0 && (
|
||||
{this.state.unreadReportCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadReportCount)}
|
||||
{numToSI(this.state.unreadReportCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -321,9 +342,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
title={I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.unreadApplicationCount,
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
},
|
||||
)}
|
||||
|
@ -334,16 +355,16 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
{I18NextService.i18n.t(
|
||||
"unread_registration_applications",
|
||||
{
|
||||
count: Number(this.unreadApplicationCount),
|
||||
count: Number(this.state.unreadApplicationCount),
|
||||
formattedCount: numToSI(
|
||||
this.unreadApplicationCount,
|
||||
this.state.unreadApplicationCount,
|
||||
),
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
{this.unreadApplicationCount > 0 && (
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span className="mx-1 badge text-bg-light">
|
||||
{numToSI(this.unreadApplicationCount)}
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
@ -441,68 +462,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
}
|
||||
}
|
||||
|
||||
get moderatesSomething(): boolean {
|
||||
const mods = UserService.Instance.myUserInfo?.moderates;
|
||||
const moderatesS = (mods && mods.length > 0) || false;
|
||||
return amAdmin() || moderatesS;
|
||||
}
|
||||
|
||||
fetchUnreads() {
|
||||
poll(async () => {
|
||||
if (window.document.visibilityState !== "hidden") {
|
||||
if (myAuth()) {
|
||||
this.setState({
|
||||
unreadInboxCountRes: await HttpService.client.getUnreadCount(),
|
||||
});
|
||||
|
||||
if (this.moderatesSomething) {
|
||||
this.setState({
|
||||
unreadReportCountRes: await HttpService.client.getReportCount({}),
|
||||
});
|
||||
}
|
||||
|
||||
if (amAdmin()) {
|
||||
this.setState({
|
||||
unreadApplicationCountRes:
|
||||
await HttpService.client.getUnreadRegistrationApplicationCount(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, updateUnreadCountsInterval);
|
||||
}
|
||||
|
||||
get unreadInboxCount(): number {
|
||||
if (this.state.unreadInboxCountRes.state === "success") {
|
||||
const data = this.state.unreadInboxCountRes.data;
|
||||
return data.replies + data.mentions + data.private_messages;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
get unreadReportCount(): number {
|
||||
if (this.state.unreadReportCountRes.state === "success") {
|
||||
const data = this.state.unreadReportCountRes.data;
|
||||
return (
|
||||
data.post_reports +
|
||||
data.comment_reports +
|
||||
(data.private_message_reports ?? 0)
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
get unreadApplicationCount(): number {
|
||||
if (this.state.unreadApplicationCountRes.state === "success") {
|
||||
const data = this.state.unreadApplicationCountRes.data;
|
||||
return data.registration_applications;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
get currentLocation() {
|
||||
return this.context.router.history.location.pathname;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import {
|
|||
import { fetchLimit, relTags } from "../../config";
|
||||
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
import {
|
||||
EmptyRequestState,
|
||||
HttpService,
|
||||
|
@ -792,6 +793,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
limit,
|
||||
}),
|
||||
});
|
||||
UnreadCounterService.Instance.update();
|
||||
}
|
||||
|
||||
async handleSortChange(val: CommentSortType) {
|
||||
|
|
83
src/shared/services/UnreadCounterService.ts
Normal file
83
src/shared/services/UnreadCounterService.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { UserService, HttpService } from "../services";
|
||||
import { updateUnreadCountsInterval } from "../config";
|
||||
import { poll } from "@utils/helpers";
|
||||
import { myAuth } from "@utils/app";
|
||||
import { amAdmin } from "@utils/roles";
|
||||
import { isBrowser } from "@utils/browser";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
/**
|
||||
* Service to poll and keep track of unread messages / notifications.
|
||||
*/
|
||||
export class UnreadCounterService {
|
||||
// fetched by HttpService.getUnreadCount, appear in inbox
|
||||
unreadPrivateMessages = 0;
|
||||
unreadReplies = 0;
|
||||
unreadMentions = 0;
|
||||
public unreadInboxCountSubject: BehaviorSubject<number> =
|
||||
new BehaviorSubject<number>(0);
|
||||
|
||||
// fetched by HttpService.getReportCount, appear in report page
|
||||
commentReportCount = 0;
|
||||
postReportCount = 0;
|
||||
messageReportCount = 0;
|
||||
public unreadReportCountSubject: BehaviorSubject<number> =
|
||||
new BehaviorSubject<number>(0);
|
||||
|
||||
// fetched by HttpService.getUnreadRegistrationApplicationCount, appear in registration application page
|
||||
public unreadApplicationCountSubject: BehaviorSubject<number> =
|
||||
new BehaviorSubject<number>(0);
|
||||
|
||||
static #instance: UnreadCounterService;
|
||||
|
||||
constructor() {
|
||||
if (isBrowser()) {
|
||||
poll(this.update, updateUnreadCountsInterval);
|
||||
}
|
||||
}
|
||||
|
||||
public update = async () => {
|
||||
if (window.document.visibilityState === "hidden") {
|
||||
return;
|
||||
}
|
||||
if (!myAuth()) {
|
||||
return;
|
||||
}
|
||||
const unreadCountRes = await HttpService.client.getUnreadCount();
|
||||
if (unreadCountRes.state === "success") {
|
||||
this.unreadPrivateMessages = unreadCountRes.data.private_messages;
|
||||
this.unreadReplies = unreadCountRes.data.replies;
|
||||
this.unreadMentions = unreadCountRes.data.mentions;
|
||||
this.unreadInboxCountSubject.next(
|
||||
this.unreadPrivateMessages + this.unreadReplies + this.unreadMentions,
|
||||
);
|
||||
}
|
||||
if (UserService.Instance.moderatesSomething) {
|
||||
const reportCountRes = await HttpService.client.getReportCount({});
|
||||
if (reportCountRes.state === "success") {
|
||||
this.commentReportCount = reportCountRes.data.comment_reports ?? 0;
|
||||
this.postReportCount = reportCountRes.data.post_reports ?? 0;
|
||||
this.messageReportCount =
|
||||
reportCountRes.data.private_message_reports ?? 0;
|
||||
this.unreadReportCountSubject.next(
|
||||
this.commentReportCount +
|
||||
this.postReportCount +
|
||||
this.messageReportCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (amAdmin()) {
|
||||
const unreadApplicationsRes =
|
||||
await HttpService.client.getUnreadRegistrationApplicationCount();
|
||||
if (unreadApplicationsRes.state === "success") {
|
||||
this.unreadApplicationCountSubject.next(
|
||||
unreadApplicationsRes.data.registration_applications,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static get Instance() {
|
||||
return this.#instance ?? (this.#instance = new this());
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import jwt_decode from "jwt-decode";
|
|||
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
|
||||
import { toast } from "../toast";
|
||||
import { I18NextService } from "./I18NextService";
|
||||
import { amAdmin } from "@utils/roles";
|
||||
import { HttpService } from ".";
|
||||
|
||||
interface Claims {
|
||||
|
@ -88,6 +89,10 @@ export class UserService {
|
|||
}
|
||||
}
|
||||
|
||||
public get moderatesSomething(): boolean {
|
||||
return amAdmin() || (this.myUserInfo?.moderates?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
public static get Instance() {
|
||||
return this.#instance || (this.#instance = new this());
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@ export { HomeCacheService } from "./HomeCacheService";
|
|||
export { HttpService } from "./HttpService";
|
||||
export { I18NextService } from "./I18NextService";
|
||||
export { UserService } from "./UserService";
|
||||
export { UnreadCounterService } from "./UnreadCounterService";
|
||||
|
|
Loading…
Reference in a new issue