diff --git a/.prettierrc b/.prettierrc index 9a6c3bc3..a1b792e6 100644 --- a/.prettierrc +++ b/.prettierrc @@ -12,6 +12,7 @@ "^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", + "^@stores/(.*)$", "^@utils/(.*)$", "", "^[./]" diff --git a/package.json b/package.json index 13515ca7..850a0be3 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "bitcoin-address-validation": "^2.2.1", "boring-avatars": "^1.7.0", "framer-motion": "^9.1.7", + "jotai": "^2.0.3", "moment": "^2.29.4", "next": "^13.2.4", "next-remove-imports": "^1.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93b703dd..733fe86e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,7 @@ specifiers: eslint-plugin-react-hooks: ^4.6.0 framer-motion: ^9.1.7 husky: ^8.0.3 + jotai: ^2.0.3 lint-staged: ^13.2.0 moment: ^2.29.4 next: ^13.2.4 @@ -65,6 +66,7 @@ dependencies: bitcoin-address-validation: 2.2.1 boring-avatars: 1.7.0 framer-motion: 9.1.7_biqbaboplfbrettd7655fr4n2y + jotai: 2.0.3_react@18.2.0 moment: 2.29.4 next: 13.2.4_biqbaboplfbrettd7655fr4n2y next-remove-imports: 1.0.10 @@ -1160,9 +1162,9 @@ packages: - encoding dev: false - /@supabase/gotrue-js/2.13.0: + /@supabase/gotrue-js/2.14.0: resolution: - { integrity: sha512-NFBHuHNUn94mP/zOQzsp1k2PtwV55Vhf6ZbTzmMpiUvIRlXhVteZcdfdoAQDIBrdxOdL7F54NFp1gIupPZka6g== } + { integrity: sha512-FI6q4n4iZ2zrEt1BnBYYe8HQ1k9t5CpBcDQxVXa8PeMwygXpzR0AcdfAsZ5Yba42C8YsBA132ti01f+RINS3UQ== } dependencies: cross-fetch: 3.1.5 transitivePeerDependencies: @@ -1178,9 +1180,9 @@ packages: - encoding dev: false - /@supabase/realtime-js/2.6.0: + /@supabase/realtime-js/2.7.0: resolution: - { integrity: sha512-tOVulMobhpxyDuu8VIImpL8FXmZOKsGNOSyS5ihJdj2xYmPPvYG+D2J51Ewfl+MFF65tweiB6p9N9bNIW1cDNA== } + { integrity: sha512-wg35ofiCpIemycmPZvvZk3jM9c9z8VvnPUBbSP9ZZN2vSOEJ9C7DZuLiiZMXsyNUzjVgIn62A1tN99T5+9O8Aw== } dependencies: '@types/phoenix': 1.5.5 websocket: 1.0.34 @@ -1202,9 +1204,9 @@ packages: { integrity: sha512-/vkpPxGDyLfTASWnVHL8vdgQxn9SX/Cs+BotTxFhLSIeGFSazC6rpQSMKu6RqzO7gjBD1KqTv0h3auWfClWs+Q== } dependencies: '@supabase/functions-js': 2.1.0 - '@supabase/gotrue-js': 2.13.0 + '@supabase/gotrue-js': 2.14.0 '@supabase/postgrest-js': 1.4.1 - '@supabase/realtime-js': 2.6.0 + '@supabase/realtime-js': 2.7.0 '@supabase/storage-js': 2.3.1 cross-fetch: 3.1.5 transitivePeerDependencies: @@ -2004,7 +2006,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001466 - electron-to-chromium: 1.4.330 + electron-to-chromium: 1.4.331 node-releases: 2.0.10 update-browserslist-db: 1.0.10_browserslist@4.21.5 @@ -2414,9 +2416,9 @@ packages: { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } dev: true - /electron-to-chromium/1.4.330: + /electron-to-chromium/1.4.331: resolution: - { integrity: sha512-PqyefhybrVdjAJ45HaPLtuVaehiSw7C3ya0aad+rvmV53IVyXmYRk3pwIOb2TxTDTnmgQdn46NjMMaysx79/6Q== } + { integrity: sha512-tdtr9y9oJl8VDiS+HeB6e/JDJqdDGjIk3yRfEMHm5rDnWQ/D5SbafybAayInxolbfbH6pouV5g7ZUAwE/WVtHw== } /emoji-regex/8.0.0: resolution: @@ -3755,6 +3757,19 @@ packages: { integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== } dev: true + /jotai/2.0.3_react@18.2.0: + resolution: + { integrity: sha512-MMjhSPAL3RoeZD9WbObufRT2quThEAEknHHridf2ma8Ml7ZVQmUiHk0ssdbR3F0h3kcwhYqSGJ59OjhPge7RRg== } + engines: { node: '>=12.20.0' } + peerDependencies: + react: '>=17.0.0' + peerDependenciesMeta: + react: + optional: true + dependencies: + react: 18.2.0 + dev: false + /js-sdsl/4.3.0: resolution: { integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== } diff --git a/src/components/appHeader/index.tsx b/src/components/appHeader/index.tsx index 9eeb3ab9..b2aa0a68 100644 --- a/src/components/appHeader/index.tsx +++ b/src/components/appHeader/index.tsx @@ -1,3 +1,5 @@ +import { NoteConnector } from '@components/note/connector'; + import { ArrowLeftIcon, ArrowRightIcon } from '@radix-ui/react-icons'; import { useRouter } from 'next/router'; @@ -15,7 +17,7 @@ export default function AppHeader() { return (
{/* macos traffic lights */}
-
+
+
+ +
); diff --git a/src/components/note/atoms/user.tsx b/src/components/note/atoms/user.tsx deleted file mode 100644 index 207c2f2d..00000000 --- a/src/components/note/atoms/user.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { DatabaseContext } from '@components/contexts/database'; -import { ImageWithFallback } from '@components/imageWithFallback'; - -import { truncate } from '@utils/truncate'; - -import { DotsHorizontalIcon } from '@radix-ui/react-icons'; -import Avatar from 'boring-avatars'; -import { memo, useCallback, useContext, useEffect, useState } from 'react'; -import Moment from 'react-moment'; - -export const User = memo(function User({ pubkey, time }: { pubkey: string; time: any }) { - const { db }: any = useContext(DatabaseContext); - const [profile, setProfile] = useState({ picture: null, name: null, username: null }); - - const insertCacheProfile = useCallback( - async (event) => { - const metadata: any = JSON.parse(event.content); - - await db.execute( - `INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES ("${pubkey}", '${JSON.stringify(metadata)}')` - ); - setProfile(metadata); - }, - [db, pubkey] - ); - - const getCacheProfile = useCallback(async () => { - const result: any = await db.select(`SELECT metadata FROM cache_profiles WHERE id = "${pubkey}"`); - return result; - }, [db, pubkey]); - - useEffect(() => { - getCacheProfile() - .then((res) => { - if (res[0] !== undefined) { - setProfile(JSON.parse(res[0].metadata)); - } else { - fetch(`https://rbr.bio/${pubkey}/metadata.json`).then((res) => - res.json().then((res) => { - // update state - setProfile(JSON.parse(res.content)); - // save profile to database - insertCacheProfile(res); - }) - ); - } - }) - .catch(console.error); - }, [getCacheProfile, insertCacheProfile, pubkey]); - - return ( -
-
- {profile.picture ? ( - - ) : ( - - )} -
-
-
-
- - {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} - - ยท - - {time} - -
-
- -
-
-
-
- ); -}); diff --git a/src/components/note/connector.tsx b/src/components/note/connector.tsx index 7ff56399..7c464fab 100644 --- a/src/components/note/connector.tsx +++ b/src/components/note/connector.tsx @@ -1,40 +1,28 @@ import { DatabaseContext } from '@components/contexts/database'; import { RelayContext } from '@components/contexts/relay'; +import { atomHasNewerNote } from '@stores/note'; + import { dateToUnix, hoursAgo } from '@utils/getDate'; -import { ReloadIcon } from '@radix-ui/react-icons'; +import { SliderIcon } from '@radix-ui/react-icons'; import { useLocalStorage } from '@rehooks/local-storage'; -import { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { useSetAtom } from 'jotai'; +import { memo, useCallback, useContext, useEffect, useRef } from 'react'; -export const NoteConnector = memo(function NoteConnector({ - setParentReload, - setHasNewNote, - currentDate, -}: { - setParentReload: any; - setHasNewNote: any; - currentDate: any; -}) { +export const NoteConnector = memo(function NoteConnector() { const { db }: any = useContext(DatabaseContext); const relayPool: any = useContext(RelayContext); const [follows]: any = useLocalStorage('follows'); const [relays]: any = useLocalStorage('relays'); - const [reload, setReload] = useState(false); - const timeout = useRef(null); - - const reloadNewsfeed = () => { - setParentReload(true); - setReload(true); - timeout.current = setTimeout(() => { - setReload(false); - }, 2000); - }; + const setHasNewerNote = useSetAtom(atomHasNewerNote); + const now = useRef(new Date()); const insertDB = useCallback( async (event: any) => { + // insert to local database await db.execute( `INSERT OR IGNORE INTO cache_notes @@ -45,7 +33,7 @@ export const NoteConnector = memo(function NoteConnector({ "${event.created_at}", "${event.kind}", '${JSON.stringify(event.tags)}', - "${event.content}" + '${JSON.stringify(event.content)}' );` ); }, @@ -58,54 +46,34 @@ export const NoteConnector = memo(function NoteConnector({ { kinds: [1], authors: follows, - since: dateToUnix(hoursAgo(12, currentDate)), + since: dateToUnix(hoursAgo(12, now.current)), }, ], relays, (event: any) => { - // show trigger update newer event - if (event.created_at > dateToUnix(currentDate)) { - setHasNewNote(true); - } // insert event to local database insertDB(event).catch(console.error); - }, - undefined, - (events: any, relayURL: any) => { - console.log(events, relayURL); + // ask user load newer note + if (event.created_at > dateToUnix(now.current)) { + setHasNewerNote(true); + } } ); - }, [relayPool, follows, currentDate, relays, insertDB, setHasNewNote]); + }, [relayPool, follows, relays, insertDB, setHasNewerNote]); useEffect(() => { fetchEvent(); - - return () => { - clearTimeout(timeout.current); - }; }, [fetchEvent]); return ( -
-
-

# following

+ <> +
+ + + + +

Relays

-
- -
- {/* #TODO: get user network status */} - - - - -

Online

-
-
-
+ ); }); diff --git a/src/components/note/content/index.tsx b/src/components/note/content/index.tsx index d583ff71..6a92d25b 100644 --- a/src/components/note/content/index.tsx +++ b/src/components/note/content/index.tsx @@ -14,7 +14,7 @@ const MarkdownPreview = dynamic(() => import('@uiw/react-markdown-preview'), { export const Content = memo(function Content({ data }: { data: any }) { const [preview, setPreview] = useState({}); - const content = useRef(data.content); + const content = useRef(JSON.parse(data.content)); const urls = useMemo( () => content.current.match( diff --git a/src/components/note/placeholder.tsx b/src/components/note/placeholder.tsx index 9769e8ce..67969508 100644 --- a/src/components/note/placeholder.tsx +++ b/src/components/note/placeholder.tsx @@ -2,7 +2,7 @@ import { memo } from 'react'; export const Placeholder = memo(function Placeholder() { return ( -
+
diff --git a/src/components/user/mini.tsx b/src/components/user/mini.tsx index 28c8f3e5..13140a0b 100644 --- a/src/components/user/mini.tsx +++ b/src/components/user/mini.tsx @@ -8,7 +8,7 @@ import { memo, useCallback, useContext, useEffect, useState } from 'react'; export const UserMini = memo(function UserMini({ pubkey }: { pubkey: string }) { const { db }: any = useContext(DatabaseContext); - const [profile, setProfile] = useState({ picture: null, display_name: null }); + const [profile, setProfile] = useState({ picture: null, name: null }); const insertCacheProfile = useCallback( async (event) => { @@ -67,7 +67,7 @@ export const UserMini = memo(function UserMini({ pubkey }: { pubkey: string }) {

- {profile.display_name ? profile.display_name : truncate(pubkey, 16, ' .... ')} + {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')}

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ca7aa418..4e978b24 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -16,7 +16,7 @@ export default function Page() { if (currentUser) { timer.current = setTimeout(() => { setLoading(false); - router.push('/newsfeed/circle'); + router.push('/newsfeed/following'); }, 1000); } else { timer.current = setTimeout(() => { diff --git a/src/pages/newsfeed/following.tsx b/src/pages/newsfeed/following.tsx index 4fd210b7..6ef22ebd 100644 --- a/src/pages/newsfeed/following.tsx +++ b/src/pages/newsfeed/following.tsx @@ -2,16 +2,16 @@ import BaseLayout from '@layouts/base'; import WithSidebarLayout from '@layouts/withSidebar'; import { DatabaseContext } from '@components/contexts/database'; -import { NoteConnector } from '@components/note/connector'; import NoteForm from '@components/note/form'; import { Placeholder } from '@components/note/placeholder'; import { Repost } from '@components/note/repost'; import { Single } from '@components/note/single'; +import { atomHasNewerNote } from '@stores/note'; + import { dateToUnix } from '@utils/getDate'; -import { ArrowUpIcon } from '@radix-ui/react-icons'; -import { writeStorage } from '@rehooks/local-storage'; +import { useAtomValue } from 'jotai'; import { useCallback, useState } from 'react'; import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useEffect, useRef } from 'react'; import { Virtuoso } from 'react-virtuoso'; @@ -20,8 +20,8 @@ export default function Page() { const { db }: any = useContext(DatabaseContext); const [data, setData] = useState([]); - const [parentReload, setParentReload] = useState(false); - const [hasNewNote, setHasNewNote] = useState(false); + const [reload, setReload] = useState(false); + const hasNewerNote = useAtomValue(atomHasNewerNote); const now = useRef(new Date()); const limit = useRef(30); @@ -49,7 +49,6 @@ export default function Page() { LIMIT ${limit.current}` ); setData((data) => [...result, ...data]); - setHasNewNote(false); }, [db]); const ItemContent = useCallback( @@ -66,22 +65,6 @@ export default function Page() { [data] ); - useEffect(() => { - const getData = async () => { - const result = await db.select( - `SELECT * FROM cache_notes WHERE created_at <= ${dateToUnix(now.current)} ORDER BY created_at DESC LIMIT ${ - limit.current - }` - ); - if (result) { - setData(result); - writeStorage('settings', new Date()); - } - }; - - getData().catch(console.error); - }, [db, parentReload]); - const computeItemKey = useCallback( (index: string | number) => { return data[index].id; @@ -89,16 +72,40 @@ export default function Page() { [data] ); + useEffect(() => { + const getData = async () => { + const result = await db.select( + `SELECT * FROM cache_notes WHERE created_at <= ${dateToUnix(now.current)} ORDER BY created_at DESC LIMIT ${ + limit.current + }` + ); + if (result.length > 0) { + setData(result); + } else { + setReload(true); + } + }; + + if (reload === false) { + getData().catch(console.error); + } else { + const timer = setTimeout(() => { + getData().catch(console.error); + }, 8000); + + return () => clearTimeout(timer); + } + }, [db, reload]); + return (
- {hasNewNote && ( + {hasNewerNote && (
)} @@ -118,7 +125,7 @@ export default function Page() { endReached={loadMore} overscan={800} increaseViewportBy={1000} - className="relative h-full w-full" + className="scrollbar-hide relative h-full w-full" style={{ contain: 'strict', }} diff --git a/src/stores/note.tsx b/src/stores/note.tsx new file mode 100644 index 00000000..e7987861 --- /dev/null +++ b/src/stores/note.tsx @@ -0,0 +1,3 @@ +import { atom } from 'jotai'; + +export const atomHasNewerNote = atom(false); diff --git a/tsconfig.json b/tsconfig.json index db64c2f8..c6ae645f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "@pages/*": ["src/pages/*"], "@layouts/*": ["src/layouts/*"], "@components/*": ["src/components/*"], + "@stores/*": ["src/stores/*"], "@utils/*": ["src/utils/*"], "@assets/*": ["src/assets/*"] },