mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-07 03:18:02 +02:00
add community browser
This commit is contained in:
parent
adb7cb7655
commit
602131819c
5
.changeset/eight-eggs-turn.md
Normal file
5
.changeset/eight-eggs-turn.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add community browse view
|
14
src/app.tsx
14
src/app.tsx
@ -56,6 +56,9 @@ import BadgesBrowseView from "./views/badges/browse";
|
||||
import BadgeDetailsView from "./views/badges/badge-details";
|
||||
import UserArticlesTab from "./views/user/articles";
|
||||
import DrawerSubViewProvider from "./providers/drawer-sub-view-provider";
|
||||
import CommunitiesHomeView from "./views/communities";
|
||||
import CommunityFindByNameView from "./views/community/find-by-name";
|
||||
import CommunityView from "./views/community/index";
|
||||
|
||||
const StreamsView = React.lazy(() => import("./views/streams"));
|
||||
const StreamView = React.lazy(() => import("./views/streams/stream"));
|
||||
@ -178,6 +181,17 @@ const router = createHashRouter([
|
||||
{ path: ":addr", element: <ListDetailsView /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "communities",
|
||||
element: <CommunitiesHomeView />,
|
||||
},
|
||||
{
|
||||
path: "c/:community",
|
||||
children: [
|
||||
{ path: "", element: <CommunityFindByNameView /> },
|
||||
{ path: ":pubkey", element: <CommunityView /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "goals",
|
||||
children: [
|
||||
|
@ -421,3 +421,9 @@ export const PerformanceIcon = createIcon({
|
||||
d: "M20 13C20 15.2091 19.1046 17.2091 17.6569 18.6569L19.0711 20.0711C20.8807 18.2614 22 15.7614 22 13 22 7.47715 17.5228 3 12 3 6.47715 3 2 7.47715 2 13 2 15.7614 3.11929 18.2614 4.92893 20.0711L6.34315 18.6569C4.89543 17.2091 4 15.2091 4 13 4 8.58172 7.58172 5 12 5 16.4183 5 20 8.58172 20 13ZM15.293 8.29297 10.793 12.793 12.2072 14.2072 16.7072 9.70718 15.293 8.29297Z",
|
||||
defaultProps,
|
||||
});
|
||||
|
||||
export const CommunityIcon = createIcon({
|
||||
displayName: "CommunityIcon",
|
||||
d: "M9.55 11.5C8.30736 11.5 7.3 10.4926 7.3 9.25C7.3 8.00736 8.30736 7 9.55 7C10.7926 7 11.8 8.00736 11.8 9.25C11.8 10.4926 10.7926 11.5 9.55 11.5ZM10 19.748V16.4C10 15.9116 10.1442 15.4627 10.4041 15.0624C10.1087 15.0213 9.80681 15 9.5 15C7.93201 15 6.49369 15.5552 5.37091 16.4797C6.44909 18.0721 8.08593 19.2553 10 19.748ZM4.45286 14.66C5.86432 13.6168 7.61013 13 9.5 13C10.5435 13 11.5431 13.188 12.4667 13.5321C13.3447 13.1888 14.3924 13 15.5 13C17.1597 13 18.6849 13.4239 19.706 14.1563C19.8976 13.4703 20 12.7471 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 12.9325 4.15956 13.8278 4.45286 14.66ZM18.8794 16.0859C18.4862 15.5526 17.1708 15 15.5 15C13.4939 15 12 15.7967 12 16.4V20C14.9255 20 17.4843 18.4296 18.8794 16.0859ZM12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM15.5 12.5C14.3954 12.5 13.5 11.6046 13.5 10.5C13.5 9.39543 14.3954 8.5 15.5 8.5C16.6046 8.5 17.5 9.39543 17.5 10.5C17.5 11.6046 16.6046 12.5 15.5 12.5Z",
|
||||
defaultProps,
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
BadgeIcon,
|
||||
ChatIcon,
|
||||
CommunityIcon,
|
||||
EmojiIcon,
|
||||
FeedIcon,
|
||||
GoalIcon,
|
||||
@ -50,6 +51,9 @@ export default function NavItems({ isInDrawer = false }: { isInDrawer?: boolean
|
||||
<Button onClick={() => navigate("/lists")} leftIcon={<ListIcon />} justifyContent="flex-start">
|
||||
Lists
|
||||
</Button>
|
||||
<Button onClick={() => navigate("/communities")} leftIcon={<CommunityIcon />} justifyContent="flex-start">
|
||||
Communities
|
||||
</Button>
|
||||
<Button onClick={() => navigate("/goals")} leftIcon={<GoalIcon />} justifyContent="flex-start">
|
||||
Goals
|
||||
</Button>
|
||||
|
@ -47,7 +47,8 @@ export function safeDecode(str: string) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export function getPubkey(result: nip19.DecodeResult) {
|
||||
export function getPubkey(result?: nip19.DecodeResult) {
|
||||
if (!result) return;
|
||||
switch (result.type) {
|
||||
case "naddr":
|
||||
case "nprofile":
|
||||
|
34
src/helpers/nostr/communities.ts
Normal file
34
src/helpers/nostr/communities.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { NostrEvent, isDTag, isPTag } from "../../types/nostr-event";
|
||||
|
||||
export const COMMUNITY_DEFINITION_KIND = 34550;
|
||||
export const COMMUNITY_APPROVAL_KIND = 4550;
|
||||
|
||||
export function getCommunityName(community: NostrEvent) {
|
||||
const name = community.tags.find(isDTag)?.[1];
|
||||
if (!name) throw new Error("Missing name");
|
||||
return name;
|
||||
}
|
||||
|
||||
export function getCommunityMods(community: NostrEvent) {
|
||||
const mods = community.tags.filter((t) => isPTag(t) && t[1] && t[3] === "moderator").map((t) => t[1]) as string[];
|
||||
return mods;
|
||||
}
|
||||
export function getCOmmunityRelays(community: NostrEvent) {
|
||||
return community.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]) as string[];
|
||||
}
|
||||
|
||||
export function getCommunityImage(community: NostrEvent) {
|
||||
return community.tags.find((t) => t[0] === "image")?.[1];
|
||||
}
|
||||
export function getCommunityDescription(community: NostrEvent) {
|
||||
return community.tags.find((t) => t[0] === "description")?.[1];
|
||||
}
|
||||
|
||||
export function validateCommunity(community: NostrEvent) {
|
||||
try {
|
||||
getCommunityName(community);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
71
src/views/communities/components/community-card.tsx
Normal file
71
src/views/communities/components/community-card.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { memo, useRef } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Card,
|
||||
CardProps,
|
||||
Center,
|
||||
Flex,
|
||||
Heading,
|
||||
Image,
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||
import { UserLink } from "../../../components/user-link";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||
import { getEventUID } from "../../../helpers/nostr/events";
|
||||
import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities";
|
||||
import CommunityDescription from "./community-description";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import CommunityModList from "./community-mod-list";
|
||||
|
||||
function CommunityCard({ community, ...props }: Omit<CardProps, "children"> & { community: NostrEvent }) {
|
||||
// if there is a parent intersection observer, register this card
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(community));
|
||||
|
||||
const image = getCommunityImage(community);
|
||||
|
||||
return (
|
||||
<Card as={LinkBox} ref={ref} variant="outline" gap="2" overflow="hidden" {...props}>
|
||||
{image ? (
|
||||
<Box
|
||||
backgroundImage={getCommunityImage(community)}
|
||||
backgroundRepeat="no-repeat"
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
aspectRatio={4 / 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 gap="2">
|
||||
<CommunityModList community={community} ml="auto" size="xs" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(CommunityCard);
|
25
src/views/communities/components/community-description.tsx
Normal file
25
src/views/communities/components/community-description.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { Box, BoxProps } from "@chakra-ui/react";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getCommunityDescription } from "../../../helpers/nostr/communities";
|
||||
import { EmbedableContent, embedUrls, truncateEmbedableContent } from "../../../helpers/embeds";
|
||||
import { renderGenericUrl } from "../../../components/embed-types";
|
||||
|
||||
export default function CommunityDescription({
|
||||
community,
|
||||
maxLength,
|
||||
...props
|
||||
}: Omit<BoxProps, "children"> & { community: NostrEvent; maxLength?: number }) {
|
||||
const description = getCommunityDescription(community);
|
||||
let content: EmbedableContent = description ? [description] : [];
|
||||
|
||||
content = embedUrls(content, [renderGenericUrl]);
|
||||
if (maxLength !== undefined) {
|
||||
content = truncateEmbedableContent(content, maxLength);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box whiteSpace="pre-wrap" {...props}>
|
||||
{content}
|
||||
</Box>
|
||||
);
|
||||
}
|
20
src/views/communities/components/community-mod-list.tsx
Normal file
20
src/views/communities/components/community-mod-list.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { AvatarGroup, AvatarGroupProps } from "@chakra-ui/react";
|
||||
|
||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getCommunityMods } from "../../../helpers/nostr/communities";
|
||||
|
||||
export default function CommunityModList({
|
||||
community,
|
||||
...props
|
||||
}: Omit<AvatarGroupProps, "children"> & { community: NostrEvent }) {
|
||||
const mods = getCommunityMods(community);
|
||||
|
||||
return (
|
||||
<AvatarGroup {...props}>
|
||||
{mods.map((pubkey) => (
|
||||
<UserAvatarLink pubkey={pubkey} />
|
||||
))}
|
||||
</AvatarGroup>
|
||||
);
|
||||
}
|
66
src/views/communities/index.tsx
Normal file
66
src/views/communities/index.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import CommunityCard from "./components/community-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
|
||||
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/relay-selection-provider";
|
||||
import { COMMUNITY_DEFINITION_KIND, validateCommunity } from "../../helpers/nostr/communities";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { NostrQuery } from "../../types/nostr-query";
|
||||
|
||||
function CommunitiesHomePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
const { relays } = useRelaySelectionContext();
|
||||
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return validateCommunity(event);
|
||||
}, []);
|
||||
|
||||
const query = useMemo(() => {
|
||||
const base: NostrQuery = { kinds: [COMMUNITY_DEFINITION_KIND] };
|
||||
if (filter?.authors) {
|
||||
base.authors = filter.authors;
|
||||
base["#p"] = filter.authors;
|
||||
}
|
||||
return base;
|
||||
}, [filter]);
|
||||
|
||||
const timeline = useTimelineLoader(`${listId}-browse-communities`, relays, query, { enabled: !!filter, eventFilter });
|
||||
|
||||
const communities = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
<PeopleListSelection />
|
||||
<RelaySelectionButton />
|
||||
</Flex>
|
||||
<SimpleGrid columns={[1, 1, 1, 2]} spacing="2">
|
||||
{communities.map((event) => (
|
||||
<CommunityCard key={getEventUID(event)} community={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CommunitiesHomeView() {
|
||||
return (
|
||||
<PeopleListProvider initList="global">
|
||||
<RelaySelectionProvider>
|
||||
<CommunitiesHomePage />
|
||||
</RelaySelectionProvider>
|
||||
</PeopleListProvider>
|
||||
);
|
||||
}
|
74
src/views/community/community-home.tsx
Normal file
74
src/views/community/community-home.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { Avatar, Box, Flex, Heading, Text } from "@chakra-ui/react";
|
||||
|
||||
import {
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
getCOmmunityRelays,
|
||||
getCommunityImage,
|
||||
getCommunityMods,
|
||||
getCommunityName,
|
||||
} from "../../helpers/nostr/communities";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { UserAvatarLink } from "../../components/user-avatar-link";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import CommunityDescription from "../communities/components/community-description";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { getEventCoordinate, getEventUID } from "../../helpers/nostr/events";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import { unique } from "../../helpers/array";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import Note from "../../components/note";
|
||||
|
||||
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
||||
const mods = getCommunityMods(community);
|
||||
const image = getCommunityImage(community);
|
||||
|
||||
const readRelays = useReadRelayUrls(getCOmmunityRelays(community));
|
||||
const timeline = useTimelineLoader(`${getEventUID(community)}-appoved-posts`, readRelays, {
|
||||
authors: unique([community.pubkey, ...mods]),
|
||||
kinds: [COMMUNITY_APPROVAL_KIND],
|
||||
"#a": [getEventCoordinate(community)],
|
||||
});
|
||||
|
||||
const approvals = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
{image && (
|
||||
<Box
|
||||
backgroundImage={getCommunityImage(community)}
|
||||
backgroundRepeat="no-repeat"
|
||||
backgroundSize="cover"
|
||||
backgroundPosition="center"
|
||||
aspectRatio={4 / 1}
|
||||
/>
|
||||
)}
|
||||
<Flex wrap="wrap" gap="2" alignItems="center">
|
||||
<Heading size="lg">{getCommunityName(community)}</Heading>
|
||||
<Text>Created by:</Text>
|
||||
<Flex gap="2">
|
||||
<UserAvatarLink pubkey={community.pubkey} size="xs" /> <UserLink pubkey={community.pubkey} />
|
||||
</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>
|
||||
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{approvals.map((approval) => (
|
||||
<Note key={getEventUID(approval)} event={approval} />
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
44
src/views/community/find-by-name.tsx
Normal file
44
src/views/community/find-by-name.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { useCallback } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import { COMMUNITY_DEFINITION_KIND, validateCommunity } from "../../helpers/nostr/communities";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import CommunityCard from "../communities/components/community-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import { Divider, Heading } from "@chakra-ui/react";
|
||||
|
||||
export default function CommunityFindByNameView() {
|
||||
const { community } = useParams() as { community: string };
|
||||
|
||||
const readRelays = useReadRelayUrls();
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return validateCommunity(event);
|
||||
}, []);
|
||||
const timeline = useTimelineLoader(
|
||||
`${community}-find-communities`,
|
||||
readRelays,
|
||||
{ kinds: [COMMUNITY_DEFINITION_KIND], "#d": [community] },
|
||||
{ enabled: !!community },
|
||||
);
|
||||
|
||||
const communities = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
<Heading>Select Community:</Heading>
|
||||
<Divider />
|
||||
{communities.map((event) => (
|
||||
<CommunityCard key={getEventUID(event)} community={event} />
|
||||
))}
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
29
src/views/community/index.tsx
Normal file
29
src/views/community/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { Spinner } from "@chakra-ui/react";
|
||||
import CommunityHomePage from "./community-home";
|
||||
import { getPubkey, isHexKey, safeDecode } from "../../helpers/nip19";
|
||||
|
||||
function useCommunityPointer() {
|
||||
const { community, pubkey } = useParams();
|
||||
|
||||
const decoded = community ? safeDecode(community) : undefined;
|
||||
if (decoded) {
|
||||
if (decoded.type === "naddr" && decoded.data.kind === COMMUNITY_DEFINITION_KIND) return decoded.data;
|
||||
} else if (community && pubkey) {
|
||||
const hexPubkey = isHexKey(pubkey) ? pubkey : getPubkey(safeDecode(pubkey));
|
||||
if (!hexPubkey) return;
|
||||
|
||||
return { kind: COMMUNITY_DEFINITION_KIND, pubkey: hexPubkey, identifier: community };
|
||||
}
|
||||
}
|
||||
|
||||
export default function CommunityView() {
|
||||
const pointer = useCommunityPointer();
|
||||
const community = useReplaceableEvent(pointer);
|
||||
|
||||
if (!community) return <Spinner />;
|
||||
|
||||
return <CommunityHomePage community={community} />;
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { Flex, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
@ -10,10 +12,7 @@ import useSubject from "../../hooks/use-subject";
|
||||
import GoalCard from "./components/goal-card";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import { GOAL_KIND, getGoalClosedDate } from "../../helpers/nostr/goal";
|
||||
import { SwipeState } from "yet-another-react-lightbox";
|
||||
import { useCallback } from "react";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import dayjs from "dayjs";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function GoalsBrowsePage() {
|
||||
|
@ -5,6 +5,7 @@ import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs";
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
|
||||
function NostrLinkPage() {
|
||||
const { link } = useParams() as { link?: string };
|
||||
@ -32,6 +33,7 @@ function NostrLinkPage() {
|
||||
if (decoded.data.kind === EMOJI_PACK_KIND) return <Navigate to={`/emojis/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === NOTE_LIST_KIND) return <Navigate to={`/lists/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === PEOPLE_LIST_KIND) return <Navigate to={`/lists/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === COMMUNITY_DEFINITION_KIND) return <Navigate to={`/c/${cleanLink}`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
Loading…
x
Reference in New Issue
Block a user