forked from Kieran/snort
Unread DMS count
This commit is contained in:
parent
a11372c9ca
commit
6736bdf359
@ -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()} />
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user