mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 06:24:25 +01:00
update
This commit is contained in:
45
src/clients/useBatchedEvent.ts
Normal file
45
src/clients/useBatchedEvent.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||
import { type Filter } from 'nostr-tools/filter';
|
||||
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useBatch, { type Task } from '@/clients/useBatch';
|
||||
import useSubscription from '@/clients/useSubscription';
|
||||
|
||||
export type UseBatchedEventProps<TaskArgs> = {
|
||||
generateKey: (args: TaskArgs) => string | number;
|
||||
mergeFilters: (args: TaskArgs[]) => Filter[];
|
||||
extractKey: (event: NostrEvent) => string | number | undefined;
|
||||
};
|
||||
|
||||
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
|
||||
return useBatch<TaskArgs, NostrEvent>(() => {
|
||||
return {
|
||||
executor: (tasks) => {
|
||||
const { generateKey, mergeFilters, extractKey } = propsProvider();
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
|
||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, NostrEvent>>(
|
||||
tasks.map((task) => [generateKey(task.args), task]),
|
||||
);
|
||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters,
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
const key = extractKey(event);
|
||||
if (key == null) return;
|
||||
const task = keyTaskMap.get(key);
|
||||
// possibly, the new event received
|
||||
if (task == null) return;
|
||||
task.resolve(event);
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export default useBatchedEvent;
|
||||
@@ -17,6 +17,7 @@ const InitialConfig: Config = {
|
||||
'wss://relay.snort.social',
|
||||
'wss://relay.current.fyi',
|
||||
'wss://relay.nostr.wirednet.jp',
|
||||
'wss://relay.mostr.pub',
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ import { createMemo, type Accessor } from 'solid-js';
|
||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useBatch, { type Task } from '@/clients/useBatch';
|
||||
import useSubscription from '@/clients/useSubscription';
|
||||
import useBatchedEvent from '@/clients/useBatchedEvent';
|
||||
|
||||
export type UseEventProps = {
|
||||
// TODO リレーURLを考慮したい
|
||||
relayUrls: string[];
|
||||
eventId: string;
|
||||
};
|
||||
@@ -16,36 +15,14 @@ export type UseEvent = {
|
||||
query: CreateQueryResult<NostrEvent>;
|
||||
};
|
||||
|
||||
const { exec } = useBatch<UseEventProps, NostrEvent>(() => {
|
||||
return {
|
||||
executor: (tasks) => {
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
const eventIdTaskMap = new Map<string, Task<UseEventProps, NostrEvent>>(
|
||||
tasks.map((task) => [task.args.eventId, task]),
|
||||
);
|
||||
const eventIds = Array.from(eventIdTaskMap.keys());
|
||||
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters: [
|
||||
{
|
||||
ids: eventIds,
|
||||
kinds: [1],
|
||||
},
|
||||
],
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
if (event.id == null) return;
|
||||
const task = eventIdTaskMap.get(event.id);
|
||||
// possibly, the new event received
|
||||
if (task == null) return;
|
||||
task.resolve(event);
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
const { exec } = useBatchedEvent<UseEventProps>(() => ({
|
||||
generateKey: ({ eventId }: UseEventProps) => eventId,
|
||||
mergeFilters: (args: UseEventProps[]) => {
|
||||
const eventIds = args.map((arg) => arg.eventId);
|
||||
return [{ kinds: [1], ids: eventIds }];
|
||||
},
|
||||
extractKey: (event: NostrEvent) => event.id,
|
||||
}));
|
||||
|
||||
const useEvent = (propsProvider: () => UseEventProps): UseEvent => {
|
||||
const props = createMemo(propsProvider);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { createMemo, type Accessor } from 'solid-js';
|
||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||
import { type Filter } from 'nostr-tools/filter';
|
||||
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useBatch, { type Task } from '@/clients/useBatch';
|
||||
import useSubscription from '@/clients/useSubscription';
|
||||
import useBatchedEvent from '@/clients/useBatchedEvent';
|
||||
import { Task } from './useBatch';
|
||||
|
||||
// TODO zodにする
|
||||
// deleted等の特殊なもの
|
||||
type StandardProfile = {
|
||||
export type StandardProfile = {
|
||||
name?: string;
|
||||
about?: string;
|
||||
picture?: string;
|
||||
@@ -17,14 +17,14 @@ type StandardProfile = {
|
||||
lud16?: string; // NIP-57
|
||||
};
|
||||
|
||||
type NonStandardProfile = {
|
||||
export type NonStandardProfile = {
|
||||
display_name?: string;
|
||||
website?: string;
|
||||
};
|
||||
|
||||
type Profile = StandardProfile & NonStandardProfile;
|
||||
export type Profile = StandardProfile & NonStandardProfile;
|
||||
|
||||
type UseProfileProps = {
|
||||
export type UseProfileProps = {
|
||||
relayUrls: string[];
|
||||
pubkey: string;
|
||||
};
|
||||
@@ -34,40 +34,17 @@ type UseProfile = {
|
||||
query: CreateQueryResult<NostrEvent>;
|
||||
};
|
||||
|
||||
const { exec } = useBatch<UseProfileProps, NostrEvent>(() => {
|
||||
return {
|
||||
executor: (tasks) => {
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
const pubkeyTaskMap = new Map<string, Task<UseProfileProps, NostrEvent>>(
|
||||
tasks.map((task) => [task.args.pubkey, task]),
|
||||
);
|
||||
const pubkeys = Array.from(pubkeyTaskMap.keys());
|
||||
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters: [
|
||||
{
|
||||
kinds: [0],
|
||||
authors: pubkeys,
|
||||
},
|
||||
],
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
if (event.id == null) return;
|
||||
const task = pubkeyTaskMap.get(event.pubkey);
|
||||
// possibly, the new event received
|
||||
if (task == null) return;
|
||||
task.resolve(event);
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
const { exec } = useBatchedEvent<UseProfileProps>(() => ({
|
||||
generateKey: ({ pubkey }: UseProfileProps): string => pubkey,
|
||||
mergeFilters: (args: UseProfileProps[]): Filter[] => {
|
||||
const pubkeys = args.map((arg) => arg.pubkey);
|
||||
return [{ kinds: [0], authors: pubkeys }];
|
||||
},
|
||||
extractKey: (event: NostrEvent): string => event.pubkey,
|
||||
}));
|
||||
|
||||
const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
|
||||
const props = createMemo(propsProvider);
|
||||
|
||||
const query = createQuery(
|
||||
() => ['useProfile', props()] as const,
|
||||
({ queryKey, signal }) => {
|
||||
@@ -82,11 +59,9 @@ const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
|
||||
);
|
||||
|
||||
const profile = () => {
|
||||
const maybeProfile = query.data;
|
||||
if (maybeProfile == null) return undefined;
|
||||
|
||||
if (query.data == null) return undefined;
|
||||
// TODO 大きすぎたりしないかどうか、JSONかどうかのチェック
|
||||
return JSON.parse(maybeProfile.content) as Profile;
|
||||
return JSON.parse(query.data.content) as Profile;
|
||||
};
|
||||
|
||||
return { profile, query };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// NIP-18 (DEPRECATED)
|
||||
import { Show, type Component } from 'solid-js';
|
||||
import { Show, Switch, Match, type Component } from 'solid-js';
|
||||
import { Event as NostrEvent } from 'nostr-tools/event';
|
||||
import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg';
|
||||
|
||||
@@ -7,6 +7,7 @@ import useConfig from '@/clients/useConfig';
|
||||
import useEvent from '@/clients/useEvent';
|
||||
import useProfile from '@/clients/useProfile';
|
||||
|
||||
import UserNameDisplay from '@/components/UserNameDisplay';
|
||||
import TextNote from '@/components/TextNote';
|
||||
|
||||
export type DeprecatedRepostProps = {
|
||||
@@ -30,19 +31,22 @@ const DeprecatedRepost: Component<DeprecatedRepostProps> = (props) => {
|
||||
<div class="h-5 w-5 shrink-0 pr-1 text-green-500" aria-hidden="true">
|
||||
<ArrowPathRoundedSquare />
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<Show when={(profile()?.display_name?.length ?? 0) > 0} fallback={props.event.pubkey}>
|
||||
{profile()?.display_name}
|
||||
</Show>
|
||||
<div class="truncate break-all">
|
||||
<UserNameDisplay pubkey={props.event.pubkey} />
|
||||
{' Reposted'}
|
||||
</div>
|
||||
</div>
|
||||
<Show
|
||||
when={event() != null}
|
||||
fallback={<Show when={eventQuery.isLoading}>loading {eventId()}</Show>}
|
||||
>
|
||||
<TextNote event={event()} />
|
||||
</Show>
|
||||
<Switch fallback="failed to load">
|
||||
<Match when={event() != null}>
|
||||
<TextNote event={event()} />
|
||||
</Match>
|
||||
<Match when={eventQuery.isLoading}>
|
||||
<div class="truncate">
|
||||
{'loading '}
|
||||
<span>{eventId()}</span>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Kind, type Event as NostrEvent } from 'nostr-tools/event';
|
||||
|
||||
import TextNote from '@/components/TextNote';
|
||||
import Reaction from '@/components/notification/Reaction';
|
||||
import DeprecatedRepost from '@/components/DeprecatedRepost';
|
||||
|
||||
export type TimelineProps = {
|
||||
events: NostrEvent[];
|
||||
@@ -19,6 +20,10 @@ const Timeline: Component<TimelineProps> = (props) => {
|
||||
<Match when={event.kind === Kind.Reaction}>
|
||||
<Reaction event={event} />
|
||||
</Match>
|
||||
{/* TODO ちゃんとnotification用のコンポーネント使う */}
|
||||
<Match when={event.kind === 1}>
|
||||
<DeprecatedRepost event={event} />
|
||||
</Match>
|
||||
</Switch>
|
||||
)}
|
||||
</For>
|
||||
|
||||
25
src/components/UserNameDisplay.tsx
Normal file
25
src/components/UserNameDisplay.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Component, Switch, Match } from 'solid-js';
|
||||
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useProfile, { type Profile } from '@/clients/useProfile';
|
||||
|
||||
type UserNameDisplayProps = {
|
||||
pubkey: string;
|
||||
};
|
||||
|
||||
const UserNameDisplay: Component<UserNameDisplayProps> = (props) => {
|
||||
const [config] = useConfig();
|
||||
const { profile } = useProfile(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: props.pubkey,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Switch fallback={`@${props.pubkey}`}>
|
||||
<Match when={(profile()?.display_name?.length ?? 0) > 0}>{profile()?.display_name}</Match>
|
||||
<Match when={(profile()?.name?.length ?? 0) > 0}>@{profile()?.name}</Match>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserNameDisplay;
|
||||
@@ -2,10 +2,12 @@ import { Switch, Match, type Component, Show } from 'solid-js';
|
||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||
import HeartSolid from 'heroicons/24/solid/heart.svg';
|
||||
|
||||
import UserNameDisplay from '@/components/UserNameDisplay';
|
||||
import TextNote from '@/components/TextNote';
|
||||
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useProfile from '@/clients/useProfile';
|
||||
import useEvent from '@/clients/useEvent';
|
||||
import TextNote from '../TextNote';
|
||||
|
||||
type ReactionProps = {
|
||||
event: NostrEvent;
|
||||
@@ -52,9 +54,7 @@ const Reaction: Component<ReactionProps> = (props) => {
|
||||
</div>
|
||||
<div>
|
||||
<span class="truncate whitespace-pre-wrap break-all font-bold">
|
||||
<Show when={profile() != null} fallback={props.event.pubkey}>
|
||||
{profile()?.display_name}
|
||||
</Show>
|
||||
<UserNameDisplay pubkey={props.event.pubkey} />
|
||||
</span>
|
||||
{' reacted'}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createSignal, type Accessor } from 'solid-js';
|
||||
|
||||
const [currentDate, setCurrentDate] = createSignal(new Date());
|
||||
|
||||
// 7 seconds is used for the interval so that the last digit of relative time is changed.
|
||||
setInterval(() => {
|
||||
setCurrentDate(new Date());
|
||||
}, 7000);
|
||||
|
||||
@@ -10,49 +10,21 @@ import TextNote from '@/components/TextNote';
|
||||
import useCommands from '@/clients/useCommands';
|
||||
import useConfig from '@/clients/useConfig';
|
||||
import useSubscription from '@/clients/useSubscription';
|
||||
import useShortcutKeys from '@/hooks/useShortcutKeys';
|
||||
import useFollowings from '@/clients/useFollowings';
|
||||
|
||||
/*
|
||||
type UseRelayProps = { pubkey: string };
|
||||
|
||||
|
||||
const publish = async (pool, event) => {
|
||||
const pub = pool.publish(writeRelays, event);
|
||||
|
||||
return new Promise((resolve, reject) => {});
|
||||
};
|
||||
*/
|
||||
// const relays = ['ws://localhost:8008'];
|
||||
//
|
||||
const pubkey = 'npub1jcsr6e38dcepf65nkmrc54mu8jd8y70eael9rv308wxpwep6sxwqgsscyc';
|
||||
const pubkeyHex = '96203d66276e3214ea93b6c78a577c3c9a7279f9ee7e51b22f3b8c17643a819c';
|
||||
import usePubkey from '@/clients/usePubkey';
|
||||
import useShortcutKeys from '@/hooks/useShortcutKeys';
|
||||
|
||||
useShortcutKeys({
|
||||
onShortcut: (s) => console.log(s),
|
||||
});
|
||||
|
||||
const dummyTextNote = (
|
||||
<TextNote
|
||||
event={
|
||||
{
|
||||
id: 12345,
|
||||
kind: 1,
|
||||
pubkey: pubkeyHex,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: 'hello',
|
||||
} as NostrEvent
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const Home: Component = () => {
|
||||
const [config] = useConfig();
|
||||
const pubkey = usePubkey();
|
||||
const commands = useCommands();
|
||||
const { followings } = useFollowings(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: pubkeyHex,
|
||||
pubkey: pubkey(),
|
||||
}));
|
||||
|
||||
const { events: followingsPosts } = useSubscription(() => ({
|
||||
@@ -60,7 +32,7 @@ const Home: Component = () => {
|
||||
filters: [
|
||||
{
|
||||
kinds: [1, 6],
|
||||
authors: followings()?.map((f) => f.pubkey) ?? [pubkeyHex],
|
||||
authors: [...followings()?.map((f) => f.pubkey), pubkey()] ?? [pubkey()],
|
||||
limit: 25,
|
||||
since: Math.floor(Date.now() / 1000) - 12 * 60 * 60,
|
||||
},
|
||||
@@ -72,7 +44,7 @@ const Home: Component = () => {
|
||||
filters: [
|
||||
{
|
||||
kinds: [1, 6],
|
||||
authors: [pubkeyHex],
|
||||
authors: [pubkey()],
|
||||
limit: 25,
|
||||
},
|
||||
],
|
||||
@@ -83,7 +55,7 @@ const Home: Component = () => {
|
||||
filters: [
|
||||
{
|
||||
kinds: [1, 6, 7],
|
||||
'#p': [pubkeyHex],
|
||||
'#p': [pubkey()],
|
||||
limit: 25,
|
||||
since: Math.floor(Date.now() / 1000) - 12 * 60 * 60,
|
||||
},
|
||||
@@ -101,6 +73,7 @@ const Home: Component = () => {
|
||||
],
|
||||
}));
|
||||
|
||||
/*
|
||||
const { events: searchPosts } = useSubscription(() => ({
|
||||
relayUrls: ['wss://relay.nostr.band/'],
|
||||
filters: [
|
||||
@@ -112,12 +85,13 @@ const Home: Component = () => {
|
||||
},
|
||||
],
|
||||
}));
|
||||
*/
|
||||
|
||||
const handlePost = ({ content }: { content: string }) => {
|
||||
commands
|
||||
.publishTextNote({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: pubkeyHex,
|
||||
pubkey: pubkey(),
|
||||
content,
|
||||
})
|
||||
.then(() => {
|
||||
@@ -144,9 +118,6 @@ const Home: Component = () => {
|
||||
<Column name="自分の投稿" width="medium">
|
||||
<Timeline events={myPosts()} />
|
||||
</Column>
|
||||
<Column name="#nostrstudy" width="medium">
|
||||
<Timeline events={searchPosts()} />
|
||||
</Column>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user