mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 14:34:25 +01:00
update
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { createSignal, createEffect, onCleanup } from 'solid-js';
|
import { createSignal, createMemo } from 'solid-js';
|
||||||
|
|
||||||
export type Task<TaskArgs, TaskResult> = {
|
export type Task<TaskArgs, TaskResult> = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -10,7 +10,7 @@ export type Task<TaskArgs, TaskResult> = {
|
|||||||
export type UseBatchProps<TaskArgs, TaskResult> = {
|
export type UseBatchProps<TaskArgs, TaskResult> = {
|
||||||
executor: (task: Task<TaskArgs, TaskResult>[]) => void;
|
executor: (task: Task<TaskArgs, TaskResult>[]) => void;
|
||||||
interval?: number;
|
interval?: number;
|
||||||
// batchSize: number;
|
batchSize?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PromiseWithCallbacks<T> = {
|
export type PromiseWithCallbacks<T> = {
|
||||||
@@ -38,24 +38,26 @@ const promiseWithCallbacks = <T>(): PromiseWithCallbacks<T> => {
|
|||||||
const useBatch = <TaskArgs, TaskResult>(
|
const useBatch = <TaskArgs, TaskResult>(
|
||||||
propsProvider: () => UseBatchProps<TaskArgs, TaskResult>,
|
propsProvider: () => UseBatchProps<TaskArgs, TaskResult>,
|
||||||
) => {
|
) => {
|
||||||
|
const props = createMemo(propsProvider);
|
||||||
|
const batchSize = createMemo(() => props().batchSize ?? 100);
|
||||||
|
const interval = createMemo(() => props().interval ?? 1000);
|
||||||
|
|
||||||
const [seqId, setSeqId] = createSignal<number>(0);
|
const [seqId, setSeqId] = createSignal<number>(0);
|
||||||
const [taskQueue, setTaskQueue] = createSignal<Task<TaskArgs, TaskResult>[]>([]);
|
const [taskQueue, setTaskQueue] = createSignal<Task<TaskArgs, TaskResult>[]>([]);
|
||||||
|
|
||||||
createEffect(() => {
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||||
const { executor, interval = 1000 } = propsProvider();
|
|
||||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
|
|
||||||
if (timeoutId == null && taskQueue().length > 0) {
|
const executeTasks = () => {
|
||||||
timeoutId = setTimeout(() => {
|
const { executor } = props();
|
||||||
const currentTaskQueue = taskQueue();
|
const currentTaskQueue = taskQueue();
|
||||||
if (currentTaskQueue.length > 0) {
|
|
||||||
setTaskQueue([]);
|
if (currentTaskQueue.length > 0) {
|
||||||
executor(currentTaskQueue);
|
setTaskQueue([]);
|
||||||
}
|
executor(currentTaskQueue);
|
||||||
timeoutId = undefined;
|
|
||||||
}, interval);
|
|
||||||
}
|
}
|
||||||
});
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
|
timeoutId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const nextId = (): number => {
|
const nextId = (): number => {
|
||||||
const id = seqId();
|
const id = seqId();
|
||||||
@@ -63,18 +65,40 @@ const useBatch = <TaskArgs, TaskResult>(
|
|||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const launchTimer = () => {
|
||||||
|
if (timeoutId == null) {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
executeTasks();
|
||||||
|
}, interval());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addTask = (task: Task<TaskArgs, TaskResult>) => {
|
||||||
|
if (taskQueue().length < batchSize()) {
|
||||||
|
setTaskQueue((currentTaskQueue) => [...currentTaskQueue, task]);
|
||||||
|
} else {
|
||||||
|
executeTasks();
|
||||||
|
setTaskQueue([task]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTask = (id: number) => {
|
||||||
|
setTaskQueue((currentTaskQueue) => currentTaskQueue.filter((task) => task.id !== id));
|
||||||
|
};
|
||||||
|
|
||||||
// enqueue task and wait response
|
// enqueue task and wait response
|
||||||
const exec = async (args: TaskArgs, signal?: AbortSignal): Promise<TaskResult> => {
|
const exec = async (args: TaskArgs, signal?: AbortSignal): Promise<TaskResult> => {
|
||||||
const { promise, resolve, reject } = promiseWithCallbacks<TaskResult>();
|
const { promise, resolve, reject } = promiseWithCallbacks<TaskResult>();
|
||||||
const id = nextId();
|
const id = nextId();
|
||||||
const newTask: Task<TaskArgs, TaskResult> = { id, args, resolve, reject };
|
const newTask: Task<TaskArgs, TaskResult> = { id, args, resolve, reject };
|
||||||
|
|
||||||
signal?.addEventListener('abort', () => {
|
addTask(newTask);
|
||||||
reject(new Error('AbortError'));
|
launchTimer();
|
||||||
setTaskQueue((currentTaskQueue) => currentTaskQueue.filter((task) => task.id !== newTask.id));
|
|
||||||
});
|
|
||||||
|
|
||||||
setTaskQueue((currentTaskQueue) => [...currentTaskQueue, newTask]);
|
signal?.addEventListener('abort', () => {
|
||||||
|
removeTask(id);
|
||||||
|
reject(new Error('AbortError'));
|
||||||
|
});
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createMemo } from 'solid-js';
|
||||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||||
import { type Filter } from 'nostr-tools/filter';
|
import { type Filter } from 'nostr-tools/filter';
|
||||||
|
|
||||||
@@ -6,16 +7,20 @@ import useBatch, { type Task } from '@/clients/useBatch';
|
|||||||
import useSubscription from '@/clients/useSubscription';
|
import useSubscription from '@/clients/useSubscription';
|
||||||
|
|
||||||
export type UseBatchedEventProps<TaskArgs> = {
|
export type UseBatchedEventProps<TaskArgs> = {
|
||||||
|
interval?: number;
|
||||||
generateKey: (args: TaskArgs) => string | number;
|
generateKey: (args: TaskArgs) => string | number;
|
||||||
mergeFilters: (args: TaskArgs[]) => Filter[];
|
mergeFilters: (args: TaskArgs[]) => Filter[];
|
||||||
extractKey: (event: NostrEvent) => string | number | undefined;
|
extractKey: (event: NostrEvent) => string | number | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
|
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
|
||||||
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
return useBatch<TaskArgs, NostrEvent>(() => {
|
return useBatch<TaskArgs, NostrEvent>(() => {
|
||||||
return {
|
return {
|
||||||
|
interval: props().interval,
|
||||||
executor: (tasks) => {
|
executor: (tasks) => {
|
||||||
const { generateKey, mergeFilters, extractKey } = propsProvider();
|
const { generateKey, mergeFilters, extractKey } = props();
|
||||||
// TODO relayUrlsを考慮する
|
// TODO relayUrlsを考慮する
|
||||||
const [config] = useConfig();
|
const [config] = useConfig();
|
||||||
|
|
||||||
@@ -36,6 +41,11 @@ const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<Tas
|
|||||||
if (task == null) return;
|
if (task == null) return;
|
||||||
task.resolve(event);
|
task.resolve(event);
|
||||||
},
|
},
|
||||||
|
onEOSE: () => {
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
task.reject(new Error('NotFound'));
|
||||||
|
});
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ const InitialConfig: Config = {
|
|||||||
'wss://relay.snort.social',
|
'wss://relay.snort.social',
|
||||||
'wss://relay.current.fyi',
|
'wss://relay.current.fyi',
|
||||||
'wss://relay.nostr.wirednet.jp',
|
'wss://relay.nostr.wirednet.jp',
|
||||||
'wss://relay.mostr.pub',
|
'wss://nostr-relay.nokotaro.com',
|
||||||
|
'wss://nostr.holybea.com',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createMemo, type Accessor } from 'solid-js';
|
import { createMemo, type Accessor } from 'solid-js';
|
||||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||||
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
import useBatchedEvent from '@/clients/useBatchedEvent';
|
import useBatchedEvent from '@/clients/useBatchedEvent';
|
||||||
|
|
||||||
@@ -26,12 +27,11 @@ const { exec } = useBatchedEvent<UseEventProps>(() => ({
|
|||||||
|
|
||||||
const useEvent = (propsProvider: () => UseEventProps): UseEvent => {
|
const useEvent = (propsProvider: () => UseEventProps): UseEvent => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
const query = createQuery(
|
const query = createQuery(
|
||||||
() => ['useEvent', props()] as const,
|
() => ['useEvent', props()] as const,
|
||||||
({ queryKey, signal }) => {
|
({ queryKey, signal }) => {
|
||||||
const [, currentProps] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
return exec(currentProps, signal);
|
return timeout(15000, `useEvent: ${currentProps.eventId}`)(exec(currentProps, signal));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 5 minutes
|
// 5 minutes
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { type Filter } from 'nostr-tools/filter';
|
|||||||
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
|
|
||||||
import useBatchedEvent from '@/clients/useBatchedEvent';
|
import useBatchedEvent from '@/clients/useBatchedEvent';
|
||||||
import { Task } from './useBatch';
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
// TODO zodにする
|
// TODO zodにする
|
||||||
// deleted等の特殊なもの
|
// deleted等の特殊なもの
|
||||||
@@ -49,7 +49,8 @@ const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
|
|||||||
() => ['useProfile', props()] as const,
|
() => ['useProfile', props()] as const,
|
||||||
({ queryKey, signal }) => {
|
({ queryKey, signal }) => {
|
||||||
const [, currentProps] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
return exec(currentProps, signal);
|
// TODO timeoutと同時にsignalでキャンセルするようにしたい
|
||||||
|
return timeout(15000, `useProfile: ${currentProps.pubkey}`)(exec(currentProps, signal));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 5 minutes
|
// 5 minutes
|
||||||
@@ -61,7 +62,12 @@ const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
|
|||||||
const profile = () => {
|
const profile = () => {
|
||||||
if (query.data == null) return undefined;
|
if (query.data == null) return undefined;
|
||||||
// TODO 大きすぎたりしないかどうか、JSONかどうかのチェック
|
// TODO 大きすぎたりしないかどうか、JSONかどうかのチェック
|
||||||
return JSON.parse(query.data.content) as Profile;
|
try {
|
||||||
|
return JSON.parse(query.data.content) as Profile;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { profile, query };
|
return { profile, query };
|
||||||
|
|||||||
@@ -1,36 +1,88 @@
|
|||||||
import { type Accessor } from 'solid-js';
|
import { createSignal, createMemo, type Signal, type Accessor } from 'solid-js';
|
||||||
import { type Event as NostrEvent } from 'nostr-tools/event';
|
import { type Event as NostrEvent } from 'nostr-tools/event';
|
||||||
import { type CreateQueryResult } from '@tanstack/solid-query';
|
import { createQuery, useQueryClient, type CreateQueryResult } from '@tanstack/solid-query';
|
||||||
|
|
||||||
import useCachedEvents from '@/clients/useCachedEvents';
|
import useConfig from '@/clients/useConfig';
|
||||||
|
import useBatch, { type Task } from '@/clients/useBatch';
|
||||||
|
import useSubscription from '@/clients/useSubscription';
|
||||||
|
import timeout from '@/utils/timeout';
|
||||||
|
|
||||||
export type UseEventProps = {
|
export type UseReactionsProps = {
|
||||||
relayUrls: string[];
|
relayUrls: string[];
|
||||||
eventId: string;
|
eventId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseEvent = {
|
export type UseReactions = {
|
||||||
reactions: Accessor<NostrEvent[]>;
|
reactions: Accessor<NostrEvent[]>;
|
||||||
reactionsGroupedByContent: Accessor<Map<string, NostrEvent[]>>;
|
reactionsGroupedByContent: Accessor<Map<string, NostrEvent[]>>;
|
||||||
isReactedBy(pubkey: string): boolean;
|
isReactedBy: (pubkey: string) => boolean;
|
||||||
query: CreateQueryResult<NostrEvent[]>;
|
invalidateReactions: () => Promise<void>;
|
||||||
|
query: CreateQueryResult<Accessor<NostrEvent[]>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useReactions = (propsProvider: () => UseEventProps): UseEvent => {
|
const { exec } = useBatch<UseReactionsProps, Accessor<NostrEvent[]>>(() => {
|
||||||
const query = useCachedEvents(() => {
|
return {
|
||||||
const { relayUrls, eventId } = propsProvider();
|
interval: 2500,
|
||||||
return {
|
executor: (tasks) => {
|
||||||
relayUrls,
|
// TODO relayUrlsを考慮する
|
||||||
filters: [
|
const [config] = useConfig();
|
||||||
{
|
|
||||||
'#e': [eventId],
|
|
||||||
kinds: [7],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const reactions = () => query.data ?? [];
|
const eventIdTaskMap = new Map<string, Task<UseReactionsProps, Accessor<NostrEvent[]>>>(
|
||||||
|
tasks.map((task) => [task.args.eventId, task]),
|
||||||
|
);
|
||||||
|
const eventIds = Array.from(eventIdTaskMap.keys());
|
||||||
|
const eventIdReactionsMap = new Map<string, Signal<NostrEvent[]>>();
|
||||||
|
|
||||||
|
useSubscription(() => ({
|
||||||
|
relayUrls: config().relayUrls,
|
||||||
|
filters: [{ kinds: [7], '#e': eventIds }],
|
||||||
|
continuous: false,
|
||||||
|
onEvent(event: NostrEvent) {
|
||||||
|
const reactTo = event.tags.find((e) => e[0] === 'e')?.[1];
|
||||||
|
if (reactTo == null) return;
|
||||||
|
const task = eventIdTaskMap.get(reactTo);
|
||||||
|
// possibly, the new event received
|
||||||
|
if (task == null) return;
|
||||||
|
|
||||||
|
const reactionsSignal =
|
||||||
|
eventIdReactionsMap.get(reactTo) ?? createSignal<NostrEvent[]>([]);
|
||||||
|
eventIdReactionsMap.set(reactTo, reactionsSignal);
|
||||||
|
|
||||||
|
const [reactions, setReactions] = reactionsSignal;
|
||||||
|
|
||||||
|
setReactions((currentReactions) => [...currentReactions, event]);
|
||||||
|
|
||||||
|
// 初回のresolveのみが有効
|
||||||
|
task.resolve(reactions);
|
||||||
|
},
|
||||||
|
onEOSE() {
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
task.resolve(() => []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const useReactions = (propsProvider: () => UseReactionsProps): UseReactions => {
|
||||||
|
const props = createMemo(propsProvider);
|
||||||
|
const queryKey = createMemo(() => ['useReactions', props()] as const);
|
||||||
|
|
||||||
|
const query = createQuery(
|
||||||
|
() => queryKey(),
|
||||||
|
({ queryKey, signal }) => {
|
||||||
|
const [, currentProps] = queryKey;
|
||||||
|
return timeout(15000, `useReactions: ${currentProps.eventId}`)(exec(currentProps, signal));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 minutes
|
||||||
|
staleTime: 1 * 60 * 1000,
|
||||||
|
cacheTime: 1 * 60 * 1000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const reactions = () => query.data?.() ?? [];
|
||||||
|
|
||||||
const reactionsGroupedByContent = () => {
|
const reactionsGroupedByContent = () => {
|
||||||
const result = new Map<string, NostrEvent[]>();
|
const result = new Map<string, NostrEvent[]>();
|
||||||
@@ -45,7 +97,12 @@ const useReactions = (propsProvider: () => UseEventProps): UseEvent => {
|
|||||||
const isReactedBy = (pubkey: string): boolean =>
|
const isReactedBy = (pubkey: string): boolean =>
|
||||||
reactions().findIndex((event) => event.pubkey === pubkey) !== -1;
|
reactions().findIndex((event) => event.pubkey === pubkey) !== -1;
|
||||||
|
|
||||||
return { reactions, reactionsGroupedByContent, isReactedBy, query };
|
const invalidateReactions = (): Promise<void> => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return queryClient.invalidateQueries(queryKey());
|
||||||
|
};
|
||||||
|
|
||||||
|
return { reactions, reactionsGroupedByContent, isReactedBy, invalidateReactions, query };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useReactions;
|
export default useReactions;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type UseSubscriptionProps = {
|
|||||||
// default is true
|
// default is true
|
||||||
continuous?: boolean;
|
continuous?: boolean;
|
||||||
onEvent?: (event: NostrEvent) => void;
|
onEvent?: (event: NostrEvent) => void;
|
||||||
|
onEOSE?: () => void;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ const useSubscription = (propsProvider: () => UseSubscriptionProps | undefined)
|
|||||||
const props = propsProvider();
|
const props = propsProvider();
|
||||||
if (props == null) return;
|
if (props == null) return;
|
||||||
|
|
||||||
const { relayUrls, filters, options, onEvent, continuous = true } = props;
|
const { relayUrls, filters, options, onEvent, onEOSE, continuous = true } = props;
|
||||||
|
|
||||||
const sub = pool().sub(relayUrls, filters, options);
|
const sub = pool().sub(relayUrls, filters, options);
|
||||||
let pushed = false;
|
let pushed = false;
|
||||||
@@ -47,6 +48,10 @@ const useSubscription = (propsProvider: () => UseSubscriptionProps | undefined)
|
|||||||
});
|
});
|
||||||
|
|
||||||
sub.on('eose', () => {
|
sub.on('eose', () => {
|
||||||
|
if (onEOSE != null) {
|
||||||
|
onEOSE();
|
||||||
|
}
|
||||||
|
|
||||||
eose = true;
|
eose = true;
|
||||||
setEvents(sortEvents(storedEvents));
|
setEvents(sortEvents(storedEvents));
|
||||||
|
|
||||||
|
|||||||
6
src/components/ReplyPostForm.tsx
Normal file
6
src/components/ReplyPostForm.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { type Component } from 'solid-js';
|
||||||
|
|
||||||
|
const ReplyPostForm = () => {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReplyPostForm;
|
||||||
@@ -11,7 +11,7 @@ import useProfile from '@/clients/useProfile';
|
|||||||
import useConfig from '@/clients/useConfig';
|
import useConfig from '@/clients/useConfig';
|
||||||
import usePubkey from '@/clients/usePubkey';
|
import usePubkey from '@/clients/usePubkey';
|
||||||
import useCommands from '@/clients/useCommands';
|
import useCommands from '@/clients/useCommands';
|
||||||
// import useReactions from '@/clients/useReactions';
|
import useReactions from '@/clients/useReactions';
|
||||||
import useDatePulser from '@/hooks/useDatePulser';
|
import useDatePulser from '@/hooks/useDatePulser';
|
||||||
import { formatRelative } from '@/utils/formatDate';
|
import { formatRelative } from '@/utils/formatDate';
|
||||||
import ColumnItem from '@/components/ColumnItem';
|
import ColumnItem from '@/components/ColumnItem';
|
||||||
@@ -33,19 +33,12 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
pubkey: props.event.pubkey,
|
pubkey: props.event.pubkey,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/*
|
const { reactions, isReactedBy, invalidateReactions } = useReactions(() => ({
|
||||||
const {
|
|
||||||
reactions,
|
|
||||||
isReactedBy,
|
|
||||||
query: reactionsQuery,
|
|
||||||
} = useReactions(() => ({
|
|
||||||
relayUrls: config().relayUrls,
|
relayUrls: config().relayUrls,
|
||||||
eventId: props.event.id,
|
eventId: props.event.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const isReactedByMe = createMemo(() => isReactedBy(pubkey()));
|
const isReactedByMe = createMemo(() => isReactedBy(pubkey()));
|
||||||
*/
|
|
||||||
const isReactedByMe = () => false;
|
|
||||||
|
|
||||||
const replyingToPubKeys = createMemo(() =>
|
const replyingToPubKeys = createMemo(() =>
|
||||||
props.event.tags.filter((tag) => tag[0] === 'p').map((e) => e[1]),
|
props.event.tags.filter((tag) => tag[0] === 'p').map((e) => e[1]),
|
||||||
@@ -64,13 +57,12 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
const handleReaction: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||||
/*
|
|
||||||
if (isReactedByMe()) {
|
if (isReactedByMe()) {
|
||||||
// TODO remove reaction
|
// TODO remove reaction
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.publishReaction({
|
.publishReaction({
|
||||||
relayUrls: config().relayUrls,
|
relayUrls: config().relayUrls,
|
||||||
@@ -79,9 +71,7 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
eventId: props.event.id,
|
eventId: props.event.id,
|
||||||
notifyPubkey: props.event.pubkey,
|
notifyPubkey: props.event.pubkey,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => invalidateReactions());
|
||||||
// reactionsQuery.refetch();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -146,7 +136,7 @@ const TextNote: Component<TextNoteProps> = (props) => {
|
|||||||
<HeartSolid />
|
<HeartSolid />
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
{/* <div class="text-sm text-zinc-400">{reactions().length}</div> */}
|
<div class="text-sm text-zinc-400">{reactions().length}</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="h-4 w-4 text-zinc-400">
|
<button class="h-4 w-4 text-zinc-400">
|
||||||
<EllipsisHorizontal />
|
<EllipsisHorizontal />
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ const Home: Component = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const { events: localTimeline } = useSubscription(() => ({
|
const { events: localTimeline } = useSubscription(() => ({
|
||||||
relayUrls: ['wss://relay-jp.nostr.wirednet.jp', 'wss://nostr.h3z.jp/'],
|
relayUrls: [
|
||||||
|
'wss://relay-jp.nostr.wirednet.jp',
|
||||||
|
'wss://nostr.h3z.jp/',
|
||||||
|
'wss://nostr.holybea.com',
|
||||||
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
kinds: [1, 6],
|
kinds: [1, 6],
|
||||||
|
|||||||
14
src/utils/timeout.ts
Normal file
14
src/utils/timeout.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const timeout =
|
||||||
|
(ms: number, info?: string) =>
|
||||||
|
<T>(promise: Promise<T>): Promise<T> => {
|
||||||
|
const timeoutPromise = new Promise<T>((_resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const message = info != null ? `TimeoutError: ${info}` : 'TimeoutError';
|
||||||
|
reject(new Error(message));
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.race([promise, timeoutPromise]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default timeout;
|
||||||
Reference in New Issue
Block a user