Unread DMS count

This commit is contained in:
Kieran 2023-01-17 10:47:00 +00:00
parent a11372c9ca
commit 6736bdf359
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
4 changed files with 73 additions and 21 deletions

View File

@ -3,14 +3,11 @@ import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useInView } from 'react-intersection-observer';
// @ts-ignore
import useEventPublisher from "../feed/EventPublisher";
// @ts-ignore
import Event from "../nostr/Event";
// @ts-ignore
import NoteTime from "./NoteTime";
// @ts-ignore
import Text from "./Text";
import { lastReadDm, setLastReadDm } from "../pages/MessagesPage";
export type DMProps = {
data: any
@ -22,9 +19,13 @@ export default function DM(props: DMProps) {
const [content, setContent] = useState("Loading...");
const [decrypted, setDecrypted] = useState(false);
const { ref, inView, entry } = useInView();
const isMe = props.data.pubkey === pubKey;
async function decrypt() {
let e = new Event(props.data);
if (!isMe) {
setLastReadDm(e.PubKey);
}
let decrypted = await publisher.decryptDm(e);
setContent(decrypted || "<ERROR>");
}
@ -37,7 +38,7 @@ export default function DM(props: DMProps) {
}, [inView, props.data]);
return (
<div className={`flex dm f-col${props.data.pubkey === pubKey ? " me" : ""}`} ref={ref}>
<div className={`flex dm f-col${isMe ? " me" : ""}`} ref={ref}>
<div><NoteTime from={props.data.created_at * 1000} fallback={'Just now'} /></div>
<div className="w-max">
<Text content={content} tags={[]} users={new Map()} />

View File

@ -11,9 +11,7 @@ import {
CheckRegisterResponse
} from "../nip05/ServiceProvider";
import AsyncButton from "./AsyncButton";
// @ts-ignore
import LNURLTip from "./LNURLTip";
// @ts-ignore
import Copy from "./Copy";
import useProfile from "../feed/ProfileFeed";
import useEventPublisher from "../feed/EventPublisher";

View File

@ -10,8 +10,9 @@ import ProfileImage from "../element/ProfileImage";
import { init } from "../state/Login";
import useLoginFeed from "../feed/LoginFeed";
import { RootState } from "../state/Store";
import { HexKey, TaggedRawEvent } from "../nostr";
import { HexKey, RawEvent, TaggedRawEvent } from "../nostr";
import { RelaySettings } from "../nostr/Connection";
import { totalUnread } from "./MessagesPage";
export default function Layout() {
const dispatch = useDispatch();
@ -21,6 +22,7 @@ export default function Layout() {
const relays = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
const notifications = useSelector<RootState, TaggedRawEvent[]>(s => s.login.notifications);
const readNotifications = useSelector<RootState, number>(s => s.login.readNotifications);
const dms = useSelector<RootState, RawEvent[]>(s => s.login.dms);
useLoginFeed();
useEffect(() => {
@ -56,11 +58,15 @@ export default function Layout() {
function accountHeader() {
const unreadNotifications = notifications?.filter(a => (a.created_at * 1000) > readNotifications).length;
const unreadDms = key ? totalUnread(dms, key) : 0;
return (
<>
<div className="btn btn-rnd mr10" onClick={(e) => navigate("/messages")}>
<div className={`btn btn-rnd${unreadDms === 0 ? " mr10" : ""}`} onClick={(e) => navigate("/messages")}>
<FontAwesomeIcon icon={faMessage} size="xl" />
</div>
{unreadDms > 0 && (<span className="unread-count">
{unreadDms > 100 ? ">99" : unreadDms}
</span>)}
<div className={`btn btn-rnd${unreadNotifications === 0 ? " mr10" : ""}`} onClick={(e) => goToNotifications(e)}>
<FontAwesomeIcon icon={faBell} size="xl" />
</div>

View File

@ -1,27 +1,31 @@
import { useMemo } from "react";
import { useSelector } from "react-redux"
import { RawEvent } from "../nostr";
// @ts-ignore
import { HexKey, RawEvent } from "../nostr";
import ProfileImage from "../element/ProfileImage";
// @ts-ignore
import { hexToBech32 } from "../Util";
type DmChat = {
pubkey: HexKey,
lastRead: number,
unreadMessages: number,
newestMessage: number
}
export default function MessagesPage() {
const pubKey = useSelector<any, string>(s => s.login.publicKey);
const myPubKey = useSelector<any, string>(s => s.login.publicKey);
const dms = useSelector<any, RawEvent[]>(s => s.login.dms);
const pubKeys = useMemo(() => {
return Array.from(new Set<string>(dms.map(a => [a.pubkey, ...a.tags.filter(b => b[0] === "p").map(b => b[1])]).flat()));
const chats = useMemo(() => {
return extractChats(dms, myPubKey);
}, [dms]);
function person(pubkey: string) {
function person(chat: DmChat) {
return (
<div className="flex mb10" key={pubkey}>
<ProfileImage pubkey={pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", pubkey)}`} />
<div className="flex mb10" key={chat.pubkey}>
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
<span className="pill">
{dms?.filter(a => a.pubkey === pubkey && a.pubkey !== pubKey).length}
{chat.unreadMessages}
</span>
</div>
)
@ -30,7 +34,50 @@ export default function MessagesPage() {
return (
<>
<h3>Messages</h3>
{pubKeys.map(person)}
{chats.sort((a, b) => b.newestMessage - a.newestMessage).map(person)}
</>
)
}
export function lastReadDm(pk: HexKey) {
let k = `dm:seen:${pk}`;
return parseInt(window.localStorage.getItem(k) ?? "0");
}
export function setLastReadDm(pk: HexKey) {
const now = Math.floor(new Date().getTime() / 1000);
let current = lastReadDm(pk);
if (current >= now) {
return;
}
let k = `dm:seen:${pk}`;
window.localStorage.setItem(k, now.toString());
}
export function totalUnread(dms: RawEvent[], myPubKey: HexKey) {
return extractChats(dms, myPubKey).reduce((acc, v) => acc += v.unreadMessages, 0);
}
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
let lastRead = lastReadDm(pk);
return dms?.filter(a => a.pubkey === pk && a.pubkey !== myPubKey && a.created_at >= lastRead).length;
}
function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
return dms.filter(a => a.pubkey === pk && a.pubkey !== myPubKey).reduce((acc, v) => acc = v.created_at > acc ? v.created_at : acc, 0);
}
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
const keys = dms.map(a => [a.pubkey, ...a.tags.filter(b => b[0] === "p").map(b => b[1])]).flat();
const filteredKeys = Array.from(new Set<string>(keys));
return filteredKeys.map(a => {
return {
pubkey: a,
lastRead: lastReadDm(a),
unreadMessages: unreadDms(dms, myPubKey, a),
newestMessage: newestMessage(dms, myPubKey, a)
} as DmChat;
})
}