diff --git a/src-tauri/prisma/migrations/20230403043730_initial/migration.sql b/src-tauri/prisma/migrations/20230404081740_initial/migration.sql similarity index 94% rename from src-tauri/prisma/migrations/20230403043730_initial/migration.sql rename to src-tauri/prisma/migrations/20230404081740_initial/migration.sql index e86009dc..a682a658 100644 --- a/src-tauri/prisma/migrations/20230403043730_initial/migration.sql +++ b/src-tauri/prisma/migrations/20230404081740_initial/migration.sql @@ -27,7 +27,7 @@ CREATE TABLE "Note" ( "content" TEXT NOT NULL, "parent_id" TEXT NOT NULL, "parent_comment_id" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" INTEGER NOT NULL, "accountId" INTEGER NOT NULL, CONSTRAINT "Note_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); @@ -38,7 +38,7 @@ CREATE TABLE "Message" ( "pubkey" TEXT NOT NULL, "content" TEXT NOT NULL, "tags" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" INTEGER NOT NULL, "accountId" INTEGER NOT NULL, CONSTRAINT "Message_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); diff --git a/src-tauri/prisma/migrations/20230405012355_add_index_to_created_at/migration.sql b/src-tauri/prisma/migrations/20230405012355_add_index_to_created_at/migration.sql new file mode 100644 index 00000000..bea82ded --- /dev/null +++ b/src-tauri/prisma/migrations/20230405012355_add_index_to_created_at/migration.sql @@ -0,0 +1,11 @@ +-- DropIndex +DROP INDEX "Message_pubkey_idx"; + +-- DropIndex +DROP INDEX "Note_eventId_idx"; + +-- CreateIndex +CREATE INDEX "Message_pubkey_createdAt_idx" ON "Message"("pubkey", "createdAt"); + +-- CreateIndex +CREATE INDEX "Note_eventId_createdAt_idx" ON "Note"("eventId", "createdAt"); diff --git a/src-tauri/prisma/schema.prisma b/src-tauri/prisma/schema.prisma index 44f8fae3..7cc44f70 100644 --- a/src-tauri/prisma/schema.prisma +++ b/src-tauri/prisma/schema.prisma @@ -36,33 +36,33 @@ model Follow { } model Note { - id Int @id @default(autoincrement()) - eventId String @unique + id Int @id @default(autoincrement()) + eventId String @unique pubkey String kind Int tags String content String parent_id String parent_comment_id String - createdAt DateTime @default(now()) + createdAt Int Account Account @relation(fields: [accountId], references: [id]) accountId Int - @@index([eventId]) + @@index([eventId, createdAt]) } model Message { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) pubkey String content String tags String - createdAt DateTime @default(now()) + createdAt Int Account Account @relation(fields: [accountId], references: [id]) accountId Int - @@index([pubkey]) + @@index([pubkey, createdAt]) } model Relay { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c6bde5af..196aa2ff 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,7 +7,7 @@ #[macro_use] extern crate objc; -use prisma_client_rust::raw; +use prisma_client_rust::Direction; use tauri::{Manager, WindowEvent}; #[cfg(target_os = "macos")] use window_ext::WindowExt; @@ -54,12 +54,30 @@ struct CreateNoteData { content: String, parent_id: String, parent_comment_id: String, + created_at: i32, account_id: i32, } +#[derive(Deserialize, Type)] +struct GetNoteByIdData { + event_id: String, +} + +#[derive(Deserialize, Type)] +struct GetNoteData { + date: i32, + limit: i32, + offset: i32, +} + +#[derive(Deserialize, Type)] +struct GetLatestNoteData { + date: i32, +} + #[tauri::command] #[specta::specta] -async fn get_account(db: DbState<'_>) -> Result, ()> { +async fn get_accounts(db: DbState<'_>) -> Result, ()> { db.account() .find_many(vec![account::active::equals(false)]) .exec() @@ -120,6 +138,7 @@ async fn create_note(db: DbState<'_>, data: CreateNoteData) -> Result, data: CreateNoteData) -> Result) -> Result, ()> { - db._query_raw(raw!("SELECT * FROM Note")) +async fn get_notes(db: DbState<'_>, data: GetNoteData) -> Result, ()> { + db.note() + .find_many(vec![note::created_at::lte(data.date)]) + .order_by(note::created_at::order(Direction::Desc)) + .take(data.limit.into()) + .skip(data.offset.into()) .exec() .await .map_err(|_| ()) @@ -141,15 +164,30 @@ async fn get_notes(db: DbState<'_>) -> Result, ()> { #[tauri::command] #[specta::specta] -async fn check_note(db: DbState<'_>) -> Result, ()> { +async fn get_latest_notes(db: DbState<'_>, data: GetLatestNoteData) -> Result, ()> { db.note() - .find_many(vec![]) - .take(5) + .find_many(vec![note::created_at::gt(data.date)]) + .order_by(note::created_at::order(Direction::Desc)) .exec() .await .map_err(|_| ()) } +#[tauri::command] +#[specta::specta] +async fn get_note_by_id(db: DbState<'_>, data: GetNoteByIdData) -> Result, ()> { + db.note() + .find_unique(note::event_id::equals(data.event_id)) + .exec() + .await + .map_err(|_| ()) +} + +#[tauri::command] +async fn count_total_notes(db: DbState<'_>) -> Result { + db.note().count(vec![]).exec().await.map_err(|_| ()) +} + #[tokio::main] async fn main() { let db = PrismaClient::_builder().build().await.unwrap(); @@ -157,13 +195,14 @@ async fn main() { #[cfg(debug_assertions)] ts::export( collect_types![ - get_account, + get_accounts, create_account, get_follows, create_follow, create_note, get_notes, - check_note + get_latest_notes, + get_note_by_id ], "../src/utils/bindings.ts", ) @@ -196,13 +235,15 @@ async fn main() { } }) .invoke_handler(tauri::generate_handler![ - get_account, + get_accounts, create_account, get_follows, create_follow, create_note, get_notes, - check_note + get_latest_notes, + get_note_by_id, + count_total_notes ]) .manage(Arc::new(db)) .run(tauri::generate_context!()) diff --git a/src/components/multiAccounts/activeAccount.tsx b/src/components/multiAccounts/activeAccount.tsx index fb244fde..9aa088df 100644 --- a/src/components/multiAccounts/activeAccount.tsx +++ b/src/components/multiAccounts/activeAccount.tsx @@ -2,8 +2,7 @@ import { RelayContext } from '@components/relaysProvider'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { createFollows } from '@utils/storage'; -import { tagsToArray } from '@utils/transform'; +import { fetchMetadata } from '@utils/metadata'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { AvatarIcon, ExitIcon, GearIcon } from '@radix-ui/react-icons'; @@ -11,7 +10,7 @@ import { writeText } from '@tauri-apps/api/clipboard'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { nip19 } from 'nostr-tools'; -import { memo, useContext, useEffect } from 'react'; +import { memo, useCallback, useContext, useEffect } from 'react'; export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any }) { const [pool, relays]: any = useContext(RelayContext); @@ -27,6 +26,21 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any } await writeText(nip19.npubEncode(user.id)); }; + const insertFollowsToStorage = useCallback( + async (tags) => { + const { createFollow } = await import('@utils/bindings'); + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + + for (const tag of tags) { + const metadata: any = await fetchMetadata(tag[1], pool, relays); + createFollow({ pubkey: tag[1], kind: 0, metadata: metadata.content, account_id: activeAccount.id }).catch( + console.error + ); + } + }, + [pool, relays] + ); + useEffect(() => { const unsubscribe = pool.subscribe( [ @@ -38,7 +52,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any } relays, (event: any) => { if (event.tags.length > 0) { - createFollows(tagsToArray(event.tags), user.id, 0); + insertFollowsToStorage(event.tags); } }, undefined, @@ -51,7 +65,7 @@ export const ActiveAccount = memo(function ActiveAccount({ user }: { user: any } return () => { unsubscribe; }; - }, [pool, relays, user.id]); + }, [insertFollowsToStorage, pool, relays, user.id]); return ( diff --git a/src/components/multiAccounts/index.tsx b/src/components/multiAccounts/index.tsx index 6e7fdc71..d23161ab 100644 --- a/src/components/multiAccounts/index.tsx +++ b/src/components/multiAccounts/index.tsx @@ -1,41 +1,37 @@ import { ActiveAccount } from '@components/multiAccounts/activeAccount'; import { InactiveAccount } from '@components/multiAccounts/inactiveAccount'; -import { activeAccountAtom } from '@stores/account'; import { APP_VERSION } from '@stores/constants'; -import { getAccounts } from '@utils/storage'; - import LumeSymbol from '@assets/icons/Lume'; import { PlusIcon } from '@radix-ui/react-icons'; -import { useAtomValue } from 'jotai'; import Link from 'next/link'; import { useCallback, useEffect, useState } from 'react'; export default function MultiAccounts() { - const activeAccount: any = useAtomValue(activeAccountAtom); const [users, setUsers] = useState([]); - const renderAccount = useCallback( - (user: { id: string }) => { - if (user.id === activeAccount.id) { - return ; - } else { - return ; - } - }, - [activeAccount.id] - ); + const renderAccount = useCallback((user: { id: string }) => { + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + + if (user.id === activeAccount.id) { + return ; + } else { + return ; + } + }, []); + + const fetchAccounts = useCallback(async () => { + const { getAccounts } = await import('@utils/bindings'); + const accounts = await getAccounts(); + // update state + setUsers(accounts); + }, []); useEffect(() => { - const fetchAccount = async () => { - const result: any = await getAccounts(); - setUsers(result); - }; - - fetchAccount().catch(console.error); - }, []); + fetchAccounts().catch(console.error); + }, [fetchAccounts]); return (
diff --git a/src/components/note/base.tsx b/src/components/note/base.tsx index f0c9e2c7..4ad3dae5 100644 --- a/src/components/note/base.tsx +++ b/src/components/note/base.tsx @@ -63,13 +63,13 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) { const getParent = useMemo(() => { if (event.parent_id) { - if (event.parent_id !== event.id && !event.content.includes('#[0]')) { + if (event.parent_id !== event.eventId && !event.content.includes('#[0]')) { return ; } } return; - }, [event.content, event.id, event.parent_id]); + }, [event.content, event.eventId, event.parent_id]); const openThread = (e) => { const selection = window.getSelection(); @@ -87,7 +87,7 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) { > <>{getParent}
- +
@@ -97,10 +97,10 @@ export const NoteBase = memo(function NoteBase({ event }: { event: any }) {
e.stopPropagation()} className="mt-5 pl-[52px]">
diff --git a/src/components/note/comment.tsx b/src/components/note/comment.tsx index 9868cb03..da739b18 100644 --- a/src/components/note/comment.tsx +++ b/src/components/note/comment.tsx @@ -60,7 +60,7 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any }) return (
- +
@@ -70,10 +70,10 @@ export const NoteComment = memo(function NoteComment({ event }: { event: any })
e.stopPropagation()} className="mt-5 pl-[52px]">
diff --git a/src/components/note/connector.tsx b/src/components/note/connector.tsx index 49852348..38500849 100644 --- a/src/components/note/connector.tsx +++ b/src/components/note/connector.tsx @@ -1,15 +1,14 @@ import { RelayContext } from '@components/relaysProvider'; -import { activeAccountAtom, lastLoginAtom } from '@stores/account'; +import { lastLoginAtom } from '@stores/account'; import { hasNewerNoteAtom } from '@stores/note'; import { dateToUnix } from '@utils/getDate'; -import { createCacheNote, getAllFollowsByID } from '@utils/storage'; -import { pubkeyArray } from '@utils/transform'; +import { getParentID, pubkeyArray } from '@utils/transform'; import { TauriEvent } from '@tauri-apps/api/event'; import { appWindow, getCurrent } from '@tauri-apps/api/window'; -import { useAtomValue, useSetAtom } from 'jotai'; +import { useSetAtom } from 'jotai'; import { useCallback, useContext, useEffect, useRef, useState } from 'react'; export default function NoteConnector() { @@ -17,30 +16,45 @@ export default function NoteConnector() { const setLastLoginAtom = useSetAtom(lastLoginAtom); const setHasNewerNote = useSetAtom(hasNewerNoteAtom); - const activeAccount: any = useAtomValue(activeAccountAtom); const [isOnline] = useState(true); - const now = useRef(new Date()); - const subscribe = useCallback(() => { - getAllFollowsByID(activeAccount.id).then((follows) => { - pool.subscribe( - [ - { - kinds: [1], - authors: pubkeyArray(follows), - since: dateToUnix(now.current), - }, - ], - relays, - (event: any) => { - // insert event to local database - createCacheNote(event); - setHasNewerNote(true); - } - ); - }); - }, [activeAccount.id, pool, relays, setHasNewerNote]); + const now = useRef(new Date()); + const unsubscribe = useRef(null); + + const subscribe = useCallback(async () => { + const { createNote } = await import('@utils/bindings'); + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + const follows = JSON.parse(localStorage.getItem('activeAccountFollows')); + + unsubscribe.current = pool.subscribe( + [ + { + kinds: [1], + authors: pubkeyArray(follows), + since: dateToUnix(now.current), + }, + ], + relays, + (event) => { + 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, + }).catch(console.error); + // notify user reload to get newer note + setHasNewerNote(true); + } + ); + }, [pool, relays, setHasNewerNote]); useEffect(() => { subscribe(); @@ -48,23 +62,25 @@ export default function NoteConnector() { setLastLoginAtom(now.current); appWindow.close(); }); - }, [activeAccount.id, pool, relays, setHasNewerNote, subscribe]); + + return () => { + unsubscribe.current; + }; + }, [setHasNewerNote, setLastLoginAtom, subscribe]); return ( - <> -
- - - - -

{isOnline ? 'Online' : 'Offline'}

-
- +
+ + + + +

{isOnline ? 'Online' : 'Offline'}

+
); } diff --git a/src/components/note/extend.tsx b/src/components/note/extend.tsx index 1c9679cc..255343b4 100644 --- a/src/components/note/extend.tsx +++ b/src/components/note/extend.tsx @@ -60,7 +60,7 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) { return (
- +
@@ -70,10 +70,10 @@ export const NoteExtend = memo(function NoteExtend({ event }: { event: any }) {
diff --git a/src/components/note/parent.tsx b/src/components/note/parent.tsx index e48e9694..8614e3f9 100644 --- a/src/components/note/parent.tsx +++ b/src/components/note/parent.tsx @@ -6,7 +6,7 @@ import { RelayContext } from '@components/relaysProvider'; import { UserExtend } from '@components/user/extend'; import { UserMention } from '@components/user/mention'; -import { createCacheNote, getNoteByID } from '@utils/storage'; +import { getParentID } from '@utils/transform'; import destr from 'destr'; import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; @@ -18,7 +18,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) { const [event, setEvent] = useState(null); const unsubscribe = useRef(null); - const fetchEvent = useCallback(() => { + const fetchEvent = useCallback(async () => { + const { createNote } = await import('@utils/bindings'); + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + unsubscribe.current = pool.subscribe( [ { @@ -31,7 +34,19 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) { // update state setEvent(event); // insert to database - createCacheNote(event); + 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, + }).catch(console.error); }, undefined, undefined, @@ -41,19 +56,26 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) { ); }, [id, pool, relays]); + const checkNoteExist = useCallback(async () => { + const { getNoteById } = await import('@utils/bindings'); + getNoteById({ event_id: id }) + .then((res) => { + if (res) { + setEvent(res); + } else { + fetchEvent(); + } + }) + .catch(console.error); + }, [fetchEvent, id]); + useEffect(() => { - getNoteByID(id).then((res) => { - if (res) { - setEvent(res); - } else { - fetchEvent(); - } - }); + checkNoteExist(); return () => { unsubscribe.current; }; - }, [fetchEvent, id]); + }, [checkNoteExist]); const content = useMemo(() => { let parsedContent = event ? event.content : null; @@ -110,7 +132,7 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
- +
@@ -120,10 +142,10 @@ export const NoteParent = memo(function NoteParent({ id }: { id: string }) {
e.stopPropagation()} className="mt-5 pl-[52px]">
diff --git a/src/components/note/repost.tsx b/src/components/note/repost.tsx index 99b00334..1a8590ce 100644 --- a/src/components/note/repost.tsx +++ b/src/components/note/repost.tsx @@ -2,7 +2,7 @@ import { RelayContext } from '@components/relaysProvider'; import { UserExtend } from '@components/user/extend'; import { UserMention } from '@components/user/mention'; -import { createCacheNote, getNoteByID } from '@utils/storage'; +import { getParentID } from '@utils/transform'; import destr from 'destr'; import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; @@ -14,7 +14,10 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) { const [event, setEvent] = useState(null); const unsubscribe = useRef(null); - const fetchEvent = useCallback(() => { + const fetchEvent = useCallback(async () => { + const { createNote } = await import('@utils/bindings'); + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + unsubscribe.current = pool.subscribe( [ { @@ -27,7 +30,19 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) { // update state setEvent(event); // insert to database - createCacheNote(event); + 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, + }).catch(console.error); }, undefined, undefined, @@ -37,19 +52,26 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) { ); }, [id, pool, relays]); + const checkNoteExist = useCallback(async () => { + const { getNoteById } = await import('@utils/bindings'); + getNoteById({ event_id: id }) + .then((res) => { + if (res) { + setEvent(res); + } else { + fetchEvent(); + } + }) + .catch(console.error); + }, [fetchEvent, id]); + useEffect(() => { - getNoteByID(id).then((res) => { - if (res) { - setEvent(res); - } else { - fetchEvent(); - } - }); + checkNoteExist(); return () => { unsubscribe.current; }; - }, [fetchEvent, id]); + }, [checkNoteExist]); const content = useMemo(() => { let parsedContent = event ? event.content : null; @@ -89,7 +111,7 @@ export const NoteRepost = memo(function NoteRepost({ id }: { id: string }) { return (
- +
diff --git a/src/components/user/base.tsx b/src/components/user/base.tsx index 9f87cfbd..e6680769 100644 --- a/src/components/user/base.tsx +++ b/src/components/user/base.tsx @@ -6,17 +6,17 @@ import { DEFAULT_AVATAR } from '@stores/constants'; import { truncate } from '@utils/truncate'; import { Author } from 'nostr-relaypool'; -import { memo, useContext, useEffect, useMemo, useState } from 'react'; +import { memo, useContext, useEffect, useState } from 'react'; export const UserBase = memo(function UserBase({ pubkey }: { pubkey: string }) { const [pool, relays]: any = useContext(RelayContext); const [profile, setProfile] = useState(null); - const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); useEffect(() => { + const user = new Author(pool, relays, pubkey); user.metaData((res) => setProfile(JSON.parse(res.content)), 0); - }, [user]); + }, [pool, relays, pubkey]); return (
diff --git a/src/components/user/extend.tsx b/src/components/user/extend.tsx index 7039cea1..d5026177 100644 --- a/src/components/user/extend.tsx +++ b/src/components/user/extend.tsx @@ -10,16 +10,15 @@ import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { useRouter } from 'next/router'; import { Author } from 'nostr-relaypool'; -import { memo, useContext, useEffect, useMemo, useState } from 'react'; +import { memo, useContext, useEffect, useState } from 'react'; dayjs.extend(relativeTime); export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: string; time: any }) { - const [pool, relays]: any = useContext(RelayContext); const router = useRouter(); + const [pool, relays]: any = useContext(RelayContext); const [profile, setProfile] = useState(null); - const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); const openUserPage = (e) => { e.stopPropagation(); @@ -27,8 +26,9 @@ export const UserExtend = memo(function UserExtend({ pubkey, time }: { pubkey: s }; useEffect(() => { + const user = new Author(pool, relays, pubkey); user.metaData((res) => setProfile(JSON.parse(res.content)), 0); - }, [user]); + }, [pool, relays, pubkey]); return (
diff --git a/src/components/user/follow.tsx b/src/components/user/follow.tsx index 58b34778..d3f8505b 100644 --- a/src/components/user/follow.tsx +++ b/src/components/user/follow.tsx @@ -1,33 +1,22 @@ import { ImageWithFallback } from '@components/imageWithFallback'; +import { RelayContext } from '@components/relaysProvider'; import { DEFAULT_AVATAR } from '@stores/constants'; -import { createCacheProfile } from '@utils/storage'; import { truncate } from '@utils/truncate'; -import { fetch } from '@tauri-apps/api/http'; -import destr from 'destr'; -import { memo, useCallback, useEffect, useState } from 'react'; +import { Author } from 'nostr-relaypool'; +import { memo, useContext, useEffect, useMemo, useState } from 'react'; export const UserFollow = memo(function UserFollow({ pubkey }: { pubkey: string }) { - const [profile, setProfile] = useState(null); + const [pool, relays]: any = useContext(RelayContext); - const fetchProfile = useCallback(async (id: string) => { - const res = await fetch(`https://rbr.bio/${id}/metadata.json`, { - method: 'GET', - timeout: 30, - }); - return res.data; - }, []); + const [profile, setProfile] = useState(null); + const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); useEffect(() => { - fetchProfile(pubkey) - .then((res: any) => { - setProfile(destr(res.content)); - createCacheProfile(res.pubkey, res.content); - }) - .catch(console.error); - }, [fetchProfile, pubkey]); + user.metaData((res) => setProfile(JSON.parse(res.content)), 0); + }, [user]); return (
diff --git a/src/components/user/large.tsx b/src/components/user/large.tsx index 0a660dd2..45e75ec8 100644 --- a/src/components/user/large.tsx +++ b/src/components/user/large.tsx @@ -9,19 +9,18 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { Author } from 'nostr-relaypool'; -import { memo, useContext, useEffect, useMemo, useState } from 'react'; +import { memo, useContext, useEffect, useState } from 'react'; dayjs.extend(relativeTime); export const UserLarge = memo(function UserLarge({ pubkey, time }: { pubkey: string; time: any }) { const [pool, relays]: any = useContext(RelayContext); - const [profile, setProfile] = useState(null); - const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); useEffect(() => { + const user = new Author(pool, relays, pubkey); user.metaData((res) => setProfile(JSON.parse(res.content)), 0); - }, [user]); + }, [pool, relays, pubkey]); return (
diff --git a/src/components/user/mention.tsx b/src/components/user/mention.tsx index 4cae27fd..e5d8da89 100644 --- a/src/components/user/mention.tsx +++ b/src/components/user/mention.tsx @@ -3,17 +3,16 @@ import { RelayContext } from '@components/relaysProvider'; import { truncate } from '@utils/truncate'; import { Author } from 'nostr-relaypool'; -import { memo, useContext, useEffect, useMemo, useState } from 'react'; +import { memo, useContext, useEffect, useState } from 'react'; export const UserMention = memo(function UserMention({ pubkey }: { pubkey: string }) { const [pool, relays]: any = useContext(RelayContext); - const [profile, setProfile] = useState(null); - const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); useEffect(() => { + const user = new Author(pool, relays, pubkey); user.metaData((res) => setProfile(JSON.parse(res.content)), 0); - }, [user]); + }, [pool, relays, pubkey]); return @{profile?.name || truncate(pubkey, 16, ' .... ')}; }); diff --git a/src/components/user/mini.tsx b/src/components/user/mini.tsx index 22b74547..2392ff62 100644 --- a/src/components/user/mini.tsx +++ b/src/components/user/mini.tsx @@ -6,17 +6,16 @@ import { DEFAULT_AVATAR } from '@stores/constants'; import { truncate } from '@utils/truncate'; import { Author } from 'nostr-relaypool'; -import { useContext, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; export const UserMini = ({ pubkey }: { pubkey: string }) => { const [pool, relays]: any = useContext(RelayContext); - const [profile, setProfile] = useState(null); - const user = useMemo(() => new Author(pool, relays, pubkey), [pubkey, pool, relays]); useEffect(() => { + const user = new Author(pool, relays, pubkey); user.metaData((res) => setProfile(JSON.parse(res.content)), 0); - }, [user]); + }, [pool, relays, pubkey]); if (profile) { return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4b0af815..cb67651c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -14,8 +14,8 @@ export default function Page() { const setActiveAccountFollows = useSetAtom(activeAccountFollowsAtom); const fetchActiveAccount = useCallback(async () => { - const { getAccount } = await import('@utils/bindings'); - return await getAccount(); + const { getAccounts } = await import('@utils/bindings'); + return await getAccounts(); }, []); const fetchFollowsByAccount = useCallback(async (id) => { diff --git a/src/pages/init.tsx b/src/pages/init.tsx index 4ab32b69..a7b7397d 100644 --- a/src/pages/init.tsx +++ b/src/pages/init.tsx @@ -2,14 +2,12 @@ import BaseLayout from '@layouts/base'; import { RelayContext } from '@components/relaysProvider'; -import { activeAccountAtom, activeAccountFollowsAtom, lastLoginAtom } from '@stores/account'; - import { dateToUnix, hoursAgo } from '@utils/getDate'; import { getParentID, pubkeyArray } from '@utils/transform'; import LumeSymbol from '@assets/icons/Lume'; -import { useAtomValue } from 'jotai'; +import { invoke } from '@tauri-apps/api/tauri'; import { useRouter } from 'next/router'; import { JSXElementConstructor, @@ -27,23 +25,22 @@ export default function Page() { const router = useRouter(); const [pool, relays]: any = useContext(RelayContext); - const activeAccount: any = useAtomValue(activeAccountAtom); - const activeAccountFollows: any = useAtomValue(activeAccountFollowsAtom); - const lastLogin: any = useAtomValue(lastLoginAtom); - const now = useRef(new Date()); const unsubscribe = useRef(null); const [eose, setEose] = useState(false); const fetchData = useCallback( - async (since) => { + async (since: Date) => { const { createNote } = await import('@utils/bindings'); + const activeAccount = JSON.parse(localStorage.getItem('activeAccount')); + const follows = JSON.parse(localStorage.getItem('activeAccountFollows')); + unsubscribe.current = pool.subscribe( [ { kinds: [1], - authors: pubkeyArray(activeAccountFollows), + authors: pubkeyArray(follows), since: dateToUnix(since), until: dateToUnix(now.current), }, @@ -59,7 +56,8 @@ export default function Page() { tags: JSON.stringify(event.tags), content: event.content, parent_id: parentID, - parent_comment_id: 'aaa', + parent_comment_id: '', + created_at: event.created_at, account_id: activeAccount.id, }).catch(console.error); }, @@ -69,22 +67,20 @@ export default function Page() { } ); }, - [activeAccount.id, activeAccountFollows, pool, relays] + [pool, relays] ); const isNoteExist = useCallback(async () => { - const { checkNote } = await import('@utils/bindings'); - checkNote() - .then((res) => { - if (res.length === 5) { - const parseDate = new Date(lastLogin); - fetchData(parseDate); - } else { - fetchData(hoursAgo(24, now.current)); - } - }) - .catch(console.error); - }, [fetchData, lastLogin]); + invoke('count_total_notes').then((res: number) => { + if (res > 0) { + const lastLogin = JSON.parse(localStorage.getItem('lastLogin')); + const parseDate = new Date(lastLogin); + fetchData(parseDate); + } else { + fetchData(hoursAgo(24, now.current)); + } + }); + }, [fetchData]); useEffect(() => { if (eose === false) { diff --git a/src/pages/newsfeed/following.tsx b/src/pages/newsfeed/following.tsx index f7fd0446..6231147d 100644 --- a/src/pages/newsfeed/following.tsx +++ b/src/pages/newsfeed/following.tsx @@ -8,7 +8,7 @@ import { Placeholder } from '@components/note/placeholder'; import { hasNewerNoteAtom } from '@stores/note'; import { dateToUnix } from '@utils/getDate'; -import { getLatestNotes, getNotes } from '@utils/storage'; +import { filteredData } from '@utils/transform'; import { ArrowUpIcon } from '@radix-ui/react-icons'; import { useAtom } from 'jotai'; @@ -48,23 +48,36 @@ export default function Page() { ); const initialData = useCallback(async () => { - const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current); - setData((data) => [...data, ...result]); + const { getNotes } = await import('@utils/bindings'); + const result: any = await getNotes({ + date: dateToUnix(now.current), + limit: limit.current, + offset: offset.current, + }); + const filteredResult = filteredData(result); + setData((data) => [...data, ...filteredResult]); }, []); const loadMore = useCallback(async () => { + const { getNotes } = await import('@utils/bindings'); offset.current += limit.current; // next query - const result: any = await getNotes(dateToUnix(now.current), limit.current, offset.current); - setData((data) => [...data, ...result]); + const result: any = await getNotes({ + date: dateToUnix(now.current), + limit: limit.current, + offset: offset.current, + }); + const filteredResult = filteredData(result); + setData((data) => [...data, ...filteredResult]); }, []); const loadLatest = useCallback(async () => { - offset.current += limit.current; + const { getLatestNotes } = await import('@utils/bindings'); // next query - const result: any = await getLatestNotes(dateToUnix(now.current)); + const result: any = await getLatestNotes({ date: dateToUnix(now.current) }); // update data - setData((data) => [...result, ...data]); + const filteredResult = filteredData(result); + setData((data) => [...data, ...filteredResult]); // hide newer trigger setHasNewerNote(false); // scroll to top diff --git a/src/stores/account.tsx b/src/stores/account.tsx index 7c70b99e..9605714f 100644 --- a/src/stores/account.tsx +++ b/src/stores/account.tsx @@ -11,4 +11,4 @@ const createMyJsonStorage = () => { export const activeAccountAtom = atomWithStorage('activeAccount', {}, createMyJsonStorage()); export const activeAccountFollowsAtom = atomWithStorage('activeAccountFollows', [], createMyJsonStorage()); -export const lastLoginAtom = atomWithStorage('lastLoginAtom', [], createMyJsonStorage()); +export const lastLoginAtom = atomWithStorage('lastLogin', [], createMyJsonStorage()); diff --git a/src/stores/relays.tsx b/src/stores/relays.tsx deleted file mode 100644 index 8b9a06e7..00000000 --- a/src/stores/relays.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { isSSR } from '@utils/ssr'; -import { getAllRelays } from '@utils/storage'; - -import { atomWithCache } from 'jotai-cache'; - -export const relaysAtom = atomWithCache(async () => { - const response = isSSR ? [] : await getAllRelays(); - return response; -}); diff --git a/src/utils/bindings.ts b/src/utils/bindings.ts index b483e75c..f755e763 100644 --- a/src/utils/bindings.ts +++ b/src/utils/bindings.ts @@ -8,8 +8,8 @@ declare global { const invoke = window.__TAURI_INVOKE__; -export function getAccount() { - return invoke('get_account'); +export function getAccounts() { + return invoke('get_accounts'); } export function createAccount(data: CreateAccountData) { @@ -28,15 +28,20 @@ export function createNote(data: CreateNoteData) { return invoke('create_note', { data }); } -export function getNotes() { - return invoke('get_notes'); +export function getNotes(data: GetNoteData) { + return invoke('get_notes', { data }); } -export function checkNote() { - return invoke('check_note'); +export function getLatestNotes(data: GetLatestNoteData) { + return invoke('get_latest_notes', { data }); +} + +export function getNoteById(data: GetNoteByIdData) { + return invoke('get_note_by_id', { data }); } export type GetFollowData = { account_id: number }; +export type GetNoteByIdData = { event_id: string }; export type Note = { id: number; eventId: string; @@ -46,9 +51,10 @@ export type Note = { content: string; parent_id: string; parent_comment_id: string; - createdAt: string; + createdAt: number; accountId: number; }; +export type GetNoteData = { date: number; limit: number; offset: number }; export type CreateFollowData = { pubkey: string; kind: number; metadata: string; account_id: number }; export type Account = { id: number; pubkey: string; privkey: string; active: boolean; metadata: string }; export type CreateNoteData = { @@ -59,7 +65,9 @@ export type CreateNoteData = { content: string; parent_id: string; parent_comment_id: string; + created_at: number; account_id: number; }; +export type GetLatestNoteData = { date: number }; export type CreateAccountData = { pubkey: string; privkey: string; metadata: string }; export type Follow = { id: number; pubkey: string; kind: number; metadata: string; accountId: number }; diff --git a/src/utils/transform.tsx b/src/utils/transform.tsx index e9bb58e5..ca664c37 100644 --- a/src/utils/transform.tsx +++ b/src/utils/transform.tsx @@ -45,3 +45,16 @@ export const getParentID = (arr, fallback) => { return parentID; }; + +export const filteredData = (obj) => { + const filteredArr = obj.reduce((item, current) => { + const x = item.find((item) => item.parent_id === current.parent_id); + if (!x) { + return item.concat([current]); + } else { + return item; + } + }, []); + + return filteredArr; +};