snort/packages/app/src/Pages/MessagesPage.tsx

156 lines
4.4 KiB
TypeScript
Raw Normal View History

2023-01-12 09:48:39 +00:00
import { useMemo } from "react";
2023-02-08 21:10:26 +00:00
import { FormattedMessage } from "react-intl";
2023-02-11 20:05:46 +00:00
import { HexKey, RawEvent } from "@snort/nostr";
2023-04-14 11:33:19 +00:00
2023-01-20 11:11:50 +00:00
import UnreadCount from "Element/UnreadCount";
import ProfileImage from "Element/ProfileImage";
2023-04-19 12:10:41 +00:00
import { dedupe, hexToBech32, unwrap } from "Util";
2023-01-20 11:11:50 +00:00
import NoteToSelf from "Element/NoteToSelf";
2023-01-26 11:34:18 +00:00
import useModeration from "Hooks/useModeration";
2023-04-14 11:33:19 +00:00
import { useDmCache } from "Hooks/useDmsCache";
import useLogin from "Hooks/useLogin";
2023-01-12 09:48:39 +00:00
2023-02-08 21:10:26 +00:00
import messages from "./messages";
2023-01-17 10:47:00 +00:00
type DmChat = {
pubkey: HexKey;
unreadMessages: number;
newestMessage: number;
};
2023-01-17 10:47:00 +00:00
2023-01-12 09:48:39 +00:00
export default function MessagesPage() {
2023-04-14 11:33:19 +00:00
const login = useLogin();
const { isMuted } = useModeration();
2023-03-29 12:10:22 +00:00
const dms = useDmCache();
const chats = useMemo(() => {
2023-04-14 11:33:19 +00:00
if (login.publicKey) {
return extractChats(
dms.filter(a => !isMuted(a.pubkey)),
login.publicKey
);
}
return [];
2023-04-19 12:10:41 +00:00
}, [dms, login.publicKey]);
const unreadCount = useMemo(() => chats.reduce((p, c) => p + c.unreadMessages, 0), [chats]);
function noteToSelf(chat: DmChat) {
2023-01-12 09:48:39 +00:00
return (
<div className="flex mb10" key={chat.pubkey}>
<NoteToSelf
clickable={true}
className="f-grow"
link={`/messages/${hexToBech32("npub", chat.pubkey)}`}
pubkey={chat.pubkey}
/>
</div>
);
}
function person(chat: DmChat) {
2023-04-14 11:33:19 +00:00
if (chat.pubkey === login.publicKey) return noteToSelf(chat);
return (
<div className="flex mb10" key={chat.pubkey}>
2023-02-09 12:26:54 +00:00
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
<UnreadCount unread={chat.unreadMessages} />
</div>
);
}
function markAllRead() {
2023-02-07 19:47:57 +00:00
for (const c of chats) {
setLastReadDm(c.pubkey);
}
}
return (
<div className="main-content">
<div className="flex">
2023-02-08 21:10:26 +00:00
<h3 className="f-grow">
<FormattedMessage {...messages.Messages} />
</h3>
<button disabled={unreadCount <= 0} type="button" onClick={() => markAllRead()}>
2023-02-08 21:10:26 +00:00
<FormattedMessage {...messages.MarkAllRead} />
</button>
</div>
{chats
.sort((a, b) => {
2023-04-14 11:33:19 +00:00
return a.pubkey === login.publicKey
? -1
: b.pubkey === login.publicKey
? 1
: b.newestMessage - a.newestMessage;
})
.map(person)}
</div>
);
2023-01-17 10:47:00 +00:00
}
export function lastReadDm(pk: HexKey) {
2023-02-07 19:47:57 +00:00
const k = `dm:seen:${pk}`;
return parseInt(window.localStorage.getItem(k) ?? "0");
2023-01-17 10:47:00 +00:00
}
export function setLastReadDm(pk: HexKey) {
const now = Math.floor(new Date().getTime() / 1000);
2023-02-07 19:47:57 +00:00
const current = lastReadDm(pk);
if (current >= now) {
return;
}
2023-02-07 19:47:57 +00:00
const k = `dm:seen:${pk}`;
window.localStorage.setItem(k, now.toString());
2023-01-17 10:47:00 +00:00
}
2023-01-18 18:53:34 +00:00
export function dmTo(e: RawEvent) {
2023-02-09 12:26:54 +00:00
const firstP = e.tags.find(b => b[0] === "p");
2023-04-19 12:10:41 +00:00
return unwrap(firstP?.[1]);
2023-01-18 18:53:34 +00:00
}
2023-03-29 12:10:22 +00:00
export function isToSelf(e: Readonly<RawEvent>, pk: HexKey) {
return e.pubkey === pk && dmTo(e) === pk;
2023-01-17 11:30:42 +00:00
}
2023-03-29 12:10:22 +00:00
export function dmsInChat(dms: readonly RawEvent[], pk: HexKey) {
2023-02-09 12:26:54 +00:00
return dms.filter(a => a.pubkey === pk || dmTo(a) === pk);
2023-01-17 11:30:42 +00:00
}
2023-01-17 10:47:00 +00:00
export function totalUnread(dms: RawEvent[], myPubKey: HexKey) {
2023-02-09 12:26:54 +00:00
return extractChats(dms, myPubKey).reduce((acc, v) => (acc += v.unreadMessages), 0);
2023-01-17 10:47:00 +00:00
}
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
if (pk === myPubKey) return 0;
2023-02-07 19:47:57 +00:00
const lastRead = lastReadDm(pk);
2023-02-09 12:26:54 +00:00
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
2023-01-17 10:47:00 +00:00
}
function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
if (pk === myPubKey) {
return dmsInChat(
2023-02-09 12:26:54 +00:00
dms.filter(d => isToSelf(d, myPubKey)),
pk
).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
}
2023-02-09 12:26:54 +00:00
return dmsInChat(dms, pk).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
2023-01-17 10:47:00 +00:00
}
2023-04-19 12:10:41 +00:00
export function dmsForLogin(dms: readonly RawEvent[], myPubKey: HexKey) {
return dms.filter(a => a.pubkey === myPubKey || (a.pubkey !== myPubKey && dmTo(a) === myPubKey));
}
2023-01-17 10:47:00 +00:00
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
2023-04-19 12:10:41 +00:00
const myDms = dmsForLogin(dms, myPubKey);
const keys = myDms.map(a => [a.pubkey, dmTo(a)]).flat();
const filteredKeys = dedupe(keys);
2023-02-09 12:26:54 +00:00
return filteredKeys.map(a => {
return {
pubkey: a,
2023-04-19 12:10:41 +00:00
unreadMessages: unreadDms(myDms, myPubKey, a),
newestMessage: newestMessage(myDms, myPubKey, a),
} as DmChat;
});
2023-01-25 18:08:53 +00:00
}