refactor event handler

This commit is contained in:
Ren Amamiya 2023-02-23 21:34:06 +07:00
parent 31a3a712ec
commit 4b73715927
9 changed files with 82 additions and 236 deletions

View File

@ -57,12 +57,12 @@ export default function CreatePost() {
return ( return (
<Dialog.Root> <Dialog.Root>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<div className="flex flex-col"> <div className="flex flex-col gap-1.5">
<div className="relative shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20"> <div className="relative h-16 shrink-0 before:pointer-events-none before:absolute before:-inset-1 before:rounded-[11px] before:border before:border-blue-500 before:opacity-0 before:ring-2 before:ring-blue-500/20 before:transition after:pointer-events-none after:absolute after:inset-px after:rounded-[7px] after:shadow-highlight after:shadow-white/5 after:transition focus-within:before:opacity-100 focus-within:after:shadow-blue-500/100 dark:focus-within:after:shadow-blue-500/20">
<textarea <textarea
readOnly readOnly
placeholder="What's your thought?" placeholder="What's your thought?"
className="relative w-full resize-none rounded-lg border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" className="relative h-16 w-full resize-none rounded-lg border border-black/5 px-3.5 py-3 text-sm shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500"
/> />
</div> </div>
<button className="inline-flex h-9 w-full items-center justify-center rounded-lg border border-white/10 bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 text-sm font-semibold shadow-input"> <button className="inline-flex h-9 w-full items-center justify-center rounded-lg border border-white/10 bg-[radial-gradient(ellipse_at_bottom_right,_var(--tw-gradient-stops))] from-gray-300 via-fuchsia-600 to-orange-600 text-sm font-semibold shadow-input">

View File

@ -2,14 +2,14 @@
import { truncate } from '@utils/truncate'; import { truncate } from '@utils/truncate';
import { useNostrEvents } from 'nostr-react'; import { useNostrEvents } from 'nostr-react';
import { useState } from 'react'; import { memo, useState } from 'react';
export default function RootUser({ userPubkey, action }: { userPubkey: string; action: string }) { export const UserRepost = memo(function UserRepost({ pubkey }: { pubkey: string }) {
const [profile, setProfile] = useState({ picture: null, name: null }); const [profile, setProfile] = useState({ picture: null, name: null });
const { onEvent } = useNostrEvents({ const { onEvent } = useNostrEvents({
filter: { filter: {
authors: [userPubkey], authors: [pubkey],
kinds: [0], kinds: [0],
}, },
}); });
@ -28,9 +28,7 @@ export default function RootUser({ userPubkey, action }: { userPubkey: string; a
return ( return (
<div className="text-zinc-400"> <div className="text-zinc-400">
<p> <p>{profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} repost</p>
{profile.name ? profile.name : truncate(userPubkey, 16, ' .... ')} {action}
</p>
</div> </div>
); );
} });

View File

@ -1,4 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import Reaction from '@components/note/atoms/reaction';
import Reply from '@components/note/atoms/reply';
import { User } from '@components/note/atoms/user';
import { ImageCard } from '@components/note/content/preview/imageCard'; import { ImageCard } from '@components/note/content/preview/imageCard';
import { Video } from '@components/note/content/preview/video'; import { Video } from '@components/note/content/preview/video';
@ -14,13 +17,13 @@ const MarkdownPreview = dynamic(() => import('@uiw/react-markdown-preview'), {
export default function Content({ data }: { data: any }) { export default function Content({ data }: { data: any }) {
const [preview, setPreview] = useState({}); const [preview, setPreview] = useState({});
const content = useRef(data); const content = useRef(data.content);
const urls = useMemo( const urls = useMemo(
() => () =>
data.match( content.current.match(
/((http|ftp|https):\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi /((http|ftp|https):\/\/)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi
), ),
[data] []
); );
useEffect(() => { useEffect(() => {
@ -61,25 +64,36 @@ export default function Content({ data }: { data: any }) {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<div> <User pubkey={data.pubkey} time={data.created_at} />
<MarkdownPreview <div className="-mt-4 pl-[60px]">
source={content.current} <div className="flex flex-col gap-6">
className={ <div className="flex flex-col">
'prose prose-zinc max-w-none break-words dark:prose-invert prose-headings:mt-3 prose-headings:mb-2 prose-p:m-0 prose-p:leading-normal prose-ul:mt-2 prose-li:my-1' <div>
} <MarkdownPreview
linkTarget="_blank" source={content.current}
disallowedElements={[ className={
'Table', 'prose prose-zinc max-w-none break-words dark:prose-invert prose-headings:mt-3 prose-headings:mb-2 prose-p:m-0 prose-p:leading-normal prose-ul:mt-2 prose-li:my-1'
'Heading ID', }
'Highlight', linkTarget="_blank"
'Fenced Code Block', disallowedElements={[
'Footnote', 'Table',
'Definition List', 'Heading ID',
'Task List', 'Highlight',
]} 'Fenced Code Block',
/> 'Footnote',
'Definition List',
'Task List',
]}
/>
</div>
<>{previewAttachment()}</>
</div>
<div className="relative z-10 -ml-1 flex items-center gap-8">
<Reply eventID={data.id} />
<Reaction eventID={data.id} eventPubkey={data.pubkey} />
</div>
</div>
</div> </div>
<div>{previewAttachment()}</div>
</div> </div>
); );
} }

View File

@ -1,55 +0,0 @@
import Reaction from '@components/note/atoms/reaction';
import Reply from '@components/note/atoms/reply';
import RootUser from '@components/note/atoms/rootUser';
import { User } from '@components/note/atoms/user';
import Content from '@components/note/content';
import { Placeholder } from '@components/note/placeholder';
import LikeSolidIcon from '@assets/icons/LikeSolid';
import { useNostrEvents } from 'nostr-react';
import { memo } from 'react';
export const Liked = memo(function Liked({
eventUser,
sourceID,
}: {
eventUser: string;
sourceID: string;
}) {
const { events } = useNostrEvents({
filter: {
ids: [sourceID],
since: 0,
kinds: [1],
limit: 1,
},
});
if (events !== undefined && events.length > 0) {
return (
<div className="flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 py-6 px-6">
<div className="flex items-center gap-1 pl-8 text-sm">
<LikeSolidIcon className="h-4 w-4 text-zinc-400" />
<div className="ml-2">
<RootUser userPubkey={eventUser} action={'like'} />
</div>
</div>
<div className="flex flex-col">
<User pubkey={events[0].pubkey} time={events[0].created_at} />
<div className="-mt-4 pl-[60px]">
<div className="flex flex-col gap-2">
<Content data={events[0].content} />
<div className="-ml-1 flex items-center gap-8">
<Reply eventID={events[0].id} />
<Reaction eventID={events[0].id} eventPubkey={events[0].pubkey} />
</div>
</div>
</div>
</div>
</div>
);
} else {
return <Placeholder />;
}
});

View File

@ -1,55 +0,0 @@
import Reaction from '@components/note/atoms/reaction';
import Reply from '@components/note/atoms/reply';
import { User } from '@components/note/atoms/user';
import Content from '@components/note/content';
import { Placeholder } from '@components/note/placeholder';
import { useNostrEvents } from 'nostr-react';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function Multi({ event }: { event: any }) {
const tags = event.tags;
const { events } = useNostrEvents({
filter: {
ids: [tags[0][1]],
since: 0,
kinds: [1],
limit: 1,
},
});
if (events !== undefined && events.length > 0) {
return (
<div className="relative flex h-min min-h-min w-full select-text flex-col overflow-hidden border-b border-zinc-800">
<div className="absolute left-[45px] top-6 h-full w-[2px] bg-zinc-800"></div>
<div className="flex flex-col bg-zinc-900 px-6 pt-6 pb-2">
<User pubkey={events[0].pubkey} time={events[0].created_at} />
<div className="-mt-4 pl-[60px]">
<div className="flex flex-col gap-2">
<Content data={events[0].content} />
<div className="-ml-1 flex items-center gap-8">
<Reply eventID={events[0].id} />
<Reaction eventID={events[0].id} eventPubkey={events[0].pubkey} />
</div>
</div>
</div>
</div>
<div className="relative flex flex-col bg-zinc-900 px-6 pb-6">
<User pubkey={event.pubkey} time={event.created_at} />
<div className="relative z-10 -mt-4 pl-[60px]">
<div className="flex flex-col gap-2">
<Content data={event.content} />
<div className="-ml-1 flex items-center gap-8">
<Reply eventID={event.id} />
<Reaction eventID={event.id} eventPubkey={event.pubkey} />
</div>
</div>
</div>
</div>
</div>
);
} else {
return <Placeholder />;
}
}

View File

@ -1,55 +1,56 @@
import Reaction from '@components/note/atoms/reaction'; /* eslint-disable @typescript-eslint/no-explicit-any */
import Reply from '@components/note/atoms/reply'; import { UserRepost } from '@components/note/atoms/userRepost';
import RootUser from '@components/note/atoms/rootUser';
import { User } from '@components/note/atoms/user';
import Content from '@components/note/content'; import Content from '@components/note/content';
import { Placeholder } from '@components/note/placeholder'; import { Placeholder } from '@components/note/placeholder';
import RepostIcon from '@assets/icons/Repost'; import RepostIcon from '@assets/icons/Repost';
import * as Dialog from '@radix-ui/react-dialog';
import dynamic from 'next/dynamic';
import { useNostrEvents } from 'nostr-react'; import { useNostrEvents } from 'nostr-react';
import { memo } from 'react'; import { memo } from 'react';
export const Repost = memo(function Repost({ const Modal = dynamic(() => import('@components/note/modal'), {
eventUser, ssr: false,
sourceID, loading: () => <></>,
}: { });
eventUser: string;
sourceID: string; export const Repost = memo(function Repost({ root, user }: { root: any; user: string }) {
}) {
const { events } = useNostrEvents({ const { events } = useNostrEvents({
filter: { filter: {
ids: [sourceID], ids: [root[0][1]],
since: 0, since: 0,
kinds: [1], kinds: [1],
limit: 1,
}, },
}); });
if (events !== undefined && events.length > 0) { if (events !== null) {
return ( return (
<div className="flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 py-6 px-6"> <Dialog.Root>
<div className="flex items-center gap-1 pl-8 text-sm"> <Dialog.Trigger>
<RepostIcon className="h-4 w-4 text-zinc-400" /> <div className="flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 py-6 px-6">
<div className="ml-2"> <div className="flex items-center gap-1 pl-8 text-sm">
<RootUser userPubkey={eventUser} action={'repost'} /> <RepostIcon className="h-4 w-4 text-zinc-400" />
</div> <div className="ml-2">
</div> <UserRepost pubkey={user} />
<div className="flex flex-col">
<User pubkey={events[0].pubkey} time={events[0].created_at} />
<div className="-mt-4 pl-[60px]">
<div className="flex flex-col gap-2">
<Content data={events[0].content} />
<div className="-ml-1 flex items-center gap-8">
<Reply eventID={events[0].id} />
<Reaction eventID={events[0].id} eventPubkey={events[0].pubkey} />
</div> </div>
</div> </div>
<Content data={events[0]} />
</div> </div>
</div> <Dialog.Portal>
</div> <Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm data-[state=open]:animate-overlayShow" />
<Dialog.Content className="fixed inset-0 overflow-y-auto">
<Modal event={events[0]} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Trigger>
</Dialog.Root>
); );
} else { } else {
return <Placeholder />; return (
<div className="border-b border-zinc-800">
<Placeholder />
</div>
);
} }
}); });

View File

@ -1,7 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import Reaction from '@components/note/atoms/reaction';
import Reply from '@components/note/atoms/reply';
import { User } from '@components/note/atoms/user';
import Content from '@components/note/content'; import Content from '@components/note/content';
import * as Dialog from '@radix-ui/react-dialog'; import * as Dialog from '@radix-ui/react-dialog';
@ -15,22 +12,13 @@ const Modal = dynamic(() => import('@components/note/modal'), {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Single = memo(function Single({ event }: { event: any }) { export const Single = memo(function Single({ event }: { event: any }) {
console.log(event);
return ( return (
<Dialog.Root> <Dialog.Root>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<div className="flex h-min min-h-min w-full cursor-pointer select-text flex-col border-b border-zinc-800 py-4 px-6 hover:bg-zinc-800"> <div className="flex h-min min-h-min w-full cursor-pointer select-text flex-col border-b border-zinc-800 py-4 px-6 hover:bg-zinc-800">
<div className="flex flex-col"> <Content data={event} />
<User pubkey={event.pubkey} time={event.created_at} />
<div className="-mt-4 pl-[60px]">
<div className="flex flex-col gap-6">
<Content data={event.content} />
<div className="relative z-10 -ml-1 flex items-center gap-8">
<Reply eventID={event.id} />
<Reaction eventID={event.id} eventPubkey={event.pubkey} />
</div>
</div>
</div>
</div>
</div> </div>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Portal> <Dialog.Portal>

View File

@ -1,5 +1,3 @@
import { Liked } from '@components/note/liked';
// import { Multi } from '@components/note/multi';
import { Placeholder } from '@components/note/placeholder'; import { Placeholder } from '@components/note/placeholder';
import { Repost } from '@components/note/repost'; import { Repost } from '@components/note/repost';
import { Single } from '@components/note/single'; import { Single } from '@components/note/single';
@ -13,15 +11,12 @@ export function Thread({ data }: { data: any }) {
(index: string | number) => { (index: string | number) => {
const event = data[index]; const event = data[index];
if (event.kind === 7) { if (event.content.includes('#[0]') && event.tags[0][0] == 'e') {
// type: like
return <Liked key={index} eventUser={event.pubkey} sourceID={event.tags[0][1]} />;
} else if (event.content === '#[0]') {
// type: repost // type: repost
return <Repost key={index} eventUser={event.pubkey} sourceID={event.tags[0][1]} />; return <Repost root={event.tags} user={event.pubkey} />;
} else { } else {
// type: default // type: default
return <Single key={index} event={event} />; return <Single event={event} />;
} }
}, },
[data] [data]

View File

@ -1,40 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import BaseLayout from '@layouts/baseLayout';
import UserLayout from '@layouts/userLayout';
import { GetStaticPaths } from 'next';
import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react';
export default function Page({ id }: { id: string }) {
return <div>{id}</div>;
}
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
fallback: 'blocking',
};
};
export async function getStaticProps(context) {
const id = context.params.id;
return {
props: { id },
};
}
Page.getLayout = function getLayout(
page:
| string
| number
| boolean
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
| ReactFragment
| ReactPortal
) {
return (
<BaseLayout>
<UserLayout>{page}</UserLayout>
</BaseLayout>
);
};