added note metadta component
This commit is contained in:
@@ -1,38 +1,8 @@
|
|||||||
import { RelayContext } from '@components/contexts/relay';
|
import { memo } from 'react';
|
||||||
|
|
||||||
import { useLocalStorage } from '@rehooks/local-storage';
|
|
||||||
import { memo, useContext, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const Reply = memo(function Reply({ eventID }: { eventID: string }) {
|
|
||||||
const relayPool: any = useContext(RelayContext);
|
|
||||||
|
|
||||||
const [relays]: any = useLocalStorage('relays');
|
|
||||||
const [reply, setReply] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
relayPool.subscribe(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
'#e': [eventID],
|
|
||||||
since: 0,
|
|
||||||
kinds: [1],
|
|
||||||
limit: 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
relays,
|
|
||||||
() => {
|
|
||||||
setReply(reply + 1);
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
(events: any, relayURL: any) => {
|
|
||||||
console.log(events, relayURL);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [eventID, relayPool, relays]);
|
|
||||||
|
|
||||||
|
export const CommentsCounter = memo(function CommentsCounter({ comments }: { comments: number }) {
|
||||||
return (
|
return (
|
||||||
<div className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
|
<div className="group flex w-16 items-center gap-1 text-sm text-zinc-500">
|
||||||
<div className="rounded-md p-1 group-hover:bg-zinc-800">
|
<div className="rounded-md p-1 group-hover:bg-zinc-800">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -49,7 +19,7 @@ export const Reply = memo(function Reply({ eventID }: { eventID: string }) {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span>{reply}</span>
|
<span>{comments}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -4,66 +4,50 @@ import { dateToUnix } from '@utils/getDate';
|
|||||||
|
|
||||||
import { useLocalStorage } from '@rehooks/local-storage';
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
import { getEventHash, signEvent } from 'nostr-tools';
|
import { getEventHash, signEvent } from 'nostr-tools';
|
||||||
import { memo, useContext, useEffect, useState } from 'react';
|
import { memo, useCallback, useContext, useState } from 'react';
|
||||||
|
|
||||||
export const Reaction = memo(function Reaction({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) {
|
export const LikesCounter = memo(function LikesCounter({
|
||||||
|
likes,
|
||||||
|
eventID,
|
||||||
|
eventPubkey,
|
||||||
|
}: {
|
||||||
|
likes: number;
|
||||||
|
eventID: string;
|
||||||
|
eventPubkey: string;
|
||||||
|
}) {
|
||||||
const relayPool: any = useContext(RelayContext);
|
const relayPool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const [relays]: any = useLocalStorage('relays');
|
const [relays]: any = useLocalStorage('relays');
|
||||||
|
|
||||||
const [reaction, setReaction] = useState(0);
|
|
||||||
const [isReact, setIsReact] = useState(false);
|
|
||||||
|
|
||||||
const [currentUser]: any = useLocalStorage('current-user');
|
const [currentUser]: any = useLocalStorage('current-user');
|
||||||
|
|
||||||
const handleReaction = (e: any) => {
|
const [isReact, setIsReact] = useState(false);
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const event: any = {
|
const handleLike = useCallback(
|
||||||
content: '+',
|
(e: any) => {
|
||||||
kind: 7,
|
e.stopPropagation();
|
||||||
tags: [
|
|
||||||
['e', eventID],
|
|
||||||
['p', eventPubkey],
|
|
||||||
],
|
|
||||||
created_at: dateToUnix(),
|
|
||||||
pubkey: currentUser.id,
|
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = signEvent(event, currentUser.privkey);
|
|
||||||
// publish event to all relays
|
|
||||||
relayPool.publish(event, relays);
|
|
||||||
// update state to change icon to filled heart
|
|
||||||
setIsReact(true);
|
|
||||||
// update reaction count
|
|
||||||
setReaction(reaction + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
const event: any = {
|
||||||
relayPool.subscribe(
|
content: '+',
|
||||||
[
|
kind: 7,
|
||||||
{
|
tags: [
|
||||||
'#e': [eventID],
|
['e', eventID],
|
||||||
since: 0,
|
['p', eventPubkey],
|
||||||
kinds: [7],
|
],
|
||||||
limit: 10,
|
created_at: dateToUnix(),
|
||||||
},
|
pubkey: currentUser.id,
|
||||||
],
|
};
|
||||||
relays,
|
event.id = getEventHash(event);
|
||||||
(event: any) => {
|
event.sig = signEvent(event, currentUser.privkey);
|
||||||
if (event.content === '🤙' || event.content === '+') {
|
// publish event to all relays
|
||||||
setReaction(reaction + 1);
|
relayPool.publish(event, relays);
|
||||||
}
|
// update state to change icon to filled heart
|
||||||
},
|
setIsReact(true);
|
||||||
undefined,
|
},
|
||||||
(events: any, relayURL: any) => {
|
[currentUser.id, currentUser.privkey, eventID, eventPubkey, relayPool, relays]
|
||||||
console.log(events, relayURL);
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [eventID, relayPool, relays]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button onClick={(e) => handleReaction(e)} className="group flex w-16 items-center gap-1.5 text-sm text-zinc-500">
|
<button onClick={(e) => handleLike(e)} className="group flex w-16 items-center gap-1 text-sm text-zinc-500">
|
||||||
<div className="rounded-md p-1 group-hover:bg-zinc-800">
|
<div className="rounded-md p-1 group-hover:bg-zinc-800">
|
||||||
{isReact ? (
|
{isReact ? (
|
||||||
<svg
|
<svg
|
||||||
@@ -91,7 +75,7 @@ export const Reaction = memo(function Reaction({ eventID, eventPubkey }: { event
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span>{reaction}</span>
|
<span>{likes}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import NoteMetadata from '@components/note/content/metadata';
|
||||||
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';
|
||||||
import { Reaction } from '@components/note/reaction';
|
|
||||||
import { Reply } from '@components/note/reply';
|
|
||||||
import { UserExtend } from '@components/user/extend';
|
import { UserExtend } from '@components/user/extend';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@@ -87,10 +86,7 @@ export const Content = memo(function Content({ data }: { data: any }) {
|
|||||||
</div>
|
</div>
|
||||||
<>{previewAttachment()}</>
|
<>{previewAttachment()}</>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10 -ml-1 flex items-center gap-8">
|
<NoteMetadata eventID={data.id} eventPubkey={data.pubkey} />
|
||||||
<Reply eventID={data.id} />
|
|
||||||
<Reaction eventID={data.id} eventPubkey={data.pubkey} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
53
src/components/note/content/metadata.tsx
Normal file
53
src/components/note/content/metadata.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { RelayContext } from '@components/contexts/relay';
|
||||||
|
import { CommentsCounter } from '@components/note/content/counter/comments';
|
||||||
|
import { LikesCounter } from '@components/note/content/counter/likes';
|
||||||
|
|
||||||
|
import { useLocalStorage } from '@rehooks/local-storage';
|
||||||
|
import { useContext, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export default function NoteMetadata({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) {
|
||||||
|
const relayPool: any = useContext(RelayContext);
|
||||||
|
const [relays]: any = useLocalStorage('relays');
|
||||||
|
|
||||||
|
const [likes, setLikes] = useState(0);
|
||||||
|
const [comments, setComments] = useState(0);
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
relayPool.subscribe(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'#e': [eventID],
|
||||||
|
since: 0,
|
||||||
|
kinds: [1, 7],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relays,
|
||||||
|
(event: any) => {
|
||||||
|
switch (event.kind) {
|
||||||
|
case 1:
|
||||||
|
setComments((comments) => (comments += 1));
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
if (event.content === '🤙' || event.content === '+') {
|
||||||
|
setLikes((likes) => (likes += 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(events: any, relayURL: any) => {
|
||||||
|
console.log(events, relayURL);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [eventID, relayPool, relays]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative z-10 -ml-1 flex items-center gap-8">
|
||||||
|
<CommentsCounter comments={comments} />
|
||||||
|
<LikesCounter likes={likes} eventID={eventID} eventPubkey={eventPubkey} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ const MDEditor = dynamic(() => import('@uiw/react-md-editor').then((mod) => mod.
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function NoteForm() {
|
export default function FormBasic() {
|
||||||
const relayPool: any = useContext(RelayContext);
|
const relayPool: any = useContext(RelayContext);
|
||||||
const [relays]: any = useLocalStorage('relays');
|
const [relays]: any = useLocalStorage('relays');
|
||||||
|
|
||||||
@@ -95,7 +95,20 @@ export default function NoteForm() {
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-zinc-700">
|
<span className="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-zinc-700">
|
||||||
<ImageIcon className="h-4 w-4 text-zinc-400" />
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="h-4 w-4 text-zinc-400"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
import { Content } from '@components/note/content';
|
import { Content } from '@components/note/content';
|
||||||
import { RootNote } from '@components/note/root';
|
import { RootNote } from '@components/note/root';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export const Note = memo(function Note({ event }: { event: any }) {
|
export const Note = memo(function Note({ event }: { event: any }) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [root, setRoot] = useState(null);
|
const [root, setRoot] = useState(null);
|
||||||
const tags = JSON.parse(event.tags);
|
const tags = JSON.parse(event.tags);
|
||||||
|
|
||||||
|
const openThread = () => {
|
||||||
|
router.push({
|
||||||
|
pathname: '/newsfeed/thread',
|
||||||
|
query: { id: root ? root : event.id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tags.length > 0) {
|
if (tags.length > 0) {
|
||||||
if (tags[0][0] === 'e') {
|
if (tags[0][0] === 'e') {
|
||||||
@@ -16,7 +26,10 @@ export const Note = memo(function Note({ event }: { event: any }) {
|
|||||||
}, [tags]);
|
}, [tags]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 flex h-min min-h-min w-full cursor-pointer select-text flex-col border-b border-zinc-800 py-5 px-3">
|
<div
|
||||||
|
onClick={() => openThread()}
|
||||||
|
className="relative z-10 flex h-min min-h-min w-full cursor-pointer select-text flex-col border-b border-zinc-800 py-5 px-3 hover:bg-black/20"
|
||||||
|
>
|
||||||
{root && (
|
{root && (
|
||||||
<>
|
<>
|
||||||
<RootNote id={root} />
|
<RootNote id={root} />
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const RootNote = memo(function RootNote({ id }: { id: string }) {
|
|||||||
if (event) {
|
if (event) {
|
||||||
return (
|
return (
|
||||||
<div className="relative pb-5">
|
<div className="relative pb-5">
|
||||||
<div className="absolute top-0 left-[21px] h-full w-px bg-zinc-800"></div>
|
<div className="absolute top-0 left-[21px] h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600"></div>
|
||||||
<Content data={event} />
|
<Content data={event} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import WithSidebarLayout from '@layouts/withSidebar';
|
|||||||
|
|
||||||
import { DatabaseContext } from '@components/contexts/database';
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
import { Note } from '@components/note';
|
import { Note } from '@components/note';
|
||||||
import NoteForm from '@components/note/form';
|
import FormBasic from '@components/note/form/basic';
|
||||||
import { Placeholder } from '@components/note/placeholder';
|
import { Placeholder } from '@components/note/placeholder';
|
||||||
|
|
||||||
import { atomHasNewerNote } from '@stores/note';
|
import { atomHasNewerNote } from '@stores/note';
|
||||||
@@ -102,7 +102,7 @@ export default function Page() {
|
|||||||
<div className="absolute top-8 left-1/2 z-50 -translate-x-1/2 transform">
|
<div className="absolute top-8 left-1/2 z-50 -translate-x-1/2 transform">
|
||||||
<button
|
<button
|
||||||
onClick={() => loadNewest()}
|
onClick={() => loadNewest()}
|
||||||
className="inline-flex h-8 transform items-center justify-center gap-1 rounded-full bg-fuchsia-500 px-3 text-sm shadow-lg shadow-lg active:translate-y-1"
|
className="inline-flex h-8 transform items-center justify-center gap-1 rounded-full bg-fuchsia-500 px-3 text-sm shadow-lg shadow-fuchsia-900/50 active:translate-y-1"
|
||||||
>
|
>
|
||||||
<span className="text-white drop-shadow">Load newest</span>
|
<span className="text-white drop-shadow">Load newest</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -112,7 +112,7 @@ export default function Page() {
|
|||||||
data={data}
|
data={data}
|
||||||
itemContent={ItemContent}
|
itemContent={ItemContent}
|
||||||
components={{
|
components={{
|
||||||
Header: () => <NoteForm />,
|
Header: () => <FormBasic />,
|
||||||
EmptyPlaceholder: () => <Placeholder />,
|
EmptyPlaceholder: () => <Placeholder />,
|
||||||
ScrollSeekPlaceholder: () => <Placeholder />,
|
ScrollSeekPlaceholder: () => <Placeholder />,
|
||||||
}}
|
}}
|
||||||
|
|||||||
61
src/pages/newsfeed/thread/index.tsx
Normal file
61
src/pages/newsfeed/thread/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import BaseLayout from '@layouts/base';
|
||||||
|
import WithSidebarLayout from '@layouts/withSidebar';
|
||||||
|
|
||||||
|
import { DatabaseContext } from '@components/contexts/database';
|
||||||
|
import { Content } from '@components/note/content';
|
||||||
|
import FormBasic from '@components/note/form/basic';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import {
|
||||||
|
JSXElementConstructor,
|
||||||
|
ReactElement,
|
||||||
|
ReactFragment,
|
||||||
|
ReactPortal,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { db }: any = useContext(DatabaseContext);
|
||||||
|
|
||||||
|
const [root, setRoot] = useState(null);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { id }: any = router.query;
|
||||||
|
|
||||||
|
const fetchRoot = useCallback(async () => {
|
||||||
|
const result = await db.select(`SELECT * FROM cache_notes WHERE id = "${id}"`);
|
||||||
|
setRoot(result[0]);
|
||||||
|
}, [db, id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRoot().catch(console.error);
|
||||||
|
}, [fetchRoot]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="scrollbar-hide flex h-full flex-col overflow-y-auto py-5">
|
||||||
|
<div className="flex h-min min-h-min w-full select-text flex-col px-3">{root && <Content data={root} />}</div>
|
||||||
|
<div>
|
||||||
|
<FormBasic />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page.getLayout = function getLayout(
|
||||||
|
page:
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| ReactElement<unknown, string | JSXElementConstructor<unknown>>
|
||||||
|
| ReactFragment
|
||||||
|
| ReactPortal
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<WithSidebarLayout>{page}</WithSidebarLayout>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user