reactions tab
continuous-integration/drone/push Build is running Details

This commit is contained in:
Martti Malmi 2024-02-07 10:36:17 +02:00
parent df16384f07
commit b07961802c
8 changed files with 65 additions and 15 deletions

View File

@ -1,4 +1,4 @@
import { EventKind, NostrLink } from "@snort/system"; import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system";
import classNames from "classnames"; import classNames from "classnames";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useInView } from "react-intersection-observer"; import { useInView } from "react-intersection-observer";
@ -8,8 +8,10 @@ import { LRUCache } from "typescript-lru-cache";
import { Relay } from "@/Cache"; import { Relay } from "@/Cache";
import NoteHeader from "@/Components/Event/Note/NoteHeader"; import NoteHeader from "@/Components/Event/Note/NoteHeader";
import NoteQuote from "@/Components/Event/Note/NoteQuote";
import { NoteText } from "@/Components/Event/Note/NoteText"; import { NoteText } from "@/Components/Event/Note/NoteText";
import { TranslationInfo } from "@/Components/Event/Note/TranslationInfo"; import { TranslationInfo } from "@/Components/Event/Note/TranslationInfo";
import Username from "@/Components/User/Username";
import useModeration from "@/Hooks/useModeration"; import useModeration from "@/Hooks/useModeration";
import { findTag } from "@/Utils"; import { findTag } from "@/Utils";
import { chainKey } from "@/Utils/Thread/ChainKey"; import { chainKey } from "@/Utils/Thread/ChainKey";
@ -146,7 +148,25 @@ function useGoToEvent(props, options) {
); );
} }
function handleNonTextNote(ev) { function Reaction({ ev }: { ev: TaggedNostrEvent }) {
const reactedToTag = ev.tags.find((tag: string[]) => tag[0] === "e");
const link = NostrLink.fromTag(reactedToTag);
if (!reactedToTag) {
return null;
}
return (
<div className="note card">
<div className="text-gray-medium font-bold">
<Username pubkey={ev.pubkey} onLinkVisit={() => {}} />
<span> </span>
<FormattedMessage defaultMessage="liked" id="TvKqBp" />
</div>
<NoteQuote link={link} />
</div>
);
}
function handleNonTextNote(ev: TaggedNostrEvent) {
const alt = findTag(ev, "alt"); const alt = findTag(ev, "alt");
if (alt) { if (alt) {
return ( return (
@ -154,6 +174,8 @@ function handleNonTextNote(ev) {
<Text id={ev.id} content={alt} tags={[]} creator={ev.pubkey} /> <Text id={ev.id} content={alt} tags={[]} creator={ev.pubkey} />
</div> </div>
); );
} else if (ev.kind === EventKind.Reaction) {
return <Reaction ev={ev} />;
} else { } else {
return ( return (
<> <>

View File

@ -20,6 +20,7 @@ export interface TimelineSubject {
items: string[]; items: string[];
relay?: Array<string>; relay?: Array<string>;
extra?: (rb: RequestBuilder) => void; extra?: (rb: RequestBuilder) => void;
kinds?: EventKind[];
} }
export type TimelineFeed = ReturnType<typeof useTimelineFeed>; export type TimelineFeed = ReturnType<typeof useTimelineFeed>;
@ -37,14 +38,14 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
return null; return null;
} }
const kinds =
subject.kinds ??
(subject.type === "profile_keyword"
? [EventKind.SetMetadata]
: [EventKind.TextNote, EventKind.Repost, EventKind.Polls]);
const b = new RequestBuilder(`timeline:${subject.type}:${subject.discriminator}`); const b = new RequestBuilder(`timeline:${subject.type}:${subject.discriminator}`);
const f = b const f = b.withFilter().kinds(kinds);
.withFilter()
.kinds(
subject.type === "profile_keyword"
? [EventKind.SetMetadata]
: [EventKind.TextNote, EventKind.Repost, EventKind.Polls],
);
if (subject.relay) { if (subject.relay) {
subject.relay.forEach(r => f.relay(r)); subject.relay.forEach(r => f.relay(r));
@ -75,7 +76,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
} }
subject.extra?.(b); subject.extra?.(b);
return b; return b;
}, [subject.type, subject.items, subject.discriminator, subject.extra]); }, [subject]);
const sub = useMemo(() => { const sub = useMemo(() => {
const rb = createBuilder(); const rb = createBuilder();

View File

@ -1,9 +1,9 @@
import debug from "debug";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { useRouteError } from "react-router-dom"; import { useRouteError } from "react-router-dom";
import AsyncButton from "@/Components/Button/AsyncButton"; import AsyncButton from "@/Components/Button/AsyncButton";
import { db } from "@/Db"; import { db } from "@/Db";
import debug from "debug";
const log = debug("ErrorPage"); const log = debug("ErrorPage");

View File

@ -24,6 +24,7 @@ import {
FollowersTab, FollowersTab,
FollowsTab, FollowsTab,
ProfileNotesTab, ProfileNotesTab,
ReactionsTab,
RelaysTab, RelaysTab,
ZapsProfileTab, ZapsProfileTab,
} from "@/Pages/Profile/ProfileTabComponents"; } from "@/Pages/Profile/ProfileTabComponents";
@ -139,6 +140,9 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
case ProfileTabType.BOOKMARKS: { case ProfileTabType.BOOKMARKS: {
return <BookMarksTab id={id} />; return <BookMarksTab id={id} />;
} }
case ProfileTabType.REACTIONS: {
return <ReactionsTab id={id} />;
}
} }
} }
@ -175,9 +179,12 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
</div> </div>
<div className="main-content"> <div className="main-content">
<div className="tabs p" ref={horizontalScroll}> <div className="tabs p" ref={horizontalScroll}>
{[ProfileTabSelectors.Notes, ProfileTabSelectors.Followers, ProfileTabSelectors.Follows].map( {[
renderTabSelector, ProfileTabSelectors.Notes,
)} ProfileTabSelectors.Reactions,
ProfileTabSelectors.Followers,
ProfileTabSelectors.Follows,
].map(renderTabSelector)}
{optionalTabs.map(renderTabSelector)} {optionalTabs.map(renderTabSelector)}
{isMe && blocked.length > 0 && renderTabSelector(ProfileTabSelectors.Blocked)} {isMe && blocked.length > 0 && renderTabSelector(ProfileTabSelectors.Blocked)}
</div> </div>

View File

@ -52,6 +52,22 @@ export function BookMarksTab({ id }: { id: HexKey }) {
return <Bookmarks pubkey={id} bookmarks={bookmarks} />; return <Bookmarks pubkey={id} bookmarks={bookmarks} />;
} }
export function ReactionsTab({ id }: { id: HexKey }) {
const subject = useMemo(
() =>
({
type: "pubkey",
items: [id],
discriminator: `reactions:${id.slice(0, 12)}`,
kinds: [EventKind.Reaction],
}) as TimelineSubject,
[id],
);
return (
<Timeline subject={subject} postsOnly={false} method={"LIMIT_UNTIL"} ignoreModeration={true} window={60 * 60 * 6} />
);
}
export function ProfileNotesTab({ id, relays, isMe }: { id: HexKey; relays?: Array<string>; isMe: boolean }) { export function ProfileNotesTab({ id, relays, isMe }: { id: HexKey; relays?: Array<string>; isMe: boolean }) {
const pinned = usePinList(id); const pinned = usePinList(id);
const options = useMemo(() => ({ showTime: false, showPinned: true, canUnpin: isMe }), [isMe]); const options = useMemo(() => ({ showTime: false, showPinned: true, canUnpin: isMe }), [isMe]);

View File

@ -17,7 +17,7 @@ const ProfileTabSelectors = {
Reactions: { Reactions: {
text: ( text: (
<> <>
<Icon name="reaction" size={16} /> <Icon name="heart-solid" size={16} />
<FormattedMessage defaultMessage="Reactions" id="XgWvGA" /> <FormattedMessage defaultMessage="Reactions" id="XgWvGA" />
</> </>
), ),

View File

@ -924,6 +924,9 @@
"Tpy00S": { "Tpy00S": {
"defaultMessage": "People" "defaultMessage": "People"
}, },
"TvKqBp": {
"defaultMessage": "liked"
},
"TwyMau": { "TwyMau": {
"defaultMessage": "Account" "defaultMessage": "Account"
}, },

View File

@ -304,6 +304,7 @@
"TdtZQ5": "Crypto", "TdtZQ5": "Crypto",
"TpgeGw": "Hex Salt..", "TpgeGw": "Hex Salt..",
"Tpy00S": "People", "Tpy00S": "People",
"TvKqBp": "liked",
"TwyMau": "Account", "TwyMau": "Account",
"U1aPPi": "Stop listening", "U1aPPi": "Stop listening",
"UDYlxu": "Pending Subscriptions", "UDYlxu": "Pending Subscriptions",