From 3aeb70f2340fea7fc08e733ec1d71a3b30386f21 Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Sun, 16 Apr 2023 16:43:17 +0700 Subject: [PATCH] added kind 6 to newsfeed and changed useMetadata to useProfileMetadata --- src/app/newsfeed/following/page.tsx | 10 +- src/app/page.tsx | 66 ++++++++----- src/components/chats/chatListItem.tsx | 4 +- src/components/chats/messageUser.tsx | 4 +- src/components/eventCollector.tsx | 94 +++++++++++------- src/components/networkStatusIndicator.tsx | 4 +- src/components/note/base.tsx | 4 +- src/components/note/quote.tsx | 2 +- src/components/note/quoteRepost.tsx | 26 +++++ src/components/note/rootNote.tsx | 99 +++++++++++++++++++ src/components/user/base.tsx | 4 +- src/components/user/extend.tsx | 4 +- src/components/user/follow.tsx | 4 +- src/components/user/large.tsx | 4 +- src/components/user/mention.tsx | 4 +- src/components/user/mini.tsx | 4 +- src/components/user/quoteRepost.tsx | 35 +++++++ .../useNetworkStatus.tsx} | 2 +- .../useProfileMetadata.tsx} | 6 +- 19 files changed, 297 insertions(+), 83 deletions(-) create mode 100644 src/components/note/quoteRepost.tsx create mode 100644 src/components/note/rootNote.tsx create mode 100644 src/components/user/quoteRepost.tsx rename src/utils/{network.tsx => hooks/useNetworkStatus.tsx} (93%) rename src/utils/{metadata.tsx => hooks/useProfileMetadata.tsx} (92%) diff --git a/src/app/newsfeed/following/page.tsx b/src/app/newsfeed/following/page.tsx index c2c1c484..60a071d4 100644 --- a/src/app/newsfeed/following/page.tsx +++ b/src/app/newsfeed/following/page.tsx @@ -3,6 +3,7 @@ import FormBase from '@components/form/base'; import { NoteBase } from '@components/note/base'; import { Placeholder } from '@components/note/placeholder'; +import { NoteQuoteRepost } from '@components/note/quoteRepost'; import { filteredNotesAtom, hasNewerNoteAtom, notesAtom } from '@stores/note'; @@ -25,7 +26,14 @@ export default function Page() { const itemContent: any = useCallback( (index: string | number) => { - return ; + switch (data[index].kind) { + case 1: + return ; + case 6: + return ; + default: + break; + } }, [data] ); diff --git a/src/app/page.tsx b/src/app/page.tsx index e8b4c290..b9a4a02c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -66,7 +66,7 @@ export default function Page() { since = dateToUnix(new Date(lastLogin)); } query.push({ - kinds: [1], + kinds: [1, 6], authors: follows, since: since, until: dateToUnix(now.current), @@ -93,32 +93,52 @@ export default function Page() { query, relays, (event) => { - if (event.kind === 1) { - const parentID = getParentID(event.tags, event.id); - // insert event to local database - createNote({ - event_id: event.id, - pubkey: event.pubkey, - kind: event.kind, - tags: JSON.stringify(event.tags), - content: event.content, - parent_id: parentID, - parent_comment_id: '', - created_at: event.created_at, - account_id: account.id, - }).catch(console.error); - } else if (event.kind === 4) { - if (event.pubkey !== account.pubkey) { - createChat({ + switch (event.kind) { + // short text note + case 1: + const parentID = getParentID(event.tags, event.id); + // insert event to local database + createNote({ + event_id: event.id, pubkey: event.pubkey, + kind: event.kind, + tags: JSON.stringify(event.tags), + content: event.content, + parent_id: parentID, + parent_comment_id: '', created_at: event.created_at, account_id: account.id, }).catch(console.error); - } - } else if (event.kind === 40) { - createChannel({ event_id: event.id, content: event.content, account_id: account.id }).catch(console.error); - } else { - console.error; + break; + // chat + case 4: + if (event.pubkey !== account.pubkey) { + createChat({ + pubkey: event.pubkey, + created_at: event.created_at, + account_id: account.id, + }).catch(console.error); + } + // repost + case 6: + createNote({ + event_id: event.id, + pubkey: event.pubkey, + kind: event.kind, + tags: JSON.stringify(event.tags), + content: event.content, + parent_id: '', + parent_comment_id: '', + created_at: event.created_at, + account_id: account.id, + }).catch(console.error); + // channel + case 40: + createChannel({ event_id: event.id, content: event.content, account_id: account.id }).catch( + console.error + ); + default: + break; } }, undefined, diff --git a/src/components/chats/chatListItem.tsx b/src/components/chats/chatListItem.tsx index 29ff0365..b192173b 100644 --- a/src/components/chats/chatListItem.tsx +++ b/src/components/chats/chatListItem.tsx @@ -2,14 +2,14 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; import { useRouter } from 'next/navigation'; export const ChatListItem = ({ pubkey }: { pubkey: string }) => { const router = useRouter(); - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); const openChat = () => { router.push(`/chats/${pubkey}`); diff --git a/src/components/chats/messageUser.tsx b/src/components/chats/messageUser.tsx index 8ff71336..19a303be 100644 --- a/src/components/chats/messageUser.tsx +++ b/src/components/chats/messageUser.tsx @@ -2,7 +2,7 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; import dayjs from 'dayjs'; @@ -11,7 +11,7 @@ import relativeTime from 'dayjs/plugin/relativeTime'; dayjs.extend(relativeTime); export const MessageUser = ({ pubkey, time }: { pubkey: string; time: number }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/eventCollector.tsx b/src/components/eventCollector.tsx index 918ff1f0..0e3eb026 100644 --- a/src/components/eventCollector.tsx +++ b/src/components/eventCollector.tsx @@ -6,7 +6,7 @@ import { RelayContext } from '@components/relaysProvider'; import { hasNewerNoteAtom } from '@stores/note'; import { dateToUnix } from '@utils/getDate'; -import { fetchMetadata } from '@utils/metadata'; +import { fetchProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { getParentID, pubkeyArray } from '@utils/transform'; import useLocalStorage, { writeStorage } from '@rehooks/local-storage'; @@ -31,7 +31,7 @@ export default function EventCollector() { const { createPleb } = await import('@utils/bindings'); for (const tag of tags) { const pubkey = tag[1]; - fetchMetadata(pubkey) + fetchProfileMetadata(pubkey) .then((res: { content: string }) => { createPleb({ pleb_id: pubkey + '-lume' + activeAccount.id.toString(), @@ -55,7 +55,7 @@ export default function EventCollector() { unsubscribe.current = pool.subscribe( [ { - kinds: [1], + kinds: [1, 6], authors: pubkeyArray(follows), since: dateToUnix(now.current), }, @@ -75,39 +75,65 @@ export default function EventCollector() { ], relays, (event) => { - if (event.kind === 1) { - const parentID = getParentID(event.tags, event.id); - // insert event to local database - createNote({ - event_id: event.id, - pubkey: event.pubkey, - kind: event.kind, - tags: JSON.stringify(event.tags), - content: event.content, - parent_id: parentID, - parent_comment_id: '', - created_at: event.created_at, - account_id: activeAccount.id, - }) - .then(() => - // notify user reload to get newer note - setHasNewerNote(true) - ) - .catch(console.error); - } else if (event.kind === 3) { - createFollowingPlebs(event.tags); - } else if (event.kind === 4) { - if (event.pubkey !== activeAccount.pubkey) { - createChat({ pubkey: event.pubkey, created_at: event.created_at, account_id: activeAccount.id }).catch( + switch (event.kind) { + // short text note + case 1: + const parentID = getParentID(event.tags, event.id); + createNote({ + event_id: event.id, + pubkey: event.pubkey, + kind: event.kind, + tags: JSON.stringify(event.tags), + content: event.content, + parent_id: parentID, + parent_comment_id: '', + created_at: event.created_at, + account_id: activeAccount.id, + }) + .then(() => + // notify user reload to get newer note + setHasNewerNote(true) + ) + .catch(console.error); + break; + // contacts + case 3: + createFollowingPlebs(event.tags); + break; + // chat + case 4: + if (event.pubkey !== activeAccount.pubkey) { + createChat({ + pubkey: event.pubkey, + created_at: event.created_at, + account_id: activeAccount.id, + }).catch(console.error); + } + // repost + case 6: + createNote({ + event_id: event.id, + pubkey: event.pubkey, + kind: event.kind, + tags: JSON.stringify(event.tags), + content: event.content, + parent_id: '', + parent_comment_id: '', + created_at: event.created_at, + account_id: activeAccount.id, + }) + .then(() => + // notify user reload to get newer note + setHasNewerNote(true) + ) + .catch(console.error); + // channel + case 40: + createChannel({ event_id: event.id, content: event.content, account_id: activeAccount.id }).catch( console.error ); - } - } else if (event.kind === 40) { - createChannel({ event_id: event.id, content: event.content, account_id: activeAccount.id }).catch( - console.error - ); - } else { - console.error; + default: + break; } } ); diff --git a/src/components/networkStatusIndicator.tsx b/src/components/networkStatusIndicator.tsx index e17bdbb3..69ca64ac 100644 --- a/src/components/networkStatusIndicator.tsx +++ b/src/components/networkStatusIndicator.tsx @@ -1,7 +1,7 @@ -import { useNavigatorOnLine } from '@utils/network'; +import { useNetworkStatus } from '@utils/hooks/useNetworkStatus'; export const NetworkStatusIndicator = () => { - const isOnline = useNavigatorOnLine(); + const isOnline = useNetworkStatus(); return (
diff --git a/src/components/note/base.tsx b/src/components/note/base.tsx index a3ec85f1..bcfeee32 100644 --- a/src/components/note/base.tsx +++ b/src/components/note/base.tsx @@ -45,7 +45,7 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) { )); // handle mentions if (tags.length > 0) { - parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match, i) => { + parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match) => { if (tags[match][0] === 'p') { // @-mentions return ; @@ -90,7 +90,7 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) { onClick={(e) => openThread(e)} className="relative z-10 m-0 flex h-min min-h-min w-full select-text flex-col border-b border-zinc-800 px-3 py-5 hover:bg-black/20" > - <>{parentNote} + {parentNote}
openUserPage(e)}> diff --git a/src/components/note/quote.tsx b/src/components/note/quote.tsx index 77059077..b1a0761f 100644 --- a/src/components/note/quote.tsx +++ b/src/components/note/quote.tsx @@ -9,7 +9,7 @@ import destr from 'destr'; import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import reactStringReplace from 'react-string-replace'; -export const NoteQuote = memo(function NoteRepost({ id }: { id: string }) { +export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) { const [pool, relays]: any = useContext(RelayContext); const [activeAccount]: any = useLocalStorage('activeAccount', {}); diff --git a/src/components/note/quoteRepost.tsx b/src/components/note/quoteRepost.tsx new file mode 100644 index 00000000..a30ea812 --- /dev/null +++ b/src/components/note/quoteRepost.tsx @@ -0,0 +1,26 @@ +import { RootNote } from '@components/note/rootNote'; +import { UserQuoteRepost } from '@components/user/quoteRepost'; + +import { memo } from 'react'; + +export const NoteQuoteRepost = memo(function NoteQuoteRepost({ event }: { event: any }) { + const rootNote = () => { + let note = null; + + if (event.content.length > 0) { + note = ; + } + + return note; + }; + + return ( +
+
+
+ +
+ {rootNote()} +
+ ); +}); diff --git a/src/components/note/rootNote.tsx b/src/components/note/rootNote.tsx new file mode 100644 index 00000000..e3153ea2 --- /dev/null +++ b/src/components/note/rootNote.tsx @@ -0,0 +1,99 @@ +import NoteMetadata from '@components/note/metadata'; +import { ImagePreview } from '@components/note/preview/image'; +import { VideoPreview } from '@components/note/preview/video'; +import { NoteQuote } from '@components/note/quote'; +import { UserExtend } from '@components/user/extend'; +import { UserMention } from '@components/user/mention'; + +import destr from 'destr'; +import { useRouter } from 'next/navigation'; +import { memo, useMemo } from 'react'; +import reactStringReplace from 'react-string-replace'; + +export const RootNote = memo(function RootNote({ event }: { event: any }) { + const router = useRouter(); + + const content = useMemo(() => { + let parsedContent = event.content; + // get data tags + const tags = destr(event.tags); + // handle urls + parsedContent = reactStringReplace(parsedContent, /(https?:\/\/\S+)/g, (match, i) => { + if (match.match(/\.(jpg|jpeg|gif|png|webp)$/i)) { + // image url + return ; + } else if (match.match(/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/i)) { + // youtube + return ; + } else if (match.match(/\.(mp4|webm)$/i)) { + // video + return ; + } else { + return ( + + {match} + + ); + } + }); + // handle #-hashtags + parsedContent = reactStringReplace(parsedContent, /#(\w+)/g, (match, i) => ( + + #{match} + + )); + // handle mentions + if (tags.length > 0) { + parsedContent = reactStringReplace(parsedContent, /\#\[(\d+)\]/gm, (match) => { + if (tags[match][0] === 'p') { + // @-mentions + return ; + } else if (tags[match][0] === 'e') { + // note-quotes + return ; + } else { + return; + } + }); + } + + return parsedContent; + }, [event.content, event.tags]); + + const openUserPage = (e) => { + e.stopPropagation(); + router.push(`/users/${event.pubkey}`); + }; + + const openThread = (e) => { + const selection = window.getSelection(); + if (selection.toString().length === 0) { + router.push(`/newsfeed/${event.parent_id}`); + } else { + e.stopPropagation(); + } + }; + + return ( +
openThread(e)} className="relative z-10 flex flex-col"> +
openUserPage(e)}> + +
+
+
+
+ {content} +
+
+
+
e.stopPropagation()} className="mt-5 pl-[52px]"> + +
+
+ ); +}); diff --git a/src/components/user/base.tsx b/src/components/user/base.tsx index 7551b520..6d11be56 100644 --- a/src/components/user/base.tsx +++ b/src/components/user/base.tsx @@ -2,13 +2,13 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; import { memo } from 'react'; export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/user/extend.tsx b/src/components/user/extend.tsx index b0dc7f86..9db2b0f3 100644 --- a/src/components/user/extend.tsx +++ b/src/components/user/extend.tsx @@ -2,7 +2,7 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; import dayjs from 'dayjs'; @@ -12,7 +12,7 @@ import { MoreHoriz } from 'iconoir-react'; dayjs.extend(relativeTime); export const UserExtend = ({ pubkey, time }: { pubkey: string; time: number }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/user/follow.tsx b/src/components/user/follow.tsx index fe7b0306..051428c0 100644 --- a/src/components/user/follow.tsx +++ b/src/components/user/follow.tsx @@ -2,11 +2,11 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; export const UserFollow = ({ pubkey }: { pubkey: string }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/user/large.tsx b/src/components/user/large.tsx index 3957cfa4..f3779860 100644 --- a/src/components/user/large.tsx +++ b/src/components/user/large.tsx @@ -2,7 +2,7 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; import dayjs from 'dayjs'; @@ -12,7 +12,7 @@ import { MoreHoriz } from 'iconoir-react'; dayjs.extend(relativeTime); export const UserLarge = ({ pubkey, time }: { pubkey: string; time: number }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/user/mention.tsx b/src/components/user/mention.tsx index ed136390..82340491 100644 --- a/src/components/user/mention.tsx +++ b/src/components/user/mention.tsx @@ -1,8 +1,8 @@ -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; export const UserMention = ({ pubkey }: { pubkey: string }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return ( @{profile?.name || profile?.username || truncate(pubkey, 16, ' .... ')} diff --git a/src/components/user/mini.tsx b/src/components/user/mini.tsx index 198ee205..8546736f 100644 --- a/src/components/user/mini.tsx +++ b/src/components/user/mini.tsx @@ -2,11 +2,11 @@ import { ImageWithFallback } from '@components/imageWithFallback'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { useMetadata } from '@utils/metadata'; +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; import { truncate } from '@utils/truncate'; export const UserMini = ({ pubkey }: { pubkey: string }) => { - const profile = useMetadata(pubkey); + const profile = useProfileMetadata(pubkey); return (
diff --git a/src/components/user/quoteRepost.tsx b/src/components/user/quoteRepost.tsx new file mode 100644 index 00000000..b96bf965 --- /dev/null +++ b/src/components/user/quoteRepost.tsx @@ -0,0 +1,35 @@ +import { ImageWithFallback } from '@components/imageWithFallback'; + +import { DEFAULT_AVATAR } from '@stores/constants'; + +import { useProfileMetadata } from '@utils/hooks/useProfileMetadata'; +import { truncate } from '@utils/truncate'; + +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +dayjs.extend(relativeTime); + +export const UserQuoteRepost = ({ pubkey, time }: { pubkey: string; time: number }) => { + const profile = useProfileMetadata(pubkey); + + return ( +
+
+ +
+
+
+ {profile?.display_name || profile?.name || truncate(pubkey, 16, ' .... ')} reposted +
+ ยท + {dayjs().to(dayjs.unix(time))} +
+
+ ); +}; diff --git a/src/utils/network.tsx b/src/utils/hooks/useNetworkStatus.tsx similarity index 93% rename from src/utils/network.tsx rename to src/utils/hooks/useNetworkStatus.tsx index 41db85a8..f61f0edc 100644 --- a/src/utils/network.tsx +++ b/src/utils/hooks/useNetworkStatus.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; const getOnLineStatus = () => typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true; -export const useNavigatorOnLine = () => { +export const useNetworkStatus = () => { const [status, setStatus] = useState(getOnLineStatus()); const setOnline = () => setStatus(true); diff --git a/src/utils/metadata.tsx b/src/utils/hooks/useProfileMetadata.tsx similarity index 92% rename from src/utils/metadata.tsx rename to src/utils/hooks/useProfileMetadata.tsx index 8f86436e..53b28c8d 100644 --- a/src/utils/metadata.tsx +++ b/src/utils/hooks/useProfileMetadata.tsx @@ -2,7 +2,7 @@ import useLocalStorage from '@rehooks/local-storage'; import { fetch } from '@tauri-apps/api/http'; import { useCallback, useEffect, useMemo, useState } from 'react'; -export const fetchMetadata = async (pubkey: string) => { +export const fetchProfileMetadata = async (pubkey: string) => { const result = await fetch(`https://rbr.bio/${pubkey}/metadata.json`, { method: 'GET', timeout: 5, @@ -10,7 +10,7 @@ export const fetchMetadata = async (pubkey: string) => { return await result.data; }; -export const useMetadata = (pubkey) => { +export const useProfileMetadata = (pubkey) => { const [activeAccount]: any = useLocalStorage('activeAccount', {}); const [plebs] = useLocalStorage('activeAccountFollows', []); const [profile, setProfile] = useState(null); @@ -47,7 +47,7 @@ export const useMetadata = (pubkey) => { useEffect(() => { if (!cacheProfile) { - fetchMetadata(pubkey) + fetchProfileMetadata(pubkey) .then((res: any) => { // update state setProfile(JSON.parse(res.content));