mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 21:00:17 +02:00
add browse lists view
This commit is contained in:
parent
239f6e934b
commit
d53a34cf12
5
.changeset/giant-actors-rule.md
Normal file
5
.changeset/giant-actors-rule.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add browse lists view
|
@ -39,6 +39,7 @@ import ListView from "./views/lists/list";
|
|||||||
import UserListsTab from "./views/user/lists";
|
import UserListsTab from "./views/user/lists";
|
||||||
|
|
||||||
import "./services/emoji-packs";
|
import "./services/emoji-packs";
|
||||||
|
import BrowseListView from "./views/lists/browse";
|
||||||
|
|
||||||
const StreamsView = React.lazy(() => import("./views/streams"));
|
const StreamsView = React.lazy(() => import("./views/streams"));
|
||||||
const StreamView = React.lazy(() => import("./views/streams/stream"));
|
const StreamView = React.lazy(() => import("./views/streams/stream"));
|
||||||
@ -125,6 +126,7 @@ const router = createHashRouter([
|
|||||||
path: "lists",
|
path: "lists",
|
||||||
children: [
|
children: [
|
||||||
{ path: "", element: <ListsView /> },
|
{ path: "", element: <ListsView /> },
|
||||||
|
{ path: "browse", element: <BrowseListView /> },
|
||||||
{ path: ":addr", element: <ListView /> },
|
{ path: ":addr", element: <ListView /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -92,13 +92,13 @@ export const Note = React.memo(({ event, variant = "outline" }: NoteProps) => {
|
|||||||
aria-label="Open External"
|
aria-label="Open External"
|
||||||
href={externalLink}
|
href={externalLink}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="link"
|
variant="ghost"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EventRelays event={event} />
|
<EventRelays event={event} />
|
||||||
<BookmarkButton event={event} aria-label="Bookmark note" size="sm" variant="link" />
|
<BookmarkButton event={event} aria-label="Bookmark note" size="xs" variant="ghost" />
|
||||||
<NoteMenu event={event} size="sm" variant="link" aria-label="More Options" />
|
<NoteMenu event={event} size="xs" variant="ghost" aria-label="More Options" />
|
||||||
</Flex>
|
</Flex>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
87
src/views/lists/browse/index.tsx
Normal file
87
src/views/lists/browse/index.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Flex, Select, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||||
|
import PeopleListProvider, { usePeopleListContext } from "../../../providers/people-list-provider";
|
||||||
|
import PeopleListSelection from "../../../components/people-list-selection/people-list-selection";
|
||||||
|
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||||
|
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
|
import {
|
||||||
|
MUTE_LIST_KIND,
|
||||||
|
NOTE_LIST_KIND,
|
||||||
|
PEOPLE_LIST_KIND,
|
||||||
|
getEventsFromList,
|
||||||
|
getListName,
|
||||||
|
getPubkeysFromList,
|
||||||
|
} from "../../../helpers/nostr/lists";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
|
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||||
|
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
|
import useSubject from "../../../hooks/use-subject";
|
||||||
|
import ListCard from "../components/list-card";
|
||||||
|
import { getEventUID } from "../../../helpers/nostr/events";
|
||||||
|
|
||||||
|
function BrowseListPage() {
|
||||||
|
const { filter, list } = usePeopleListContext();
|
||||||
|
const showEmpty = useDisclosure();
|
||||||
|
const showMute = useDisclosure();
|
||||||
|
const [listKind, setListKind] = useState(PEOPLE_LIST_KIND);
|
||||||
|
|
||||||
|
const eventFilter = useCallback(
|
||||||
|
(event: NostrEvent) => {
|
||||||
|
if (event.kind !== listKind) return false;
|
||||||
|
if (!showEmpty.isOpen && getPubkeysFromList(event).length === 0 && getEventsFromList(event).length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!showMute.isOpen && event.kind === PEOPLE_LIST_KIND && getListName(event) === "mute") ||
|
||||||
|
event.kind === MUTE_LIST_KIND
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[showEmpty.isOpen, showMute.isOpen, listKind],
|
||||||
|
);
|
||||||
|
const readRelays = useReadRelayUrls();
|
||||||
|
const timeline = useTimelineLoader(
|
||||||
|
`${list}-lists`,
|
||||||
|
readRelays,
|
||||||
|
{ ...filter, kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND] },
|
||||||
|
{ enabled: !!filter, eventFilter },
|
||||||
|
);
|
||||||
|
|
||||||
|
const lists = useSubject(timeline.timeline);
|
||||||
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntersectionObserverProvider callback={callback}>
|
||||||
|
<Flex direction="column" gap="2" p="2">
|
||||||
|
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||||
|
<PeopleListSelection />
|
||||||
|
<Select w="sm" value={listKind} onChange={(e) => setListKind(parseInt(e.target.value))}>
|
||||||
|
<option value={PEOPLE_LIST_KIND}>People List</option>
|
||||||
|
<option value={NOTE_LIST_KIND}>Note List</option>
|
||||||
|
</Select>
|
||||||
|
<Switch checked={showEmpty.isOpen} onChange={showEmpty.onToggle} whiteSpace="pre">
|
||||||
|
Show Empty
|
||||||
|
</Switch>
|
||||||
|
<Switch checked={showMute.isOpen} onChange={showMute.onToggle} whiteSpace="pre">
|
||||||
|
Show Mute
|
||||||
|
</Switch>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
|
{lists.map((event) => (
|
||||||
|
<ListCard key={getEventUID(event)} event={event} />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Flex>
|
||||||
|
</IntersectionObserverProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BrowseListView() {
|
||||||
|
return (
|
||||||
|
<PeopleListProvider>
|
||||||
|
<BrowseListPage />
|
||||||
|
</PeopleListProvider>
|
||||||
|
);
|
||||||
|
}
|
@ -12,6 +12,8 @@ import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
|||||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||||
import { EventRelays } from "../../../components/note/note-relays";
|
import { EventRelays } from "../../../components/note/note-relays";
|
||||||
import { NoteLink } from "../../../components/note-link";
|
import { NoteLink } from "../../../components/note-link";
|
||||||
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
export default function ListCard({ cord, event: maybeEvent }: { cord?: string; event?: NostrEvent }) {
|
export default function ListCard({ cord, event: maybeEvent }: { cord?: string; event?: NostrEvent }) {
|
||||||
const event = maybeEvent ?? (cord ? useReplaceableEvent(cord as string) : undefined);
|
const event = maybeEvent ?? (cord ? useReplaceableEvent(cord as string) : undefined);
|
||||||
@ -22,9 +24,13 @@ export default function ListCard({ cord, event: maybeEvent }: { cord?: string; e
|
|||||||
const link =
|
const link =
|
||||||
event.kind === Kind.Contacts ? createCoordinate(Kind.Contacts, event.pubkey) : getSharableEventNaddr(event);
|
event.kind === Kind.Contacts ? createCoordinate(Kind.Contacts, event.pubkey) : getSharableEventNaddr(event);
|
||||||
|
|
||||||
|
// if there is a parent intersection observer, register this card
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, event.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card ref={ref}>
|
||||||
<CardHeader p="2" pb="0" flex="1">
|
<CardHeader p="2" pb="0">
|
||||||
<Heading size="md">
|
<Heading size="md">
|
||||||
<Link as={RouterLink} to={`/lists/${link}`}>
|
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||||
{getListName(event)}
|
{getListName(event)}
|
||||||
@ -51,7 +57,7 @@ export default function ListCard({ cord, event: maybeEvent }: { cord?: string; e
|
|||||||
{notes.length > 0 && (
|
{notes.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Text>Notes ({notes.length}):</Text>
|
<Text>Notes ({notes.length}):</Text>
|
||||||
<Flex gap="2" wrap="wrap">
|
<Flex gap="2" overflow="hidden">
|
||||||
{notes.map(({ id, relay }) => (
|
{notes.map(({ id, relay }) => (
|
||||||
<NoteLink key={id} noteId={id} />
|
<NoteLink key={id} noteId={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Button, Divider, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
import { Button, Divider, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, Link as RouterLink } from "react-router-dom";
|
||||||
import { Kind } from "nostr-tools";
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
@ -24,6 +24,9 @@ function ListsPage() {
|
|||||||
return (
|
return (
|
||||||
<Flex direction="column" p="2" gap="2">
|
<Flex direction="column" p="2" gap="2">
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
|
<Button as={RouterLink} to="/lists/browse">
|
||||||
|
Browse Lists
|
||||||
|
</Button>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Link as RouterList, useNavigate, useParams } from "react-router-dom";
|
import { Link as RouterList, useNavigate, useParams } from "react-router-dom";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { UserLink } from "../../components/user-link";
|
import { UserLink } from "../../components/user-link";
|
||||||
import { Button, Divider, Flex, Heading, SimpleGrid } from "@chakra-ui/react";
|
import { Button, Divider, Flex, Heading, SimpleGrid } from "@chakra-ui/react";
|
||||||
import { ArrowLeftSIcon } from "../../components/icons";
|
import { ArrowLeftSIcon } from "../../components/icons";
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
import { useDeleteEventContext } from "../../providers/delete-event-provider";
|
||||||
import { parseCoordinate } from "../../helpers/nostr/events";
|
import { getEventCoordinate, parseCoordinate } from "../../helpers/nostr/events";
|
||||||
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
import { EventRelays } from "../../components/note/note-relays";
|
import { EventRelays } from "../../components/note/note-relays";
|
||||||
@ -61,6 +62,12 @@ export default function ListView() {
|
|||||||
|
|
||||||
<EventRelays event={event} />
|
<EventRelays event={event} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
as={RouterLink}
|
||||||
|
to={{ pathname: "/", search: new URLSearchParams({ people: getEventCoordinate(event) }).toString() }}
|
||||||
|
>
|
||||||
|
View Feed
|
||||||
|
</Button>
|
||||||
{isAuthor && (
|
{isAuthor && (
|
||||||
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
|
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
|
||||||
Delete
|
Delete
|
||||||
|
Loading…
x
Reference in New Issue
Block a user