This commit is contained in:
Kieran 2023-08-03 22:03:02 +01:00
parent 1ae71b90e8
commit ed109f0d2b
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 114 additions and 58 deletions

View File

@ -287,6 +287,11 @@
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</symbol>
<symbol id="reverse-left" viewBox="0 0 24 24" fill="none">
<g>
<path d="M8.70711 3.70711C9.09763 3.31658 9.09763 2.68342 8.70711 2.29289C8.31658 1.90237 7.68342 1.90237 7.29289 2.29289L3.29289 6.29289C2.90237 6.68342 2.90237 7.31658 3.29289 7.70711L7.29289 11.7071C7.68342 12.0976 8.31658 12.0976 8.70711 11.7071C9.09763 11.3166 9.09763 10.6834 8.70711 10.2929L6.41421 8L14 8C16.7614 8 19 10.2386 19 13C19 15.7614 16.7614 18 14 18H4C3.44772 18 3 18.4477 3 19C3 19.5523 3.44772 20 4 20H14C17.866 20 21 16.866 21 13C21 9.13401 17.866 6 14 6L6.41421 6L8.70711 3.70711Z" fill="currentColor"/>
</g>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -5,7 +5,7 @@
text-decoration: none;
user-select: none;
min-width: 0;
gap: 12px;
gap: 8px;
}
.pfp .avatar {

View File

@ -1,7 +1,28 @@
.notification-group {
display: flex;
padding: 16px 24px 26px 0;
min-height: 100px;
}
.notification-group > div:first-of-type {
width: 64px;
min-width: 64px;
max-width: 64px;
align-items: center;
}
.notification-group > div:first-of-type > div:first-of-type {
height: 40px;
display: flex;
flex-direction: column;
gap: 12px;
justify-content: center;
}
.notification-group > div:first-of-type > div {
align-self: center;
font-size: 16px;
font-weight: 600;
line-height: 1em;
}
.notification-group .avatar {
@ -9,10 +30,6 @@
height: 40px;
}
.notification-group .pfp {
gap: unset;
}
.notification-group .names {
display: flex;
gap: 24px;
@ -21,13 +38,7 @@
line-height: 1em;
}
.notification-group .names > div:first-of-type {
width: 24px;
text-align: center;
}
.notification-group .content {
margin-left: 48px;
font-size: 14px;
line-height: 22px;
color: var(--font-secondary-color);

View File

@ -13,13 +13,12 @@ import {
import { unwrap } from "@snort/shared";
import { useUserProfile } from "@snort/system-react";
import { useInView } from "react-intersection-observer";
import { FormattedMessage } from "react-intl";
import { FormattedMessage, useIntl } from "react-intl";
import useLogin from "Hooks/useLogin";
import { markNotificationsRead } from "Login";
import { Notifications, UserCache } from "Cache";
import { dedupe, findTag, orderDescending } from "SnortUtils";
import Note from "Element/Note";
import Icon from "Icons/Icon";
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
import useModeration from "Hooks/useModeration";
@ -27,6 +26,7 @@ import { System } from "index";
import useEventFeed from "Feed/EventFeed";
import Text from "Element/Text";
import { formatShort } from "Number";
import { useNavigate } from "react-router-dom";
function notificationContext(ev: TaggedRawEvent) {
switch (ev.kind) {
@ -47,11 +47,20 @@ function notificationContext(ev: TaggedRawEvent) {
break;
}
case EventKind.Repost:
case EventKind.TextNote:
case EventKind.Reaction: {
const thread = EventExt.extractThread(ev);
const id = unwrap(thread?.replyTo?.value ?? thread?.root?.value ?? ev.id);
return createNostrLink(NostrPrefix.Event, id);
const tag = unwrap(thread?.replyTo ?? thread?.root ?? { value: ev.id, key: "e" });
if (tag.key === "e") {
return createNostrLink(NostrPrefix.Event, unwrap(tag.value));
} else if (tag.key === "a") {
const [kind, author, d] = unwrap(tag.value).split(":");
return createNostrLink(NostrPrefix.Address, d, undefined, Number(kind), author);
} else {
throw new Error("Unknown thread context");
}
}
case EventKind.TextNote: {
return createNostrLink(NostrPrefix.Note, ev.id);
}
}
}
@ -98,8 +107,26 @@ export default function NotificationsPage() {
function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
const { ref, inView } = useInView({ triggerOnce: true });
const { formatMessage } = useIntl();
const kind = evs[0].kind;
const zaps = useMemo(() => {
return evs.filter(a => a.kind === EventKind.ZapReceipt).map(a => parseZap(a, UserCache));
}, [evs]);
const pubkeys = dedupe(
evs.map(a => {
if (a.kind === EventKind.ZapReceipt) {
const zap = unwrap(zaps.find(b => b.id === a.id));
return zap.anonZap ? "anon" : zap.sender ?? a.pubkey;
}
return a.pubkey;
})
);
const firstPubkey = pubkeys[0];
const firstPubkeyProfile = useUserProfile(System, inView ? (firstPubkey === "anon" ? "" : firstPubkey) : "");
const context = notificationContext(evs[0]);
const totalZaps = zaps.reduce((acc, v) => acc + v.amount, 0);
const iconName = () => {
switch (kind) {
case EventKind.Reaction:
@ -108,12 +135,17 @@ function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
return "zap-solid";
case EventKind.Repost:
return "repeat";
case EventKind.TextNote:
return "reverse-left";
}
return "";
};
const actionName = (n: number, name: string) => {
switch (kind) {
case EventKind.TextNote: {
return "";
}
case EventKind.Reaction: {
return (
<FormattedMessage
@ -151,58 +183,56 @@ function NotificationGroup({ evs }: { evs: Array<TaggedRawEvent> }) {
return `${kind}'d your post`;
};
if (kind === EventKind.TextNote) {
return (
<>
{evs.map(v => (
<Note data={v} related={[]} />
))}
</>
);
}
const zaps = useMemo(() => {
return evs.filter(a => a.kind === EventKind.ZapReceipt).map(a => parseZap(a, UserCache));
}, [evs]);
const pubkeys = dedupe(
evs.map(a => {
if (a.kind === EventKind.ZapReceipt) {
const zap = zaps.find(a => a.id === a.id);
return zap?.sender ?? a.pubkey;
}
return a.pubkey;
})
);
const firstPubkey = pubkeys[0];
const firstPubkeyProfile = useUserProfile(System, inView ? firstPubkey : "");
const context = notificationContext(evs[0]);
const totalZaps = zaps.reduce((acc, v) => acc + v.amount, 0);
return (
<div className="card notification-group" ref={ref}>
<div className="flex g24">
<Icon name={iconName()} size={24} />
<div className="flex g8">
{pubkeys.map(v => (
<ProfileImage key={v} showUsername={false} pubkey={v} size={40} />
))}
</div>
</div>
<div className="names">
<div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div>
{actionName(pubkeys.length - 1, getDisplayName(firstPubkeyProfile, firstPubkey))}
</div>
<div className="content">{context && <NotificationContext link={context} />}</div>
{inView && (
<>
<div className="flex f-col g12">
<div>
<Icon name={iconName()} size={24} className={iconName()} />
</div>
<div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div>
</div>
<div className="flex f-col g12 w-max">
<div className="flex">
{pubkeys
.filter(a => a !== "anon")
.slice(0, 12)
.map(v => (
<ProfileImage
key={v}
showUsername={kind === EventKind.TextNote}
pubkey={v}
size={40}
overrideUsername={v === "" ? formatMessage({ defaultMessage: "Anon" }) : undefined}
/>
))}
</div>
{kind !== EventKind.TextNote && (
<div className="names">
{actionName(
pubkeys.length - 1,
firstPubkey === "anon"
? formatMessage({ defaultMessage: "Anon" })
: getDisplayName(firstPubkeyProfile, firstPubkey)
)}
</div>
)}
<div className="content">{context && <NotificationContext link={context} />}</div>
</div>
</>
)}
</div>
);
}
function NotificationContext({ link }: { link: NostrLink }) {
const { data: ev } = useEventFeed(link);
const navigate = useNavigate();
const content = ev?.content ?? "";
return (
<div className="card">
<div onClick={() => navigate(`/${link.encode()}`)} className="pointer">
<Text
content={content.length > 120 ? `${content.substring(0, 120)}...` : content}
tags={ev?.tags ?? []}

View File

@ -14,6 +14,8 @@
--success: #2ad544;
--warning: #ff8800;
--live: #f83838;
--heart: #ef4444;
--zap: #ff710a;
--gray-superlight: #eee;
--gray-light: #999;
@ -739,3 +741,11 @@ button.tall {
background: transparent !important;
position: absolute !important;
}
svg.heart-solid {
color: var(--heart);
}
svg.zap-solid {
color: var(--zap);
}