Cleanup list card

This commit is contained in:
hzrd149
2023-11-07 07:32:34 +09:00
parent c2bc6294ce
commit 1efdc28570
14 changed files with 296 additions and 236 deletions

View File

@@ -1,77 +0,0 @@
import { useCallback, useMemo } from "react";
import { Button, ButtonProps, IconButton, Image, useDisclosure } from "@chakra-ui/react";
import { NostrEvent } from "../types/nostr-event";
import useEventReactions from "../hooks/use-event-reactions";
import { DislikeIcon, LikeIcon } from "./icons";
import { draftEventReaction, groupReactions } from "../helpers/nostr/reactions";
import ReactionDetailsModal from "./reaction-details-modal";
import { useSigningContext } from "../providers/signing-provider";
import clientRelaysService from "../services/client-relays";
import NostrPublishAction from "../classes/nostr-publish-action";
import eventReactionsService from "../services/event-reactions";
import { useCurrentAccount } from "../hooks/use-current-account";
export function ReactionIcon({ emoji, url }: { emoji: string; url?: string }) {
if (emoji === "+") return <LikeIcon />;
if (emoji === "-") return <DislikeIcon />;
if (url) return <Image src={url} title={emoji} alt={emoji} w="1em" h="1em" display="inline" />;
return <span>{emoji}</span>;
}
function ReactionGroupButton({
emoji,
url,
count,
...props
}: Omit<ButtonProps, "leftIcon" | "children"> & { emoji: string; count: number; url?: string }) {
if (count <= 1) {
return <IconButton icon={<ReactionIcon emoji={emoji} url={url} />} aria-label="Reaction" {...props} />;
}
return (
<Button leftIcon={<ReactionIcon emoji={emoji} url={url} />} title={emoji} {...props}>
{count > 1 && count}
</Button>
);
}
export default function EventReactionButtons({ event, max }: { event: NostrEvent; max?: number }) {
const account = useCurrentAccount();
const detailsModal = useDisclosure();
const reactions = useEventReactions(event.id) ?? [];
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
const { requestSignature } = useSigningContext();
const addReaction = useCallback(async (emoji = "+", url?: string) => {
const draft = draftEventReaction(event, emoji, url);
const signed = await requestSignature(draft);
if (signed) {
const writeRelays = clientRelaysService.getWriteUrls();
new NostrPublishAction("Reaction", writeRelays, signed);
eventReactionsService.handleEvent(signed);
}
}, []);
if (grouped.length === 0) return null;
const clamped = Array.from(grouped);
if (max !== undefined) clamped.length = max;
return (
<>
{clamped.map((group) => (
<ReactionGroupButton
key={group.emoji}
emoji={group.emoji}
url={group.url}
count={group.pubkeys.length}
onClick={() => addReaction(group.emoji, group.url)}
colorScheme={account && group.pubkeys.includes(account?.pubkey) ? "primary" : undefined}
/>
))}
<Button onClick={detailsModal.onOpen}>Show all</Button>
{detailsModal.isOpen && <ReactionDetailsModal isOpen onClose={detailsModal.onClose} reactions={reactions} />}
</>
);
}

View File

@@ -0,0 +1,37 @@
import { useCallback } from "react";
import { useToast } from "@chakra-ui/react";
import { ReactionGroup, draftEventReaction } from "../../helpers/nostr/reactions";
import { useCurrentAccount } from "../../hooks/use-current-account";
import { useSigningContext } from "../../providers/signing-provider";
import { NostrEvent } from "../../types/nostr-event";
import clientRelaysService from "../../services/client-relays";
import NostrPublishAction from "../../classes/nostr-publish-action";
import eventReactionsService from "../../services/event-reactions";
export function useAddReaction(event: NostrEvent, grouped: ReactionGroup[]) {
const account = useCurrentAccount();
const toast = useToast();
const { requestSignature } = useSigningContext();
return useCallback(
async (emoji = "+", url?: string) => {
try {
const group = grouped.find((g) => g.emoji === emoji);
if (account && group && group.pubkeys.includes(account?.pubkey)) return;
const draft = draftEventReaction(event, emoji, url);
const signed = await requestSignature(draft);
if (signed) {
const writeRelays = clientRelaysService.getWriteUrls();
new NostrPublishAction("Reaction", writeRelays, signed);
eventReactionsService.handleEvent(signed);
}
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
}
},
[grouped, account, toast, requestSignature],
);
}

View File

@@ -0,0 +1,41 @@
import { useMemo } from "react";
import { Button, useDisclosure } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import useEventReactions from "../../hooks/use-event-reactions";
import { groupReactions } from "../../helpers/nostr/reactions";
import ReactionDetailsModal from "../reaction-details-modal";
import { useCurrentAccount } from "../../hooks/use-current-account";
import ReactionGroupButton from "./reaction-group-button";
import { useAddReaction } from "./common-hooks";
export default function EventReactionButtons({ event, max }: { event: NostrEvent; max?: number }) {
const account = useCurrentAccount();
const detailsModal = useDisclosure();
const reactions = useEventReactions(event.id) ?? [];
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
const addReaction = useAddReaction(event, grouped);
if (grouped.length === 0) return null;
const clamped = Array.from(grouped);
if (max !== undefined) clamped.length = max;
return (
<>
{clamped.map((group) => (
<ReactionGroupButton
key={group.emoji}
emoji={group.emoji}
url={group.url}
count={group.pubkeys.length}
onClick={() => addReaction(group.emoji, group.url)}
colorScheme={account && group.pubkeys.includes(account?.pubkey) ? "primary" : undefined}
/>
))}
<Button onClick={detailsModal.onOpen}>Show all</Button>
{detailsModal.isOpen && <ReactionDetailsModal isOpen onClose={detailsModal.onClose} reactions={reactions} />}
</>
);
}

View File

@@ -0,0 +1,18 @@
import { Button, ButtonProps, IconButton } from "@chakra-ui/react";
import ReactionIcon from "./reaction-icon";
export default function ReactionGroupButton({
emoji,
url,
count,
...props
}: Omit<ButtonProps, "leftIcon" | "children"> & { emoji: string; count: number; url?: string }) {
if (count <= 1) {
return <IconButton icon={<ReactionIcon emoji={emoji} url={url} />} aria-label="Reaction" {...props} />;
}
return (
<Button leftIcon={<ReactionIcon emoji={emoji} url={url} />} title={emoji} {...props}>
{count > 1 && count}
</Button>
);
}

View File

@@ -0,0 +1,9 @@
import { Image } from "@chakra-ui/react";
import { DislikeIcon, LikeIcon } from "../icons";
export default function ReactionIcon({ emoji, url }: { emoji: string; url?: string }) {
if (emoji === "+") return <LikeIcon />;
if (emoji === "-") return <DislikeIcon />;
if (url) return <Image src={url} title={emoji} alt={emoji} w="1em" h="1em" display="inline" />;
return <span>{emoji}</span>;
}

View File

@@ -0,0 +1,28 @@
import { useMemo } from "react";
import { NostrEvent } from "../../types/nostr-event";
import useEventReactions from "../../hooks/use-event-reactions";
import { groupReactions } from "../../helpers/nostr/reactions";
import { useCurrentAccount } from "../../hooks/use-current-account";
import ReactionGroupButton from "./reaction-group-button";
import { useAddReaction } from "./common-hooks";
import { ButtonProps } from "@chakra-ui/react";
export default function SimpleLikeButton({ event, ...props }: Omit<ButtonProps, "children"> & { event: NostrEvent }) {
const account = useCurrentAccount();
const reactions = useEventReactions(event.id) ?? [];
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
const addReaction = useAddReaction(event, grouped);
const group = grouped.find((g) => g.emoji === "+");
return (
<ReactionGroupButton
emoji="+"
count={group?.pubkeys.length ?? 0}
onClick={() => addReaction("+")}
colorScheme={account && group?.pubkeys.includes(account?.pubkey) ? "primary" : undefined}
{...props}
/>
);
}

View File

@@ -12,7 +12,7 @@ import GhostToolbar from "./ghost-toolbar";
import { useBreakpointValue } from "../../providers/breakpoint-provider";
import SearchModal from "../search-modal";
import { useLocation } from "react-router-dom";
import ChatWindows from "../chat-windows";
// import ChatWindows from "../chat-windows";
export default function Layout({ children }: { children: React.ReactNode }) {
const isMobile = useBreakpointValue({ base: true, md: false });
@@ -66,7 +66,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
</Flex>
{isGhost && <GhostToolbar />}
{searchModal.isOpen && <SearchModal isOpen onClose={searchModal.onClose} />}
{!isMobile && <ChatWindows />}
{/* {!isMobile && <ChatWindows />} */}
</>
);
}

View File

@@ -2,7 +2,7 @@ import { ButtonGroup, ButtonGroupProps, Divider } from "@chakra-ui/react";
import { NostrEvent } from "../../../types/nostr-event";
import ReactionButton from "./reaction-button";
import EventReactionButtons from "../../event-reactions";
import EventReactionButtons from "../../event-reactions/event-reactions";
import useEventReactions from "../../../hooks/use-event-reactions";
import { useBreakpointValue } from "../../../providers/breakpoint-provider";

View File

@@ -21,7 +21,7 @@ export type NoteZapButtonProps = Omit<ButtonProps, "children"> & {
export default function NoteZapButton({ event, allowComment, showEventPreview, ...props }: NoteZapButtonProps) {
const account = useCurrentAccount();
const { metadata } = useUserLNURLMetadata(event.pubkey);
const zaps = useEventZaps(event.id);
const zaps = useEventZaps(getEventUID(event));
const { isOpen, onOpen, onClose } = useDisclosure();
const hasZapped = !!account && zaps.some((zap) => zap.request.pubkey === account.pubkey);

View File

@@ -18,7 +18,7 @@ import { useMemo } from "react";
import { NostrEvent } from "../types/nostr-event";
import { groupReactions } from "../helpers/nostr/reactions";
import { ReactionIcon } from "./event-reactions";
import { ReactionIcon } from "./event-reactions/event-reactions";
import UserAvatarLink from "./user-avatar-link";
import { UserLink } from "./user-link";

View File

@@ -1,6 +1,7 @@
import { Kind } from "nostr-tools";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import { DraftNostrEvent, NostrEvent, Tag } from "../../types/nostr-event";
import dayjs from "dayjs";
import { getEventCoordinate, isReplaceable } from "./events";
export type ReactionGroup = { emoji: string; url?: string; name?: string; count: number; pubkeys: string[] };
@@ -20,14 +21,15 @@ export function groupReactions(reactions: NostrEvent[]) {
return Array.from(Object.values(groups)).sort((a, b) => b.pubkeys.length - a.pubkeys.length);
}
export function draftEventReaction(reacted: NostrEvent, emoji = "+", url?: string) {
// only keep the e, and p tags on the parent event
const inheritedTags = reacted.tags.filter((tag) => tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"));
export function draftEventReaction(event: NostrEvent, emoji = "+", url?: string) {
const tags: Tag[] = [
["e", event.id],
["p", event.pubkey],
];
const draft: DraftNostrEvent = {
kind: Kind.Reaction,
content: url ? ":" + emoji + ":" : emoji,
tags: [...inheritedTags, ["e", reacted.id], ["p", reacted.pubkey]],
tags: isReplaceable(event.kind) ? [...tags, ["a", getEventCoordinate(event)]] : tags,
created_at: dayjs().unix(),
};

View File

@@ -1,16 +1,17 @@
import { memo, useRef } from "react";
import { Link as RouterLink } from "react-router-dom";
import {
AvatarGroup,
ButtonGroup,
Card,
CardBody,
CardFooter,
CardHeader,
CardProps,
Flex,
Heading,
Link,
LinkBox,
LinkProps,
SimpleGrid,
Text,
} from "@chakra-ui/react";
import { Kind, nip19 } from "nostr-tools";
@@ -29,15 +30,20 @@ import { getSharableEventAddress } from "../../../helpers/nip19";
import { NostrEvent } from "../../../types/nostr-event";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { createCoordinate } from "../../../services/replaceable-event-requester";
import { NoteLink } from "../../../components/note-link";
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
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";
import { getArticleTitle } from "../../../helpers/nostr/long-form";
import { buildAppSelectUrl } from "../../../helpers/nostr/apps";
import { CommunityIcon, NotesIcon } from "../../../components/icons";
import User01 from "../../../components/icons/user-01";
import HoverLinkOverlay from "../../../components/hover-link-overlay";
import NoteZapButton from "../../../components/note/note-zap-button";
import Link01 from "../../../components/icons/link-01";
import File02 from "../../../components/icons/file-02";
import SimpleLikeButton from "../../../components/event-reactions/simple-like-button";
function ArticleLinkLoader({ pointer, ...props }: { pointer: nip19.AddressPointer } & Omit<LinkProps, "children">) {
const article = useReplaceableEvent(pointer);
@@ -64,62 +70,33 @@ export function ListCardContent({ list, ...props }: Omit<CardProps, "children">
const references = getReferencesFromList(list);
return (
<>
<Text>
Updated: <Timestamp timestamp={list.created_at} />
</Text>
<SimpleGrid spacing="2" columns={4}>
{people.length > 0 && (
<>
<Text>People ({people.length}):</Text>
<AvatarGroup overflow="hidden" mb="2" max={16} size="sm">
{people.map(({ pubkey, relay }) => (
<UserAvatarLink key={pubkey} pubkey={pubkey} relay={relay} />
))}
</AvatarGroup>
</>
<Text>
<User01 boxSize={5} /> {people.length}
</Text>
)}
{notes.length > 0 && (
<Flex gap="2" overflow="hidden" wrap="wrap">
<Text>Notes ({notes.length}):</Text>
{notes.slice(0, 4).map(({ id, relay }) => (
<NoteLink key={id} noteId={id} />
))}
</Flex>
<Text>
<NotesIcon boxSize={5} /> {notes.length}
</Text>
)}
{references.length > 0 && (
<Flex gap="2" overflow="hidden" wrap="wrap">
<Text>References ({references.length})</Text>
{references.slice(0, 3).map(({ url, petname }) => (
<Link maxW="200" href={url} isExternal whiteSpace="pre" color="blue.500" isTruncated>
{petname || url}
</Link>
))}
</Flex>
)}
{communities.length > 0 && (
<Flex gap="2" overflow="hidden" wrap="wrap">
<Text>Communities ({communities.length}):</Text>
{communities.map((pointer) => (
<Link
key={JSON.stringify(pointer)}
as={RouterLink}
to={`/c/${pointer.identifier}/${nip19.npubEncode(pointer.pubkey)}`}
color="blue.500"
>
{pointer.identifier}
</Link>
))}
</Flex>
<Text>
<Link01 boxSize={5} /> {references.length}
</Text>
)}
{articles.length > 0 && (
<Flex overflow="hidden" direction="column" wrap="wrap">
<Text>Articles ({articles.length}):</Text>
{articles.slice(0, 4).map((pointer) => (
<ArticleLinkLoader key={JSON.stringify(pointer)} pointer={pointer} isTruncated />
))}
</Flex>
<Text>
<File02 /> {articles.length}
</Text>
)}
</>
{communities.length > 0 && (
<Text>
<CommunityIcon boxSize={5} /> {communities.length}
</Text>
)}
</SimpleGrid>
);
}
@@ -135,12 +112,12 @@ function ListCardRender({
useRegisterIntersectionEntity(ref, getEventUID(list));
return (
<Card ref={ref} variant="outline" {...props}>
<CardHeader display="flex" gap="2" alignItems="center" p="2" pb="0">
<Card as={LinkBox} ref={ref} variant="outline" {...props}>
<CardHeader display="flex" gap="2" p="4" alignItems="center">
<Heading size="md" isTruncated>
<Link as={RouterLink} to={`/lists/${link}`}>
<HoverLinkOverlay as={RouterLink} to={`/lists/${link}`}>
{getListName(list)}
</Link>
</HoverLinkOverlay>
</Heading>
{!hideCreator && (
<>
@@ -149,14 +126,19 @@ function ListCardRender({
<UserLink pubkey={list.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
</>
)}
<ButtonGroup size="xs" variant="ghost" ml="auto">
</CardHeader>
<CardBody py="0" px="4">
<ListCardContent list={list} />
</CardBody>
<CardFooter p="2">
<NoteZapButton event={list} size="sm" variant="ghost" />
{/* TODO: reactions are tagging every user in list */}
<SimpleLikeButton event={list} variant="ghost" size="sm" />
<ButtonGroup size="sm" variant="ghost" ml="auto">
<ListFavoriteButton list={list} />
<ListMenu list={list} aria-label="list menu" />
</ButtonGroup>
</CardHeader>
<CardBody p="2">
<ListCardContent list={list} />
</CardBody>
</CardFooter>
</Card>
);
}

View File

@@ -18,16 +18,16 @@ import {
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import UserCard from "./components/user-card";
import OpenGraphCard from "../../components/open-graph-card";
import NoteCard from "./components/note-card";
import { TrustProvider } from "../../providers/trust";
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";
import { EmbedEvent, EmbedEventPointer } from "../../components/embed-event";
import { encodePointer } from "../../helpers/nip19";
import { DecodeResult } from "nostr-tools/lib/types/nip19";
import useSingleEvent from "../../hooks/use-single-event";
function useListCoordinate() {
const { addr } = useParams() as { addr: string };
@@ -43,6 +43,12 @@ function useListCoordinate() {
return parsed.data;
}
function BookmarkedEvent({ id, relay }: { id: string; relay?: string }) {
const event = useSingleEvent(id, relay ? [relay] : undefined);
return event ? <EmbedEvent event={event} /> : <>Loading {id}</>;
}
export default function ListDetailsView() {
const navigate = useNavigate();
const coordinate = useListCoordinate();
@@ -67,56 +73,54 @@ export default function ListDetailsView() {
const references = getReferencesFromList(list);
return (
<VerticalPageLayout overflow="hidden" h="full">
<Flex gap="2" alignItems="center">
<Button onClick={() => navigate(-1)} leftIcon={<ChevronLeftIcon />}>
Back
</Button>
<Heading size="md" isTruncated>
{getListName(list)}
</Heading>
<ListFavoriteButton list={list} size="sm" />
<Spacer />
<ListFeedButton list={list} />
{isAuthor && !isSpecialListKind(list.kind) && (
<Button colorScheme="red" onClick={() => deleteEvent(list).then(() => navigate("/lists"))}>
Delete
<TrustProvider trust>
<VerticalPageLayout overflow="hidden" h="full">
<Flex gap="2" alignItems="center">
<Button onClick={() => navigate(-1)} leftIcon={<ChevronLeftIcon />}>
Back
</Button>
<Heading size="md" isTruncated>
{getListName(list)}
</Heading>
<ListFavoriteButton list={list} size="sm" />
<Spacer />
<ListFeedButton list={list} />
{isAuthor && !isSpecialListKind(list.kind) && (
<Button colorScheme="red" onClick={() => deleteEvent(list).then(() => navigate("/lists"))}>
Delete
</Button>
)}
<ListMenu aria-label="More options" list={list} />
</Flex>
{people.length > 0 && (
<>
<Heading size="lg">People</Heading>
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{people.map(({ pubkey, relay }) => (
<UserCard pubkey={pubkey} relay={relay} list={list} />
))}
</SimpleGrid>
</>
)}
<ListMenu aria-label="More options" list={list} />
</Flex>
{people.length > 0 && (
<>
<Heading size="lg">People</Heading>
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{people.map(({ pubkey, relay }) => (
<UserCard pubkey={pubkey} relay={relay} list={list} />
))}
</SimpleGrid>
</>
)}
{notes.length > 0 && (
<>
<Heading size="lg">Notes</Heading>
<TrustProvider trust>
{notes.length > 0 && (
<>
<Heading size="lg">Notes</Heading>
<Flex gap="2" direction="column">
{notes.map(({ id, relay }) => (
<NoteCard id={id} relay={relay} />
<BookmarkedEvent id={id} relay={relay} />
))}
</Flex>
</TrustProvider>
</>
)}
</>
)}
{references.length > 0 && (
<>
<Heading size="lg">References</Heading>
<TrustProvider trust>
{references.length > 0 && (
<>
<Heading size="lg">References</Heading>
<Flex gap="2" direction="column">
{references.map(({ url, petname }) => (
<>
@@ -125,32 +129,32 @@ export default function ListDetailsView() {
</>
))}
</Flex>
</TrustProvider>
</>
)}
</>
)}
{communities.length > 0 && (
<>
<Heading size="lg">Communities</Heading>
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((pointer) => (
<EmbedEventPointer key={nip19.naddrEncode(pointer)} pointer={{ type: "naddr", data: pointer }} />
))}
</SimpleGrid>
</>
)}
{communities.length > 0 && (
<>
<Heading size="lg">Communities</Heading>
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
{communities.map((pointer) => (
<EmbedEventPointer key={nip19.naddrEncode(pointer)} pointer={{ type: "naddr", data: pointer }} />
))}
</SimpleGrid>
</>
)}
{articles.length > 0 && (
<>
<Heading size="lg">Articles</Heading>
<Flex gap="2" direction="column">
{articles.map((pointer) => {
const decode: DecodeResult = { type: "naddr", data: pointer };
return <EmbedEventPointer key={encodePointer(decode)} pointer={decode} />;
})}
</Flex>
</>
)}
</VerticalPageLayout>
{articles.length > 0 && (
<>
<Heading size="lg">Articles</Heading>
<Flex gap="2" direction="column">
{articles.map((pointer) => {
const decode: DecodeResult = { type: "naddr", data: pointer };
return <EmbedEventPointer key={encodePointer(decode)} pointer={decode} />;
})}
</Flex>
</>
)}
</VerticalPageLayout>
</TrustProvider>
);
}

View File

@@ -13,6 +13,7 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
import { Kind } from "nostr-tools";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { NostrEvent } from "../../types/nostr-event";
import UserName from "../../components/user-name";
export default function UserListsTab() {
const { pubkey } = useOutletContext() as { pubkey: string };
@@ -24,30 +25,36 @@ export default function UserListsTab() {
const timeline = useTimelineLoader(
pubkey + "-lists",
readRelays,
{
authors: [pubkey],
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
},
[
{
authors: [pubkey],
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
},
{
"#p": [pubkey],
kinds: [PEOPLE_LIST_KIND],
},
],
{ eventFilter },
);
const lists = useSubject(timeline.timeline);
const callback = useTimelineCurserIntersectionCallback(timeline);
const peopleLists = lists.filter((event) => event.kind === PEOPLE_LIST_KIND);
const noteLists = lists.filter((event) => event.kind === NOTE_LIST_KIND);
const peopleLists = lists.filter((event) => event.pubkey === pubkey && event.kind === PEOPLE_LIST_KIND);
const noteLists = lists.filter((event) => event.pubkey === pubkey && event.kind === NOTE_LIST_KIND);
const otherLists = lists.filter((event) => event.pubkey !== pubkey && event.kind === PEOPLE_LIST_KIND);
return (
<IntersectionObserverProvider callback={callback}>
<VerticalPageLayout>
<VerticalPageLayout>
<IntersectionObserverProvider callback={callback}>
<Heading size="md" mt="2">
Special lists
</Heading>
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
<ListCard cord={`${Kind.Contacts}:${pubkey}`} />
<ListCard cord={`${MUTE_LIST_KIND}:${pubkey}`} />
<ListCard cord={`${PIN_LIST_KIND}:${pubkey}`} />
<ListCard cord={`${Kind.Contacts}:${pubkey}`} hideCreator />
<ListCard cord={`${MUTE_LIST_KIND}:${pubkey}`} hideCreator />
<ListCard cord={`${PIN_LIST_KIND}:${pubkey}`} hideCreator />
</SimpleGrid>
{peopleLists.length > 0 && (
@@ -55,10 +62,9 @@ export default function UserListsTab() {
<Heading size="md" mt="2">
People lists
</Heading>
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{peopleLists.map((event) => (
<ListCard key={getEventUID(event)} list={event} />
<ListCard key={getEventUID(event)} list={event} hideCreator />
))}
</SimpleGrid>
</>
@@ -69,15 +75,25 @@ export default function UserListsTab() {
<Heading size="md" mt="2">
Bookmark lists
</Heading>
<Divider />
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{noteLists.map((event) => (
<ListCard key={getEventUID(event)} list={event} />
<ListCard key={getEventUID(event)} list={event} hideCreator />
))}
</SimpleGrid>
</>
)}
</VerticalPageLayout>
</IntersectionObserverProvider>
</IntersectionObserverProvider>
<IntersectionObserverProvider callback={callback}>
<Heading size="md" mt="2">
Lists <UserName pubkey={pubkey} /> is in
</Heading>
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
{otherLists.map((event) => (
<ListCard key={getEventUID(event)} list={event} />
))}
</SimpleGrid>
</IntersectionObserverProvider>
</VerticalPageLayout>
);
}