mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
fix bug with threading
lots more cleanup
This commit is contained in:
parent
e0a1b2ed1b
commit
f0a89ac59e
@ -41,7 +41,7 @@ export const App = () => {
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/e/:id"
|
||||
path="/n/:id"
|
||||
element={
|
||||
<RequireSetup>
|
||||
<EventPage />
|
||||
|
18
src/components/note-link.tsx
Normal file
18
src/components/note-link.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Link, LinkProps } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip-19";
|
||||
import { truncatedId } from "../helpers/nostr-event";
|
||||
|
||||
export type NoteLinkProps = LinkProps & {
|
||||
noteId: string;
|
||||
};
|
||||
|
||||
export const NoteLink = ({ noteId, ...props }: NoteLinkProps) => {
|
||||
const note1 = normalizeToBech32(noteId, Bech32Prefix.Note) ?? noteId;
|
||||
|
||||
return (
|
||||
<Link as={RouterLink} to={`/n/${note1}`} {...props}>
|
||||
{truncatedId(note1)}
|
||||
</Link>
|
||||
);
|
||||
};
|
@ -22,19 +22,19 @@ import { UserAvatarLink } from "../user-avatar-link";
|
||||
import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
||||
|
||||
import { PostContents } from "./post-contents";
|
||||
import { PostMenu } from "./post-menu";
|
||||
import { PostCC } from "./post-cc";
|
||||
import { NoteContents } from "./note-contents";
|
||||
import { NoteMenu } from "./note-menu";
|
||||
import { NoteCC } from "./note-cc";
|
||||
import { isReply } from "../../helpers/nostr-event";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import identity from "../../services/identity";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import { ArrowDownS } from "../icons";
|
||||
|
||||
export type PostProps = {
|
||||
export type NoteProps = {
|
||||
event: NostrEvent;
|
||||
};
|
||||
export const Post = React.memo(({ event }: PostProps) => {
|
||||
export const Note = React.memo(({ event }: NoteProps) => {
|
||||
const metadata = useUserMetadata(event.pubkey);
|
||||
|
||||
const pubkey = useSubject(identity.pubkey);
|
||||
@ -54,27 +54,25 @@ export const Post = React.memo(({ event }: PostProps) => {
|
||||
{getUserDisplayName(metadata, event.pubkey)}
|
||||
</Link>
|
||||
</Heading>
|
||||
<Link as={RouterLink} to={`/e/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} ml="2">
|
||||
<Link as={RouterLink} to={`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} ml="2">
|
||||
{moment(event.created_at * 1000).fromNow()}
|
||||
</Link>
|
||||
{isReply(event) && <PostCC event={event} />}
|
||||
{isReply(event) && <NoteCC event={event} />}
|
||||
</Box>
|
||||
</Flex>
|
||||
<PostMenu event={event} />
|
||||
<NoteMenu event={event} />
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody padding="0">
|
||||
<VStack alignItems="flex-start" justifyContent="stretch">
|
||||
<Box overflow="hidden" width="100%">
|
||||
<PostContents event={event} trusted={following.includes(event.pubkey)} />
|
||||
</Box>
|
||||
</VStack>
|
||||
<Box overflow="hidden" width="100%">
|
||||
<NoteContents event={event} trusted={following.includes(event.pubkey)} />
|
||||
</Box>
|
||||
</CardBody>
|
||||
{/* <CardFooter padding="0" gap="2"> */}
|
||||
{/* <Button
|
||||
size="sm"
|
||||
variant="link"
|
||||
onClick={() => navigate(`/e/${normalizeToBech32(event.id, Bech32Prefix.Note)}`)}
|
||||
onClick={() => navigate(`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`)}
|
||||
>
|
||||
Replies
|
||||
</Button> */}
|
@ -2,7 +2,7 @@ import { Text } from "@chakra-ui/react";
|
||||
import { isPTag, NostrEvent } from "../../types/nostr-event";
|
||||
import { UserLink } from "../user-link";
|
||||
|
||||
export const PostCC = ({ event }: { event: NostrEvent }) => {
|
||||
export const NoteCC = ({ event }: { event: NostrEvent }) => {
|
||||
const hasCC = event.tags.some(isPTag);
|
||||
if (!hasCC) return null;
|
||||
|
@ -5,6 +5,7 @@ import { TweetEmbed } from "../tweet-embed";
|
||||
import { UserLink } from "../user-link";
|
||||
import { normalizeToHex } from "../../helpers/nip-19";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { NoteLink } from "../note-link";
|
||||
|
||||
const BlurredImage = (props: ImageProps) => {
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
@ -18,12 +19,12 @@ const embeds: {
|
||||
// Lightning Invoice
|
||||
{
|
||||
regexp: /(lightning:)?(LNBC[A-Za-z0-9]+)/im,
|
||||
render: (match) => <InlineInvoiceCard key={match[0]} paymentRequest={match[2]} />,
|
||||
render: (match) => <InlineInvoiceCard paymentRequest={match[2]} />,
|
||||
},
|
||||
// Twitter tweet
|
||||
{
|
||||
regexp: /^https?:\/\/twitter\.com\/(?:\#!\/)?(\w+)\/status(es)?\/(\d+)/im,
|
||||
render: (match) => <TweetEmbed key={match[0]} href={match[0]} conversation={false} />,
|
||||
render: (match) => <TweetEmbed href={match[0]} conversation={false} />,
|
||||
},
|
||||
// Youtube Video
|
||||
{
|
||||
@ -89,7 +90,7 @@ const embeds: {
|
||||
regexp: /(https?:\/\/)([\da-z\.-]+\.[a-z\.]{2,6})([\/\w\.-]+\.(svg|gif|png|jpg|jpeg|webp|avif))[^\s]*/im,
|
||||
render: (match, trusted) => {
|
||||
const ImageComponent = trusted ? Image : BlurredImage;
|
||||
return <ImageComponent key={match[0]} src={match[0]} width="100%" maxWidth="30rem" />;
|
||||
return <ImageComponent src={match[0]} width="100%" maxWidth="30rem" />;
|
||||
},
|
||||
},
|
||||
// Video
|
||||
@ -97,7 +98,7 @@ const embeds: {
|
||||
regexp: /(https?:\/\/)([\da-z\.-]+\.[a-z\.]{2,6})([\/\w\.-]+\.(mp4|mkv|webm|mov))[^\s]*/im,
|
||||
render: (match) => (
|
||||
<AspectRatio ratio={16 / 9} maxWidth="30rem">
|
||||
<video key={match[0]} src={match[0]} controls />
|
||||
<video src={match[0]} controls />
|
||||
</AspectRatio>
|
||||
),
|
||||
},
|
||||
@ -105,7 +106,7 @@ const embeds: {
|
||||
{
|
||||
regexp: /(https?:\/\/[^\s]+)/im,
|
||||
render: (match) => (
|
||||
<Link key={match[0]} color="blue.500" href={match[0]} target="_blank">
|
||||
<Link color="blue.500" href={match[0]} target="_blank">
|
||||
{match[0]}
|
||||
</Link>
|
||||
),
|
||||
@ -123,15 +124,20 @@ const embeds: {
|
||||
}
|
||||
},
|
||||
},
|
||||
// Nostr Embeds
|
||||
// Nostr Mention Links
|
||||
{
|
||||
regexp: /#\[(\d+)\]/,
|
||||
render: (match, event) => {
|
||||
const index = parseInt(match[1]);
|
||||
const tag = event?.tags[index];
|
||||
|
||||
if (tag && tag[0] === "p" && tag[1]) {
|
||||
return <UserLink color="blue.500" pubkey={tag[1]} />;
|
||||
if (tag) {
|
||||
if (tag[0] === "p" && tag[1]) {
|
||||
return <UserLink color="blue.500" pubkey={tag[1]} />;
|
||||
}
|
||||
if (tag[0] === "e" && tag[1]) {
|
||||
return <NoteLink color="blue.500" noteId={tag[1]} />;
|
||||
}
|
||||
}
|
||||
|
||||
return match[0];
|
||||
@ -161,15 +167,19 @@ function embedContent(content: string, event?: NostrEvent, trusted: boolean = fa
|
||||
return [content];
|
||||
}
|
||||
|
||||
export type PostContentsProps = {
|
||||
export type NoteContentsProps = {
|
||||
event: NostrEvent;
|
||||
trusted?: boolean;
|
||||
};
|
||||
|
||||
export const PostContents = React.memo(({ event, trusted }: PostContentsProps) => {
|
||||
export const NoteContents = React.memo(({ event, trusted }: NoteContentsProps) => {
|
||||
const parts = embedContent(event.content, event, trusted ?? false);
|
||||
|
||||
return (
|
||||
<Box whiteSpace="pre-wrap">{parts.map((part) => (typeof part === "string" ? <span>{part}</span> : part))}</Box>
|
||||
<Box whiteSpace="pre-wrap">
|
||||
{parts.map((part, i) => (
|
||||
<span key={"part-" + i}>{part}</span>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
});
|
@ -16,8 +16,9 @@ import { NostrEvent } from "../../types/nostr-event";
|
||||
import { MenuIconButton } from "../menu-icon-button";
|
||||
|
||||
import { ClipboardIcon, CodeIcon, IMAGE_ICONS } from "../icons";
|
||||
import { getReferences } from "../../helpers/nostr-event";
|
||||
|
||||
export const PostMenu = ({ event }: { event: NostrEvent }) => {
|
||||
export const NoteMenu = ({ event }: { event: NostrEvent }) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [_clipboardState, copyToClipboard] = useCopyToClipboard();
|
||||
const noteId = normalizeToBech32(event.id, Bech32Prefix.Note);
|
||||
@ -74,6 +75,7 @@ export const PostMenu = ({ event }: { event: NostrEvent }) => {
|
||||
<ModalCloseButton />
|
||||
<ModalBody overflow="auto">
|
||||
<pre>{JSON.stringify(event, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(getReferences(event), null, 2)}</pre>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
@ -10,9 +10,10 @@ export type UserLinkProps = LinkProps & {
|
||||
|
||||
export const UserLink = ({ pubkey, ...props }: UserLinkProps) => {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
||||
|
||||
return (
|
||||
<Link as={RouterLink} to={`/u/${normalizeToBech32(pubkey, Bech32Prefix.Pubkey)}`} {...props}>
|
||||
<Link as={RouterLink} to={`/u/${npub}`} {...props}>
|
||||
@{getUserDisplayName(metadata, pubkey)}
|
||||
</Link>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ export function isReply(event: NostrEvent) {
|
||||
return !!event.tags.find((tag) => isETag(tag) && tag[3] !== "mention");
|
||||
}
|
||||
|
||||
export function isPost(event: NostrEvent) {
|
||||
export function isNote(event: NostrEvent) {
|
||||
return !isReply(event);
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,6 @@ export function linkEvents(events: NostrEvent[]) {
|
||||
idToChildren[refs.replyId] = idToChildren[refs.replyId] || [];
|
||||
idToChildren[refs.replyId].push(event);
|
||||
}
|
||||
if (refs.rootId && refs.rootId !== refs.replyId) {
|
||||
idToChildren[refs.rootId] = idToChildren[refs.rootId] || [];
|
||||
idToChildren[refs.rootId].push(event);
|
||||
}
|
||||
|
||||
replies.set(event.id, {
|
||||
event,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Kind0ParsedContent } from "../types/nostr-event";
|
||||
import { normalizeToBech32 } from "./nip-19";
|
||||
import { Bech32Prefix, normalizeToBech32 } from "./nip-19";
|
||||
import { truncatedId } from "./nostr-event";
|
||||
|
||||
export function getUserDisplayName(metadata: Kind0ParsedContent | undefined, pubkey: string) {
|
||||
@ -8,5 +8,5 @@ export function getUserDisplayName(metadata: Kind0ParsedContent | undefined, pub
|
||||
} else if (metadata?.name) {
|
||||
return metadata.name;
|
||||
}
|
||||
return truncatedId(normalizeToBech32(pubkey) ?? pubkey);
|
||||
return truncatedId(normalizeToBech32(pubkey, Bech32Prefix.Pubkey) ?? pubkey);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Alert, AlertDescription, AlertIcon, AlertTitle, Flex, Spinner, Text } f
|
||||
import { Page } from "../../components/page";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { normalizeToHex } from "../../helpers/nip-19";
|
||||
import { Post } from "../../components/post";
|
||||
import { Note } from "../../components/note";
|
||||
import { useThreadLoader } from "../../hooks/use-thread-loader";
|
||||
import { ThreadPost } from "./thread-post";
|
||||
|
||||
@ -39,10 +39,12 @@ export const EventView = ({ eventId }: EventViewProps) => {
|
||||
|
||||
if (loading) return <Spinner />;
|
||||
|
||||
let pageContent = <span>Missing Event</span>;
|
||||
|
||||
const isRoot = rootId === focusId;
|
||||
const rootPost = thread.get(rootId);
|
||||
if (isRoot && rootPost) {
|
||||
return <ThreadPost post={rootPost} initShowReplies />;
|
||||
pageContent = <ThreadPost post={rootPost} initShowReplies />;
|
||||
}
|
||||
|
||||
const post = thread.get(focusId);
|
||||
@ -56,16 +58,21 @@ export const EventView = ({ eventId }: EventViewProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4" overflow="auto" flex={1} pb="4" pt="4">
|
||||
{parentPosts.map((post) => (
|
||||
<Post key={post.event.id} event={post.event} />
|
||||
pageContent = (
|
||||
<>
|
||||
{parentPosts.map((parent) => (
|
||||
<Note key={parent.event.id + "-rely"} event={parent.event} />
|
||||
))}
|
||||
<ThreadPost key={post.event.id} post={post} initShowReplies />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
} else if (events[focusId]) {
|
||||
return <Post event={events[focusId]} />;
|
||||
pageContent = <Note event={events[focusId]} />;
|
||||
}
|
||||
return <span>Missing Event</span>;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4" overflow="auto" flex={1} pb="4" pt="4" pl="1" pr="1">
|
||||
{pageContent}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Button, Flex, useDisclosure } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { Post } from "../../components/post";
|
||||
import { Note } from "../../components/note";
|
||||
import { ThreadItem as ThreadItemData } from "../../helpers/thread";
|
||||
|
||||
export type ThreadItemProps = {
|
||||
@ -14,7 +14,7 @@ export const ThreadPost = ({ post, initShowReplies }: ThreadItemProps) => {
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
<Post event={post.event} />
|
||||
<Note event={post.event} />
|
||||
{post.children.length > 0 && (
|
||||
<>
|
||||
<Button variant="link" size="sm" alignSelf="flex-start" onClick={toggle}>
|
||||
@ -23,7 +23,7 @@ export const ThreadPost = ({ post, initShowReplies }: ThreadItemProps) => {
|
||||
{showReplies && (
|
||||
<Flex direction="column" gap="2" pl="4" borderLeftColor="gray.500" borderLeftWidth="1px">
|
||||
{post.children.map((child) => (
|
||||
<ThreadPost key={post.event.id} post={child} />
|
||||
<ThreadPost key={child.event.id} post={child} />
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
|
@ -2,13 +2,13 @@ import { useEffect, useState } from "react";
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { mergeAll, from } from "rxjs";
|
||||
import { Post } from "../../components/post";
|
||||
import { Note } from "../../components/note";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import identity from "../../services/identity";
|
||||
import userContactsService from "../../services/user-contacts";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
import { isPost } from "../../helpers/nostr-event";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
|
||||
function useExtendedContacts(pubkey: string) {
|
||||
const [extendedContacts, setExtendedContacts] = useState<string[]>([]);
|
||||
@ -42,17 +42,17 @@ export const DiscoverTab = () => {
|
||||
|
||||
const contactsOfContacts = useExtendedContacts(pubkey);
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`discover-posts`,
|
||||
`discover`,
|
||||
{ authors: contactsOfContacts, kinds: [1], since: moment().subtract(1, "hour").unix() },
|
||||
{ pageSize: moment.duration(1, "hour").asSeconds(), enabled: contactsOfContacts.length > 0 }
|
||||
);
|
||||
|
||||
const timeline = events.filter(isPost);
|
||||
const timeline = events.filter(isNote);
|
||||
|
||||
return (
|
||||
<Flex direction="column" overflow="auto" gap="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
<Note key={event.id} event={event} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Button, Flex, FormControl, FormLabel, Spinner, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import moment from "moment";
|
||||
import { Post } from "../../components/post";
|
||||
import { isPost } from "../../helpers/nostr-event";
|
||||
import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import identity from "../../services/identity";
|
||||
|
||||
export const FollowingPostsTab = () => {
|
||||
export const FollowingTab = () => {
|
||||
const pubkey = useSubject(identity.pubkey);
|
||||
const contacts = useUserContacts(pubkey);
|
||||
const [search, setSearch] = useSearchParams();
|
||||
@ -24,7 +24,7 @@ export const FollowingPostsTab = () => {
|
||||
{ pageSize: moment.duration(2, "hour").asSeconds(), enabled: following.length > 0 }
|
||||
);
|
||||
|
||||
const timeline = showReplies ? events : events.filter(isPost);
|
||||
const timeline = showReplies ? events : events.filter(isNote);
|
||||
|
||||
return (
|
||||
<Flex direction="column" overflow="auto" gap="2">
|
||||
@ -35,7 +35,7 @@ export const FollowingPostsTab = () => {
|
||||
<Switch id="show-replies" isChecked={showReplies} onChange={onToggle} />
|
||||
</FormControl>
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
<Note key={event.id} event={event} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
@ -1,22 +1,22 @@
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { Post } from "../../components/post";
|
||||
import { isPost } from "../../helpers/nostr-event";
|
||||
import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
export const GlobalTab = () => {
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`global-posts`,
|
||||
`global`,
|
||||
{ kinds: [1], since: moment().subtract(5, "minutes").unix() },
|
||||
{ pageSize: moment.duration(5, "minutes").asSeconds() }
|
||||
);
|
||||
|
||||
const timeline = events.filter(isPost);
|
||||
const timeline = events.filter(isNote);
|
||||
|
||||
return (
|
||||
<Flex direction="column" overflow="auto" gap="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
<Note key={event.id} event={event} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
// import { useMatch, useNavigate } from "react-router-dom";
|
||||
import { DiscoverTab } from "./discover-tab";
|
||||
import { FollowingPostsTab } from "./following-posts-tab";
|
||||
import { FollowingTab } from "./following-tab";
|
||||
import { GlobalTab } from "./global-tab";
|
||||
|
||||
export const HomeView = () => {
|
||||
@ -28,7 +28,7 @@ export const HomeView = () => {
|
||||
</TabList>
|
||||
<TabPanels overflow="auto" height="100%">
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<FollowingPostsTab />
|
||||
<FollowingTab />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<DiscoverTab />
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
Box,
|
||||
} from "@chakra-ui/react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { UserPostsTab } from "./posts";
|
||||
import { UserNotesTab } from "./notes";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { UserAvatar } from "../../components/user-avatar";
|
||||
import { getUserDisplayName } from "../../helpers/user-metadata";
|
||||
@ -80,7 +80,7 @@ export const UserView = ({ pubkey }: UserViewProps) => {
|
||||
{header}
|
||||
<Tabs display="flex" flexDirection="column" flexGrow="1" overflow={isMobile ? undefined : "hidden"} isLazy>
|
||||
<TabList overflow={isMobile ? "auto" : undefined}>
|
||||
<Tab>Posts</Tab>
|
||||
<Tab>Notes</Tab>
|
||||
<Tab>Replies</Tab>
|
||||
<Tab>Followers</Tab>
|
||||
<Tab>Following</Tab>
|
||||
@ -89,7 +89,7 @@ export const UserView = ({ pubkey }: UserViewProps) => {
|
||||
|
||||
<TabPanels overflow={isMobile ? undefined : "auto"} height="100%">
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserPostsTab pubkey={pubkey} />
|
||||
<UserNotesTab pubkey={pubkey} />
|
||||
</TabPanel>
|
||||
<TabPanel pr={0} pl={0}>
|
||||
<UserRepliesTab pubkey={pubkey} />
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { Post } from "../../components/post";
|
||||
import { isPost } from "../../helpers/nostr-event";
|
||||
import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
export const UserPostsTab = ({ pubkey }: { pubkey: string }) => {
|
||||
export const UserNotesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`${pubkey} posts`,
|
||||
`${pubkey} notes`,
|
||||
{ authors: [pubkey], kinds: [1], since: moment().subtract(1, "day").unix() },
|
||||
{ pageSize: moment.duration(1, "day").asSeconds() }
|
||||
);
|
||||
const timeline = events.filter(isPost);
|
||||
const timeline = events.filter(isNote);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" pr="2" pl="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
<Note key={event.id} event={event} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
@ -1,6 +1,6 @@
|
||||
import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { Post } from "../../components/post";
|
||||
import { Note } from "../../components/note";
|
||||
import { isReply } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
|
||||
@ -15,7 +15,7 @@ export const UserRepliesTab = ({ pubkey }: { pubkey: string }) => {
|
||||
return (
|
||||
<Flex direction="column" gap="2" pr="2" pl="2">
|
||||
{timeline.map((event) => (
|
||||
<Post key={event.id} event={event} />
|
||||
<Note key={event.id} event={event} />
|
||||
))}
|
||||
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
|
||||
</Flex>
|
||||
|
Loading…
x
Reference in New Issue
Block a user