mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
show community members
fix quote repost cache issue
This commit is contained in:
parent
ae3ef94c17
commit
d7e289a65d
5
.changeset/plenty-laws-rule.md
Normal file
5
.changeset/plenty-laws-rule.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Show community members
|
@ -20,7 +20,7 @@ export function QuoteRepostButton({
|
||||
|
||||
const handleClick = () => {
|
||||
const nevent = getSharableEventAddress(event);
|
||||
openModal({ initContent: "\nnostr:" + nevent });
|
||||
openModal({ cacheFormKey: null, initContent: "\nnostr:" + nevent });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -56,7 +56,7 @@ type FormValues = {
|
||||
};
|
||||
|
||||
export type PostModalProps = {
|
||||
cacheFormKey?: string;
|
||||
cacheFormKey?: string | null;
|
||||
initContent?: string;
|
||||
initCommunity?: string;
|
||||
};
|
||||
|
@ -4,14 +4,15 @@ import { useMount, useUnmount } from "react-use";
|
||||
|
||||
// TODO: make these caches expire
|
||||
export default function useCacheForm<TFieldValues extends FieldValues = FieldValues>(
|
||||
key: string,
|
||||
key: string | null,
|
||||
getValues: UseFormGetValues<TFieldValues>,
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
state: UseFormStateReturn<TFieldValues>,
|
||||
) {
|
||||
const storageKey = key + "-form-values";
|
||||
const storageKey = key && key + "-form-values";
|
||||
|
||||
useMount(() => {
|
||||
if (storageKey === null) return;
|
||||
try {
|
||||
const cached = localStorage.getItem(storageKey);
|
||||
localStorage.removeItem(storageKey);
|
||||
@ -29,12 +30,14 @@ export default function useCacheForm<TFieldValues extends FieldValues = FieldVal
|
||||
const stateRef = useRef<UseFormStateReturn<TFieldValues>>(state);
|
||||
stateRef.current = state;
|
||||
useUnmount(() => {
|
||||
if (storageKey === null) return;
|
||||
if (stateRef.current.isDirty && !stateRef.current.isSubmitted) {
|
||||
localStorage.setItem(storageKey, JSON.stringify(getValues()));
|
||||
} else localStorage.removeItem(storageKey);
|
||||
});
|
||||
|
||||
return useCallback(() => {
|
||||
if (storageKey === null) return;
|
||||
localStorage.removeItem(storageKey);
|
||||
}, [storageKey]);
|
||||
}
|
||||
|
13
src/hooks/use-count-community-members.ts
Normal file
13
src/hooks/use-count-community-members.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
|
||||
import { getEventCoordinate } from "../helpers/nostr/events";
|
||||
import { NOTE_LIST_KIND } from "../helpers/nostr/lists";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import useEventCount from "./use-event-count";
|
||||
|
||||
export default function useCountCommunityMembers(community: NostrEvent) {
|
||||
return useEventCount({
|
||||
"#a": [getEventCoordinate(community)],
|
||||
"#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER],
|
||||
kinds: [NOTE_LIST_KIND],
|
||||
});
|
||||
}
|
@ -9,7 +9,7 @@ import relayPoolService from "./relay-pool";
|
||||
// TODO: move this to settings
|
||||
const COUNT_RELAY = "wss://relay.nostr.band";
|
||||
|
||||
const RATE_LIMIT = 10 / 1000;
|
||||
const RATE_LIMIT = 1000;
|
||||
|
||||
class EventCountService {
|
||||
subjects = new SuperMap<string, Subject<number>>(() => new Subject<number>());
|
||||
|
@ -10,6 +10,9 @@ import {
|
||||
Heading,
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
Tag,
|
||||
TagLabel,
|
||||
TagLeftIcon,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
@ -21,12 +24,17 @@ import CommunityDescription from "./community-description";
|
||||
import useCountCommunityPosts from "../hooks/use-count-community-post";
|
||||
import UserAvatarLink from "../../../components/user-avatar-link";
|
||||
import { UserLink } from "../../../components/user-link";
|
||||
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
import { CommunityIcon } from "../../../components/icons";
|
||||
import User01 from "../../../components/icons/user-01";
|
||||
|
||||
function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(community));
|
||||
|
||||
const name = getCommunityName(community);
|
||||
const countMembers = useCountCommunityMembers(community);
|
||||
|
||||
// NOTE: disabled because nostr.band has a rate limit
|
||||
// const notesInLastMonth = useCountCommunityPosts(community);
|
||||
@ -60,6 +68,13 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
|
||||
<UserAvatarLink pubkey={community.pubkey} size="sm" />
|
||||
<Text>by</Text>
|
||||
<UserLink pubkey={community.pubkey} />
|
||||
{countMembers && (
|
||||
<Tag variant="solid" ml="auto" alignSelf="flex-end" textShadow="none">
|
||||
<TagLeftIcon as={User01} boxSize={4} />
|
||||
<TagLabel>{readablizeSats(countMembers)}</TagLabel>
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{/* {notesInLastMonth !== undefined && <Text ml="auto">{notesInLastMonth} Posts in the past month</Text>} */}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
77
src/views/community/components/community-members-modal.tsx
Normal file
77
src/views/community/components/community-members-modal.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
ModalProps,
|
||||
SimpleGrid,
|
||||
} from "@chakra-ui/react";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, getCommunityRelays } from "../../../helpers/nostr/communities";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/events";
|
||||
import { NOTE_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||
import { UserLink } from "../../../components/user-link";
|
||||
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
|
||||
import UserAvatarLink from "../../../components/user-avatar-link";
|
||||
|
||||
function UserCard({ pubkey }: { pubkey: string }) {
|
||||
return (
|
||||
<Flex overflow="hidden" gap="4" alignItems="center">
|
||||
<UserAvatarLink pubkey={pubkey} />
|
||||
<Flex direction="column" flex={1} overflow="hidden">
|
||||
<UserLink pubkey={pubkey} fontWeight="bold" />
|
||||
<UserDnsIdentityIcon pubkey={pubkey} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ({ community, onClose, ...props }: Omit<ModalProps, "children"> & { community: NostrEvent }) {
|
||||
const communityCoordinate = getEventCoordinate(community);
|
||||
const readRelays = useReadRelayUrls(getCommunityRelays(community));
|
||||
const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, {
|
||||
"#a": [communityCoordinate],
|
||||
"#d": [SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER],
|
||||
kinds: [NOTE_LIST_KIND],
|
||||
});
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Modal onClose={onClose} size="4xl" {...props}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader px="4" pt="4" pb="0">
|
||||
Community Members
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody p="4">
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="4">
|
||||
{lists.map((list) => (
|
||||
<UserCard key={list.id} pubkey={list.pubkey} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter px="4" pt="0" pb="4">
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
@ -24,74 +24,90 @@ import { RelayIconStack } from "../../../components/relay-icon-stack";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import CommunityJoinButton from "../../communities/components/community-subscribe-button";
|
||||
import CommunityMenu from "./community-menu";
|
||||
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
import CommunityMembersModal from "./community-members-modal";
|
||||
|
||||
export default function HorizontalCommunityDetails({
|
||||
community,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||
const membersModal = useDisclosure();
|
||||
const communityRelays = getCommunityRelays(community);
|
||||
const mods = getCommunityMods(community);
|
||||
const description = getCommunityDescription(community);
|
||||
const rules = getCommunityRules(community);
|
||||
|
||||
const more = useDisclosure();
|
||||
const countMembers = useCountCommunityMembers(community);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardBody>
|
||||
<ButtonGroup float="right">
|
||||
<CommunityJoinButton community={community} />
|
||||
<CommunityMenu community={community} aria-label="More" />
|
||||
</ButtonGroup>
|
||||
{description && (
|
||||
<>
|
||||
<Heading size="sm" mb="2">
|
||||
Description
|
||||
</Heading>
|
||||
<CommunityDescription community={community} mb="2" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{more.isOpen ? (
|
||||
<SimpleGrid spacing="4" columns={2}>
|
||||
<Box>
|
||||
<Heading size="sm" mb="2">
|
||||
Mods
|
||||
<>
|
||||
<Card {...props}>
|
||||
<CardBody>
|
||||
<ButtonGroup float="right">
|
||||
<CommunityJoinButton community={community} />
|
||||
<CommunityMenu community={community} aria-label="More" />
|
||||
</ButtonGroup>
|
||||
{description && (
|
||||
<>
|
||||
<Heading size="sm" mb="1">
|
||||
Description
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
{mods.map((pubkey) => (
|
||||
<Flex gap="2">
|
||||
<UserAvatarLink pubkey={pubkey} size="xs" />
|
||||
<UserLink pubkey={pubkey} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
{rules && (
|
||||
<CommunityDescription community={community} mb="1" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{more.isOpen ? (
|
||||
<SimpleGrid spacing="4" columns={2}>
|
||||
<Box>
|
||||
<Heading size="sm" mb="2">
|
||||
Rules
|
||||
</Heading>
|
||||
<Text whiteSpace="pre-wrap">{rules}</Text>
|
||||
</Box>
|
||||
)}
|
||||
{communityRelays.length > 0 && (
|
||||
<Box>
|
||||
<Heading size="sm" mt="4" mb="2">
|
||||
Relays
|
||||
<Heading size="sm" mb="1">
|
||||
Mods
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
<RelayIconStack relays={communityRelays} />
|
||||
{mods.map((pubkey) => (
|
||||
<Flex gap="2">
|
||||
<UserAvatarLink pubkey={pubkey} size="xs" />
|
||||
<UserLink pubkey={pubkey} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<Button variant="link" onClick={more.onOpen} w="full">
|
||||
Show more
|
||||
</Button>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Box as="button" textAlign="start" cursor="pointer" onClick={membersModal.onOpen}>
|
||||
<Heading size="sm" mb="1">
|
||||
Members
|
||||
</Heading>
|
||||
<Text>{countMembers ? readablizeSats(countMembers) : "unknown"}</Text>
|
||||
</Box>
|
||||
{rules && (
|
||||
<Box>
|
||||
<Heading size="sm" mb="1">
|
||||
Rules
|
||||
</Heading>
|
||||
<Text whiteSpace="pre-wrap">{rules}</Text>
|
||||
</Box>
|
||||
)}
|
||||
{communityRelays.length > 0 && (
|
||||
<Box>
|
||||
<Heading size="sm" mt="4" mb="1">
|
||||
Relays
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
<RelayIconStack relays={communityRelays} />
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<Button variant="link" onClick={more.onOpen} w="full">
|
||||
Show more
|
||||
</Button>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
{membersModal.isOpen && (
|
||||
<CommunityMembersModal isOpen={membersModal.isOpen} onClose={membersModal.onClose} community={community} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Box, ButtonGroup, Card, CardProps, Flex, Heading, Text } from "@chakra-ui/react";
|
||||
import { Box, ButtonGroup, Card, CardProps, Flex, Heading, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import {
|
||||
getCommunityDescription,
|
||||
getCommunityMods,
|
||||
@ -12,59 +12,78 @@ import { RelayIconStack } from "../../../components/relay-icon-stack";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import CommunityJoinButton from "../../communities/components/community-subscribe-button";
|
||||
import CommunityMenu from "./community-menu";
|
||||
import useCountCommunityMembers from "../../../hooks/use-count-community-members";
|
||||
import CommunityMembersModal from "./community-members-modal";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
|
||||
export default function VerticalCommunityDetails({
|
||||
community,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||
const membersModal = useDisclosure();
|
||||
const communityRelays = getCommunityRelays(community);
|
||||
const mods = getCommunityMods(community);
|
||||
const description = getCommunityDescription(community);
|
||||
const rules = getCommunityRules(community);
|
||||
|
||||
const countMembers = useCountCommunityMembers(community);
|
||||
|
||||
return (
|
||||
<Card p="4" {...props}>
|
||||
{description && (
|
||||
<>
|
||||
<Heading size="sm" mb="2">
|
||||
About
|
||||
</Heading>
|
||||
<CommunityDescription community={community} maxLength={256} showExpand />
|
||||
</>
|
||||
)}
|
||||
<ButtonGroup w="full" my="2">
|
||||
<CommunityJoinButton community={community} flex={1} />
|
||||
<CommunityMenu community={community} aria-label="More" />
|
||||
</ButtonGroup>
|
||||
<Heading size="sm" mt="4" mb="2">
|
||||
Mods
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
{mods.map((pubkey) => (
|
||||
<Flex gap="2">
|
||||
<UserAvatarLink pubkey={pubkey} size="xs" />
|
||||
<UserLink pubkey={pubkey} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
{rules && (
|
||||
<>
|
||||
<Heading size="sm" mt="4" mb="2">
|
||||
Rules
|
||||
</Heading>
|
||||
<Text whiteSpace="pre-wrap">{rules}</Text>
|
||||
</>
|
||||
)}
|
||||
{communityRelays.length > 0 && (
|
||||
<>
|
||||
<Heading size="sm" mt="4" mb="2">
|
||||
Relays
|
||||
<>
|
||||
<Card p="4" gap="4" {...props}>
|
||||
{description && (
|
||||
<Box>
|
||||
<Heading size="sm" mb="1">
|
||||
About
|
||||
</Heading>
|
||||
<CommunityDescription community={community} maxLength={256} showExpand />
|
||||
</Box>
|
||||
)}
|
||||
<ButtonGroup w="full">
|
||||
<CommunityJoinButton community={community} flex={1} />
|
||||
<CommunityMenu community={community} aria-label="More" />
|
||||
</ButtonGroup>
|
||||
<Box>
|
||||
<Heading size="sm" mb="1">
|
||||
Mods
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
<RelayIconStack relays={communityRelays} />
|
||||
{mods.map((pubkey) => (
|
||||
<Flex gap="2">
|
||||
<UserAvatarLink pubkey={pubkey} size="xs" />
|
||||
<UserLink pubkey={pubkey} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
</Box>
|
||||
<Box as="button" textAlign="start" cursor="pointer" onClick={membersModal.onOpen}>
|
||||
<Heading size="sm" mb="1">
|
||||
Members
|
||||
</Heading>
|
||||
<Text>{countMembers ? readablizeSats(countMembers) : "unknown"}</Text>
|
||||
</Box>
|
||||
{rules && (
|
||||
<Box>
|
||||
<Heading size="sm" mb="1">
|
||||
Rules
|
||||
</Heading>
|
||||
<Text whiteSpace="pre-wrap">{rules}</Text>
|
||||
</Box>
|
||||
)}
|
||||
{communityRelays.length > 0 && (
|
||||
<Box>
|
||||
<Heading size="sm" mb="1">
|
||||
Relays
|
||||
</Heading>
|
||||
<Flex direction="column" gap="2">
|
||||
<RelayIconStack relays={communityRelays} />
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
{membersModal.isOpen && (
|
||||
<CommunityMembersModal isOpen={membersModal.isOpen} onClose={membersModal.onClose} community={community} />
|
||||
)}
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user