mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-20 13:01:07 +02:00
add community subscribe and unsubscribe buttons
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
@@ -21,6 +21,8 @@ import EmbeddedList from "./event-types/embedded-list";
|
|||||||
import EmbeddedArticle from "./event-types/embedded-article";
|
import EmbeddedArticle from "./event-types/embedded-article";
|
||||||
import EmbeddedBadge from "./event-types/embedded-badge";
|
import EmbeddedBadge from "./event-types/embedded-badge";
|
||||||
import EmbeddedStreamMessage from "./event-types/embedded-stream-message";
|
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 = {
|
export type EmbedProps = {
|
||||||
goalProps?: EmbeddedGoalOptions;
|
goalProps?: EmbeddedGoalOptions;
|
||||||
@@ -49,6 +51,8 @@ export function EmbedEvent({
|
|||||||
return <EmbeddedBadge badge={event} {...cardProps} />;
|
return <EmbeddedBadge badge={event} {...cardProps} />;
|
||||||
case STREAM_CHAT_MESSAGE_KIND:
|
case STREAM_CHAT_MESSAGE_KIND:
|
||||||
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
||||||
|
case COMMUNITY_DEFINITION_KIND:
|
||||||
|
return <EmbeddedCommunity community={event} {...cardProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||||
|
@@ -18,8 +18,8 @@ import { useSigningContext } from "../../../providers/signing-provider";
|
|||||||
import useUserLists from "../../../hooks/use-user-lists";
|
import useUserLists from "../../../hooks/use-user-lists";
|
||||||
import {
|
import {
|
||||||
NOTE_LIST_KIND,
|
NOTE_LIST_KIND,
|
||||||
draftAddEvent,
|
listAddEvent,
|
||||||
draftRemoveEvent,
|
listRemoveEvent,
|
||||||
getEventsFromList,
|
getEventsFromList,
|
||||||
getListName,
|
getListName,
|
||||||
} from "../../../helpers/nostr/lists";
|
} from "../../../helpers/nostr/lists";
|
||||||
@@ -55,11 +55,11 @@ export default function BookmarkButton({ event, ...props }: { event: NostrEvent
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (addToList) {
|
if (addToList) {
|
||||||
const draft = draftAddEvent(addToList, event.id);
|
const draft = listAddEvent(addToList, event.id);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
|
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
|
||||||
} else if (removeFromList) {
|
} else if (removeFromList) {
|
||||||
const draft = draftRemoveEvent(removeFromList, event.id);
|
const draft = listRemoveEvent(removeFromList, event.id);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
|
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
|
||||||
}
|
}
|
||||||
|
@@ -20,8 +20,8 @@ import {
|
|||||||
PEOPLE_LIST_KIND,
|
PEOPLE_LIST_KIND,
|
||||||
createEmptyContactList,
|
createEmptyContactList,
|
||||||
createEmptyMuteList,
|
createEmptyMuteList,
|
||||||
draftAddPerson,
|
listAddPerson,
|
||||||
draftRemovePerson,
|
listRemovePerson,
|
||||||
getListName,
|
getListName,
|
||||||
getPubkeysFromList,
|
getPubkeysFromList,
|
||||||
isPubkeyInList,
|
isPubkeyInList,
|
||||||
@@ -62,11 +62,11 @@ function UsersLists({ pubkey }: { pubkey: string }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (addToList) {
|
if (addToList) {
|
||||||
const draft = draftAddPerson(addToList, pubkey);
|
const draft = listAddPerson(addToList, pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
|
const pub = new NostrPublishAction("Add to list", writeRelays, signed);
|
||||||
} else if (removeFromList) {
|
} else if (removeFromList) {
|
||||||
const draft = draftRemovePerson(removeFromList, pubkey);
|
const draft = listRemovePerson(removeFromList, pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Remove from list", writeRelays, signed);
|
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 isDisabled = account?.readonly ?? true;
|
||||||
|
|
||||||
const handleFollow = useAsyncErrorHandler(async () => {
|
const handleFollow = useAsyncErrorHandler(async () => {
|
||||||
const draft = draftAddPerson(contacts || createEmptyContactList(), pubkey);
|
const draft = listAddPerson(contacts || createEmptyContactList(), pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Follow", clientRelaysService.getWriteUrls(), signed);
|
const pub = new NostrPublishAction("Follow", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
});
|
});
|
||||||
const handleUnfollow = useAsyncErrorHandler(async () => {
|
const handleUnfollow = useAsyncErrorHandler(async () => {
|
||||||
const draft = draftRemovePerson(contacts || createEmptyContactList(), pubkey);
|
const draft = listRemovePerson(contacts || createEmptyContactList(), pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Unfollow", clientRelaysService.getWriteUrls(), signed);
|
const pub = new NostrPublishAction("Unfollow", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { NostrEvent, isDTag, isPTag } from "../../types/nostr-event";
|
import { NostrEvent, isDTag, isPTag } from "../../types/nostr-event";
|
||||||
|
|
||||||
|
export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
|
||||||
export const COMMUNITY_DEFINITION_KIND = 34550;
|
export const COMMUNITY_DEFINITION_KIND = 34550;
|
||||||
export const COMMUNITY_APPROVAL_KIND = 4550;
|
export const COMMUNITY_APPROVAL_KIND = 4550;
|
||||||
|
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import dayjs from "dayjs";
|
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 { DraftNostrEvent, NostrEvent, isATag, isDTag, isETag, isPTag } from "../../types/nostr-event";
|
||||||
|
import { parseCoordinate } from "./events";
|
||||||
|
|
||||||
export const PEOPLE_LIST_KIND = 30000;
|
export const PEOPLE_LIST_KIND = 30000;
|
||||||
export const NOTE_LIST_KIND = 30001;
|
export const NOTE_LIST_KIND = 30001;
|
||||||
@@ -31,6 +34,20 @@ export function getEventsFromList(event: NostrEvent) {
|
|||||||
export function getCoordinatesFromList(event: NostrEvent) {
|
export function getCoordinatesFromList(event: NostrEvent) {
|
||||||
return event.tags.filter(isATag).map((t) => ({ coordinate: t[1], relay: t[2] }));
|
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) {
|
export function isPubkeyInList(event?: NostrEvent, pubkey?: string) {
|
||||||
if (!pubkey || !event) return false;
|
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");
|
if (list.tags.some((t) => t[0] === "p" && t[1] === pubkey)) throw new Error("person already in list");
|
||||||
|
return {
|
||||||
const draft: DraftNostrEvent = {
|
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
kind: list.kind,
|
kind: list.kind,
|
||||||
content: list.content,
|
content: list.content,
|
||||||
tags: [...list.tags, relay ? ["p", pubkey, relay] : ["p", pubkey]],
|
tags: [...list.tags, relay ? ["p", pubkey, relay] : ["p", pubkey]],
|
||||||
};
|
};
|
||||||
|
|
||||||
return draft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function draftRemovePerson(list: NostrEvent | DraftNostrEvent, pubkey: string) {
|
export function listRemovePerson(list: NostrEvent | DraftNostrEvent, pubkey: string): DraftNostrEvent {
|
||||||
const draft: DraftNostrEvent = {
|
return {
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
kind: list.kind,
|
kind: list.kind,
|
||||||
content: list.content,
|
content: list.content,
|
||||||
tags: list.tags.filter((t) => !(t[0] === "p" && t[1] === pubkey)),
|
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");
|
if (list.tags.some((t) => t[0] === "e" && t[1] === event)) throw new Error("event already in list");
|
||||||
|
return {
|
||||||
const draft: DraftNostrEvent = {
|
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
kind: list.kind,
|
kind: list.kind,
|
||||||
content: list.content,
|
content: list.content,
|
||||||
tags: [...list.tags, relay ? ["e", event, relay] : ["e", event]],
|
tags: [...list.tags, relay ? ["e", event, relay] : ["e", event]],
|
||||||
};
|
};
|
||||||
|
|
||||||
return draft;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function draftRemoveEvent(list: NostrEvent | DraftNostrEvent, event: string) {
|
export function listRemoveEvent(list: NostrEvent | DraftNostrEvent, event: string): DraftNostrEvent {
|
||||||
const draft: DraftNostrEvent = {
|
return {
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
kind: list.kind,
|
kind: list.kind,
|
||||||
content: list.content,
|
content: list.content,
|
||||||
tags: list.tags.filter((t) => !(t[0] === "e" && t[1] === event)),
|
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)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
26
src/hooks/use-subscribed-communities-list.ts
Normal file
26
src/hooks/use-subscribed-communities-list.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
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 { useSigningContext } from "../providers/signing-provider";
|
||||||
import clientRelaysService from "../services/client-relays";
|
import clientRelaysService from "../services/client-relays";
|
||||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||||
@@ -15,13 +15,13 @@ export default function useUserMuteFunctions(pubkey: string) {
|
|||||||
const isMuted = isPubkeyInList(muteList, pubkey);
|
const isMuted = isPubkeyInList(muteList, pubkey);
|
||||||
|
|
||||||
const mute = useAsyncErrorHandler(async () => {
|
const mute = useAsyncErrorHandler(async () => {
|
||||||
const draft = draftAddPerson(muteList || createEmptyMuteList(), pubkey);
|
const draft = listAddPerson(muteList || createEmptyMuteList(), pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
new NostrPublishAction("Mute", clientRelaysService.getWriteUrls(), signed);
|
new NostrPublishAction("Mute", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
});
|
});
|
||||||
const unmute = useAsyncErrorHandler(async () => {
|
const unmute = useAsyncErrorHandler(async () => {
|
||||||
const draft = draftRemovePerson(muteList || createEmptyMuteList(), pubkey);
|
const draft = listRemovePerson(muteList || createEmptyMuteList(), pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
new NostrPublishAction("Unmute", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
@@ -1,18 +1,6 @@
|
|||||||
import { memo, useRef } from "react";
|
import { memo, useRef } from "react";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
import {
|
import { Box, Card, CardProps, Center, Flex, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
|
||||||
Avatar,
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardProps,
|
|
||||||
Center,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
Image,
|
|
||||||
LinkBox,
|
|
||||||
LinkOverlay,
|
|
||||||
Text,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
|
|
||||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||||
import { UserLink } from "../../../components/user-link";
|
import { UserLink } from "../../../components/user-link";
|
||||||
@@ -39,10 +27,10 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
|
|||||||
backgroundRepeat="no-repeat"
|
backgroundRepeat="no-repeat"
|
||||||
backgroundSize="cover"
|
backgroundSize="cover"
|
||||||
backgroundPosition="center"
|
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)}
|
{getCommunityName(community)}
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
@@ -45,7 +45,7 @@ function CommunitiesHomePage() {
|
|||||||
<PeopleListSelection />
|
<PeopleListSelection />
|
||||||
<RelaySelectionButton />
|
<RelaySelectionButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
<SimpleGrid columns={[1, 1, 1, 2]} spacing="2">
|
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
||||||
{communities.map((event) => (
|
{communities.map((event) => (
|
||||||
<CommunityCard key={getEventUID(event)} community={event} />
|
<CommunityCard key={getEventUID(event)} community={event} />
|
||||||
))}
|
))}
|
||||||
|
@@ -20,6 +20,7 @@ import useSubject from "../../hooks/use-subject";
|
|||||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||||
import Note from "../../components/note";
|
import Note from "../../components/note";
|
||||||
|
import CommunityJoinButton from "../communities/components/community-subscribe-button";
|
||||||
|
|
||||||
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
||||||
const mods = getCommunityMods(community);
|
const mods = getCommunityMods(community);
|
||||||
@@ -52,6 +53,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
|||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
|
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<CommunityJoinButton community={community} ml="auto" />
|
||||||
</Flex>
|
</Flex>
|
||||||
<CommunityDescription community={community} />
|
<CommunityDescription community={community} />
|
||||||
<Flex wrap="wrap" gap="2">
|
<Flex wrap="wrap" gap="2">
|
||||||
|
@@ -11,7 +11,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer"
|
|||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
import CommunityCard from "../communities/components/community-card";
|
import CommunityCard from "../communities/components/community-card";
|
||||||
import { getEventUID } from "../../helpers/nostr/events";
|
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() {
|
export default function CommunityFindByNameView() {
|
||||||
const { community } = useParams() as { community: string };
|
const { community } = useParams() as { community: string };
|
||||||
@@ -35,9 +35,11 @@ export default function CommunityFindByNameView() {
|
|||||||
<VerticalPageLayout>
|
<VerticalPageLayout>
|
||||||
<Heading>Select Community:</Heading>
|
<Heading>Select Community:</Heading>
|
||||||
<Divider />
|
<Divider />
|
||||||
{communities.map((event) => (
|
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
||||||
<CommunityCard key={getEventUID(event)} community={event} />
|
{communities.map((event) => (
|
||||||
))}
|
<CommunityCard key={getEventUID(event)} community={event} />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
</IntersectionObserverProvider>
|
</IntersectionObserverProvider>
|
||||||
);
|
);
|
||||||
|
@@ -71,7 +71,7 @@ function BrowseListPage() {
|
|||||||
|
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{lists.map((event) => (
|
{lists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
|
@@ -13,10 +13,17 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
|
||||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||||
import { UserLink } from "../../../components/user-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 { getSharableEventAddress } from "../../../helpers/nip19";
|
||||||
import { NostrEvent } from "../../../types/nostr-event";
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
import useReplaceableEvent from "../../../hooks/use-replaceable-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 { getEventUID } from "../../../helpers/nostr/events";
|
||||||
import ListMenu from "./list-menu";
|
import ListMenu from "./list-menu";
|
||||||
import Timestamp from "../../../components/timestamp";
|
import Timestamp from "../../../components/timestamp";
|
||||||
|
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
|
||||||
|
|
||||||
function ListCardRender({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
function ListCardRender({ list, ...props }: Omit<CardProps, "children"> & { list: NostrEvent }) {
|
||||||
const people = getPubkeysFromList(event);
|
const people = getPubkeysFromList(list);
|
||||||
const notes = getEventsFromList(event);
|
const notes = getEventsFromList(list);
|
||||||
const link = isSpecialListKind(event.kind)
|
const coordinates = getParsedCordsFromList(list);
|
||||||
? createCoordinate(event.kind, event.pubkey)
|
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
|
||||||
: getSharableEventAddress(event);
|
const link = isSpecialListKind(list.kind) ? createCoordinate(list.kind, list.pubkey) : getSharableEventAddress(list);
|
||||||
|
|
||||||
// if there is a parent intersection observer, register this card
|
// if there is a parent intersection observer, register this card
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
useRegisterIntersectionEntity(ref, getEventUID(list));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card ref={ref} variant="outline" {...props}>
|
<Card ref={ref} variant="outline" {...props}>
|
||||||
<CardHeader display="flex" alignItems="center" p="2" pb="0">
|
<CardHeader display="flex" alignItems="center" p="2" pb="0">
|
||||||
<Heading size="md" isTruncated>
|
<Heading size="md" isTruncated>
|
||||||
<Link as={RouterLink} to={`/lists/${link}`}>
|
<Link as={RouterLink} to={`/lists/${link}`}>
|
||||||
{getListName(event)}
|
{getListName(list)}
|
||||||
</Link>
|
</Link>
|
||||||
</Heading>
|
</Heading>
|
||||||
<ButtonGroup size="sm" ml="auto">
|
<ButtonGroup size="sm" ml="auto">
|
||||||
<ListFavoriteButton list={event} />
|
<ListFavoriteButton list={list} />
|
||||||
<ListMenu list={event} aria-label="list menu" />
|
<ListMenu list={list} aria-label="list menu" />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody p="2">
|
<CardBody p="2">
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Text>Created by:</Text>
|
<Text>Created by:</Text>
|
||||||
<UserAvatarLink pubkey={event.pubkey} size="xs" />
|
<UserAvatarLink pubkey={list.pubkey} size="xs" />
|
||||||
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
<UserLink pubkey={list.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Text>
|
<Text>
|
||||||
Updated: <Timestamp timestamp={event.created_at} />
|
Updated: <Timestamp timestamp={list.created_at} />
|
||||||
</Text>
|
</Text>
|
||||||
{people.length > 0 && (
|
{people.length > 0 && (
|
||||||
<>
|
<>
|
||||||
@@ -82,18 +90,34 @@ function ListCardRender({ event, ...props }: Omit<CardProps, "children"> & { eve
|
|||||||
</Flex>
|
</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>
|
</CardBody>
|
||||||
<CardFooter p="2" display="flex" pt="0">
|
<CardFooter p="2" display="flex" pt="0">
|
||||||
<EventRelays event={event} ml="auto" />
|
<EventRelays event={list} ml="auto" />
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</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);
|
const event = maybeEvent ?? (cord ? useReplaceableEvent(cord as string) : undefined);
|
||||||
if (!event) return null;
|
if (!event) return null;
|
||||||
else return <ListCardRender event={event} />;
|
else return <ListCardRender list={event} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(ListCard);
|
export default memo(ListCard);
|
||||||
|
@@ -8,7 +8,7 @@ import { UserAvatar } from "../../../components/user-avatar";
|
|||||||
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
|
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
|
||||||
import { NostrEvent } from "../../../types/nostr-event";
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
|
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 { useSigningContext } from "../../../providers/signing-provider";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import clientRelaysService from "../../../services/client-relays";
|
import clientRelaysService from "../../../services/client-relays";
|
||||||
@@ -23,7 +23,7 @@ export default function UserCard({ pubkey, relay, list, ...props }: UserCardProp
|
|||||||
const { requestSignature } = useSigningContext();
|
const { requestSignature } = useSigningContext();
|
||||||
|
|
||||||
const handleRemoveFromList = useAsyncErrorHandler(async () => {
|
const handleRemoveFromList = useAsyncErrorHandler(async () => {
|
||||||
const draft = draftRemovePerson(list, pubkey);
|
const draft = listRemovePerson(list, pubkey);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Remove from list", clientRelaysService.getWriteUrls(), signed);
|
const pub = new NostrPublishAction("Remove from list", clientRelaysService.getWriteUrls(), signed);
|
||||||
}, [list]);
|
}, [list]);
|
||||||
|
@@ -58,7 +58,7 @@ function ListsPage() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{peopleLists.map((event) => (
|
{peopleLists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
@@ -69,7 +69,7 @@ function ListsPage() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{noteLists.map((event) => (
|
{noteLists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
@@ -80,7 +80,7 @@ function ListsPage() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{favoriteLists.map((event) => (
|
{favoriteLists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
|
@@ -7,7 +7,7 @@ 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 { 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 useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
import UserCard from "./components/user-card";
|
import UserCard from "./components/user-card";
|
||||||
import NoteCard from "./components/note-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 ListFavoriteButton from "./components/list-favorite-button";
|
||||||
import ListFeedButton from "./components/list-feed-button";
|
import ListFeedButton from "./components/list-feed-button";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
|
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||||
|
import { EmbedEventPointer } from "../../components/embed-event";
|
||||||
|
|
||||||
function useListCoordinate() {
|
function useListCoordinate() {
|
||||||
const { addr } = useParams() as { addr: string };
|
const { addr } = useParams() as { addr: string };
|
||||||
@@ -37,18 +39,20 @@ export default function ListDetailsView() {
|
|||||||
const { deleteEvent } = useDeleteEventContext();
|
const { deleteEvent } = useDeleteEventContext();
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
|
|
||||||
const event = useReplaceableEvent(coordinate);
|
const list = useReplaceableEvent(coordinate);
|
||||||
|
|
||||||
if (!event)
|
if (!list)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
Looking for list "{coordinate.identifier}" created by <UserLink pubkey={coordinate.pubkey} />
|
Looking for list "{coordinate.identifier}" created by <UserLink pubkey={coordinate.pubkey} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const isAuthor = account?.pubkey === event.pubkey;
|
const isAuthor = account?.pubkey === list.pubkey;
|
||||||
const people = getPubkeysFromList(event);
|
const people = getPubkeysFromList(list);
|
||||||
const notes = getEventsFromList(event);
|
const notes = getEventsFromList(list);
|
||||||
|
const coordinates = getParsedCordsFromList(list);
|
||||||
|
const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalPageLayout overflow="hidden" h="full">
|
<VerticalPageLayout overflow="hidden" h="full">
|
||||||
@@ -58,19 +62,19 @@ export default function ListDetailsView() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Heading size="md" isTruncated>
|
<Heading size="md" isTruncated>
|
||||||
{getListName(event)}
|
{getListName(list)}
|
||||||
</Heading>
|
</Heading>
|
||||||
<ListFavoriteButton list={event} size="sm" />
|
<ListFavoriteButton list={list} size="sm" />
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
||||||
<ListFeedButton list={event} />
|
<ListFeedButton list={list} />
|
||||||
{isAuthor && (
|
{isAuthor && (
|
||||||
<Button colorScheme="red" onClick={() => deleteEvent(event).then(() => navigate("/lists"))}>
|
<Button colorScheme="red" onClick={() => deleteEvent(list).then(() => navigate("/lists"))}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<ListMenu aria-label="More options" list={event} />
|
<ListMenu aria-label="More options" list={list} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{people.length > 0 && (
|
{people.length > 0 && (
|
||||||
@@ -79,7 +83,7 @@ export default function ListDetailsView() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{people.map(({ pubkey, relay }) => (
|
{people.map(({ pubkey, relay }) => (
|
||||||
<UserCard pubkey={pubkey} relay={relay} list={event} />
|
<UserCard pubkey={pubkey} relay={relay} list={list} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
@@ -98,6 +102,18 @@ export default function ListDetailsView() {
|
|||||||
</TrustProvider>
|
</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>
|
</VerticalPageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -48,7 +48,7 @@ export default function UserListsTab() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{peopleLists.map((event) => (
|
{peopleLists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
@@ -62,7 +62,7 @@ export default function UserListsTab() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||||
{noteLists.map((event) => (
|
{noteLists.map((event) => (
|
||||||
<ListCard key={getEventUID(event)} event={event} />
|
<ListCard key={getEventUID(event)} list={event} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</>
|
</>
|
||||||
|
Reference in New Issue
Block a user