add network to account

This commit is contained in:
Ren Amamiya
2023-08-04 15:38:38 +07:00
parent 4c7826bbb3
commit 373a0f0608
13 changed files with 103 additions and 89 deletions

View File

@@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE accounts ADD network JSON;

View File

@@ -89,6 +89,12 @@ fn main() {
sql: include_str!("../migrations/20230725010250_update_default_relays.sql"), sql: include_str!("../migrations/20230725010250_update_default_relays.sql"),
kind: MigrationKind::Up, kind: MigrationKind::Up,
}, },
Migration {
version: 20230804083544,
description: "add network to accounts",
sql: include_str!("../migrations/20230804083544_add_network_to_account.sql"),
kind: MigrationKind::Up,
},
], ],
) )
.build(), .build(),

View File

@@ -18,7 +18,7 @@ import { WelcomeScreen } from '@app/auth/welcome';
import { ChannelScreen } from '@app/channel'; import { ChannelScreen } from '@app/channel';
import { ChatScreen } from '@app/chats'; import { ChatScreen } from '@app/chats';
import { ErrorScreen } from '@app/error'; import { ErrorScreen } from '@app/error';
import { NoteScreen } from '@app/note'; import { EventScreen } from '@app/events';
import { Root } from '@app/root'; import { Root } from '@app/root';
import { AccountSettingsScreen } from '@app/settings/account'; import { AccountSettingsScreen } from '@app/settings/account';
import { GeneralSettingsScreen } from '@app/settings/general'; import { GeneralSettingsScreen } from '@app/settings/general';
@@ -85,7 +85,7 @@ const router = createBrowserRouter([
children: [ children: [
{ path: 'space', element: <SpaceScreen /> }, { path: 'space', element: <SpaceScreen /> },
{ path: 'trending', element: <TrendingScreen /> }, { path: 'trending', element: <TrendingScreen /> },
{ path: 'note/:id', element: <NoteScreen /> }, { path: 'events/:id', element: <EventScreen /> },
{ path: 'users/:pubkey', element: <UserScreen /> }, { path: 'users/:pubkey', element: <UserScreen /> },
{ path: 'chats/:pubkey', element: <ChatScreen /> }, { path: 'chats/:pubkey', element: <ChatScreen /> },
{ path: 'channel/:id', element: <ChannelScreen /> }, { path: 'channel/:id', element: <ChannelScreen /> },

51
src/app/events/index.tsx Normal file
View File

@@ -0,0 +1,51 @@
import { useParams } from 'react-router-dom';
import {
NoteActions,
NoteContent,
NoteReplyForm,
NoteStats,
ThreadUser,
} from '@shared/notes';
import { RepliesList } from '@shared/notes/replies/list';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { useAccount } from '@utils/hooks/useAccount';
import { useEvent } from '@utils/hooks/useEvent';
export function EventScreen() {
const { id } = useParams();
const { account } = useAccount();
const { status, data } = useEvent(id);
return (
<div className="mx-auto w-[600px]">
<div className="scrollbar-hide flex h-full w-full flex-col gap-1.5 overflow-y-auto pt-11">
{status === 'loading' ? (
<div className="px-3 py-1.5">
<div className="rounded-xl bg-white/10 px-3 py-3">
<NoteSkeleton />
</div>
</div>
) : (
<div className="h-min w-full px-3 pt-1.5">
<div className="rounded-xl bg-white/10 px-3 pt-3">
<ThreadUser pubkey={data.pubkey} time={data.created_at} />
<div className="mt-2">
<NoteContent content={data.content} />
</div>
<div>
<NoteActions id={data.id} pubkey={data.pubkey} noOpenThread={true} />
<NoteStats id={data.id} />
</div>
</div>
</div>
)}
<div className="px-3">
<NoteReplyForm id={id} pubkey={account.pubkey} />
<RepliesList id={id} />
</div>
</div>
</div>
);
}

View File

@@ -1,49 +0,0 @@
import { useParams } from 'react-router-dom';
import { useLiveThread } from '@app/space/hooks/useLiveThread';
import { NoteMetadata } from '@shared/notes/metadata';
import { NoteReplyForm } from '@shared/notes/replies/form';
import { RepliesList } from '@shared/notes/replies/list';
import { NoteSkeleton } from '@shared/notes/skeleton';
import { User } from '@shared/user';
import { useAccount } from '@utils/hooks/useAccount';
import { useEvent } from '@utils/hooks/useEvent';
export function NoteScreen() {
const { id } = useParams();
const { account } = useAccount();
const { status, data } = useEvent(id);
useLiveThread(id);
return (
<div className="mx-auto w-[600px]">
<div className="scrollbar-hide flex h-full w-full flex-col gap-1.5 overflow-y-auto pb-20 pt-16">
{status === 'loading' ? (
<div className="px-3 py-1.5">
<div className="shadow-input rounded-md bg-white/10 px-3 py-3">
<NoteSkeleton />
</div>
</div>
) : (
<div className="h-min w-full px-3 py-1.5">
<div className="rounded-md bg-zinc-900 px-5 pt-5">
<User pubkey={data.pubkey} time={data.created_at} />
<div className="mt-3">
<NoteMetadata id={data.event_id || id} />
</div>
</div>
<div className="mt-3 rounded-md bg-zinc-900">
{account && <NoteReplyForm rootID={id} userPubkey={account.pubkey} />}
</div>
</div>
)}
<div className="px-3">
<RepliesList id={id} />
</div>
</div>
</div>
);
}

View File

@@ -27,27 +27,39 @@ export function Root() {
const { ndk, relayUrls, fetcher } = useNDK(); const { ndk, relayUrls, fetcher } = useNDK();
const { status, account } = useAccount(); const { status, account } = useAccount();
async function getFollows() { async function fetchNetwork() {
const authors: string[] = []; const network = new Set<string>();
// fetch user's follows
const user = ndk.getUser({ hexpubkey: account.pubkey }); const user = ndk.getUser({ hexpubkey: account.pubkey });
const follows = await user.follows(); const follows = await user.follows();
follows.forEach((follow: NDKUser) => { follows.forEach((follow: NDKUser) => {
authors.push(nip19.decode(follow.npub).data as string); network.add(nip19.decode(follow.npub).data as string);
}); });
// update follows in db // update user's follows in db
await updateAccount('follows', authors, account.pubkey); await updateAccount('follows', [...network]);
return authors; // fetch network
for (const item of network) {
const user = ndk.getUser({ hexpubkey: item });
const follows = await user.follows();
follows.forEach((follow: NDKUser) => {
network.add(nip19.decode(follow.npub).data as string);
});
}
// update user's network in db
await updateAccount('network', [...network]);
return [...network];
} }
async function fetchNotes() { async function fetchNotes() {
try { try {
const follows = await getFollows(); const network = await fetchNetwork();
if (follows.length > 0) { if (network.length > 0) {
let since: number; let since: number;
if (totalNotes === 0 || lastLogin === 0) { if (totalNotes === 0 || lastLogin === 0) {
since = nHoursAgo(48); since = nHoursAgo(48);
@@ -57,7 +69,7 @@ export function Root() {
const events = fetcher.allEventsIterator( const events = fetcher.allEventsIterator(
relayUrls, relayUrls,
{ kinds: [1], authors: follows }, { kinds: [1], authors: network },
{ since: since }, { since: since },
{ skipVerification: true } { skipVerification: true }
); );

View File

@@ -16,7 +16,7 @@ import { LumeEvent } from '@utils/types';
const ITEM_PER_PAGE = 10; const ITEM_PER_PAGE = 10;
export function FollowingBlock() { export function NetworkBlock() {
// subscribe for live update // subscribe for live update
useNewsfeed(); useNewsfeed();
@@ -130,7 +130,7 @@ export function FollowingBlock() {
ref={parentRef} ref={parentRef}
className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20" className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20"
> >
<TitleBar title="Your Circle" /> <TitleBar title="Network" />
<div className="h-full"> <div className="h-full">
{status === 'loading' ? ( {status === 'loading' ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">

View File

@@ -2,9 +2,9 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { FeedBlock } from '@app/space/components/blocks/feed'; import { FeedBlock } from '@app/space/components/blocks/feed';
import { FollowingBlock } from '@app/space/components/blocks/following';
import { HashtagBlock } from '@app/space/components/blocks/hashtag'; import { HashtagBlock } from '@app/space/components/blocks/hashtag';
import { ImageBlock } from '@app/space/components/blocks/image'; import { ImageBlock } from '@app/space/components/blocks/image';
import { NetworkBlock } from '@app/space/components/blocks/network';
import { ThreadBlock } from '@app/space/components/blocks/thread'; import { ThreadBlock } from '@app/space/components/blocks/thread';
import { UserBlock } from '@app/space/components/blocks/user'; import { UserBlock } from '@app/space/components/blocks/user';
import { FeedModal } from '@app/space/components/modals/feed'; import { FeedModal } from '@app/space/components/modals/feed';
@@ -53,7 +53,7 @@ export function SpaceScreen() {
return ( return (
<div className="scrollbar-hide flex h-full w-full flex-nowrap divide-x divide-white/5 overflow-x-auto overflow-y-hidden"> <div className="scrollbar-hide flex h-full w-full flex-nowrap divide-x divide-white/5 overflow-x-auto overflow-y-hidden">
<FollowingBlock /> <NetworkBlock />
{status === 'loading' ? ( {status === 'loading' ? (
<div className="flex w-[350px] shrink-0 flex-col"> <div className="flex w-[350px] shrink-0 flex-col">
<div className="flex w-full flex-1 items-center justify-center p-3"> <div className="flex w-full flex-1 items-center justify-center p-3">

View File

@@ -14,8 +14,6 @@ export default class TauriAdapter implements NDKCacheAdapter {
public async query(subscription: NDKSubscription): Promise<void> { public async query(subscription: NDKSubscription): Promise<void> {
const { filter } = subscription; const { filter } = subscription;
// if this filter uses both authors and kinds, then we need to query for each combination of author and kind
// and then combine the results
if (filter.authors && filter.kinds) { if (filter.authors && filter.kinds) {
const promises = []; const promises = [];

View File

@@ -63,15 +63,12 @@ export async function createAccount(
} }
// update account // update account
export async function updateAccount( export async function updateAccount(column: string, value: string | string[]) {
column: string,
value: string | string[],
pubkey: string
) {
const db = await connect(); const db = await connect();
return await db.execute(`UPDATE accounts SET ${column} = ? WHERE pubkey = ?;`, [ const account = await getActiveAccount();
return await db.execute(`UPDATE accounts SET ${column} = ? WHERE id = ?;`, [
value, value,
pubkey, account.id,
]); ]);
} }

View File

@@ -18,14 +18,10 @@ export function ComposerModal() {
const { account } = useAccount(); const { account } = useAccount();
const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]); const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]);
const closeModal = () => {
toggle(false);
};
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true)); useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
return ( return (
<Dialog.Root open={open}> <Dialog.Root open={open} onOpenChange={toggle}>
<Dialog.Trigger asChild> <Dialog.Trigger asChild>
<button <button
type="button" type="button"
@@ -51,7 +47,7 @@ export function ComposerModal() {
</div> </div>
</div> </div>
<Dialog.Close <Dialog.Close
onClick={() => closeModal()} onClick={() => toggle(false)}
className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800" className="inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-zinc-800"
> >
<CancelIcon className="h-5 w-5 text-white/50" /> <CancelIcon className="h-5 w-5 text-white/50" />

View File

@@ -8,14 +8,15 @@ import { Link } from 'react-router-dom';
import { HorizontalDotsIcon } from '@shared/icons'; import { HorizontalDotsIcon } from '@shared/icons';
export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
const nevent = nip19.neventEncode(id as unknown as EventPointer);
const copyID = async () => { const copyID = async () => {
await writeText(nevent); await writeText(nip19.neventEncode({ id: id, author: pubkey } as EventPointer));
}; };
const copyLink = async () => { const copyLink = async () => {
await writeText('https://nostr.com/' + nevent); await writeText(
'https://nostr.com/' +
nip19.neventEncode({ id: id, author: pubkey } as EventPointer)
);
}; };
return ( return (
@@ -41,12 +42,12 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
<Popover.Portal> <Popover.Portal>
<Popover.Content className="w-[200px] overflow-hidden rounded-md bg-white/10 backdrop-blur-xl focus:outline-none"> <Popover.Content className="w-[200px] overflow-hidden rounded-md bg-white/10 backdrop-blur-xl focus:outline-none">
<div className="flex flex-col p-2"> <div className="flex flex-col p-2">
<button <Link
type="button" to={`/app/events/${id}`}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10" className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
> >
Open as new screen Open as new screen
</button> </Link>
<button <button
type="button" type="button"
onClick={() => copyLink()} onClick={() => copyLink()}
@@ -62,7 +63,6 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
Copy ID Copy ID
</button> </button>
<Link <Link
type="button"
to={`/app/users/${pubkey}`} to={`/app/users/${pubkey}`}
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10" className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
> >

View File

@@ -20,9 +20,10 @@ export interface Account extends NDKUserProfile {
id: number; id: number;
npub: string; npub: string;
pubkey: string; pubkey: string;
privkey: string;
follows: string[] | string; follows: string[] | string;
network: string[] | string;
is_active: number; is_active: number;
privkey?: string; // deprecated
} }
export interface Profile extends NDKUserProfile { export interface Profile extends NDKUserProfile {