add network to account
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
ALTER TABLE accounts ADD network JSON;
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
51
src/app/events/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
3
src/utils/types.d.ts
vendored
3
src/utils/types.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user