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 (
-
+
);
}
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 (
<>
- } onClick={openModal} {...props}>
+ } onClick={relaysModal.onOpen} {...props}>
{relays.length} {relays.length === 1 ? "Relay" : "Relays"}
+ {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 && }
+
+
@@ -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(() => {