mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-08 20:08:02 +02:00
Add simple community views
This commit is contained in:
parent
a62297c287
commit
0f876421ab
5
.changeset/spotty-pumas-yawn.md
Normal file
5
.changeset/spotty-pumas-yawn.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add simple community views
|
@ -13,6 +13,7 @@ export default function EmbeddedCommunity({
|
|||||||
...props
|
...props
|
||||||
}: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
}: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||||
const image = getCommunityImage(community);
|
const image = getCommunityImage(community);
|
||||||
|
const name = getCommunityName(community);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card as={LinkBox} variant="outline" gap="2" overflow="hidden" {...props}>
|
<Card as={LinkBox} variant="outline" gap="2" overflow="hidden" {...props}>
|
||||||
@ -20,23 +21,20 @@ export default function EmbeddedCommunity({
|
|||||||
<Box
|
<Box
|
||||||
backgroundImage={getCommunityImage(community)}
|
backgroundImage={getCommunityImage(community)}
|
||||||
backgroundRepeat="no-repeat"
|
backgroundRepeat="no-repeat"
|
||||||
backgroundSize="cover"
|
backgroundSize="contain"
|
||||||
backgroundPosition="center"
|
backgroundPosition="center"
|
||||||
aspectRatio={3 / 1}
|
aspectRatio={3 / 1}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center aspectRatio={4 / 1} fontWeight="bold" fontSize="2xl">
|
<Center aspectRatio={4 / 1} fontWeight="bold" fontSize="2xl">
|
||||||
{getCommunityName(community)}
|
{name}
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
<Flex direction="column" flex={1} px="2" pb="2">
|
<Flex direction="column" flex={1} px="2" pb="2">
|
||||||
<Flex wrap="wrap" gap="2" alignItems="center">
|
<Flex wrap="wrap" gap="2" alignItems="center">
|
||||||
<Heading size="lg">
|
<Heading size="lg">
|
||||||
<LinkOverlay
|
<LinkOverlay as={RouterLink} to={`/c/${encodeURIComponent(name)}/${nip19.npubEncode(community.pubkey)}`}>
|
||||||
as={RouterLink}
|
{name}
|
||||||
to={`/c/${encodeURIComponent(getCommunityName(community))}/${nip19.npubEncode(community.pubkey)}`}
|
|
||||||
>
|
|
||||||
{getCommunityName(community)}
|
|
||||||
</LinkOverlay>
|
</LinkOverlay>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text>Created by:</Text>
|
<Text>Created by:</Text>
|
||||||
|
@ -101,6 +101,14 @@ export default function NavItems() {
|
|||||||
>
|
>
|
||||||
Streams
|
Streams
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate("/communities")}
|
||||||
|
leftIcon={<CommunityIcon />}
|
||||||
|
colorScheme={active === "communities" ? "brand" : undefined}
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
Communities
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/lists")}
|
onClick={() => navigate("/lists")}
|
||||||
leftIcon={<ListIcon />}
|
leftIcon={<ListIcon />}
|
||||||
@ -109,14 +117,6 @@ export default function NavItems() {
|
|||||||
>
|
>
|
||||||
Lists
|
Lists
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button
|
|
||||||
onClick={() => navigate("/communities")}
|
|
||||||
leftIcon={<CommunityIcon />}
|
|
||||||
colorScheme={active === "communities" ? "brand" : undefined}
|
|
||||||
{...buttonProps}
|
|
||||||
>
|
|
||||||
Communities
|
|
||||||
</Button> */}
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/goals")}
|
onClick={() => navigate("/goals")}
|
||||||
leftIcon={<GoalIcon />}
|
leftIcon={<GoalIcon />}
|
||||||
|
@ -10,11 +10,13 @@ import {
|
|||||||
Flex,
|
Flex,
|
||||||
IconButton,
|
IconButton,
|
||||||
Link,
|
Link,
|
||||||
|
Text,
|
||||||
useBreakpointValue,
|
useBreakpointValue,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent, isATag } from "../../types/nostr-event";
|
||||||
import { UserAvatarLink } from "../user-avatar-link";
|
import { UserAvatarLink } from "../user-avatar-link";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { NoteMenu } from "./note-menu";
|
import { NoteMenu } from "./note-menu";
|
||||||
import { EventRelays } from "./note-relays";
|
import { EventRelays } from "./note-relays";
|
||||||
@ -36,10 +38,12 @@ import BookmarkButton from "./components/bookmark-button";
|
|||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
import NoteReactions from "./components/note-reactions";
|
import NoteReactions from "./components/note-reactions";
|
||||||
import ReplyForm from "../../views/note/components/reply-form";
|
import ReplyForm from "../../views/note/components/reply-form";
|
||||||
import { getReferences } from "../../helpers/nostr/events";
|
import { getEventCoordinate, getReferences, parseCoordinate } from "../../helpers/nostr/events";
|
||||||
import Timestamp from "../timestamp";
|
import Timestamp from "../timestamp";
|
||||||
import OpenInDrawerButton from "../open-in-drawer-button";
|
import OpenInDrawerButton from "../open-in-drawer-button";
|
||||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||||
|
import { COMMUNITY_DEFINITION_KIND, getCommunityName } from "../../helpers/nostr/communities";
|
||||||
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
|
|
||||||
export type NoteProps = Omit<CardProps, "children"> & {
|
export type NoteProps = Omit<CardProps, "children"> & {
|
||||||
event: NostrEvent;
|
event: NostrEvent;
|
||||||
@ -67,6 +71,11 @@ export const Note = React.memo(
|
|||||||
|
|
||||||
// find mostr external link
|
// find mostr external link
|
||||||
const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr" || t[0] === "proxy"), [event])?.[1];
|
const externalLink = useMemo(() => event.tags.find((t) => t[0] === "mostr" || t[0] === "proxy"), [event])?.[1];
|
||||||
|
const communityPointer = useMemo(() => {
|
||||||
|
const tag = event.tags.find((t) => isATag(t) && t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
|
||||||
|
return tag?.[1] ? parseCoordinate(tag[1], true) : undefined;
|
||||||
|
}, [event]);
|
||||||
|
const community = useReplaceableEvent(communityPointer);
|
||||||
|
|
||||||
const showReactionsOnNewLine = useBreakpointValue({ base: true, md: false });
|
const showReactionsOnNewLine = useBreakpointValue({ base: true, md: false });
|
||||||
|
|
||||||
@ -81,7 +90,7 @@ export const Note = React.memo(
|
|||||||
data-event-id={event.id}
|
data-event-id={event.id}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<CardHeader padding="2">
|
<CardHeader p="2">
|
||||||
<Flex flex="1" gap="2" alignItems="center">
|
<Flex flex="1" gap="2" alignItems="center">
|
||||||
<UserAvatarLink pubkey={event.pubkey} size={["xs", "sm"]} />
|
<UserAvatarLink pubkey={event.pubkey} size={["xs", "sm"]} />
|
||||||
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="lg" />
|
||||||
@ -95,6 +104,15 @@ export const Note = React.memo(
|
|||||||
<Timestamp timestamp={event.created_at} />
|
<Timestamp timestamp={event.created_at} />
|
||||||
</NoteLink>
|
</NoteLink>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{community && (
|
||||||
|
<Text fontStyle="italic">
|
||||||
|
Posted in{" "}
|
||||||
|
<Link as={RouterLink} to={`/c/${getCommunityName(community)}/${community.pubkey}`} color="blue.500">
|
||||||
|
{getCommunityName(community)}
|
||||||
|
</Link>{" "}
|
||||||
|
community
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody p="0">
|
<CardBody p="0">
|
||||||
<NoteContentWithWarning event={event} />
|
<NoteContentWithWarning event={event} />
|
||||||
|
30
src/components/post-modal/community-select.tsx
Normal file
30
src/components/post-modal/community-select.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Select, SelectProps } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
import useSubscribedCommunitiesList from "../../hooks/use-subscribed-communities-list";
|
||||||
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
|
import { getCommunityName } from "../../helpers/nostr/communities";
|
||||||
|
import { AddressPointer } from "nostr-tools/lib/nip19";
|
||||||
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
|
import { getEventCoordinate } from "../../helpers/nostr/events";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
|
function CommunityOption({ pointer }: { pointer: AddressPointer }) {
|
||||||
|
const community = useReplaceableEvent(pointer);
|
||||||
|
if (!community) return;
|
||||||
|
|
||||||
|
return <option value={getEventCoordinate(community)}>{getCommunityName(community)}</option>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommunitySelect = forwardRef<HTMLSelectElement, Omit<SelectProps, "children">>(({ ...props }, ref) => {
|
||||||
|
const account = useCurrentAccount();
|
||||||
|
const { pointers } = useSubscribedCommunitiesList(account?.pubkey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select placeholder="Select community" {...props} ref={ref}>
|
||||||
|
{pointers.map((pointer) => (
|
||||||
|
<CommunityOption key={pointer.identifier + pointer.pubkey} pointer={pointer} />
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export default CommunitySelect;
|
@ -15,6 +15,8 @@ import {
|
|||||||
ModalProps,
|
ModalProps,
|
||||||
VisuallyHiddenInput,
|
VisuallyHiddenInput,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@ -32,6 +34,7 @@ import { UserAvatarStack } from "../compact-user-stack";
|
|||||||
import MagicTextArea, { RefType } from "../magic-textarea";
|
import MagicTextArea, { RefType } from "../magic-textarea";
|
||||||
import { useContextEmojis } from "../../providers/emoji-provider";
|
import { useContextEmojis } from "../../providers/emoji-provider";
|
||||||
import { nostrBuildUploadImage } from "../../helpers/nostr-build";
|
import { nostrBuildUploadImage } from "../../helpers/nostr-build";
|
||||||
|
import CommunitySelect from "./community-select";
|
||||||
|
|
||||||
export default function PostModal({
|
export default function PostModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -50,6 +53,7 @@ export default function PostModal({
|
|||||||
content: initContent,
|
content: initContent,
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
nsfwReason: "",
|
nsfwReason: "",
|
||||||
|
community: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
watch("content");
|
watch("content");
|
||||||
@ -82,7 +86,7 @@ export default function PostModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getDraft = useCallback(() => {
|
const getDraft = useCallback(() => {
|
||||||
const { content, nsfw, nsfwReason } = getValues();
|
const { content, nsfw, nsfwReason, community } = getValues();
|
||||||
|
|
||||||
let updatedDraft = finalizeNote({
|
let updatedDraft = finalizeNote({
|
||||||
content: content,
|
content: content,
|
||||||
@ -94,6 +98,9 @@ export default function PostModal({
|
|||||||
if (nsfw) {
|
if (nsfw) {
|
||||||
updatedDraft.tags.push(nsfwReason ? ["content-warning", nsfwReason] : ["content-warning"]);
|
updatedDraft.tags.push(nsfwReason ? ["content-warning", nsfwReason] : ["content-warning"]);
|
||||||
}
|
}
|
||||||
|
if (community) {
|
||||||
|
updatedDraft.tags.push(["a", community]);
|
||||||
|
}
|
||||||
|
|
||||||
const contentMentions = getContentMentions(updatedDraft.content);
|
const contentMentions = getContentMentions(updatedDraft.content);
|
||||||
updatedDraft = createEmojiTags(updatedDraft, emojis);
|
updatedDraft = createEmojiTags(updatedDraft, emojis);
|
||||||
@ -190,6 +197,10 @@ export default function PostModal({
|
|||||||
</Flex>
|
</Flex>
|
||||||
{moreOptions.isOpen && (
|
{moreOptions.isOpen && (
|
||||||
<>
|
<>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Post to community</FormLabel>
|
||||||
|
<CommunitySelect w="sm" {...register("community")} />
|
||||||
|
</FormControl>
|
||||||
<Flex gap="2" direction="column">
|
<Flex gap="2" direction="column">
|
||||||
<Switch {...register("nsfw")}>NSFW</Switch>
|
<Switch {...register("nsfw")}>NSFW</Switch>
|
||||||
{getValues().nsfw && <Input {...register("nsfwReason")} placeholder="Reason" maxW="50%" />}
|
{getValues().nsfw && <Input {...register("nsfwReason")} placeholder="Reason" maxW="50%" />}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { validateEvent } from "nostr-tools";
|
||||||
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 SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
|
||||||
@ -25,6 +26,16 @@ export function getCommunityDescription(community: NostrEvent) {
|
|||||||
return community.tags.find((t) => t[0] === "description")?.[1];
|
return community.tags.find((t) => t[0] === "description")?.[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getApprovedEmbeddedNote(approval: NostrEvent) {
|
||||||
|
if (!approval.content) return null;
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(approval.content);
|
||||||
|
validateEvent(json);
|
||||||
|
return (json as NostrEvent) ?? null;
|
||||||
|
} catch (e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function validateCommunity(community: NostrEvent) {
|
export function validateCommunity(community: NostrEvent) {
|
||||||
try {
|
try {
|
||||||
getCommunityName(community);
|
getCommunityName(community);
|
||||||
|
@ -153,11 +153,20 @@ export function getEventCoordinate(event: NostrEvent) {
|
|||||||
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
||||||
return d ? `${event.kind}:${event.pubkey}:${d}` : `${event.kind}:${event.pubkey}`;
|
return d ? `${event.kind}:${event.pubkey}:${d}` : `${event.kind}:${event.pubkey}`;
|
||||||
}
|
}
|
||||||
|
export function pointerToATag(pointer: AddressPointer): ATag {
|
||||||
|
const relay = pointer.relays?.[0];
|
||||||
|
const coordinate = `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
|
||||||
|
return relay ? ["a", coordinate, relay] : ["a", coordinate];
|
||||||
|
}
|
||||||
|
|
||||||
export type CustomEventPointer = Omit<AddressPointer, "identifier"> & {
|
export type CustomEventPointer = Omit<AddressPointer, "identifier"> & {
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
};
|
};
|
||||||
export function parseCoordinate(a: string): CustomEventPointer | null {
|
|
||||||
|
export function parseCoordinate(a: string): CustomEventPointer | null;
|
||||||
|
export function parseCoordinate(a: string, requireD: false): CustomEventPointer | null;
|
||||||
|
export function parseCoordinate(a: string, requireD: true): AddressPointer;
|
||||||
|
export function parseCoordinate(a: string, requireD = false): CustomEventPointer | null {
|
||||||
const parts = a.split(":") as (string | undefined)[];
|
const parts = a.split(":") as (string | undefined)[];
|
||||||
const kind = parts[0] && parseInt(parts[0]);
|
const kind = parts[0] && parseInt(parts[0]);
|
||||||
const pubkey = parts[1];
|
const pubkey = parts[1];
|
||||||
@ -165,6 +174,7 @@ export function parseCoordinate(a: string): CustomEventPointer | null {
|
|||||||
|
|
||||||
if (!kind) return null;
|
if (!kind) return null;
|
||||||
if (!pubkey) return null;
|
if (!pubkey) return null;
|
||||||
|
if (requireD && !d) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind,
|
kind,
|
||||||
|
@ -17,6 +17,7 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
|
|||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(community));
|
useRegisterIntersectionEntity(ref, getEventUID(community));
|
||||||
|
|
||||||
|
const name = getCommunityName(community);
|
||||||
const image = getCommunityImage(community);
|
const image = getCommunityImage(community);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,23 +26,20 @@ function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & {
|
|||||||
<Box
|
<Box
|
||||||
backgroundImage={getCommunityImage(community)}
|
backgroundImage={getCommunityImage(community)}
|
||||||
backgroundRepeat="no-repeat"
|
backgroundRepeat="no-repeat"
|
||||||
backgroundSize="cover"
|
backgroundSize="contain"
|
||||||
backgroundPosition="center"
|
backgroundPosition="center"
|
||||||
aspectRatio={3 / 1}
|
aspectRatio={3 / 1}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Center aspectRatio={3 / 1} fontWeight="bold" fontSize="2xl">
|
<Center aspectRatio={3 / 1} fontWeight="bold" fontSize="2xl">
|
||||||
{getCommunityName(community)}
|
{name}
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
<Flex direction="column" flex={1} px="2" pb="2">
|
<Flex direction="column" flex={1} px="2" pb="2">
|
||||||
<Flex wrap="wrap" gap="2" alignItems="center">
|
<Flex wrap="wrap" gap="2" alignItems="center">
|
||||||
<Heading size="lg">
|
<Heading size="lg">
|
||||||
<LinkOverlay
|
<LinkOverlay as={RouterLink} to={`/c/${encodeURIComponent(name)}/${nip19.npubEncode(community.pubkey)}`}>
|
||||||
as={RouterLink}
|
{name}
|
||||||
to={`/c/${encodeURIComponent(getCommunityName(community))}/${nip19.npubEncode(community.pubkey)}`}
|
|
||||||
>
|
|
||||||
{getCommunityName(community)}
|
|
||||||
</LinkOverlay>
|
</LinkOverlay>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text>Created by:</Text>
|
<Text>Created by:</Text>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { Avatar, Box, Flex, Heading, Text } from "@chakra-ui/react";
|
import { useRef } from "react";
|
||||||
|
import { Box, Flex, Heading, Text } from "@chakra-ui/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COMMUNITY_APPROVAL_KIND,
|
COMMUNITY_APPROVAL_KIND,
|
||||||
getCOmmunityRelays,
|
getApprovedEmbeddedNote,
|
||||||
|
getCOmmunityRelays as getCommunityRelays,
|
||||||
getCommunityImage,
|
getCommunityImage,
|
||||||
getCommunityMods,
|
getCommunityMods,
|
||||||
getCommunityName,
|
getCommunityName,
|
||||||
} from "../../helpers/nostr/communities";
|
} from "../../helpers/nostr/communities";
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent, isETag } from "../../types/nostr-event";
|
||||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||||
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";
|
||||||
@ -18,16 +20,41 @@ import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
|||||||
import { unique } from "../../helpers/array";
|
import { unique } from "../../helpers/array";
|
||||||
import useSubject from "../../hooks/use-subject";
|
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, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||||
import Note from "../../components/note";
|
|
||||||
import CommunityJoinButton from "../communities/components/community-subscribe-button";
|
import CommunityJoinButton from "../communities/components/community-subscribe-button";
|
||||||
|
import useSingleEvent from "../../hooks/use-single-event";
|
||||||
|
import { EmbedEvent } from "../../components/embed-event";
|
||||||
|
import { AdditionalRelayProvider, useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
||||||
|
import { RelayIconStack } from "../../components/relay-icon-stack";
|
||||||
|
|
||||||
|
function ApprovedEvent({ approval }: { approval: NostrEvent }) {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, getEventUID(approval));
|
||||||
|
|
||||||
|
const additionalRelays = useAdditionalRelayContext();
|
||||||
|
const embeddedEvent = getApprovedEmbeddedNote(approval);
|
||||||
|
const eventTag = approval.tags.find(isETag);
|
||||||
|
|
||||||
|
const loadEvent = useSingleEvent(
|
||||||
|
eventTag?.[1],
|
||||||
|
eventTag?.[2] ? [eventTag[2], ...additionalRelays] : additionalRelays,
|
||||||
|
);
|
||||||
|
const event = loadEvent || embeddedEvent;
|
||||||
|
if (!event) return;
|
||||||
|
return (
|
||||||
|
<Box ref={ref}>
|
||||||
|
<EmbedEvent event={event} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
||||||
const mods = getCommunityMods(community);
|
const mods = getCommunityMods(community);
|
||||||
const image = getCommunityImage(community);
|
const image = getCommunityImage(community);
|
||||||
|
|
||||||
const readRelays = useReadRelayUrls(getCOmmunityRelays(community));
|
const communityRelays = getCommunityRelays(community);
|
||||||
const timeline = useTimelineLoader(`${getEventUID(community)}-appoved-posts`, readRelays, {
|
const readRelays = useReadRelayUrls(communityRelays);
|
||||||
|
const timeline = useTimelineLoader(`${getEventUID(community)}-approved-posts`, readRelays, {
|
||||||
authors: unique([community.pubkey, ...mods]),
|
authors: unique([community.pubkey, ...mods]),
|
||||||
kinds: [COMMUNITY_APPROVAL_KIND],
|
kinds: [COMMUNITY_APPROVAL_KIND],
|
||||||
"#a": [getEventCoordinate(community)],
|
"#a": [getEventCoordinate(community)],
|
||||||
@ -37,40 +64,49 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
|||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VerticalPageLayout>
|
<AdditionalRelayProvider relays={communityRelays}>
|
||||||
{image && (
|
<VerticalPageLayout pt={image && "0"}>
|
||||||
<Box
|
{image && (
|
||||||
backgroundImage={getCommunityImage(community)}
|
<Box
|
||||||
backgroundRepeat="no-repeat"
|
backgroundImage={getCommunityImage(community)}
|
||||||
backgroundSize="cover"
|
backgroundRepeat="no-repeat"
|
||||||
backgroundPosition="center"
|
backgroundSize="contain"
|
||||||
aspectRatio={4 / 1}
|
backgroundPosition="center"
|
||||||
/>
|
aspectRatio={4 / 1}
|
||||||
)}
|
backgroundColor="rgba(0,0,0,0.2)"
|
||||||
<Flex wrap="wrap" gap="2" alignItems="center">
|
/>
|
||||||
<Heading size="lg">{getCommunityName(community)}</Heading>
|
)}
|
||||||
<Text>Created by:</Text>
|
<Flex wrap="wrap" gap="2" alignItems="center">
|
||||||
<Flex gap="2">
|
<Heading size="lg">{getCommunityName(community)}</Heading>
|
||||||
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
|
<Text>Created by:</Text>
|
||||||
</Flex>
|
|
||||||
<CommunityJoinButton community={community} ml="auto" />
|
|
||||||
</Flex>
|
|
||||||
<CommunityDescription community={community} />
|
|
||||||
<Flex wrap="wrap" gap="2">
|
|
||||||
<Text>Moderators:</Text>
|
|
||||||
{mods.map((pubkey) => (
|
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<UserAvatarLink pubkey={pubkey} size="xs" />
|
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
|
||||||
<UserLink pubkey={pubkey} />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
<CommunityJoinButton community={community} ml="auto" />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<CommunityDescription community={community} />
|
||||||
|
<Flex wrap="wrap" gap="2">
|
||||||
|
<Text>Moderators:</Text>
|
||||||
|
{mods.map((pubkey) => (
|
||||||
|
<Flex gap="2">
|
||||||
|
<UserAvatarLink pubkey={pubkey} size="xs" />
|
||||||
|
<UserLink pubkey={pubkey} />
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
{communityRelays.length > 0 && (
|
||||||
|
<Flex wrap="wrap" gap="2">
|
||||||
|
<Text>Relays:</Text>
|
||||||
|
<RelayIconStack relays={communityRelays} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
<IntersectionObserverProvider callback={callback}>
|
<IntersectionObserverProvider callback={callback}>
|
||||||
{approvals.map((approval) => (
|
{approvals.map((approval) => (
|
||||||
<Note key={getEventUID(approval)} event={approval} />
|
<ApprovedEvent key={getEventUID(approval)} approval={approval} />
|
||||||
))}
|
))}
|
||||||
</IntersectionObserverProvider>
|
</IntersectionObserverProvider>
|
||||||
</VerticalPageLayout>
|
</VerticalPageLayout>
|
||||||
|
</AdditionalRelayProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user