diff --git a/.changeset/proud-pillows-promise.md b/.changeset/proud-pillows-promise.md new file mode 100644 index 000000000..b96346ffd --- /dev/null +++ b/.changeset/proud-pillows-promise.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Allow user to select people list for home feed diff --git a/src/app.tsx b/src/app.tsx index 8b66c69cc..3ebb21995 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -4,12 +4,10 @@ import { Spinner } from "@chakra-ui/react"; import { ErrorBoundary } from "./components/error-boundary"; import Layout from "./components/layout"; -import HomeView from "./views/home"; +import HomeView from "./views/home/index"; import SettingsView from "./views/settings"; import LoginView from "./views/login"; import ProfileView from "./views/profile"; -import FollowingTab from "./views/home/following-tab"; -import GlobalTab from "./views/home/global-tab"; import HashTagView from "./views/hashtag"; import UserView from "./views/user"; import UserNotesTab from "./views/user/notes"; @@ -137,11 +135,6 @@ const router = createHashRouter([ { path: "", element: , - children: [ - { path: "", element: }, - { path: "following", element: }, - { path: "global", element: }, - ], }, ], }, diff --git a/src/classes/subject.ts b/src/classes/subject.ts index a4b3aece0..eeebd9f25 100644 --- a/src/classes/subject.ts +++ b/src/classes/subject.ts @@ -37,13 +37,15 @@ export class Subject implements Connectable { }); } - subscribe(listener: ListenerFn, ctx?: Object) { + subscribe(listener: ListenerFn, ctx?: Object, initCall = true) { if (!this.findListener(listener, ctx)) { this.listeners.push([listener, ctx]); - if (this.value !== undefined) { - if (ctx) listener.call(ctx, this.value); - else listener(this.value); + if (initCall) { + if (this.value !== undefined) { + if (ctx) listener.call(ctx, this.value); + else listener(this.value); + } } } return this; diff --git a/src/components/people-list-selection/people-list-selection.tsx b/src/components/people-list-selection/people-list-selection.tsx index 6df25d1e4..3bd43b86f 100644 --- a/src/components/people-list-selection/people-list-selection.tsx +++ b/src/components/people-list-selection/people-list-selection.tsx @@ -1,46 +1,57 @@ -import { Select, SelectProps } from "@chakra-ui/react"; -import { usePeopleListContext } from "./people-list-provider"; -import useUserLists from "../../hooks/use-user-lists"; -import { useCurrentAccount } from "../../hooks/use-current-account"; -import { getListName } from "../../helpers/nostr/lists"; -import { getEventCoordinate } from "../../helpers/nostr/events"; +import { + Button, + ButtonProps, + Menu, + MenuButton, + MenuDivider, + MenuItemOption, + MenuList, + MenuOptionGroup, +} from "@chakra-ui/react"; import { Kind } from "nostr-tools"; -function UserListOptions() { - const account = useCurrentAccount()!; - const lists = useUserLists(account?.pubkey); - - return ( - <> - {lists.map((list) => ( - - ))} - - ); -} +import { usePeopleListContext } from "../../providers/people-list-provider"; +import useUserLists from "../../hooks/use-user-lists"; +import { useCurrentAccount } from "../../hooks/use-current-account"; +import { PEOPLE_LIST_KIND, getListName } from "../../helpers/nostr/lists"; +import { getEventCoordinate } from "../../helpers/nostr/events"; export default function PeopleListSelection({ hideGlobalOption = false, ...props }: { hideGlobalOption?: boolean; -} & Omit) { - const account = useCurrentAccount()!; - const { list, setList } = usePeopleListContext(); +} & Omit) { + const account = useCurrentAccount(); + const lists = useUserLists(account?.pubkey); + const { list, setList, listEvent } = usePeopleListContext(); + + const handleSelect = (value: string | string[]) => { + console.log(value); + if (typeof value === "string") { + setList(value); + } + }; return ( - + + + {listEvent ? getListName(listEvent) : list === "global" ? "Global" : "Following"} + + + + {account && Following} + {!hideGlobalOption && Global} + {account && } + {lists + .filter((l) => l.kind === PEOPLE_LIST_KIND) + .map((list) => ( + + {getListName(list)} + + ))} + + + ); } diff --git a/src/components/relay-selection/relay-selection-button.tsx b/src/components/relay-selection/relay-selection-button.tsx index df0393f6f..60bdc8d6f 100644 --- a/src/components/relay-selection/relay-selection-button.tsx +++ b/src/components/relay-selection/relay-selection-button.tsx @@ -1,15 +1,20 @@ -import { Button, ButtonProps } from "@chakra-ui/react"; +import { Button, ButtonProps, useDisclosure } from "@chakra-ui/react"; import { RelayIcon } from "../icons"; import { useRelaySelectionContext } from "../../providers/relay-selection-provider"; +import RelaySelectionModal from "./relay-selection-modal"; export default function RelaySelectionButton({ ...props }: ButtonProps) { - const { openModal, relays } = useRelaySelectionContext(); + const relaysModal = useDisclosure(); + const { setSelected, relays } = useRelaySelectionContext(); return ( <> - + {relaysModal.isOpen && ( + + )} ); } diff --git a/src/components/user-follow-button.tsx b/src/components/user-follow-button.tsx index beaf2414c..b81a211a0 100644 --- a/src/components/user-follow-button.tsx +++ b/src/components/user-follow-button.tsx @@ -40,7 +40,7 @@ function UsersLists({ pubkey }: { pubkey: string }) { const [isLoading, setLoading] = useState(false); const newListModal = useDisclosure(); - const lists = useUserLists(pubkey); + const lists = useUserLists(account.pubkey); const inLists = lists.filter((list) => getPubkeysFromList(list).some((p) => p.pubkey === pubkey)); diff --git a/src/hooks/use-subject.ts b/src/hooks/use-subject.ts index a7c4c32ad..06a062dd9 100644 --- a/src/hooks/use-subject.ts +++ b/src/hooks/use-subject.ts @@ -7,12 +7,9 @@ function useSubject(subject?: Subject): Value | un function useSubject(subject?: Subject) { const [_, setValue] = useState(subject?.value); useEffect(() => { - const handler = (value: Value) => setValue(value); - setValue(subject?.value); - subject?.subscribe(handler); - + subject?.subscribe(setValue, undefined, false); return () => { - subject?.unsubscribe(handler); + subject?.unsubscribe(setValue, undefined); }; }, [subject, setValue]); diff --git a/src/hooks/use-user-lists.ts b/src/hooks/use-user-lists.ts index 0cd70ccb8..a7d8916a1 100644 --- a/src/hooks/use-user-lists.ts +++ b/src/hooks/use-user-lists.ts @@ -3,12 +3,17 @@ import { useReadRelayUrls } from "./use-client-relays"; import useSubject from "./use-subject"; import useTimelineLoader from "./use-timeline-loader"; -export default function useUserLists(pubkey: string, additionalRelays: string[] = []) { +export default function useUserLists(pubkey?: string, additionalRelays: string[] = []) { const readRelays = useReadRelayUrls(additionalRelays); - const timeline = useTimelineLoader(`${pubkey}-lists`, readRelays, { - authors: [pubkey], - kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND], - }); + const timeline = useTimelineLoader( + `${pubkey}-lists`, + readRelays, + { + authors: pubkey ? [pubkey] : [], + kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND], + }, + { enabled: !!pubkey }, + ); return useSubject(timeline.timeline); } diff --git a/src/components/people-list-selection/people-list-provider.tsx b/src/providers/people-list-provider.tsx similarity index 75% rename from src/components/people-list-selection/people-list-provider.tsx rename to src/providers/people-list-provider.tsx index d5ffe1669..f09d1c825 100644 --- a/src/components/people-list-selection/people-list-provider.tsx +++ b/src/providers/people-list-provider.tsx @@ -1,12 +1,14 @@ import { PropsWithChildren, createContext, useContext, useMemo, useState } from "react"; import { Kind } from "nostr-tools"; -import { useCurrentAccount } from "../../hooks/use-current-account"; -import { getPubkeysFromList } from "../../helpers/nostr/lists"; -import useReplaceableEvent from "../../hooks/use-replaceable-event"; +import { useCurrentAccount } from "../hooks/use-current-account"; +import { getPubkeysFromList } from "../helpers/nostr/lists"; +import useReplaceableEvent from "../hooks/use-replaceable-event"; +import { NostrEvent } from "../types/nostr-event"; export type PeopleListContextType = { list?: string; + listEvent?: NostrEvent; people: { pubkey: string; relay?: string }[] | undefined; setList: (list: string | undefined) => void; }; @@ -26,9 +28,10 @@ export default function PeopleListProvider({ children }: PropsWithChildren) { () => ({ people, list: listCord, + listEvent, setList, }), - [listCord, setList, people], + [listCord, setList, people, listEvent], ); return {children}; diff --git a/src/providers/relay-selection-provider.tsx b/src/providers/relay-selection-provider.tsx index aaff95aa9..ef33eefbe 100644 --- a/src/providers/relay-selection-provider.tsx +++ b/src/providers/relay-selection-provider.tsx @@ -1,20 +1,17 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { useReadRelayUrls } from "../hooks/use-client-relays"; -import { useDisclosure } from "@chakra-ui/react"; -import RelaySelectionModal from "../components/relay-selection/relay-selection-modal"; -import { unique } from "../helpers/array"; import { useLocation, useNavigate } from "react-router-dom"; +import { useReadRelayUrls } from "../hooks/use-client-relays"; +import { unique } from "../helpers/array"; + type RelaySelectionContextType = { relays: string[]; setSelected: (relays: string[]) => void; - openModal: () => void; }; export const RelaySelectionContext = createContext({ relays: [], setSelected: () => {}, - openModal: () => {}, }); export function useRelaySelectionContext() { @@ -34,7 +31,6 @@ export default function RelaySelectionProvider({ overrideDefault, additionalDefaults, }: RelaySelectionProviderProps) { - const relaysModal = useDisclosure(); const { state } = useLocation(); const navigate = useNavigate(); @@ -44,19 +40,22 @@ export default function RelaySelectionProvider({ if (overrideDefault) return overrideDefault; if (additionalDefaults) return unique([...userReadRelays, ...additionalDefaults]); return userReadRelays; - }, [state?.relays, overrideDefault, userReadRelays, additionalDefaults]); + }, [state?.relays, overrideDefault, userReadRelays.join("|"), additionalDefaults]); - const setSelected = useCallback((relays: string[]) => { - navigate(".", { state: { relays }, replace: true }); - }, []); - - return ( - - {children} - - {relaysModal.isOpen && ( - - )} - + const setSelected = useCallback( + (relays: string[]) => { + navigate(".", { state: { relays }, replace: true }); + }, + [navigate], ); + + const context = useMemo( + () => ({ + relays, + setSelected, + }), + [relays.join("|"), setSelected], + ); + + return {children}; } diff --git a/src/providers/trust.tsx b/src/providers/trust.tsx index b03a6d551..9ea453287 100644 --- a/src/providers/trust.tsx +++ b/src/providers/trust.tsx @@ -18,8 +18,8 @@ export function TrustProvider({ const parentTrust = useContext(TrustContext); const account = useCurrentAccount(); - const contacts = useUserContactList(account?.pubkey) - const following = contacts ? getPubkeysFromList(contacts).map(p => p.pubkey) : [] + const contactList = useUserContactList(account?.pubkey); + const following = contactList ? getPubkeysFromList(contactList).map((p) => p.pubkey) : []; const isEventTrusted = trust || (!!event && (event.pubkey === account?.pubkey || following.includes(event.pubkey))); diff --git a/src/services/relay-info.ts b/src/services/relay-info.ts index 81dd85ffc..266ce7957 100644 --- a/src/services/relay-info.ts +++ b/src/services/relay-info.ts @@ -1,11 +1,12 @@ import db from "./db"; import { fetchWithCorsFallback } from "../helpers/cors"; +import { isHex } from "../helpers/nip19"; export type RelayInformationDocument = { name: string; description: string; icon?: string; - pubkey: string; + pubkey?: string; contact: string; supported_nips?: number[]; software: string; @@ -13,6 +14,13 @@ export type RelayInformationDocument = { payments_url?: string; }; +function sanitizeInfo(info: RelayInformationDocument) { + if (info.pubkey && !isHex(info.pubkey)) { + delete info.pubkey; + } + return info; +} + async function fetchInfo(relay: string) { const url = new URL(relay); url.protocol = url.protocol === "ws:" ? "http" : "https"; @@ -21,6 +29,8 @@ async function fetchInfo(relay: string) { (res) => res.json() as Promise, ); + sanitizeInfo(infoDoc); + memoryCache.set(relay, infoDoc); await db.put("relayInfo", infoDoc, relay); diff --git a/src/views/home/following-tab.tsx b/src/views/home/following-tab.tsx deleted file mode 100644 index 989d6edf7..000000000 --- a/src/views/home/following-tab.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useCallback } from "react"; -import { Flex, FormControl, FormLabel, Switch } from "@chakra-ui/react"; -import { useSearchParams } from "react-router-dom"; -import { Kind } from "nostr-tools"; - -import { isReply, truncatedId } from "../../helpers/nostr/events"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { useReadRelayUrls } from "../../hooks/use-client-relays"; -import { useCurrentAccount } from "../../hooks/use-current-account"; -import RequireCurrentAccount from "../../providers/require-current-account"; -import { NostrEvent } from "../../types/nostr-event"; -import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page"; -import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type"; -import useUserContactList from "../../hooks/use-user-contact-list"; -import { getPubkeysFromList } from "../../helpers/nostr/lists"; - -function FollowingTabBody() { - const account = useCurrentAccount()!; - const contacts = useUserContactList(account.pubkey); - const [search, setSearch] = useSearchParams(); - const showReplies = search.has("replies"); - const onToggle = () => { - showReplies ? setSearch({}) : setSearch({ replies: "show" }); - }; - - const timelinePageEventFilter = useTimelinePageEventFilter(); - const eventFilter = useCallback( - (event: NostrEvent) => { - if (!showReplies && isReply(event)) return false; - return timelinePageEventFilter(event); - }, - [showReplies, timelinePageEventFilter], - ); - - const following = contacts ? getPubkeysFromList(contacts).map((p) => p.pubkey) : []; - const readRelays = useReadRelayUrls(); - const timeline = useTimelineLoader( - `${truncatedId(account.pubkey)}-following`, - readRelays, - { authors: following, kinds: [Kind.Text, Kind.Repost, 2] }, - { enabled: following.length > 0, eventFilter }, - ); - - const header = ( - - - - Show Replies - - - - - - ); - - return ; -} - -export default function FollowingTab() { - return ( - - - - ); -} diff --git a/src/views/home/global-tab.tsx b/src/views/home/global-tab.tsx deleted file mode 100644 index 2cf873be0..000000000 --- a/src/views/home/global-tab.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useCallback } from "react"; -import { Flex, FormControl, FormLabel, Switch, useDisclosure } from "@chakra-ui/react"; -import { isReply } from "../../helpers/nostr/events"; -import { useAppTitle } from "../../hooks/use-app-title"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { NostrEvent } from "../../types/nostr-event"; -import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; -import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider"; -import useRelaysChanged from "../../hooks/use-relays-changed"; -import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page"; -import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type"; -import { useSearchParams } from "react-router-dom"; -import { safeUrl } from "../../helpers/parse"; - -function GlobalPage() { - const readRelays = useRelaySelectionRelays(); - const { isOpen: showReplies, onToggle } = useDisclosure(); - - useAppTitle("global"); - - const timelineEventFilter = useTimelinePageEventFilter(); - const eventFilter = useCallback( - (event: NostrEvent) => { - if (!showReplies && isReply(event)) return false; - return timelineEventFilter(event); - }, - [showReplies, timelineEventFilter], - ); - const timeline = useTimelineLoader(`global`, readRelays, { kinds: [1] }, { eventFilter }); - useRelaysChanged(readRelays, () => timeline.reset()); - - const header = ( - - - - - - Show Replies - - - - - ); - - return ; -} - -export default function GlobalTab() { - const [params] = useSearchParams(); - - // wrap the global page with another relay selection so it dose not effect the rest of the app - let relays = ["wss://welcome.nostr.wine"]; - - const setRelay = params.get("relay"); - if (setRelay) { - const url = safeUrl(setRelay); - relays = [setRelay]; - } - - return ( - - - - ); -} diff --git a/src/views/home/index.tsx b/src/views/home/index.tsx index 9afaca39a..ef02e64d3 100644 --- a/src/views/home/index.tsx +++ b/src/views/home/index.tsx @@ -1,40 +1,55 @@ -import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; -import { Outlet, useMatches, useNavigate } from "react-router-dom"; +import { useCallback } from "react"; +import { Flex } from "@chakra-ui/react"; +import { Kind } from "nostr-tools"; -const tabs = [ - { label: "Following", path: "/following" }, - { label: "Global", path: "/global" }, -]; +import { isReply, truncatedId } from "../../helpers/nostr/events"; +import useTimelineLoader from "../../hooks/use-timeline-loader"; +import { useCurrentAccount } from "../../hooks/use-current-account"; +import { NostrEvent } from "../../types/nostr-event"; +import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page"; +import TimelineViewTypeButtons from "../../components/timeline-page/timeline-view-type"; +import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; +import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; +import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider"; +import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/relay-selection-provider"; + +function HomePage() { + const timelinePageEventFilter = useTimelinePageEventFilter(); + const eventFilter = useCallback( + (event: NostrEvent) => { + if (isReply(event)) return false; + return timelinePageEventFilter(event); + }, + [timelinePageEventFilter], + ); + + const { relays } = useRelaySelectionContext(); + const { people, list } = usePeopleListContext(); + + const kinds = [Kind.Text, Kind.Repost, 2]; + const query = people && people.length > 0 ? { authors: people.map((p) => p.pubkey), kinds } : { kinds }; + const timeline = useTimelineLoader(`${list}-home-feed`, relays, query, { + enabled: !!people && people.length > 0, + eventFilter, + }); + + const header = ( + + + + + + ); + + return ; +} export default function HomeView() { - const navigate = useNavigate(); - const matches = useMatches(); - - const activeTab = tabs.indexOf(tabs.find((t) => matches[matches.length - 1].pathname === t.path) ?? tabs[0]); - return ( - navigate(tabs[v].path)} - colorScheme="brand" - > - - {tabs.map(({ label }) => ( - {label} - ))} - - - {tabs.map(({ label }) => ( - - - - ))} - - + + + + + ); } diff --git a/src/views/lists/components/list-card.tsx b/src/views/lists/components/list-card.tsx index 1bfe5f63b..9d6edceb3 100644 --- a/src/views/lists/components/list-card.tsx +++ b/src/views/lists/components/list-card.tsx @@ -40,7 +40,7 @@ export default function ListCard({ cord, event: maybeEvent }: { cord?: string; e {people.length} people {people.map(({ pubkey, relay }) => ( - + ))} diff --git a/src/views/messages/chat.tsx b/src/views/messages/chat.tsx index c3894bbf1..446527eb7 100644 --- a/src/views/messages/chat.tsx +++ b/src/views/messages/chat.tsx @@ -15,7 +15,6 @@ import { DraftNostrEvent } from "../../types/nostr-event"; import RequireCurrentAccount from "../../providers/require-current-account"; import { Message } from "./message"; import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { truncatedId } from "../../helpers/nostr/events"; import { useCurrentAccount } from "../../hooks/use-current-account"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import IntersectionObserverProvider from "../../providers/intersection-observer"; @@ -31,7 +30,7 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) { const readRelays = useReadRelayUrls(); - const timeline = useTimelineLoader(`${truncatedId(pubkey)}-${truncatedId(account.pubkey)}-messages`, readRelays, [ + const timeline = useTimelineLoader(`${pubkey}-${account.pubkey}-messages`, readRelays, [ { kinds: [Kind.EncryptedDirectMessage], "#p": [account.pubkey], diff --git a/src/views/streams/components/status-badge.tsx b/src/views/streams/components/status-badge.tsx index a0bf4eced..4c578a4ae 100644 --- a/src/views/streams/components/status-badge.tsx +++ b/src/views/streams/components/status-badge.tsx @@ -1,12 +1,12 @@ -import { Badge } from "@chakra-ui/react"; +import { Badge, BadgeProps } from "@chakra-ui/react"; import { ParsedStream } from "../../../helpers/nostr/stream"; -export default function StreamStatusBadge({ stream }: { stream: ParsedStream }) { +export default function StreamStatusBadge({ stream, ...props }: { stream: ParsedStream } & Omit) { switch (stream.status) { case "live": - return live; + return live; case "ended": - return ended; + return ended; } return null; } diff --git a/src/views/streams/components/stream-card.tsx b/src/views/streams/components/stream-card.tsx index ff07278a5..054d766bf 100644 --- a/src/views/streams/components/stream-card.tsx +++ b/src/views/streams/components/stream-card.tsx @@ -1,32 +1,16 @@ -import { useRef } from "react"; +import { memo, useRef } from "react"; +import dayjs from "dayjs"; +import { Box, Card, CardBody, CardProps, Flex, Heading, LinkBox, LinkOverlay, Tag, Text } from "@chakra-ui/react"; + import { ParsedStream } from "../../../helpers/nostr/stream"; -import { - Badge, - Card, - CardBody, - CardFooter, - CardProps, - Divider, - Flex, - Heading, - Image, - LinkBox, - LinkOverlay, - Spacer, - Tag, - Text, -} from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; import { UserAvatar } from "../../../components/user-avatar"; import { UserLink } from "../../../components/user-link"; -import dayjs from "dayjs"; import StreamStatusBadge from "./status-badge"; -import { EventRelays } from "../../../components/note/note-relays"; import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer"; import useEventNaddr from "../../../hooks/use-event-naddr"; -import StreamDebugButton from "./stream-debug-button"; -export default function StreamCard({ stream, ...props }: CardProps & { stream: ParsedStream }) { +function StreamCard({ stream, ...props }: CardProps & { stream: ParsedStream }) { const { title, image } = stream; // if there is a parent intersection observer, register this card @@ -36,9 +20,17 @@ export default function StreamCard({ stream, ...props }: CardProps & { stream: P const naddr = useEventNaddr(stream.event, stream.relays); return ( - + - {image && {title}} + + @@ -59,13 +51,7 @@ export default function StreamCard({ stream, ...props }: CardProps & { stream: P )} {stream.starts && Started: {dayjs.unix(stream.starts).fromNow()}} - - - - - - - ); } +export default memo(StreamCard); diff --git a/src/views/streams/index.tsx b/src/views/streams/index.tsx index 408750dbd..4d1080e0c 100644 --- a/src/views/streams/index.tsx +++ b/src/views/streams/index.tsx @@ -1,34 +1,20 @@ -import { useCallback, useState } from "react"; -import { Code, Flex, Select, SimpleGrid } from "@chakra-ui/react"; +import { Divider, Flex, Heading, Select, SimpleGrid } from "@chakra-ui/react"; import useTimelineLoader from "../../hooks/use-timeline-loader"; import IntersectionObserverProvider from "../../providers/intersection-observer"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import useSubject from "../../hooks/use-subject"; import StreamCard from "./components/stream-card"; -import { STREAM_KIND, parseStreamEvent } from "../../helpers/nostr/stream"; -import { NostrEvent } from "../../types/nostr-event"; +import { STREAM_KIND } from "../../helpers/nostr/stream"; import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; import RelaySelectionProvider, { useRelaySelectionRelays } from "../../providers/relay-selection-provider"; import useRelaysChanged from "../../hooks/use-relays-changed"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; -import PeopleListProvider, { usePeopleListContext } from "../../components/people-list-selection/people-list-provider"; +import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider"; import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status"; import useParsedStreams from "../../hooks/use-parsed-streams"; function StreamsPage() { const relays = useRelaySelectionRelays(); - const [filterStatus, setFilterStatus] = useState("live"); - - const eventFilter = useCallback( - (event: NostrEvent) => { - try { - const parsed = parseStreamEvent(event); - return parsed.status === filterStatus; - } catch (e) {} - return false; - }, - [filterStatus], - ); const { people, list } = usePeopleListContext(); const query = @@ -39,7 +25,7 @@ function StreamsPage() { ] : { kinds: [STREAM_KIND] }; - const timeline = useTimelineLoader(`${list}-streams`, relays, query, { eventFilter }); + const timeline = useTimelineLoader(`${list}-streams`, relays, query); useRelaysChanged(relays, () => timeline.reset()); @@ -48,19 +34,25 @@ function StreamsPage() { const events = useSubject(timeline.timeline); const streams = useParsedStreams(events); + const liveStreams = streams.filter((stream) => stream.status === "live"); + const endedStreams = streams.filter((stream) => stream.status === "ended"); + return ( - - {streams.map((stream) => ( + {liveStreams.map((stream) => ( + + ))} + + Ended + + + {endedStreams.map((stream) => ( ))} diff --git a/src/views/user/components/user-profile-menu.tsx b/src/views/user/components/user-profile-menu.tsx index ce76b0e34..267188b30 100644 --- a/src/views/user/components/user-profile-menu.tsx +++ b/src/views/user/components/user-profile-menu.tsx @@ -1,6 +1,7 @@ import { MenuItem, useDisclosure } from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; import { nip19 } from "nostr-tools"; +import { useCopyToClipboard } from "react-use"; import { MenuIconButton, MenuIconButtonProps } from "../../../components/menu-icon-button"; import { ChatIcon, ClipboardIcon, CodeIcon, ExternalLinkIcon, RelayIcon, SpyIcon } from "../../../components/icons"; @@ -10,7 +11,6 @@ import { getUserDisplayName } from "../../../helpers/user-metadata"; import { useUserRelays } from "../../../hooks/use-user-relays"; import { RelayMode } from "../../../classes/relay"; import UserDebugModal from "../../../components/debug-modals/user-debug-modal"; -import { useCopyToClipboard } from "react-use"; import { useSharableProfileId } from "../../../hooks/use-shareable-profile-id"; import { buildAppSelectUrl } from "../../../helpers/nostr/apps"; import { truncatedId } from "../../../helpers/nostr/events"; diff --git a/src/views/user/followers.tsx b/src/views/user/followers.tsx index 6f0bc79db..b03b9ef7f 100644 --- a/src/views/user/followers.tsx +++ b/src/views/user/followers.tsx @@ -29,7 +29,7 @@ export default function UserFollowersTab() { const contextRelays = useAdditionalRelayContext(); const readRelays = useReadRelayUrls(contextRelays); - const timeline = useTimelineLoader(`${truncatedId(pubkey)}-followers`, readRelays, { + const timeline = useTimelineLoader(`${pubkey}-followers`, readRelays, { "#p": [pubkey], kinds: [Kind.Contacts], }); diff --git a/src/views/user/likes.tsx b/src/views/user/likes.tsx index 938f2749a..af56c1057 100644 --- a/src/views/user/likes.tsx +++ b/src/views/user/likes.tsx @@ -56,7 +56,7 @@ export default function UserLikesTab() { const contextRelays = useAdditionalRelayContext(); const readRelays = useReadRelayUrls(contextRelays); - const timeline = useTimelineLoader(`${truncatedId(pubkey)}-likes`, readRelays, { authors: [pubkey], kinds: [7] }); + const timeline = useTimelineLoader(`${pubkey}-likes`, readRelays, { authors: [pubkey], kinds: [7] }); const likes = useSubject(timeline.timeline); diff --git a/src/views/user/relays.tsx b/src/views/user/relays.tsx index 08b2f54e7..5c0af4cb7 100644 --- a/src/views/user/relays.tsx +++ b/src/views/user/relays.tsx @@ -56,7 +56,7 @@ const UserRelaysTab = () => { const userRelays = useUserRelays(pubkey); const readRelays = useReadRelayUrls(userRelays.map((r) => r.url)); - const timeline = useTimelineLoader(`${truncatedId(pubkey)}-relay-reviews`, readRelays, { + const timeline = useTimelineLoader(`${pubkey}-relay-reviews`, readRelays, { authors: [pubkey], kinds: [1985], "#l": ["review/relay"], diff --git a/src/views/user/reports.tsx b/src/views/user/reports.tsx index c52640d57..71c9c2fdd 100644 --- a/src/views/user/reports.tsx +++ b/src/views/user/reports.tsx @@ -1,8 +1,9 @@ import { Flex, Text } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; + import { NoteLink } from "../../components/note-link"; import { UserLink } from "../../components/user-link"; -import { filterTagsByContentRefs, truncatedId } from "../../helpers/nostr/events"; +import { filterTagsByContentRefs } from "../../helpers/nostr/events"; import useTimelineLoader from "../../hooks/use-timeline-loader"; import { isETag, isPTag, NostrEvent } from "../../types/nostr-event"; import { useAdditionalRelayContext } from "../../providers/additional-relay-context"; @@ -39,7 +40,7 @@ export default function UserReportsTab() { const { pubkey } = useOutletContext() as { pubkey: string }; const contextRelays = useAdditionalRelayContext(); - const timeline = useTimelineLoader(`${truncatedId(pubkey)}-reports`, contextRelays, { + const timeline = useTimelineLoader(`${pubkey}-reports`, contextRelays, { authors: [pubkey], kinds: [1984], }); diff --git a/src/views/user/zaps.tsx b/src/views/user/zaps.tsx index 4eec31707..23aec8427 100644 --- a/src/views/user/zaps.tsx +++ b/src/views/user/zaps.tsx @@ -8,7 +8,6 @@ import { NoteLink } from "../../components/note-link"; import { UserAvatarLink } from "../../components/user-avatar-link"; import { UserLink } from "../../components/user-link"; import { readablizeSats } from "../../helpers/bolt11"; -import { truncatedId } from "../../helpers/nostr/events"; import { isProfileZap, isNoteZap, parseZapEvent, totalZaps } from "../../helpers/zaps"; import useTimelineLoader from "../../hooks/use-timeline-loader"; import { NostrEvent } from "../../types/nostr-event"; @@ -90,12 +89,7 @@ const UserZapsTab = () => { [filter], ); - const timeline = useTimelineLoader( - `${truncatedId(pubkey)}-zaps`, - relays, - { "#p": [pubkey], kinds: [9735] }, - { eventFilter }, - ); + const timeline = useTimelineLoader(`${pubkey}-zaps`, relays, { "#p": [pubkey], kinds: [9735] }, { eventFilter }); const events = useSubject(timeline.timeline); const zaps = useMemo(() => {