mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-02 08:58:36 +02:00
small cleanup for communities
This commit is contained in:
parent
8a03bbb60e
commit
4b6dd2d4e4
@ -14,7 +14,7 @@ import {
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { NostrEvent, isATag } from "../../types/nostr-event";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import UserAvatarLink from "../user-avatar-link";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
@ -32,17 +32,16 @@ import { QuoteRepostButton } from "./components/quote-repost-button";
|
||||
import { ExternalLinkIcon, ReplyIcon } from "../icons";
|
||||
import NoteContentWithWarning from "./note-content-with-warning";
|
||||
import { TrustProvider } from "../../providers/trust";
|
||||
import { NoteLink } from "../note-link";
|
||||
import { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
||||
import BookmarkButton from "./components/bookmark-button";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import NoteReactions from "./components/note-reactions";
|
||||
import ReplyForm from "../../views/note/components/reply-form";
|
||||
import { getReferences, parseCoordinate } from "../../helpers/nostr/events";
|
||||
import { getReferences } from "../../helpers/nostr/events";
|
||||
import Timestamp from "../timestamp";
|
||||
import OpenInDrawerButton from "../open-in-drawer-button";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { COMMUNITY_DEFINITION_KIND, getCommunityName } from "../../helpers/nostr/communities";
|
||||
import { getCommunityName, getEventCommunityPointer } from "../../helpers/nostr/communities";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
import HoverLinkOverlay from "../hover-link-overlay";
|
||||
@ -76,11 +75,8 @@ export const Note = React.memo(
|
||||
|
||||
// find mostr external link
|
||||
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 communityPointer = useMemo(() => getEventCommunityPointer(event), [event]);
|
||||
const community = useReplaceableEvent(communityPointer ?? undefined);
|
||||
|
||||
const showReactionsOnNewLine = useBreakpointValue({ base: true, md: false });
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Flex, Heading, Link, SkeletonText, Text } from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import { isATag, isETag, NostrEvent } from "../../../types/nostr-event";
|
||||
import { isETag, NostrEvent } from "../../../types/nostr-event";
|
||||
import { Note } from "../../note";
|
||||
import NoteMenu from "../../note/note-menu";
|
||||
import UserAvatar from "../../user-avatar";
|
||||
@ -15,8 +15,8 @@ import { useRegisterIntersectionEntity } from "../../../providers/intersection-o
|
||||
import useSingleEvent from "../../../hooks/use-single-event";
|
||||
import { EmbedEvent } from "../../embed-event";
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import { parseCoordinate, parseHardcodedNoteContent } from "../../../helpers/nostr/events";
|
||||
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
|
||||
import { parseHardcodedNoteContent } from "../../../helpers/nostr/events";
|
||||
import { getEventCommunityPointer } from "../../../helpers/nostr/communities";
|
||||
|
||||
export default function RepostNote({ event }: { event: NostrEvent }) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@ -31,8 +31,7 @@ export default function RepostNote({ event }: { event: NostrEvent }) {
|
||||
const loadedNote = useSingleEvent(eventId, readRelays);
|
||||
const note = hardCodedNote || loadedNote;
|
||||
|
||||
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
|
||||
const communityCoordinate = communityTag && parseCoordinate(communityTag[1]);
|
||||
const communityCoordinate = getEventCommunityPointer(event);
|
||||
|
||||
if (note && muteFilter(note)) return;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { validateEvent } from "nostr-tools";
|
||||
import { NostrEvent, isDTag, isETag, isPTag } from "../../types/nostr-event";
|
||||
import { NostrEvent, isATag, isDTag, isETag, isPTag } from "../../types/nostr-event";
|
||||
import { getMatchLink, getMatchNostrLink } from "../regexp";
|
||||
import { ReactionGroup } from "./reactions";
|
||||
import { parseCoordinate } from "./events";
|
||||
|
||||
export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities";
|
||||
export const COMMUNITY_DEFINITION_KIND = 34550;
|
||||
@ -89,3 +90,8 @@ export function getCommunityPostVote(grouped: ReactionGroup[]) {
|
||||
const vote = (up?.pubkeys.length ?? 0) - (down?.pubkeys.length ?? 0);
|
||||
return { up, down, vote };
|
||||
}
|
||||
|
||||
export function getEventCommunityPointer(event: NostrEvent){
|
||||
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
|
||||
return communityTag ? parseCoordinate(communityTag[1], true) : null;
|
||||
}
|
||||
|
@ -131,8 +131,7 @@ function CommunitiesHomePage() {
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
.filter((event) => event.kind !== COMMUNITY_APPROVAL_KIND)
|
||||
.filter((e) => (showUnapproved.isOpen ? true : approvalMap.has(e.id)))
|
||||
.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && (showUnapproved.isOpen ? true : approvalMap.has(e.id)))
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
@ -166,7 +165,7 @@ function CommunitiesHomePage() {
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{approved.map(({ event, approvals }) => (
|
||||
<ApprovedEvent key={event.id} event={event} approvals={approvals ?? []} />
|
||||
<ApprovedEvent key={event.id} event={event} approvals={approvals ?? []} showCommunity />
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
@ -202,10 +201,10 @@ function CommunitiesHomePage() {
|
||||
<DrawerCloseButton />
|
||||
<DrawerHeader p="4">Joined Communities</DrawerHeader>
|
||||
|
||||
<DrawerBody display="flex" flexDirection="column" gap="2" px="4" py="0">
|
||||
<DrawerBody display="flex" flexDirection="column" gap="2" px="4" pt="0" pb="8" overflowY="auto">
|
||||
{communities.map((community) => (
|
||||
<ErrorBoundary key={getEventCoordinate(community)}>
|
||||
<CommunityCard community={community} />
|
||||
<CommunityCard community={community} flexShrink={0} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</DrawerBody>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useContext } from "react";
|
||||
import { Button, ButtonGroup, Divider, Flex, Heading, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import { Outlet, Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { Kind, nip19 } from "nostr-tools";
|
||||
@ -24,14 +25,16 @@ import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { getEventCoordinate, getEventUID } from "../../helpers/nostr/events";
|
||||
import { WritingIcon } from "../../components/icons";
|
||||
import { useContext } from "react";
|
||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||
import CommunityEditModal from "./components/community-edit-modal";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
|
||||
function getCommunityPath(community: NostrEvent) {
|
||||
return `/c/${encodeURIComponent(getCommunityName(community))}/${nip19.npubEncode(community.pubkey)}`;
|
||||
}
|
||||
|
||||
export type RouterContext = { community: NostrEvent; timeline: TimelineLoader };
|
||||
|
||||
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
||||
const image = getCommunityImage(community);
|
||||
const location = useLocation();
|
||||
@ -128,7 +131,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Outlet context={{ community, timeline }} />
|
||||
<Outlet context={{ community, timeline } satisfies RouterContext} />
|
||||
</Flex>
|
||||
|
||||
{!verticalLayout && (
|
||||
|
@ -5,13 +5,15 @@ import PostVoteButtons from "./post-vote-buttions";
|
||||
import CommunityPost from "./community-post";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
|
||||
const ApprovedEvent = memo(({ event, approvals }: { event: NostrEvent; approvals: NostrEvent[] }) => {
|
||||
return (
|
||||
<Flex gap="2" alignItems="flex-start">
|
||||
<PostVoteButtons event={event} flexShrink={0} />
|
||||
<CommunityPost event={event} approvals={approvals} flex={1} />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
const ApprovedEvent = memo(
|
||||
({ event, approvals, showCommunity }: { event: NostrEvent; approvals: NostrEvent[]; showCommunity?: boolean }) => {
|
||||
return (
|
||||
<Flex gap="2" alignItems="flex-start">
|
||||
<PostVoteButtons event={event} flexShrink={0} />
|
||||
<CommunityPost event={event} approvals={approvals} flex={1} showCommunity={showCommunity} />
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default ApprovedEvent;
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
CardProps,
|
||||
Flex,
|
||||
Heading,
|
||||
Link,
|
||||
LinkBox,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
@ -16,7 +17,7 @@ import dayjs from "dayjs";
|
||||
import { Kind } from "nostr-tools";
|
||||
|
||||
import { NostrEvent, isETag } from "../../../types/nostr-event";
|
||||
import { getPostSubject } from "../../../helpers/nostr/communities";
|
||||
import { getEventCommunityPointer, getPostSubject } from "../../../helpers/nostr/communities";
|
||||
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
@ -40,6 +41,7 @@ export function ApprovalIcon({ approval }: { approval: NostrEvent }) {
|
||||
export type CommunityPostPropTypes = {
|
||||
event: NostrEvent;
|
||||
approvals: NostrEvent[];
|
||||
showCommunity?: boolean;
|
||||
};
|
||||
|
||||
function PostSubject({ event }: { event: NostrEvent }) {
|
||||
@ -83,11 +85,14 @@ function Approvals({ approvals }: { approvals: NostrEvent[] }) {
|
||||
export function CommunityTextPost({
|
||||
event,
|
||||
approvals,
|
||||
showCommunity,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & CommunityPostPropTypes) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||
|
||||
const communityPointer = getEventCommunityPointer(event);
|
||||
|
||||
return (
|
||||
<Card as={LinkBox} ref={ref} {...props}>
|
||||
<PostSubject event={event} />
|
||||
@ -98,6 +103,14 @@ export function CommunityTextPost({
|
||||
<Text>
|
||||
Posted {dayjs.unix(event.created_at).fromNow()} by <UserLink pubkey={event.pubkey} fontWeight="bold" />
|
||||
</Text>
|
||||
{showCommunity && communityPointer && (
|
||||
<Text>
|
||||
to{" "}
|
||||
<Link as={RouterLink} to={`/c/${communityPointer.identifier}/${communityPointer.pubkey}`} fontWeight="bold">
|
||||
{communityPointer.identifier}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
<Flex gap="2" alignItems="center" ml="auto">
|
||||
{approvals.length > 0 && <Approvals approvals={approvals} />}
|
||||
<CommunityPostMenu event={event} approvals={approvals} aria-label="More Options" size="xs" variant="ghost" />
|
||||
@ -110,6 +123,7 @@ export function CommunityTextPost({
|
||||
export function CommunityRepostPost({
|
||||
event,
|
||||
approvals,
|
||||
showCommunity,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & CommunityPostPropTypes) {
|
||||
const encodedRepost = parseHardcodedNoteContent(event);
|
||||
@ -126,6 +140,8 @@ export function CommunityRepostPost({
|
||||
const muteFilter = useUserMuteFilter();
|
||||
if (repost && muteFilter(repost)) return;
|
||||
|
||||
const communityPointer = getEventCommunityPointer(event);
|
||||
|
||||
return (
|
||||
<Card as={LinkBox} ref={ref} {...props}>
|
||||
{repost && (
|
||||
@ -140,6 +156,14 @@ export function CommunityRepostPost({
|
||||
<Text>
|
||||
Shared {dayjs.unix(event.created_at).fromNow()} by <UserLink pubkey={event.pubkey} fontWeight="bold" />
|
||||
</Text>
|
||||
{showCommunity && communityPointer && (
|
||||
<Text>
|
||||
to{" "}
|
||||
<Link as={RouterLink} to={`/c/${communityPointer.identifier}/${communityPointer.pubkey}`} fontWeight="bold">
|
||||
{communityPointer.identifier}
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
<Flex gap="2" alignItems="center" ml="auto">
|
||||
{approvals.length > 0 && <Approvals approvals={approvals} />}
|
||||
<CommunityPostMenu event={event} approvals={approvals} aria-label="More Options" size="xs" variant="ghost" />
|
||||
@ -149,16 +173,12 @@ export function CommunityRepostPost({
|
||||
);
|
||||
}
|
||||
|
||||
export default function CommunityPost({
|
||||
event,
|
||||
approvals,
|
||||
...props
|
||||
}: Omit<CardProps, "children"> & CommunityPostPropTypes) {
|
||||
export default function CommunityPost({ event, ...props }: Omit<CardProps, "children"> & CommunityPostPropTypes) {
|
||||
switch (event.kind) {
|
||||
case Kind.Text:
|
||||
return <CommunityTextPost event={event} approvals={approvals} {...props} />;
|
||||
return <CommunityTextPost event={event} {...props} />;
|
||||
case Kind.Repost:
|
||||
return <CommunityRepostPost event={event} approvals={approvals} {...props} />;
|
||||
return <CommunityRepostPost event={event} {...props} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1,21 +1,16 @@
|
||||
import { memo } from "react";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
import { buildApprovalMap, getCommunityMods } from "../../../helpers/nostr/communities";
|
||||
import { COMMUNITY_APPROVAL_KIND, buildApprovalMap, getCommunityMods } from "../../../helpers/nostr/communities";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||
import PostVoteButtons from "../components/post-vote-buttions";
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import CommunityPost from "../components/community-post";
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import ApprovedEvent from "../components/community-approved-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
|
||||
export default function CommunityNewestView() {
|
||||
const { community, timeline } = useOutletContext() as { community: NostrEvent; timeline: TimelineLoader };
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
const muteFilter = useUserMuteFilter();
|
||||
const mods = getCommunityMods(community);
|
||||
|
||||
@ -23,7 +18,7 @@ export default function CommunityNewestView() {
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
.filter((e) => approvalMap.has(e.id))
|
||||
.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && approvalMap.has(e.id))
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
|
@ -15,13 +15,13 @@ import useSubject from "../../../hooks/use-subject";
|
||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import { CheckIcon } from "../../../components/icons";
|
||||
import { useSigningContext } from "../../../providers/signing-provider";
|
||||
import { useCurrentAccount } from "../../../hooks/use-current-account";
|
||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
||||
import CommunityPost from "../components/community-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
|
||||
type PendingProps = {
|
||||
event: NostrEvent;
|
||||
@ -84,7 +84,7 @@ function ModPendingPost({ event, community, approvals }: PendingProps) {
|
||||
|
||||
export default function CommunityPendingView() {
|
||||
const account = useCurrentAccount();
|
||||
const { community, timeline } = useOutletContext() as { community: NostrEvent; timeline: TimelineLoader };
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { memo, useMemo } from "react";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { useMemo } from "react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
|
||||
import {
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
buildApprovalMap,
|
||||
getCommunityMods,
|
||||
getCommunityPostVote,
|
||||
@ -10,19 +10,16 @@ import {
|
||||
} from "../../../helpers/nostr/communities";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||
import PostVoteButtons from "../components/post-vote-buttions";
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import CommunityPost from "../components/community-post";
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import useEventsReactions from "../../../hooks/use-events-reactions";
|
||||
import { groupReactions } from "../../../helpers/nostr/reactions";
|
||||
import ApprovedEvent from "../components/community-approved-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
|
||||
export default function CommunityTrendingView() {
|
||||
const { community, timeline } = useOutletContext() as { community: NostrEvent; timeline: TimelineLoader };
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
const muteFilter = useUserMuteFilter();
|
||||
const mods = getCommunityMods(community);
|
||||
|
||||
@ -30,7 +27,7 @@ export default function CommunityTrendingView() {
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
.filter((e) => approvalMap.has(e.id))
|
||||
.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && approvalMap.has(e.id))
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user