add community subscribe and unsubscribe buttons

This commit is contained in:
hzrd149
2023-09-14 21:28:28 -05:00
parent 602131819c
commit 174ddd592f
19 changed files with 289 additions and 89 deletions

View File

@@ -0,0 +1,49 @@
import { Link as RouterLink } from "react-router-dom";
import { Box, Card, CardProps, Center, Flex, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
import { nip19 } from "nostr-tools";
import { UserAvatarLink } from "../../../components/user-avatar-link";
import { UserLink } from "../../../components/user-link";
import { NostrEvent } from "../../../types/nostr-event";
import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities";
import CommunityDescription from "../../../views/communities/components/community-description";
export default function EmbeddedCommunity({
community,
...props
}: Omit<CardProps, "children"> & { community: NostrEvent }) {
const image = getCommunityImage(community);
return (
<Card as={LinkBox} variant="outline" gap="2" overflow="hidden" {...props}>
{image ? (
<Box
backgroundImage={getCommunityImage(community)}
backgroundRepeat="no-repeat"
backgroundSize="cover"
backgroundPosition="center"
aspectRatio={3 / 1}
/>
) : (
<Center aspectRatio={4 / 1} fontWeight="bold" fontSize="2xl">
{getCommunityName(community)}
</Center>
)}
<Flex direction="column" flex={1} px="2" pb="2">
<Flex wrap="wrap" gap="2" alignItems="center">
<Heading size="lg">
<LinkOverlay
as={RouterLink}
to={`/c/${encodeURIComponent(getCommunityName(community))}/${nip19.npubEncode(community.pubkey)}`}
>
{getCommunityName(community)}
</LinkOverlay>
</Heading>
<Text>Created by:</Text>
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
</Flex>
<CommunityDescription community={community} maxLength={128} flex={1} />
</Flex>
</Card>
);
}

View File

@@ -21,6 +21,8 @@ import EmbeddedList from "./event-types/embedded-list";
import EmbeddedArticle from "./event-types/embedded-article";
import EmbeddedBadge from "./event-types/embedded-badge";
import EmbeddedStreamMessage from "./event-types/embedded-stream-message";
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
import EmbeddedCommunity from "./event-types/embedded-community";
export type EmbedProps = {
goalProps?: EmbeddedGoalOptions;
@@ -49,6 +51,8 @@ export function EmbedEvent({
return <EmbeddedBadge badge={event} {...cardProps} />;
case STREAM_CHAT_MESSAGE_KIND:
return <EmbeddedStreamMessage message={event} {...cardProps} />;
case COMMUNITY_DEFINITION_KIND:
return <EmbeddedCommunity community={event} {...cardProps} />;
}
return <EmbeddedUnknown event={event} {...cardProps} />;

View File

@@ -18,8 +18,8 @@ import { useSigningContext } from "../../../providers/signing-provider";
import useUserLists from "../../../hooks/use-user-lists";
import {
NOTE_LIST_KIND,
draftAddEvent,
draftRemoveEvent,
listAddEvent,
listRemoveEvent,
getEventsFromList,
getListName,
} from "../../../helpers/nostr/lists";
@@ -55,11 +55,11 @@ export default function BookmarkButton({ event, ...props }: { event: NostrEvent
);
if (addToList) {
const draft = draftAddEvent(addToList, event.id);
const draft = listAddEvent(addToList, event.id);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
} else if (removeFromList) {
const draft = draftRemoveEvent(removeFromList, event.id);
const draft = listRemoveEvent(removeFromList, event.id);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
}

View File

@@ -20,8 +20,8 @@ import {
PEOPLE_LIST_KIND,
createEmptyContactList,
createEmptyMuteList,
draftAddPerson,
draftRemovePerson,
listAddPerson,
listRemovePerson,
getListName,
getPubkeysFromList,
isPubkeyInList,
@@ -62,11 +62,11 @@ function UsersLists({ pubkey }: { pubkey: string }) {
);
if (addToList) {
const draft = draftAddPerson(addToList, pubkey);
const draft = listAddPerson(addToList, pubkey);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
} else if (removeFromList) {
const draft = draftRemovePerson(removeFromList, pubkey);
const draft = listRemovePerson(removeFromList, pubkey);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
}
@@ -125,13 +125,13 @@ export const UserFollowButton = ({ pubkey, showLists, ...props }: UserFollowButt
const isDisabled = account?.readonly ?? true;
const handleFollow = useAsyncErrorHandler(async () => {
const draft = draftAddPerson(contacts || createEmptyContactList(), pubkey);
const draft = listAddPerson(contacts || createEmptyContactList(), pubkey);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Follow", clientRelaysService.getWriteUrls(), signed);
replaceableEventLoaderService.handleEvent(signed);
});
const handleUnfollow = useAsyncErrorHandler(async () => {
const draft = draftRemovePerson(contacts || createEmptyContactList(), pubkey);
const draft = listRemovePerson(contacts || createEmptyContactList(), pubkey);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Unfollow", clientRelaysService.getWriteUrls(), signed);
replaceableEventLoaderService.handleEvent(signed);

View File

@@ -1,5 +1,6 @@
import { NostrEvent, isDTag, isPTag } from "../../types/nostr-event";
export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
export const COMMUNITY_DEFINITION_KIND = 34550;
export const COMMUNITY_APPROVAL_KIND = 4550;

View File

@@ -1,6 +1,9 @@
import dayjs from "dayjs";
import { Kind } from "nostr-tools";
import { Kind, nip19 } from "nostr-tools";
import { AddressPointer } from "nostr-tools/lib/nip19";
import { DraftNostrEvent, NostrEvent, isATag, isDTag, isETag, isPTag } from "../../types/nostr-event";
import { parseCoordinate } from "./events";
export const PEOPLE_LIST_KIND = 30000;
export const NOTE_LIST_KIND = 30001;
@@ -31,6 +34,20 @@ export function getEventsFromList(event: NostrEvent) {
export function getCoordinatesFromList(event: NostrEvent) {
return event.tags.filter(isATag).map((t) => ({ coordinate: t[1], relay: t[2] }));
}
export function getParsedCordsFromList(event: NostrEvent) {
const pointers: AddressPointer[] = [];
for (const tag of event.tags) {
if (!tag[1]) continue;
const relay = tag[2];
const parsed = parseCoordinate(tag[1]);
if (!parsed?.identifier) continue;
pointers.push({ ...parsed, identifier: parsed?.identifier, relays: relay ? [relay] : undefined });
}
return pointers;
}
export function isPubkeyInList(event?: NostrEvent, pubkey?: string) {
if (!pubkey || !event) return false;
@@ -54,50 +71,63 @@ export function createEmptyMuteList(): DraftNostrEvent {
};
}
export function draftAddPerson(list: NostrEvent | DraftNostrEvent, pubkey: string, relay?: string) {
export function listAddPerson(list: NostrEvent | DraftNostrEvent, pubkey: string, relay?: string): DraftNostrEvent {
if (list.tags.some((t) => t[0] === "p" && t[1] === pubkey)) throw new Error("person already in list");
const draft: DraftNostrEvent = {
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: [...list.tags, relay ? ["p", pubkey, relay] : ["p", pubkey]],
};
return draft;
}
export function draftRemovePerson(list: NostrEvent | DraftNostrEvent, pubkey: string) {
const draft: DraftNostrEvent = {
export function listRemovePerson(list: NostrEvent | DraftNostrEvent, pubkey: string): DraftNostrEvent {
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: list.tags.filter((t) => !(t[0] === "p" && t[1] === pubkey)),
};
return draft;
}
export function draftAddEvent(list: NostrEvent | DraftNostrEvent, event: string, relay?: string) {
export function listAddEvent(list: NostrEvent | DraftNostrEvent, event: string, relay?: string): DraftNostrEvent {
if (list.tags.some((t) => t[0] === "e" && t[1] === event)) throw new Error("event already in list");
const draft: DraftNostrEvent = {
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: [...list.tags, relay ? ["e", event, relay] : ["e", event]],
};
return draft;
}
export function draftRemoveEvent(list: NostrEvent | DraftNostrEvent, event: string) {
const draft: DraftNostrEvent = {
export function listRemoveEvent(list: NostrEvent | DraftNostrEvent, event: string): DraftNostrEvent {
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: list.tags.filter((t) => !(t[0] === "e" && t[1] === event)),
};
return draft;
}
export function listAddCoordinate(
list: NostrEvent | DraftNostrEvent,
coordinate: string,
relay?: string,
): DraftNostrEvent {
if (list.tags.some((t) => t[0] === "a" && t[1] === coordinate)) throw new Error("coordinate already in list");
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: [...list.tags, relay ? ["a", coordinate, relay] : ["a", coordinate]],
};
}
export function listRemoveCoordinate(list: NostrEvent | DraftNostrEvent, coordinate: string): DraftNostrEvent {
return {
created_at: dayjs().unix(),
kind: list.kind,
content: list.content,
tags: list.tags.filter((t) => !(t[0] === "a" && t[1] === coordinate)),
};
}

View File

@@ -0,0 +1,26 @@
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
import { NOTE_LIST_KIND, getParsedCordsFromList } from "../helpers/nostr/lists";
import { useCurrentAccount } from "./use-current-account";
import useReplaceableEvent from "./use-replaceable-event";
export default function useSubscribedCommunitiesList(pubkey?: string) {
const account = useCurrentAccount();
const key = pubkey ?? account?.pubkey;
const list = useReplaceableEvent(
key
? {
kind: NOTE_LIST_KIND,
identifier: SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER,
pubkey: key,
}
: undefined,
);
const pointers = list ? getParsedCordsFromList(list).filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND) : [];
return {
list,
pointers,
};
}

View File

@@ -1,5 +1,5 @@
import NostrPublishAction from "../classes/nostr-publish-action";
import { createEmptyMuteList, draftAddPerson, draftRemovePerson, isPubkeyInList } from "../helpers/nostr/lists";
import { createEmptyMuteList, listAddPerson, listRemovePerson, isPubkeyInList } from "../helpers/nostr/lists";
import { useSigningContext } from "../providers/signing-provider";
import clientRelaysService from "../services/client-relays";
import replaceableEventLoaderService from "../services/replaceable-event-requester";
@@ -15,13 +15,13 @@ export default function useUserMuteFunctions(pubkey: string) {
const isMuted = isPubkeyInList(muteList, pubkey);
const mute = useAsyncErrorHandler(async () => {
const draft = draftAddPerson(muteList || createEmptyMuteList(), pubkey);
const draft = listAddPerson(muteList || createEmptyMuteList(), pubkey);
const signed = await requestSignature(draft);
new NostrPublishAction("Mute", clientRelaysService.getWriteUrls(), signed);
replaceableEventLoaderService.handleEvent(signed);
});
const unmute = useAsyncErrorHandler(async () => {
const draft = draftRemovePerson(muteList || createEmptyMuteList(), pubkey);
const draft = listRemovePerson(muteList || createEmptyMuteList(), pubkey);
const signed = await requestSignature(draft);
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
replaceableEventLoaderService.handleEvent(signed);

View File

@@ -1,18 +1,6 @@
import { memo, useRef } from "react";
import { Link as RouterLink } from "react-router-dom";
import {
Avatar,
Box,
Card,
CardProps,
Center,
Flex,
Heading,
Image,
LinkBox,
LinkOverlay,
Text,
} from "@chakra-ui/react";
import { Box, Card, CardProps, Center, Flex, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
import { UserAvatarLink } from "../../../components/user-avatar-link";
import { UserLink } from "../../../components/user-link";
@@ -39,10 +27,10 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
backgroundRepeat="no-repeat"
backgroundSize="cover"
backgroundPosition="center"
aspectRatio={4 / 1}
aspectRatio={3 / 1}
/>
) : (
<Center aspectRatio={4 / 1} fontWeight="bold" fontSize="2xl">
<Center aspectRatio={3 / 1} fontWeight="bold" fontSize="2xl">
{getCommunityName(community)}
</Center>
)}

View File

@@ -0,0 +1,58 @@
import { useCallback } from "react";
import dayjs from "dayjs";
import { Button, ButtonProps, useToast } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
import useSubscribedCommunitiesList from "../../../hooks/use-subscribed-communities-list";
import { useCurrentAccount } from "../../../hooks/use-current-account";
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityName } from "../../../helpers/nostr/communities";
import { NOTE_LIST_KIND, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
import { getEventCoordinate } from "../../../helpers/nostr/events";
import { useSigningContext } from "../../../providers/signing-provider";
import NostrPublishAction from "../../../classes/nostr-publish-action";
import clientRelaysService from "../../../services/client-relays";
export default function CommunityJoinButton({
community,
...props
}: Omit<ButtonProps, "children"> & { community: NostrEvent }) {
const account = useCurrentAccount();
const { list, pointers } = useSubscribedCommunitiesList(account?.pubkey);
const { requestSignature } = useSigningContext();
const toast = useToast();
const isSubscribed = pointers.find(
(cord) => cord.identifier === getCommunityName(community) && cord.pubkey === community.pubkey,
);
const handleClick = useCallback(async () => {
try {
const favList = list || {
kind: NOTE_LIST_KIND,
content: "",
created_at: dayjs().unix(),
tags: [["d", SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER]],
};
let draft: DraftNostrEvent;
if (isSubscribed) {
draft = listRemoveCoordinate(favList, getEventCoordinate(community));
} else {
draft = listAddCoordinate(favList, getEventCoordinate(community));
}
const signed = await requestSignature(draft);
console.log(signed);
new NostrPublishAction(isSubscribed ? "Unsubscribe" : "Subscribe", clientRelaysService.getWriteUrls(), signed);
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
}
}, [isSubscribed, list, community]);
return (
<Button onClick={handleClick} {...props}>
{isSubscribed ? "Unsubscribe" : "Subscribe"}
</Button>
);
}

View File

@@ -45,7 +45,7 @@ function CommunitiesHomePage() {
<PeopleListSelection />
<RelaySelectionButton />
</Flex>
<SimpleGrid columns={[1, 1, 1, 2]} spacing="2">
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((event) => (
<CommunityCard key={getEventUID(event)} community={event} />
))}

View File

@@ -20,6 +20,7 @@ import useSubject from "../../hooks/use-subject";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import Note from "../../components/note";
import CommunityJoinButton from "../communities/components/community-subscribe-button";
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
const mods = getCommunityMods(community);
@@ -52,6 +53,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
<Flex gap="2">
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
</Flex>
<CommunityJoinButton community={community} ml="auto" />
</Flex>
<CommunityDescription community={community} />
<Flex wrap="wrap" gap="2">

View File

@@ -11,7 +11,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer"
import VerticalPageLayout from "../../components/vertical-page-layout";
import CommunityCard from "../communities/components/community-card";
import { getEventUID } from "../../helpers/nostr/events";
import { Divider, Heading } from "@chakra-ui/react";
import { Divider, Heading, SimpleGrid } from "@chakra-ui/react";
export default function CommunityFindByNameView() {
const { community } = useParams() as { community: string };
@@ -35,9 +35,11 @@ export default function CommunityFindByNameView() {
<VerticalPageLayout>
<Heading>Select Community:</Heading>
<Divider />
{communities.map((event) => (
<CommunityCard key={getEventUID(event)} community={event} />
))}
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((event) => (
<CommunityCard key={getEventUID(event)} community={event} />
))}
</SimpleGrid>
</VerticalPageLayout>
</IntersectionObserverProvider>
);

View File

@@ -71,7 +71,7 @@ function BrowseListPage() {
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{lists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</VerticalPageLayout>

View File

@@ -13,10 +13,17 @@ import {
Link,
Text,
} from "@chakra-ui/react";
import { nip19 } from "nostr-tools";
import { UserAvatarLink } from "../../../components/user-avatar-link";
import { UserLink } from "../../../components/user-link";
import { getEventsFromList, getListName, getPubkeysFromList, isSpecialListKind } from "../../../helpers/nostr/lists";
import {
getEventsFromList,
getListName,
getParsedCordsFromList,
getPubkeysFromList,
isSpecialListKind,
} from "../../../helpers/nostr/lists";
import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
@@ -28,39 +35,40 @@ import ListFavoriteButton from "./list-favorite-button";
import { getEventUID } from "../../../helpers/nostr/events";
import ListMenu from "./list-menu";
import Timestamp from "../../../components/timestamp";
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
function ListCardRender({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
const people = getPubkeysFromList(event);
const notes = getEventsFromList(event);
const link = isSpecialListKind(event.kind)
? createCoordinate(event.kind, event.pubkey)
: getSharableEventAddress(event);
function ListCardRender({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
const people = getPubkeysFromList(list);
const notes = getEventsFromList(list);
const coordinates = getParsedCordsFromList(list);
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
// if there is a parent intersection observer, register this card
const ref = useRef<HTMLDivElement | null>(null);
useRegisterIntersectionEntity(ref, getEventUID(event));
useRegisterIntersectionEntity(ref, getEventUID(list));
return (
<Card ref={ref} variant="outline" {...props}>
<CardHeader display="flex" alignItems="center" p="2" pb="0">
<Heading size="md" isTruncated>
<Link as={RouterLink} to={`/lists/${link}`}>
{getListName(event)}
{getListName(list)}
</Link>
</Heading>
<ButtonGroup size="sm" ml="auto">
<ListFavoriteButton list={event} />
<ListMenu list={event} aria-label="list menu" />
<ListFavoriteButton list={list} />
<ListMenu list={list} aria-label="list menu" />
</ButtonGroup>
</CardHeader>
<CardBody p="2">
<Flex gap="2">
<Text>Created by:</Text>
<UserAvatarLink pubkey={event.pubkey} size="xs" />
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
<UserAvatarLink pubkey={list.pubkey} size="xs" />
<UserLink pubkey={list.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
</Flex>
<Text>
Updated: <Timestamp timestamp={event.created_at} />
Updated: <Timestamp timestamp={list.created_at} />
</Text>
{people.length > 0 && (
<>
@@ -82,18 +90,34 @@ function ListCardRender({ event, ...props }: Omit<CardProps, "children"> & { eve
</Flex>
</>
)}
{communities.length > 0 && (
<>
<Text>Communities ({communities.length}):</Text>
<Flex gap="2" overflow="hidden">
{communities.map((pointer) => (
<Link
as={RouterLink}
to={`/c/${pointer.identifier}/${nip19.npubEncode(pointer.pubkey)}`}
color="blue.500"
>
{pointer.identifier}
</Link>
))}
</Flex>
</>
)}
</CardBody>
<CardFooter p="2" display="flex" pt="0">
<EventRelays event={event} ml="auto" />
<EventRelays event={list} ml="auto" />
</CardFooter>
</Card>
);
}
function ListCard({ cord, event: maybeEvent }: { cord?: string; event?: NostrEvent }) {
function ListCard({ cord, list: maybeEvent }: { cord?: string; list?: NostrEvent }) {
const event = maybeEvent ?? (cord ? useReplaceableEvent(cord as string) : undefined);
if (!event) return null;
else return <ListCardRender event={event} />;
else return <ListCardRender list={event} />;
}
export default memo(ListCard);

View File

@@ -8,7 +8,7 @@ import { UserAvatar } from "../../../components/user-avatar";
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
import { NostrEvent } from "../../../types/nostr-event";
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
import { draftRemovePerson } from "../../../helpers/nostr/lists";
import { listRemovePerson } from "../../../helpers/nostr/lists";
import { useSigningContext } from "../../../providers/signing-provider";
import NostrPublishAction from "../../../classes/nostr-publish-action";
import clientRelaysService from "../../../services/client-relays";
@@ -23,7 +23,7 @@ export default function UserCard({ pubkey, relay, list, ...props }: UserCardProp
const { requestSignature } = useSigningContext();
const handleRemoveFromList = useAsyncErrorHandler(async () => {
const draft = draftRemovePerson(list, pubkey);
const draft = listRemovePerson(list, pubkey);
const signed = await requestSignature(draft);
const pub = new NostrPublishAction("Remove from list", clientRelaysService.getWriteUrls(), signed);
}, [list]);

View File

@@ -58,7 +58,7 @@ function ListsPage() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{peopleLists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</>
@@ -69,7 +69,7 @@ function ListsPage() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{noteLists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</>
@@ -80,7 +80,7 @@ function ListsPage() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{favoriteLists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</>

View File

@@ -7,7 +7,7 @@ import { ArrowLeftSIcon } from "../../components/icons";
import { useCurrentAccount } from "../../hooks/use-current-account";
import { useDeleteEventContext } from "../../providers/delete-event-provider";
import { parseCoordinate } from "../../helpers/nostr/events";
import { getEventsFromList, getListName, getPubkeysFromList } from "../../helpers/nostr/lists";
import { getEventsFromList, getListName, getParsedCordsFromList, getPubkeysFromList } from "../../helpers/nostr/lists";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import UserCard from "./components/user-card";
import NoteCard from "./components/note-card";
@@ -16,6 +16,8 @@ import ListMenu from "./components/list-menu";
import ListFavoriteButton from "./components/list-favorite-button";
import ListFeedButton from "./components/list-feed-button";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
import { EmbedEventPointer } from "../../components/embed-event";
function useListCoordinate() {
const { addr } = useParams() as { addr: string };
@@ -37,18 +39,20 @@ export default function ListDetailsView() {
const { deleteEvent } = useDeleteEventContext();
const account = useCurrentAccount();
const event = useReplaceableEvent(coordinate);
const list = useReplaceableEvent(coordinate);
if (!event)
if (!list)
return (
<>
Looking for list "{coordinate.identifier}" created by <UserLink pubkey={coordinate.pubkey} />
</>
);
const isAuthor = account?.pubkey === event.pubkey;
const people = getPubkeysFromList(event);
const notes = getEventsFromList(event);
const isAuthor = account?.pubkey === list.pubkey;
const people = getPubkeysFromList(list);
const notes = getEventsFromList(list);
const coordinates = getParsedCordsFromList(list);
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
return (
<VerticalPageLayout overflow="hidden" h="full">
@@ -58,19 +62,19 @@ export default function ListDetailsView() {
</Button>
<Heading size="md" isTruncated>
{getListName(event)}
{getListName(list)}
</Heading>
<ListFavoriteButton list={event} size="sm" />
<ListFavoriteButton list={list} size="sm" />
<Spacer />
<ListFeedButton list={event} />
<ListFeedButton list={list} />
{isAuthor && (
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
<Button colorScheme="red" onClick={() => deleteEvent(list).then(() => navigate("/lists"))}>
Delete
</Button>
)}
<ListMenu aria-label="More options" list={event} />
<ListMenu aria-label="More options" list={list} />
</Flex>
{people.length > 0 && (
@@ -79,7 +83,7 @@ export default function ListDetailsView() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{people.map(({ pubkey, relay }) => (
<UserCard pubkey={pubkey} relay={relay} list={event} />
<UserCard pubkey={pubkey} relay={relay} list={list} />
))}
</SimpleGrid>
</>
@@ -98,6 +102,18 @@ export default function ListDetailsView() {
</TrustProvider>
</>
)}
{communities.length > 0 && (
<>
<Heading size="md">Communities</Heading>
<Divider />
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((pointer) => (
<EmbedEventPointer key={nip19.naddrEncode(pointer)} pointer={{ type: "naddr", data: pointer }} />
))}
</SimpleGrid>
</>
)}
</VerticalPageLayout>
);
}

View File

@@ -48,7 +48,7 @@ export default function UserListsTab() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{peopleLists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</>
@@ -62,7 +62,7 @@ export default function UserListsTab() {
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{noteLists.map((event) => (
<ListCard key={getEventUID(event)} event={event} />
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</>