mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 20:11:31 +02:00
add community pending view
This commit is contained in:
11
src/app.tsx
11
src/app.tsx
@@ -59,6 +59,8 @@ import BadgeDetailsView from "./views/badges/badge-details";
|
|||||||
import CommunitiesHomeView from "./views/communities";
|
import CommunitiesHomeView from "./views/communities";
|
||||||
import CommunityFindByNameView from "./views/community/find-by-name";
|
import CommunityFindByNameView from "./views/community/find-by-name";
|
||||||
import CommunityView from "./views/community/index";
|
import CommunityView from "./views/community/index";
|
||||||
|
import CommunityPendingView from "./views/community/views/pending";
|
||||||
|
import CommunityNewView from "./views/community/views/new";
|
||||||
|
|
||||||
import RelaysView from "./views/relays";
|
import RelaysView from "./views/relays";
|
||||||
import RelayView from "./views/relays/relay";
|
import RelayView from "./views/relays/relay";
|
||||||
@@ -216,7 +218,14 @@ const router = createHashRouter([
|
|||||||
path: "c/:community",
|
path: "c/:community",
|
||||||
children: [
|
children: [
|
||||||
{ path: "", element: <CommunityFindByNameView /> },
|
{ path: "", element: <CommunityFindByNameView /> },
|
||||||
{ path: ":pubkey", element: <CommunityView /> },
|
{
|
||||||
|
path: ":pubkey",
|
||||||
|
element: <CommunityView />,
|
||||||
|
children: [
|
||||||
|
{ path: "", element: <CommunityNewView /> },
|
||||||
|
{ path: "pending", element: <CommunityPendingView /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@ import { getEventUID } from "../helpers/nostr/events";
|
|||||||
import { NostrEvent } from "../types/nostr-event";
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
import Subject from "./subject";
|
import Subject from "./subject";
|
||||||
|
|
||||||
export type EventFilter = (event: NostrEvent) => boolean;
|
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||||
|
|
||||||
export default class EventStore {
|
export default class EventStore {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -59,7 +59,7 @@ export default class EventStore {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const event = events.shift();
|
const event = events.shift();
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
if (filter && !filter(event)) continue;
|
if (filter && !filter(event, this)) continue;
|
||||||
if (i === nth) return event;
|
if (i === nth) return event;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ export default class EventStore {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const event = events.pop();
|
const event = events.pop();
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
if (filter && !filter(event)) continue;
|
if (filter && !filter(event, this)) continue;
|
||||||
if (i === nth) return event;
|
if (i === nth) return event;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ function addToQuery(filter: NostrRequestFilter, query: NostrQuery) {
|
|||||||
|
|
||||||
const BLOCK_SIZE = 30;
|
const BLOCK_SIZE = 30;
|
||||||
|
|
||||||
export type EventFilter = (event: NostrEvent) => boolean;
|
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||||
|
|
||||||
export class RelayTimelineLoader {
|
export class RelayTimelineLoader {
|
||||||
relay: string;
|
relay: string;
|
||||||
@@ -134,7 +134,8 @@ export default class TimelineLoader {
|
|||||||
|
|
||||||
private updateTimeline() {
|
private updateTimeline() {
|
||||||
if (this.eventFilter) {
|
if (this.eventFilter) {
|
||||||
this.timeline.next(this.events.getSortedEvents().filter(this.eventFilter));
|
const filter = this.eventFilter;
|
||||||
|
this.timeline.next(this.events.getSortedEvents().filter((e) => filter(e, this.events)));
|
||||||
} else this.timeline.next(this.events.getSortedEvents());
|
} else this.timeline.next(this.events.getSortedEvents());
|
||||||
}
|
}
|
||||||
private handleEvent(event: NostrEvent) {
|
private handleEvent(event: NostrEvent) {
|
||||||
@@ -207,7 +208,7 @@ export default class TimelineLoader {
|
|||||||
// update the subscription with the new query
|
// update the subscription with the new query
|
||||||
this.subscription.setQuery(addToQuery(query, { limit: BLOCK_SIZE / 2 }));
|
this.subscription.setQuery(addToQuery(query, { limit: BLOCK_SIZE / 2 }));
|
||||||
}
|
}
|
||||||
setFilter(filter?: (event: NostrEvent) => boolean) {
|
setFilter(filter?: EventFilter) {
|
||||||
this.eventFilter = filter;
|
this.eventFilter = filter;
|
||||||
this.updateTimeline();
|
this.updateTimeline();
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ export function getCommunityMods(community: NostrEvent) {
|
|||||||
const mods = community.tags.filter((t) => isPTag(t) && t[1] && t[3] === "moderator").map((t) => t[1]) as string[];
|
const mods = community.tags.filter((t) => isPTag(t) && t[1] && t[3] === "moderator").map((t) => t[1]) as string[];
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
export function getCOmmunityRelays(community: NostrEvent) {
|
export function getCommunityRelays(community: NostrEvent) {
|
||||||
return community.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]) as string[];
|
return community.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]) as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useUnmount } from "react-use";
|
import { useUnmount } from "react-use";
|
||||||
import { NostrRequestFilter } from "../types/nostr-query";
|
import { NostrRequestFilter } from "../types/nostr-query";
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
|
||||||
import timelineCacheService from "../services/timeline-cache";
|
import timelineCacheService from "../services/timeline-cache";
|
||||||
|
import { EventFilter } from "../classes/timeline-loader";
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
eventFilter?: (event: NostrEvent) => boolean;
|
eventFilter?: EventFilter;
|
||||||
cursor?: number;
|
cursor?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -50,8 +50,13 @@ export default function CommunityJoinButton({
|
|||||||
}, [isSubscribed, list, community]);
|
}, [isSubscribed, list, community]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleClick} {...props}>
|
<Button
|
||||||
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
onClick={handleClick}
|
||||||
|
variant={isSubscribed ? "outline" : "solid"}
|
||||||
|
colorScheme={isSubscribed ? "red" : "green"}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{isSubscribed ? "Leave" : "Join"}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,53 +1,26 @@
|
|||||||
import { useRef } from "react";
|
import { Box, Button, ButtonGroup, Card, Flex, Heading, Text } from "@chakra-ui/react";
|
||||||
import { Box, Button, Card, Flex, Heading, Text } from "@chakra-ui/react";
|
import { Outlet, Link as RouterLink, useLocation } from "react-router-dom";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COMMUNITY_APPROVAL_KIND,
|
getCommunityRelays as getCommunityRelays,
|
||||||
getApprovedEmbeddedNote,
|
|
||||||
getCOmmunityRelays as getCommunityRelays,
|
|
||||||
getCommunityImage,
|
getCommunityImage,
|
||||||
getCommunityMods,
|
getCommunityMods,
|
||||||
getCommunityName,
|
getCommunityName,
|
||||||
getCommunityDescription,
|
getCommunityDescription,
|
||||||
} from "../../helpers/nostr/communities";
|
} from "../../helpers/nostr/communities";
|
||||||
import { NostrEvent, isETag } from "../../types/nostr-event";
|
import { NostrEvent } 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";
|
||||||
import CommunityDescription from "../communities/components/community-description";
|
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, { useRegisterIntersectionEntity } from "../../providers/intersection-observer";
|
|
||||||
import CommunityJoinButton from "../communities/components/community-subscribe-button";
|
import CommunityJoinButton from "../communities/components/community-subscribe-button";
|
||||||
import useSingleEvent from "../../hooks/use-single-event";
|
import { AdditionalRelayProvider } from "../../providers/additional-relay-context";
|
||||||
import { EmbedEvent } from "../../components/embed-event";
|
|
||||||
import { AdditionalRelayProvider, useAdditionalRelayContext } from "../../providers/additional-relay-context";
|
|
||||||
import { RelayIconStack } from "../../components/relay-icon-stack";
|
import { RelayIconStack } from "../../components/relay-icon-stack";
|
||||||
|
|
||||||
function ApprovedEvent({ approval }: { approval: NostrEvent }) {
|
import TrendUp01 from "../../components/icons/trend-up-01";
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
import Clock from "../../components/icons/clock";
|
||||||
useRegisterIntersectionEntity(ref, getEventUID(approval));
|
import Hourglass03 from "../../components/icons/hourglass-03";
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CommunityDetails({ community }: { community: NostrEvent }) {
|
function CommunityDetails({ community }: { community: NostrEvent }) {
|
||||||
const communityRelays = getCommunityRelays(community);
|
const communityRelays = getCommunityRelays(community);
|
||||||
@@ -55,14 +28,16 @@ function CommunityDetails({ community }: { community: NostrEvent }) {
|
|||||||
const description = getCommunityDescription(community);
|
const description = getCommunityDescription(community);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card p="2" w="xs" flexShrink={0}>
|
<Card p="4" w="xs" flexShrink={0}>
|
||||||
{description && (
|
{description && (
|
||||||
<>
|
<>
|
||||||
<Heading size="sm">Description:</Heading>
|
<Heading size="sm" mb="2">
|
||||||
|
Description:
|
||||||
|
</Heading>
|
||||||
<CommunityDescription community={community} maxLength={256} showExpand />
|
<CommunityDescription community={community} maxLength={256} showExpand />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Heading size="sm" mt="2">
|
<Heading size="sm" mt="4" mb="2">
|
||||||
Moderators:
|
Moderators:
|
||||||
</Heading>
|
</Heading>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
@@ -75,7 +50,7 @@ function CommunityDetails({ community }: { community: NostrEvent }) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{communityRelays.length > 0 && (
|
{communityRelays.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Heading size="sm" mt="2">
|
<Heading size="sm" mt="4" mb="2">
|
||||||
Relays:
|
Relays:
|
||||||
</Heading>
|
</Heading>
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
@@ -87,20 +62,18 @@ function CommunityDetails({ community }: { community: NostrEvent }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommunityPath(community: NostrEvent) {
|
||||||
|
return `/c/${encodeURIComponent(getCommunityName(community))}/${nip19.npubEncode(community.pubkey)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
export default function CommunityHomePage({ community }: { community: NostrEvent }) {
|
||||||
const mods = getCommunityMods(community);
|
|
||||||
const image = getCommunityImage(community);
|
const image = getCommunityImage(community);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const communityRelays = getCommunityRelays(community);
|
const communityRelays = getCommunityRelays(community);
|
||||||
const readRelays = useReadRelayUrls(communityRelays);
|
|
||||||
const timeline = useTimelineLoader(`${getEventUID(community)}-approved-posts`, readRelays, {
|
|
||||||
authors: unique([community.pubkey, ...mods]),
|
|
||||||
kinds: [COMMUNITY_APPROVAL_KIND],
|
|
||||||
"#a": [getEventCoordinate(community)],
|
|
||||||
});
|
|
||||||
|
|
||||||
const approvals = useSubject(timeline.timeline);
|
let active = "new";
|
||||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
if (location.pathname.endsWith("/pending")) active = "pending";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdditionalRelayProvider relays={communityRelays}>
|
<AdditionalRelayProvider relays={communityRelays}>
|
||||||
@@ -124,13 +97,31 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
|||||||
<CommunityJoinButton community={community} ml="auto" />
|
<CommunityJoinButton community={community} ml="auto" />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap="2" alignItems="flex-start">
|
<Flex gap="4" alignItems="flex-start">
|
||||||
<Flex direction="column" gap="2" flex={1}>
|
<Flex direction="column" gap="4" flex={1}>
|
||||||
<IntersectionObserverProvider callback={callback}>
|
<ButtonGroup size="sm">
|
||||||
{approvals.map((approval) => (
|
<Button leftIcon={<TrendUp01 />} isDisabled>
|
||||||
<ApprovedEvent key={getEventUID(approval)} approval={approval} />
|
Trending
|
||||||
))}
|
</Button>
|
||||||
</IntersectionObserverProvider>
|
<Button
|
||||||
|
leftIcon={<Clock />}
|
||||||
|
as={RouterLink}
|
||||||
|
to={getCommunityPath(community)}
|
||||||
|
colorScheme={active === "new" ? "primary" : "gray"}
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<Hourglass03 />}
|
||||||
|
as={RouterLink}
|
||||||
|
to={getCommunityPath(community) + "/pending"}
|
||||||
|
colorScheme={active == "pending" ? "primary" : "gray"}
|
||||||
|
>
|
||||||
|
Pending
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Outlet context={{ community }} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<CommunityDetails community={community} />
|
<CommunityDetails community={community} />
|
||||||
|
69
src/views/community/views/new.tsx
Normal file
69
src/views/community/views/new.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
import { Box } from "@chakra-ui/react";
|
||||||
|
import { useOutletContext } from "react-router-dom";
|
||||||
|
|
||||||
|
import { unique } from "../../../helpers/array";
|
||||||
|
import {
|
||||||
|
COMMUNITY_APPROVAL_KIND,
|
||||||
|
getApprovedEmbeddedNote,
|
||||||
|
getCommunityMods,
|
||||||
|
getCommunityRelays,
|
||||||
|
} from "../../../helpers/nostr/communities";
|
||||||
|
import { getEventCoordinate, getEventUID } from "../../../helpers/nostr/events";
|
||||||
|
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
|
import useSubject from "../../../hooks/use-subject";
|
||||||
|
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
|
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||||
|
import { NostrEvent, isETag } from "../../../types/nostr-event";
|
||||||
|
import { EmbedEvent } from "../../../components/embed-event";
|
||||||
|
import useSingleEvent from "../../../hooks/use-single-event";
|
||||||
|
import { useAdditionalRelayContext } from "../../../providers/additional-relay-context";
|
||||||
|
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
|
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||||
|
|
||||||
|
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 CommunityNewView() {
|
||||||
|
const { community } = useOutletContext() as { community: NostrEvent };
|
||||||
|
const mods = getCommunityMods(community);
|
||||||
|
|
||||||
|
const readRelays = useReadRelayUrls(getCommunityRelays(community));
|
||||||
|
const timeline = useTimelineLoader(`${getEventUID(community)}-approved-posts`, readRelays, {
|
||||||
|
authors: unique([community.pubkey, ...mods]),
|
||||||
|
kinds: [COMMUNITY_APPROVAL_KIND],
|
||||||
|
"#a": [getEventCoordinate(community)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const approvals = useSubject(timeline.timeline);
|
||||||
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IntersectionObserverProvider callback={callback}>
|
||||||
|
{approvals.map((approval) => (
|
||||||
|
<ApprovedEvent key={getEventUID(approval)} approval={approval} />
|
||||||
|
))}
|
||||||
|
</IntersectionObserverProvider>
|
||||||
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
69
src/views/community/views/pending.tsx
Normal file
69
src/views/community/views/pending.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { useCallback, useRef } from "react";
|
||||||
|
import { Box } from "@chakra-ui/react";
|
||||||
|
import { useOutletContext } from "react-router-dom";
|
||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
|
||||||
|
import { NostrEvent, isETag } from "../../../types/nostr-event";
|
||||||
|
import { getEventCoordinate, getEventUID } from "../../../helpers/nostr/events";
|
||||||
|
import { COMMUNITY_APPROVAL_KIND, getCommunityRelays } from "../../../helpers/nostr/communities";
|
||||||
|
import { useReadRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
|
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||||
|
import useSubject from "../../../hooks/use-subject";
|
||||||
|
import IntersectionObserverProvider, { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
|
import { EmbedEvent } from "../../../components/embed-event";
|
||||||
|
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
|
import TimelineActionAndStatus from "../../../components/timeline-page/timeline-action-and-status";
|
||||||
|
import EventStore from "../../../classes/event-store";
|
||||||
|
|
||||||
|
function PendingPost({ event }: { event: NostrEvent }) {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
useRegisterIntersectionEntity(ref, getEventUID(event));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={ref}>
|
||||||
|
<EmbedEvent event={event} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CommunityPendingView() {
|
||||||
|
const { community } = useOutletContext() as { community: NostrEvent };
|
||||||
|
|
||||||
|
const readRelays = useReadRelayUrls(getCommunityRelays(community));
|
||||||
|
|
||||||
|
const eventFilter = useCallback((event: NostrEvent, store: EventStore) => event.kind !== COMMUNITY_APPROVAL_KIND, []);
|
||||||
|
const timeline = useTimelineLoader(
|
||||||
|
`${getEventUID(community)}-pending-posts`,
|
||||||
|
readRelays,
|
||||||
|
{
|
||||||
|
kinds: [Kind.Text, COMMUNITY_APPROVAL_KIND],
|
||||||
|
"#a": [getEventCoordinate(community)],
|
||||||
|
},
|
||||||
|
{ eventFilter },
|
||||||
|
);
|
||||||
|
|
||||||
|
const events = useSubject(timeline.timeline);
|
||||||
|
|
||||||
|
const approvals = new Set<string>();
|
||||||
|
for (const [_, event] of timeline.events.events) {
|
||||||
|
if (event.kind === COMMUNITY_APPROVAL_KIND) {
|
||||||
|
for (const tag of event.tags) {
|
||||||
|
if (isETag(tag)) approvals.add(tag[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pending = events.filter((e) => !approvals.has(e.id));
|
||||||
|
|
||||||
|
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IntersectionObserverProvider callback={callback}>
|
||||||
|
{pending.map((event) => (
|
||||||
|
<PendingPost key={getEventUID(event)} event={event} />
|
||||||
|
))}
|
||||||
|
</IntersectionObserverProvider>
|
||||||
|
<TimelineActionAndStatus timeline={timeline} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user