feat: timeline fragments
This commit is contained in:
parent
ffda4b1a43
commit
ff2da6c5fd
@ -1,15 +1,21 @@
|
|||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
|
import { useReactions } from "@snort/system-react";
|
||||||
|
|
||||||
import { useArticles } from "Feed/ArticlesFeed";
|
import { useArticles } from "Feed/ArticlesFeed";
|
||||||
import { orderDescending } from "SnortUtils";
|
import { orderDescending } from "SnortUtils";
|
||||||
import Note from "../Event/Note";
|
import Note from "../Event/Note";
|
||||||
import { useReactions } from "Feed/Reactions";
|
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { DeckContext } from "Pages/DeckLayout";
|
import { DeckContext } from "Pages/DeckLayout";
|
||||||
|
|
||||||
export default function Articles() {
|
export default function Articles() {
|
||||||
const data = useArticles();
|
const data = useArticles();
|
||||||
const deck = useContext(DeckContext);
|
const deck = useContext(DeckContext);
|
||||||
const related = useReactions("articles:reactions", data.data?.map(v => NostrLink.fromEvent(v)) ?? []);
|
const related = useReactions(
|
||||||
|
"articles:reactions",
|
||||||
|
data.data?.map(v => NostrLink.fromEvent(v)) ?? [],
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { NostrEvent, NostrLink } from "@snort/system";
|
import { NostrEvent, NostrLink } from "@snort/system";
|
||||||
|
import { useEventFeed } from "@snort/system-react";
|
||||||
|
|
||||||
import { findTag } from "SnortUtils";
|
import { findTag } from "SnortUtils";
|
||||||
import { useEventFeed } from "Feed/EventFeed";
|
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import Reveal from "Element/Event/Reveal";
|
import Reveal from "Element/Event/Reveal";
|
||||||
import { MediaElement } from "Element/Embed/MediaElement";
|
import { MediaElement } from "Element/Embed/MediaElement";
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEventFeed } from "Feed/EventFeed";
|
|
||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
|
import { useEventFeed } from "@snort/system-react";
|
||||||
|
|
||||||
import Note from "Element/Event/Note";
|
import Note from "Element/Event/Note";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
|
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import "./Timeline.css";
|
import "./Timeline.css";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { TaggedNostrEvent, EventKind } from "@snort/system";
|
||||||
import { TaggedNostrEvent, EventKind, u256 } from "@snort/system";
|
|
||||||
|
|
||||||
import Icon from "Icons/Icon";
|
|
||||||
import { dedupeByPubkey, findTag } from "SnortUtils";
|
import { dedupeByPubkey, findTag } from "SnortUtils";
|
||||||
import ProfileImage from "Element/User/ProfileImage";
|
|
||||||
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
|
import useTimelineFeed, { TimelineFeed, TimelineSubject } from "Feed/TimelineFeed";
|
||||||
import Note from "Element/Event/Note";
|
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { LiveStreams } from "Element/LiveStreams";
|
import { LiveStreams } from "Element/LiveStreams";
|
||||||
|
import { TimelineRenderer } from "./TimelineFragment";
|
||||||
|
|
||||||
export interface TimelineProps {
|
export interface TimelineProps {
|
||||||
postsOnly: boolean;
|
postsOnly: boolean;
|
||||||
@ -37,8 +34,6 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
const feed: TimelineFeed = useTimelineFeed(props.subject, feedOptions);
|
const feed: TimelineFeed = useTimelineFeed(props.subject, feedOptions);
|
||||||
|
|
||||||
const { muted, isMuted } = useModeration();
|
const { muted, isMuted } = useModeration();
|
||||||
const { ref, inView } = useInView();
|
|
||||||
|
|
||||||
const filterPosts = useCallback(
|
const filterPosts = useCallback(
|
||||||
(nts: readonly TaggedNostrEvent[]) => {
|
(nts: readonly TaggedNostrEvent[]) => {
|
||||||
const a = [...nts.filter(a => a.kind !== EventKind.LiveEvent)];
|
const a = [...nts.filter(a => a.kind !== EventKind.LiveEvent)];
|
||||||
@ -56,12 +51,6 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
const latestFeed = useMemo(() => {
|
const latestFeed = useMemo(() => {
|
||||||
return filterPosts(feed.latest ?? []).filter(a => !mainFeed.some(b => b.id === a.id));
|
return filterPosts(feed.latest ?? []).filter(a => !mainFeed.some(b => b.id === a.id));
|
||||||
}, [feed, filterPosts]);
|
}, [feed, filterPosts]);
|
||||||
const relatedFeed = useCallback(
|
|
||||||
(id: u256) => {
|
|
||||||
return (feed.related ?? []).filter(a => findTag(a, "e") === id);
|
|
||||||
},
|
|
||||||
[feed.related],
|
|
||||||
);
|
|
||||||
const liveStreams = useMemo(() => {
|
const liveStreams = useMemo(() => {
|
||||||
return (feed.main ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
return (feed.main ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
||||||
}, [feed]);
|
}, [feed]);
|
||||||
@ -80,42 +69,12 @@ const Timeline = (props: TimelineProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LiveStreams evs={liveStreams} />
|
<LiveStreams evs={liveStreams} />
|
||||||
{latestFeed.length > 0 && (
|
<TimelineRenderer
|
||||||
<>
|
frags={[{ events: mainFeed }]}
|
||||||
<div className="card latest-notes" onClick={() => onShowLatest()} ref={ref}>
|
related={feed.related ?? []}
|
||||||
{latestAuthors.slice(0, 3).map(p => {
|
latest={latestAuthors}
|
||||||
return <ProfileImage pubkey={p} showUsername={false} link={""} showProfileCard={false} />;
|
showLatest={t => onShowLatest(t)}
|
||||||
})}
|
/>
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
|
||||||
values={{ n: latestFeed.length }}
|
|
||||||
/>
|
|
||||||
<Icon name="arrowUp" />
|
|
||||||
</div>
|
|
||||||
{!inView && (
|
|
||||||
<div className="card latest-notes latest-notes-fixed pointer fade-in" onClick={() => onShowLatest(true)}>
|
|
||||||
{latestAuthors.slice(0, 3).map(p => {
|
|
||||||
return <ProfileImage pubkey={p} showUsername={false} link={""} showProfileCard={false} />;
|
|
||||||
})}
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
|
||||||
values={{ n: latestFeed.length }}
|
|
||||||
/>
|
|
||||||
<Icon name="arrowUp" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{mainFeed.map(e => (
|
|
||||||
<Note
|
|
||||||
key={e.id}
|
|
||||||
searchedValue={props.subject.discriminator}
|
|
||||||
data={e}
|
|
||||||
related={relatedFeed(e.id)}
|
|
||||||
ignoreModeration={props.ignoreModeration}
|
|
||||||
depth={0}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{(props.loadMore === undefined || props.loadMore === true) && (
|
{(props.loadMore === undefined || props.loadMore === true) && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button type="button" onClick={() => feed.loadMore()}>
|
<button type="button" onClick={() => feed.loadMore()}>
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import "./Timeline.css";
|
import "./Timeline.css";
|
||||||
import { ReactNode, useCallback, useContext, useMemo, useState, useSyncExternalStore } from "react";
|
import { ReactNode, useCallback, useContext, useMemo, useState, useSyncExternalStore } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { TaggedNostrEvent, EventKind, u256, NostrEvent, NostrLink } from "@snort/system";
|
import { EventKind, NostrEvent, NostrLink } from "@snort/system";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext, useReactions } from "@snort/system-react";
|
||||||
import { useInView } from "react-intersection-observer";
|
|
||||||
|
|
||||||
import { dedupeByPubkey, findTag, orderDescending } from "SnortUtils";
|
import { dedupeByPubkey, findTag, orderDescending } from "SnortUtils";
|
||||||
import Note from "Element/Event/Note";
|
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { FollowsFeed } from "Cache";
|
import { FollowsFeed } from "Cache";
|
||||||
import { LiveStreams } from "Element/LiveStreams";
|
import { LiveStreams } from "Element/LiveStreams";
|
||||||
import { useReactions } from "Feed/Reactions";
|
|
||||||
import AsyncButton from "../AsyncButton";
|
import AsyncButton from "../AsyncButton";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import ProfileImage from "Element/User/ProfileImage";
|
import { TimelineRenderer } from "./TimelineFragment";
|
||||||
import Icon from "Icons/Icon";
|
|
||||||
|
|
||||||
export interface TimelineFollowsProps {
|
export interface TimelineFollowsProps {
|
||||||
postsOnly: boolean;
|
postsOnly: boolean;
|
||||||
@ -37,11 +33,12 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
const reactions = useReactions(
|
const reactions = useReactions(
|
||||||
"follows-feed-reactions",
|
"follows-feed-reactions",
|
||||||
feed.map(a => NostrLink.fromEvent(a)),
|
feed.map(a => NostrLink.fromEvent(a)),
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { muted, isMuted } = useModeration();
|
const { muted, isMuted } = useModeration();
|
||||||
const { ref, inView } = useInView();
|
|
||||||
|
|
||||||
const sortedFeed = useMemo(() => orderDescending(feed), [feed]);
|
const sortedFeed = useMemo(() => orderDescending(feed), [feed]);
|
||||||
|
|
||||||
@ -63,13 +60,6 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
return filterPosts((sortedFeed ?? []).filter(a => a.created_at > latest));
|
return filterPosts((sortedFeed ?? []).filter(a => a.created_at > latest));
|
||||||
}, [sortedFeed, latest]);
|
}, [sortedFeed, latest]);
|
||||||
|
|
||||||
const relatedFeed = useCallback(
|
|
||||||
(id: u256) => {
|
|
||||||
return (reactions?.data ?? []).filter(a => findTag(a, "e") === id);
|
|
||||||
},
|
|
||||||
[reactions],
|
|
||||||
);
|
|
||||||
|
|
||||||
const liveStreams = useMemo(() => {
|
const liveStreams = useMemo(() => {
|
||||||
return (sortedFeed ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
return (sortedFeed ?? []).filter(a => a.kind === EventKind.LiveEvent && findTag(a, "status") === "live");
|
||||||
}, [sortedFeed]);
|
}, [sortedFeed]);
|
||||||
@ -88,44 +78,18 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(props.liveStreams ?? true) && <LiveStreams evs={liveStreams} />}
|
{(props.liveStreams ?? true) && <LiveStreams evs={liveStreams} />}
|
||||||
{latestFeed.length > 0 && (
|
<TimelineRenderer
|
||||||
<>
|
frags={[
|
||||||
<div className="card latest-notes" onClick={() => onShowLatest()} ref={ref}>
|
{
|
||||||
{latestAuthors.slice(0, 3).map(p => {
|
events: mainFeed,
|
||||||
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowingMark={false} />;
|
},
|
||||||
})}
|
]}
|
||||||
<FormattedMessage
|
related={reactions.data ?? []}
|
||||||
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
latest={latestAuthors}
|
||||||
values={{ n: latestFeed.length }}
|
showLatest={t => onShowLatest(t)}
|
||||||
/>
|
noteOnClick={props.noteOnClick}
|
||||||
<Icon name="arrowUp" />
|
noteRenderer={props.noteRenderer}
|
||||||
</div>
|
/>
|
||||||
{!inView && (
|
|
||||||
<div className="card latest-notes latest-notes-fixed pointer fade-in" onClick={() => onShowLatest(true)}>
|
|
||||||
{latestAuthors.slice(0, 3).map(p => {
|
|
||||||
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowingMark={false} />;
|
|
||||||
})}
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
|
||||||
values={{ n: latestFeed.length }}
|
|
||||||
/>
|
|
||||||
<Icon name="arrowUp" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{mainFeed.map(
|
|
||||||
a =>
|
|
||||||
props.noteRenderer?.(a) ?? (
|
|
||||||
<Note
|
|
||||||
data={a as TaggedNostrEvent}
|
|
||||||
related={relatedFeed(a.id)}
|
|
||||||
key={a.id}
|
|
||||||
depth={0}
|
|
||||||
onClick={props.noteOnClick}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
<div className="flex items-center p">
|
<div className="flex items-center p">
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
81
packages/app/src/Element/Feed/TimelineFragment.tsx
Normal file
81
packages/app/src/Element/Feed/TimelineFragment.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { TaggedNostrEvent } from "@snort/system";
|
||||||
|
import Note from "Element/Event/Note";
|
||||||
|
import ProfileImage from "Element/User/ProfileImage";
|
||||||
|
import Icon from "Icons/Icon";
|
||||||
|
import { findTag } from "SnortUtils";
|
||||||
|
import { ReactNode, useCallback } from "react";
|
||||||
|
import { useInView } from "react-intersection-observer";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
export interface TimelineFragment {
|
||||||
|
events: Array<TaggedNostrEvent>;
|
||||||
|
title?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimelineFragmentProps {
|
||||||
|
frags: Array<TimelineFragment>;
|
||||||
|
related: Array<TaggedNostrEvent>;
|
||||||
|
/**
|
||||||
|
* List of pubkeys who have posted recently
|
||||||
|
*/
|
||||||
|
latest: Array<string>;
|
||||||
|
showLatest: (toTop: boolean) => void;
|
||||||
|
noteRenderer?: (ev: TaggedNostrEvent) => ReactNode;
|
||||||
|
noteOnClick?: (ev: TaggedNostrEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TimelineRenderer(props: TimelineFragmentProps) {
|
||||||
|
const { ref, inView } = useInView();
|
||||||
|
const relatedFeed = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
return props.related.filter(a => findTag(a, "e") === id);
|
||||||
|
},
|
||||||
|
[props.related],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.latest.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="card latest-notes" onClick={() => props.showLatest(false)} ref={ref}>
|
||||||
|
{props.latest.slice(0, 3).map(p => {
|
||||||
|
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowingMark={false} />;
|
||||||
|
})}
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
||||||
|
values={{ n: props.latest.length }}
|
||||||
|
/>
|
||||||
|
<Icon name="arrowUp" />
|
||||||
|
</div>
|
||||||
|
{!inView && (
|
||||||
|
<div
|
||||||
|
className="card latest-notes latest-notes-fixed pointer fade-in"
|
||||||
|
onClick={() => props.showLatest(true)}>
|
||||||
|
{props.latest.slice(0, 3).map(p => {
|
||||||
|
return <ProfileImage pubkey={p} showUsername={false} link={""} showFollowingMark={false} />;
|
||||||
|
})}
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
|
||||||
|
values={{ n: props.latest.length }}
|
||||||
|
/>
|
||||||
|
<Icon name="arrowUp" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{props.frags.map(f => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{f.title}
|
||||||
|
{f.events.map(
|
||||||
|
e =>
|
||||||
|
props.noteRenderer?.(e) ?? (
|
||||||
|
<Note data={e} related={relatedFeed(e.id)} key={e.id} depth={0} onClick={props.noteOnClick} />
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { NostrEvent, NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { NostrEvent, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
|
import { useReactions } from "@snort/system-react";
|
||||||
|
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import Note from "Element/Event/Note";
|
import Note from "Element/Event/Note";
|
||||||
import NostrBandApi from "External/NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
import { useReactions } from "Feed/Reactions";
|
|
||||||
import { ErrorOrOffline } from "Element/ErrorOrOffline";
|
import { ErrorOrOffline } from "Element/ErrorOrOffline";
|
||||||
|
|
||||||
export default function TrendingNotes() {
|
export default function TrendingNotes() {
|
||||||
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
const [posts, setPosts] = useState<Array<NostrEvent>>();
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const related = useReactions("trending", posts?.map(a => NostrLink.fromEvent(a)) ?? []);
|
const related = useReactions("trending", posts?.map(a => NostrLink.fromEvent(a)) ?? [], undefined, true);
|
||||||
|
|
||||||
async function loadTrendingNotes() {
|
async function loadTrendingNotes() {
|
||||||
const api = new NostrBandApi();
|
const api = new NostrBandApi();
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { EventKind, NostrLink, RequestBuilder, NoteCollection, EventExt } from "@snort/system";
|
import { EventKind, NostrLink, RequestBuilder, NoteCollection, EventExt } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useReactions, useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { useReactions } from "./Reactions";
|
|
||||||
|
|
||||||
export default function useThreadFeed(link: NostrLink) {
|
export default function useThreadFeed(link: NostrLink) {
|
||||||
const [root, setRoot] = useState<NostrLink>();
|
const [root, setRoot] = useState<NostrLink>();
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { EventKind, NostrLink, NoteCollection, RequestBuilder } from "@snort/system";
|
import { EventKind, NostrLink, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useReactions, useRequestBuilder } from "@snort/system-react";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
|
|
||||||
import useTimelineWindow from "Hooks/useTimelineWindow";
|
import useTimelineWindow from "Hooks/useTimelineWindow";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { SearchRelays } from "Const";
|
import { SearchRelays } from "Const";
|
||||||
import { useReactions } from "./Reactions";
|
|
||||||
|
|
||||||
export interface TimelineFeedOptions {
|
export interface TimelineFeedOptions {
|
||||||
method: "TIME_RANGE" | "LIMIT_UNTIL";
|
method: "TIME_RANGE" | "LIMIT_UNTIL";
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { dedupe, unwrap } from "@snort/shared";
|
import { dedupe, unwrap } from "@snort/shared";
|
||||||
import { EventKind, parseNostrLink } from "@snort/system";
|
import { EventKind, parseNostrLink } from "@snort/system";
|
||||||
|
import { useEventFeed } from "@snort/system-react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { Hour } from "Const";
|
import { Hour } from "Const";
|
||||||
import Timeline from "Element/Feed/Timeline";
|
import Timeline from "Element/Feed/Timeline";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import { useEventFeed } from "Feed/EventFeed";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
export function ListFeedPage() {
|
export function ListFeedPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
@ -4,7 +4,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
|||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { NostrLink, TLVEntryType, UserMetadata, decodeTLV } from "@snort/system";
|
import { NostrLink, TLVEntryType, UserMetadata, decodeTLV } from "@snort/system";
|
||||||
import { useUserProfile, useUserSearch } from "@snort/system-react";
|
import { useEventFeed, useUserProfile, useUserSearch } from "@snort/system-react";
|
||||||
|
|
||||||
import UnreadCount from "Element/UnreadCount";
|
import UnreadCount from "Element/UnreadCount";
|
||||||
import ProfileImage from "Element/User/ProfileImage";
|
import ProfileImage from "Element/User/ProfileImage";
|
||||||
@ -21,7 +21,6 @@ import Text from "Element/Text";
|
|||||||
import { Chat, ChatType, createChatLink, useChatSystem } from "chat";
|
import { Chat, ChatType, createChatLink, useChatSystem } from "chat";
|
||||||
import Modal from "Element/Modal";
|
import Modal from "Element/Modal";
|
||||||
import ProfilePreview from "Element/User/ProfilePreview";
|
import ProfilePreview from "Element/User/ProfilePreview";
|
||||||
import { useEventFeed } from "Feed/EventFeed";
|
|
||||||
import { LoginSession, LoginStore } from "Login";
|
import { LoginSession, LoginStore } from "Login";
|
||||||
import { Nip28ChatSystem } from "chat/nip28";
|
import { Nip28ChatSystem } from "chat/nip28";
|
||||||
import { ChatParticipantProfile } from "Element/Chat/ChatParticipant";
|
import { ChatParticipantProfile } from "Element/Chat/ChatParticipant";
|
||||||
|
@ -2,7 +2,7 @@ import "./Notifications.css";
|
|||||||
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
||||||
import { EventExt, EventKind, NostrEvent, NostrLink, NostrPrefix, TaggedNostrEvent, parseZap } from "@snort/system";
|
import { EventExt, EventKind, NostrEvent, NostrLink, NostrPrefix, TaggedNostrEvent, parseZap } from "@snort/system";
|
||||||
import { unixNow, unwrap } from "@snort/shared";
|
import { unixNow, unwrap } from "@snort/shared";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useEventFeed, useUserProfile } from "@snort/system-react";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -15,7 +15,6 @@ import { dedupe, findTag, orderAscending, orderDescending, getDisplayName } from
|
|||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import ProfileImage from "Element/User/ProfileImage";
|
import ProfileImage from "Element/User/ProfileImage";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { useEventFeed } from "Feed/EventFeed";
|
|
||||||
import Text from "Element/Text";
|
import Text from "Element/Text";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
import { LiveEvent } from "Element/LiveEvent";
|
import { LiveEvent } from "Element/LiveEvent";
|
||||||
|
@ -5,3 +5,5 @@ export * from "./useSystemState";
|
|||||||
export * from "./useUserProfile";
|
export * from "./useUserProfile";
|
||||||
export * from "./useUserSearch";
|
export * from "./useUserSearch";
|
||||||
export * from "./useEventReactions";
|
export * from "./useEventReactions";
|
||||||
|
export * from "./useReactions";
|
||||||
|
export * from "./useEventFeed";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { RequestBuilder, ReplaceableNoteStore, NostrLink, NoteCollection } from "@snort/system";
|
import { RequestBuilder, ReplaceableNoteStore, NostrLink, NoteCollection } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "./useRequestBuilder";
|
||||||
|
|
||||||
export function useEventFeed(link: NostrLink) {
|
export function useEventFeed(link: NostrLink) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
@ -1,13 +1,16 @@
|
|||||||
import { RequestBuilder, EventKind, NoteCollection, NostrLink } from "@snort/system";
|
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
|
||||||
import useLogin from "Hooks/useLogin";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { RequestBuilder, EventKind, NoteCollection, NostrLink } from "@snort/system";
|
||||||
|
import { useRequestBuilder } from "./useRequestBuilder";
|
||||||
|
|
||||||
export function useReactions(subId: string, ids: Array<NostrLink>, others?: (rb: RequestBuilder) => void) {
|
export function useReactions(
|
||||||
const { preferences: pref } = useLogin();
|
subId: string,
|
||||||
|
ids: Array<NostrLink>,
|
||||||
|
others?: (rb: RequestBuilder) => void,
|
||||||
|
leaveOpen?: boolean,
|
||||||
|
) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const rb = new RequestBuilder(subId);
|
const rb = new RequestBuilder(subId);
|
||||||
|
rb.withOptions({ leaveOpen });
|
||||||
|
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const grouped = ids.reduce(
|
const grouped = ids.reduce(
|
||||||
@ -20,13 +23,7 @@ export function useReactions(subId: string, ids: Array<NostrLink>, others?: (rb:
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const [, v] of Object.entries(grouped)) {
|
for (const [, v] of Object.entries(grouped)) {
|
||||||
rb.withFilter()
|
rb.withFilter().kinds([EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]).replyToLink(v);
|
||||||
.kinds(
|
|
||||||
pref.enableReactions
|
|
||||||
? [EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]
|
|
||||||
: [EventKind.ZapReceipt, EventKind.Repost],
|
|
||||||
)
|
|
||||||
.replyToLink(v);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
others?.(rb);
|
others?.(rb);
|
Loading…
Reference in New Issue
Block a user