mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-12 13:49:33 +02:00
add support for kind 6 events in communities
This commit is contained in:
parent
b6129e2cf3
commit
8871aedae5
5
.changeset/silver-kings-explain.md
Normal file
5
.changeset/silver-kings-explain.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for kind 6 events in communities
|
@ -48,6 +48,7 @@ import { useCurrentAccount } from "../../hooks/use-current-account";
|
|||||||
import useCacheForm from "../../hooks/use-cache-form";
|
import useCacheForm from "../../hooks/use-cache-form";
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
|
subject: string;
|
||||||
content: string;
|
content: string;
|
||||||
nsfw: boolean;
|
nsfw: boolean;
|
||||||
nsfwReason: string;
|
nsfwReason: string;
|
||||||
@ -59,6 +60,7 @@ export type PostModalProps = {
|
|||||||
cacheFormKey?: string | null;
|
cacheFormKey?: string | null;
|
||||||
initContent?: string;
|
initContent?: string;
|
||||||
initCommunity?: string;
|
initCommunity?: string;
|
||||||
|
requireSubject?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PostModal({
|
export default function PostModal({
|
||||||
@ -67,6 +69,7 @@ export default function PostModal({
|
|||||||
cacheFormKey = "new-note",
|
cacheFormKey = "new-note",
|
||||||
initContent = "",
|
initContent = "",
|
||||||
initCommunity = "",
|
initCommunity = "",
|
||||||
|
requireSubject,
|
||||||
}: Omit<ModalProps, "children"> & PostModalProps) {
|
}: Omit<ModalProps, "children"> & PostModalProps) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const account = useCurrentAccount()!;
|
const account = useCurrentAccount()!;
|
||||||
@ -78,6 +81,7 @@ export default function PostModal({
|
|||||||
|
|
||||||
const { getValues, setValue, watch, register, handleSubmit, formState, reset } = useForm<FormValues>({
|
const { getValues, setValue, watch, register, handleSubmit, formState, reset } = useForm<FormValues>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
subject: "",
|
||||||
content: initContent,
|
content: initContent,
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
nsfwReason: "",
|
nsfwReason: "",
|
||||||
@ -124,7 +128,7 @@ export default function PostModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getDraft = useCallback(() => {
|
const getDraft = useCallback(() => {
|
||||||
const { content, nsfw, nsfwReason, community, split } = getValues();
|
const { content, nsfw, nsfwReason, community, split, subject } = getValues();
|
||||||
|
|
||||||
let updatedDraft = finalizeNote({
|
let updatedDraft = finalizeNote({
|
||||||
content: content,
|
content: content,
|
||||||
@ -141,6 +145,9 @@ export default function PostModal({
|
|||||||
if (community) {
|
if (community) {
|
||||||
updatedDraft.tags.push(["a", community]);
|
updatedDraft.tags.push(["a", community]);
|
||||||
}
|
}
|
||||||
|
if (subject) {
|
||||||
|
updatedDraft.tags.push(["subject", subject]);
|
||||||
|
}
|
||||||
|
|
||||||
const contentMentions = getContentMentions(updatedDraft.content);
|
const contentMentions = getContentMentions(updatedDraft.content);
|
||||||
updatedDraft = createEmojiTags(updatedDraft, emojis);
|
updatedDraft = createEmojiTags(updatedDraft, emojis);
|
||||||
@ -177,6 +184,7 @@ export default function PostModal({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{requireSubject && <Input {...register("subject", { required: true })} isRequired placeholder="Subject" />}
|
||||||
<MagicTextArea
|
<MagicTextArea
|
||||||
autoFocus
|
autoFocus
|
||||||
mb="2"
|
mb="2"
|
||||||
|
@ -1,32 +1,22 @@
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { Flex, Heading, SkeletonText, Text } from "@chakra-ui/react";
|
import { Flex, Heading, Link, SkeletonText, Text } from "@chakra-ui/react";
|
||||||
import { Kind, validateEvent } from "nostr-tools";
|
import { Kind } from "nostr-tools";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { isETag, NostrEvent } from "../../../types/nostr-event";
|
import { isATag, isETag, NostrEvent } from "../../../types/nostr-event";
|
||||||
import { Note } from "../../note";
|
import { Note } from "../../note";
|
||||||
import NoteMenu from "../../note/note-menu";
|
import NoteMenu from "../../note/note-menu";
|
||||||
import UserAvatar from "../../user-avatar";
|
import UserAvatar from "../../user-avatar";
|
||||||
import { UserDnsIdentityIcon } from "../../user-dns-identity-icon";
|
import { UserDnsIdentityIcon } from "../../user-dns-identity-icon";
|
||||||
import { UserLink } from "../../user-link";
|
import { UserLink } from "../../user-link";
|
||||||
import { TrustProvider } from "../../../providers/trust";
|
import { TrustProvider } from "../../../providers/trust";
|
||||||
import { safeJson } from "../../../helpers/parse";
|
|
||||||
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
import useSingleEvent from "../../../hooks/use-single-event";
|
import useSingleEvent from "../../../hooks/use-single-event";
|
||||||
import { EmbedEvent } from "../../embed-event";
|
import { EmbedEvent } from "../../embed-event";
|
||||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||||
|
import { parseCoordinate, parseHardcodedNoteContent } from "../../../helpers/nostr/events";
|
||||||
function parseHardcodedNoteContent(event: NostrEvent) {
|
import { COMMUNITY_DEFINITION_KIND } from "../../../helpers/nostr/communities";
|
||||||
const json = safeJson(event.content, null);
|
|
||||||
if (!json) return null;
|
|
||||||
|
|
||||||
// ensure the note has tags
|
|
||||||
json.tags = json.tags || [];
|
|
||||||
|
|
||||||
validateEvent(json);
|
|
||||||
|
|
||||||
return (json as NostrEvent) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RepostNote({ event }: { event: NostrEvent }) {
|
export default function RepostNote({ event }: { event: NostrEvent }) {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
@ -41,6 +31,9 @@ export default function RepostNote({ event }: { event: NostrEvent }) {
|
|||||||
const loadedNote = useSingleEvent(eventId, readRelays);
|
const loadedNote = useSingleEvent(eventId, readRelays);
|
||||||
const note = hardCodedNote || loadedNote;
|
const note = hardCodedNote || loadedNote;
|
||||||
|
|
||||||
|
const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(COMMUNITY_DEFINITION_KIND + ":"));
|
||||||
|
const communityCoordinate = communityTag && parseCoordinate(communityTag[1]);
|
||||||
|
|
||||||
if (note && muteFilter(note)) return;
|
if (note && muteFilter(note)) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,10 +45,19 @@ export default function RepostNote({ event }: { event: NostrEvent }) {
|
|||||||
<UserLink pubkey={event.pubkey} />
|
<UserLink pubkey={event.pubkey} />
|
||||||
</Heading>
|
</Heading>
|
||||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||||
<Text as="span" whiteSpace="pre" mr="auto">
|
<Text as="span" whiteSpace="pre">
|
||||||
Shared note
|
{communityCoordinate ? `Shared to` : `Shared`}
|
||||||
</Text>
|
</Text>
|
||||||
<NoteMenu event={event} size="sm" variant="link" aria-label="note options" />
|
{communityCoordinate && (
|
||||||
|
<Link
|
||||||
|
as={RouterLink}
|
||||||
|
to={`/c/${communityCoordinate.identifier}/${communityCoordinate.pubkey}`}
|
||||||
|
fontWeight="bold"
|
||||||
|
>
|
||||||
|
{communityCoordinate.identifier}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<NoteMenu event={event} size="sm" variant="link" aria-label="note options" ml="auto" />
|
||||||
</Flex>
|
</Flex>
|
||||||
{!note ? (
|
{!note ? (
|
||||||
<SkeletonText />
|
<SkeletonText />
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19, validateEvent } from "nostr-tools";
|
||||||
|
|
||||||
import { ATag, DraftNostrEvent, isDTag, isETag, isPTag, NostrEvent, RTag, Tag } from "../../types/nostr-event";
|
import { ATag, DraftNostrEvent, isDTag, isETag, isPTag, NostrEvent, RTag, Tag } from "../../types/nostr-event";
|
||||||
import { RelayConfig, RelayMode } from "../../classes/relay";
|
import { RelayConfig, RelayMode } from "../../classes/relay";
|
||||||
import { getMatchNostrLink } from "../regexp";
|
import { getMatchNostrLink } from "../regexp";
|
||||||
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||||
|
import { safeJson } from "../parse";
|
||||||
|
|
||||||
export function truncatedId(str: string, keep = 6) {
|
export function truncatedId(str: string, keep = 6) {
|
||||||
if (str.length < keep * 2 + 3) return str;
|
if (str.length < keep * 2 + 3) return str;
|
||||||
@ -204,3 +205,15 @@ export function draftRemoveCoordinate(list: NostrEvent | DraftNostrEvent, coordi
|
|||||||
|
|
||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseHardcodedNoteContent(event: NostrEvent) {
|
||||||
|
const json = safeJson(event.content, null);
|
||||||
|
if (!json) return null;
|
||||||
|
|
||||||
|
// ensure the note has tags
|
||||||
|
json.tags = json.tags || [];
|
||||||
|
|
||||||
|
validateEvent(json);
|
||||||
|
|
||||||
|
return (json as NostrEvent) ?? null;
|
||||||
|
}
|
||||||
|
@ -82,7 +82,11 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
|||||||
colorScheme="primary"
|
colorScheme="primary"
|
||||||
leftIcon={<WritingIcon />}
|
leftIcon={<WritingIcon />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openModal({ cacheFormKey: communityCoordinate + "-new-post", initCommunity: communityCoordinate })
|
openModal({
|
||||||
|
cacheFormKey: communityCoordinate + "-new-post",
|
||||||
|
initCommunity: communityCoordinate,
|
||||||
|
requireSubject: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
New Post
|
New Post
|
||||||
|
137
src/views/community/components/community-post.tsx
Normal file
137
src/views/community/components/community-post.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { MouseEventHandler, useCallback, useRef } from "react";
|
||||||
|
import { AvatarGroup, Card, CardBody, CardFooter, CardHeader, Heading, LinkBox, Text } from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
|
import { NostrEvent, isETag } from "../../../types/nostr-event";
|
||||||
|
import { 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";
|
||||||
|
import { InlineNoteContent } from "../../../components/note/inline-note-content";
|
||||||
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
|
import { getEventUID, parseHardcodedNoteContent } from "../../../helpers/nostr/events";
|
||||||
|
import { UserLink } from "../../../components/user-link";
|
||||||
|
import UserAvatarLink from "../../../components/user-avatar-link";
|
||||||
|
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||||
|
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
|
import useSingleEvent from "../../../hooks/use-single-event";
|
||||||
|
|
||||||
|
export function ApprovalIcon({ approval }: { approval: NostrEvent }) {
|
||||||
|
const ref = useRef<HTMLAnchorElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, getEventUID(approval));
|
||||||
|
|
||||||
|
return <UserAvatarLink pubkey={approval.pubkey} ref={ref} size="xs" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommunityPostPropTypes = {
|
||||||
|
event: NostrEvent;
|
||||||
|
approvals: NostrEvent[];
|
||||||
|
community: NostrEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
function PostSubject({ event }: { event: NostrEvent }) {
|
||||||
|
const subject = getPostSubject(event);
|
||||||
|
|
||||||
|
const navigate = useNavigateInDrawer();
|
||||||
|
const to = `/n/${getSharableEventAddress(event)}`;
|
||||||
|
const handleClick = useCallback<MouseEventHandler>(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
navigate(to);
|
||||||
|
},
|
||||||
|
[navigate, to],
|
||||||
|
);
|
||||||
|
|
||||||
|
return subject ? (
|
||||||
|
<CardHeader px="2" pt="4" pb="0" overflow="hidden">
|
||||||
|
<Heading size="md" overflow="hidden" isTruncated>
|
||||||
|
<HoverLinkOverlay as={RouterLink} to={to} onClick={handleClick}>
|
||||||
|
{getPostSubject(event)}
|
||||||
|
</HoverLinkOverlay>
|
||||||
|
</Heading>
|
||||||
|
</CardHeader>
|
||||||
|
) : (
|
||||||
|
<HoverLinkOverlay as={RouterLink} to={to} onClick={handleClick} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function Approvals({ approvals }: { approvals: NostrEvent[] }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text fontSize="sm" ml="auto">
|
||||||
|
Approved by
|
||||||
|
</Text>
|
||||||
|
<AvatarGroup>
|
||||||
|
{approvals.map((approval) => (
|
||||||
|
<ApprovalIcon key={approval.id} approval={approval} />
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommunityTextPost({ event, approvals, community }: CommunityPostPropTypes) {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card as={LinkBox} overflow="hidden" ref={ref}>
|
||||||
|
<PostSubject event={event} />
|
||||||
|
<CardBody p="2">
|
||||||
|
<InlineNoteContent event={event} maxLength={96} />
|
||||||
|
</CardBody>
|
||||||
|
<CardFooter display="flex" gap="2" alignItems="center" p="2">
|
||||||
|
<Text>
|
||||||
|
Posted {dayjs.unix(event.created_at).fromNow()} by <UserLink pubkey={event.pubkey} fontWeight="bold" />
|
||||||
|
</Text>
|
||||||
|
{approvals.length > 0 && <Approvals approvals={approvals} />}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommunityRepostPost({ event, approvals, community }: CommunityPostPropTypes) {
|
||||||
|
const encodedRepost = parseHardcodedNoteContent(event);
|
||||||
|
|
||||||
|
const [_, eventId, relay] = event.tags.find(isETag) ?? [];
|
||||||
|
const readRelays = useReadRelayUrls(relay ? [relay] : []);
|
||||||
|
|
||||||
|
const loadedRepost = useSingleEvent(eventId, readRelays);
|
||||||
|
const repost = encodedRepost || loadedRepost;
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, repost && getEventUID(repost));
|
||||||
|
|
||||||
|
const muteFilter = useUserMuteFilter();
|
||||||
|
if (repost && muteFilter(repost)) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card as={LinkBox} overflow="hidden" ref={ref}>
|
||||||
|
{repost && (
|
||||||
|
<>
|
||||||
|
<PostSubject event={repost} />
|
||||||
|
<CardBody p="2">
|
||||||
|
<InlineNoteContent event={repost} maxLength={96} />
|
||||||
|
</CardBody>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<CardFooter display="flex" gap="2" alignItems="center" p="2">
|
||||||
|
<Text>
|
||||||
|
Shared {dayjs.unix(event.created_at).fromNow()} by <UserLink pubkey={event.pubkey} fontWeight="bold" />
|
||||||
|
</Text>
|
||||||
|
{approvals.length > 0 && <Approvals approvals={approvals} />}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CommunityPost({ event, approvals, community }: CommunityPostPropTypes) {
|
||||||
|
switch (event.kind) {
|
||||||
|
case Kind.Text:
|
||||||
|
return <CommunityTextPost event={event} approvals={approvals} community={community} />;
|
||||||
|
case Kind.Repost:
|
||||||
|
return <CommunityRepostPost event={event} approvals={approvals} community={community} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
@ -1,92 +1,24 @@
|
|||||||
import { MouseEventHandler, memo, useCallback, useRef } from "react";
|
import { memo } from "react";
|
||||||
import { AvatarGroup, Card, CardBody, CardFooter, CardHeader, Flex, Heading, LinkBox, Text } from "@chakra-ui/react";
|
import { Flex } from "@chakra-ui/react";
|
||||||
import { useOutletContext, Link as RouterLink } from "react-router-dom";
|
import { useOutletContext } from "react-router-dom";
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
import { buildApprovalMap, getCommunityMods, getPostSubject } from "../../../helpers/nostr/communities";
|
import { buildApprovalMap, getCommunityMods } from "../../../helpers/nostr/communities";
|
||||||
import { getEventUID } from "../../../helpers/nostr/events";
|
|
||||||
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 { NostrEvent } from "../../../types/nostr-event";
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import IntersectionObserverProvider from "../../../providers/intersection-observer";
|
||||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||||
import PostVoteButtons from "../components/post-vote-buttions";
|
import PostVoteButtons from "../components/post-vote-buttions";
|
||||||
import TimelineLoader from "../../../classes/timeline-loader";
|
import TimelineLoader from "../../../classes/timeline-loader";
|
||||||
import UserAvatarLink from "../../../components/user-avatar-link";
|
import CommunityPost from "../components/community-post";
|
||||||
import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider";
|
|
||||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
|
||||||
import { InlineNoteContent } from "../../../components/note/inline-note-content";
|
|
||||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
|
||||||
import { UserLink } from "../../../components/user-link";
|
|
||||||
|
|
||||||
function ApprovalIcon({ approval }: { approval: NostrEvent }) {
|
|
||||||
const ref = useRef<HTMLAnchorElement | null>(null);
|
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(approval));
|
|
||||||
|
|
||||||
return <UserAvatarLink pubkey={approval.pubkey} ref={ref} size="xs" />;
|
|
||||||
}
|
|
||||||
const ApprovedEvent = memo(
|
const ApprovedEvent = memo(
|
||||||
({ event, approvals, community }: { event: NostrEvent; approvals: NostrEvent[]; community: NostrEvent }) => {
|
({ event, approvals, community }: { event: NostrEvent; approvals: NostrEvent[]; community: NostrEvent }) => {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
const navigate = useNavigateInDrawer();
|
|
||||||
const to = `/n/${getSharableEventAddress(event)}`;
|
|
||||||
|
|
||||||
const handleClick = useCallback<MouseEventHandler>(
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
navigate(to);
|
|
||||||
},
|
|
||||||
[navigate, to],
|
|
||||||
);
|
|
||||||
|
|
||||||
const subject = getPostSubject(event);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex ref={ref} gap="2" alignItems="flex-start" overflow="hidden">
|
<Flex gap="2" alignItems="flex-start" overflow="hidden">
|
||||||
<PostVoteButtons event={event} community={community} />
|
<PostVoteButtons event={event} community={community} />
|
||||||
<Flex gap="2" direction="column" flex={1} overflow="hidden">
|
<Flex gap="2" direction="column" flex={1} overflow="hidden">
|
||||||
<Card as={LinkBox} overflow="hidden">
|
<CommunityPost event={event} community={community} approvals={approvals} />
|
||||||
{subject ? (
|
|
||||||
<CardHeader px="2" pt="4" pb="0" overflow="hidden">
|
|
||||||
<Heading size="md" overflow="hidden" isTruncated>
|
|
||||||
<HoverLinkOverlay as={RouterLink} to={to} onClick={handleClick}>
|
|
||||||
{getPostSubject(event)}
|
|
||||||
</HoverLinkOverlay>
|
|
||||||
</Heading>
|
|
||||||
</CardHeader>
|
|
||||||
) : (
|
|
||||||
<HoverLinkOverlay as={RouterLink} to={to} onClick={handleClick} />
|
|
||||||
)}
|
|
||||||
<CardBody p="2">
|
|
||||||
<InlineNoteContent event={event} maxLength={96} />
|
|
||||||
</CardBody>
|
|
||||||
<CardFooter display="flex" gap="2" alignItems="center" p="2">
|
|
||||||
<Text>
|
|
||||||
Posted {dayjs.unix(event.created_at).fromNow()} by <UserLink pubkey={event.pubkey} fontWeight="bold" />
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="sm" ml="auto">
|
|
||||||
Approved by
|
|
||||||
</Text>
|
|
||||||
<AvatarGroup>
|
|
||||||
{approvals.map((approval) => (
|
|
||||||
<ApprovalIcon key={approval.id} approval={approval} />
|
|
||||||
))}
|
|
||||||
</AvatarGroup>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
} from "../../../helpers/nostr/communities";
|
} from "../../../helpers/nostr/communities";
|
||||||
import useSubject from "../../../hooks/use-subject";
|
import useSubject from "../../../hooks/use-subject";
|
||||||
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
import { EmbedEvent } from "../../../components/embed-event";
|
|
||||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||||
import TimelineLoader from "../../../classes/timeline-loader";
|
import TimelineLoader from "../../../classes/timeline-loader";
|
||||||
@ -22,13 +21,15 @@ import { useSigningContext } from "../../../providers/signing-provider";
|
|||||||
import { useCurrentAccount } from "../../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../../hooks/use-current-account";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
|
import CommunityPost from "../components/community-post";
|
||||||
|
|
||||||
type PendingProps = {
|
type PendingProps = {
|
||||||
event: NostrEvent;
|
event: NostrEvent;
|
||||||
|
approvals: NostrEvent[];
|
||||||
community: NostrEvent;
|
community: NostrEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ModPendingPost({ event, community }: PendingProps) {
|
function ModPendingPost({ event, community, approvals }: PendingProps) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { requestSignature } = useSigningContext();
|
const { requestSignature } = useSigningContext();
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ function ModPendingPost({ event, community }: PendingProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" gap="2" ref={ref}>
|
<Flex direction="column" gap="2" ref={ref}>
|
||||||
<EmbedEvent event={event} />
|
<CommunityPost event={event} approvals={approvals} community={community} />
|
||||||
<Flex gap="2">
|
<Flex gap="2">
|
||||||
<Button
|
<Button
|
||||||
colorScheme="primary"
|
colorScheme="primary"
|
||||||
@ -81,17 +82,6 @@ function ModPendingPost({ event, community }: PendingProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PendingPost({ event }: PendingProps) {
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(event));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box ref={ref}>
|
|
||||||
<EmbedEvent event={event} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CommunityPendingView() {
|
export default function CommunityPendingView() {
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const { community, timeline } = useOutletContext() as { community: NostrEvent; timeline: TimelineLoader };
|
const { community, timeline } = useOutletContext() as { community: NostrEvent; timeline: TimelineLoader };
|
||||||
@ -105,13 +95,13 @@ export default function CommunityPendingView() {
|
|||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
const isMod = !!account && mods.includes(account?.pubkey);
|
const isMod = !!account && mods.includes(account?.pubkey);
|
||||||
const PostComponent = isMod ? ModPendingPost : PendingPost;
|
const PostComponent = isMod ? ModPendingPost : CommunityPost;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IntersectionObserverProvider callback={callback}>
|
<IntersectionObserverProvider callback={callback}>
|
||||||
{pending.map((event) => (
|
{pending.map((event) => (
|
||||||
<PostComponent key={getEventUID(event)} event={event} community={community} />
|
<PostComponent key={getEventUID(event)} event={event} community={community} approvals={[]} />
|
||||||
))}
|
))}
|
||||||
</IntersectionObserverProvider>
|
</IntersectionObserverProvider>
|
||||||
<TimelineActionAndStatus timeline={timeline} />
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user