mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-18 11:13:30 +00:00
fix mention in composer and improve error handling
This commit is contained in:
parent
cc315a190a
commit
e6d35bc635
@ -1,4 +1,5 @@
|
|||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
@ -40,7 +41,10 @@ export function SplashScreen() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setErrorMessage(e);
|
setErrorMessage(e);
|
||||||
console.log('prefetch failed, error: ', e);
|
await message(`Something wrong: ${e}`, {
|
||||||
|
title: 'Lume',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// inspire by: https://github.com/nostr-dev-kit/ndk-react/
|
// inspire by: https://github.com/nostr-dev-kit/ndk-react/
|
||||||
import NDK from '@nostr-dev-kit/ndk';
|
import NDK from '@nostr-dev-kit/ndk';
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import TauriAdapter from '@libs/ndk/cache';
|
import TauriAdapter from '@libs/ndk/cache';
|
||||||
@ -28,7 +29,7 @@ export const NDKInstance = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort('timeout'), 5000);
|
const timeoutId = setTimeout(() => controller.abort('timeout'), 10000);
|
||||||
|
|
||||||
const requests = urls.map((url) =>
|
const requests = urls.map((url) =>
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
@ -58,14 +59,16 @@ export const NDKInstance = () => {
|
|||||||
// return all validate relays
|
// return all validate relays
|
||||||
return verifiedRelays;
|
return verifiedRelays;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('ndk instance error: ', e);
|
console.error('verify relay failed with error: ', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initNDK() {
|
async function initNDK() {
|
||||||
let explicitRelayUrls: string[];
|
let explicitRelayUrls: string[];
|
||||||
const explicitRelayUrlsFromDB = await db.getExplicitRelayUrls();
|
const explicitRelayUrlsFromDB = await db.getExplicitRelayUrls();
|
||||||
|
|
||||||
console.log('relays in db: ', explicitRelayUrlsFromDB);
|
console.log('relays in db: ', explicitRelayUrlsFromDB);
|
||||||
|
console.log('ndk cache adapter: ', cacheAdapter);
|
||||||
|
|
||||||
if (explicitRelayUrlsFromDB) {
|
if (explicitRelayUrlsFromDB) {
|
||||||
explicitRelayUrls = await verifyRelays(explicitRelayUrlsFromDB);
|
explicitRelayUrls = await verifyRelays(explicitRelayUrlsFromDB);
|
||||||
@ -73,13 +76,21 @@ export const NDKInstance = () => {
|
|||||||
explicitRelayUrls = await verifyRelays(FULL_RELAYS);
|
explicitRelayUrls = await verifyRelays(FULL_RELAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('ndk cache adapter: ', cacheAdapter);
|
if (explicitRelayUrls.length < 1) {
|
||||||
const instance = new NDK({ explicitRelayUrls, cacheAdapter });
|
await message('Something is wrong. No relays have been found.', {
|
||||||
|
title: 'Lume',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new NDK({ explicitRelayUrls, cacheAdapter });
|
||||||
try {
|
try {
|
||||||
await instance.connect(10000);
|
await instance.connect(10000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('NDK instance init failed: ', error);
|
await message(`NDK instance init failed: ${error}`, {
|
||||||
|
title: 'Lume',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setNDK(instance);
|
setNDK(instance);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import Database from '@tauri-apps/plugin-sql';
|
import Database from '@tauri-apps/plugin-sql';
|
||||||
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
|
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@ -15,11 +16,18 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
const [db, setDB] = useState<LumeStorage>(undefined);
|
const [db, setDB] = useState<LumeStorage>(undefined);
|
||||||
|
|
||||||
async function initLumeStorage() {
|
async function initLumeStorage() {
|
||||||
const sqlite = await Database.load('sqlite:lume.db');
|
try {
|
||||||
const lumeStorage = new LumeStorage(sqlite);
|
const sqlite = await Database.load('sqlite:lume.db');
|
||||||
|
const lumeStorage = new LumeStorage(sqlite);
|
||||||
|
|
||||||
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
||||||
setDB(lumeStorage);
|
setDB(lumeStorage);
|
||||||
|
} catch (e) {
|
||||||
|
await message(`Cannot initialize database: ${e}`, {
|
||||||
|
title: 'Lume',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,75 +1,84 @@
|
|||||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import { type SuggestionProps } from '@tiptap/suggestion';
|
||||||
|
import {
|
||||||
|
ForwardedRef,
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { MentionItem } from '@shared/composer';
|
import { MentionItem } from '@shared/composer';
|
||||||
|
|
||||||
export const MentionList = forwardRef((props: any, ref: any) => {
|
export const MentionList = forwardRef(
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
(props: SuggestionProps, ref: ForwardedRef<unknown>) => {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
const selectItem = (index) => {
|
const selectItem = (index) => {
|
||||||
const item = props.items[index];
|
const item = props.items[index];
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
props.command({ id: item });
|
props.command({ id: item });
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const upHandler = () => {
|
|
||||||
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
const downHandler = () => {
|
|
||||||
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
const enterHandler = () => {
|
|
||||||
selectItem(selectedIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => setSelectedIndex(0), [props.items]);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
onKeyDown: ({ event }) => {
|
|
||||||
if (event.key === 'ArrowUp') {
|
|
||||||
upHandler();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (event.key === 'ArrowDown') {
|
const upHandler = () => {
|
||||||
downHandler();
|
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
|
||||||
return true;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === 'Enter') {
|
const downHandler = () => {
|
||||||
enterHandler();
|
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
||||||
return true;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
const enterHandler = () => {
|
||||||
},
|
selectItem(selectedIndex);
|
||||||
}));
|
};
|
||||||
|
|
||||||
return (
|
useEffect(() => setSelectedIndex(0), [props.items]);
|
||||||
<div className="flex w-[250px] flex-col rounded-xl border-t border-zinc-700/50 bg-zinc-800 px-3 py-3">
|
|
||||||
{props.items.length ? (
|
useImperativeHandle(ref, () => ({
|
||||||
props.items.map((item: NDKUserProfile, index: number) => (
|
onKeyDown: ({ event }) => {
|
||||||
<button
|
if (event.key === 'ArrowUp') {
|
||||||
className={twMerge(
|
upHandler();
|
||||||
'h-11 w-full rounded-lg px-2 text-start text-sm font-medium hover:bg-zinc-700',
|
return true;
|
||||||
`${index === selectedIndex ? 'is-selected' : ''}`
|
}
|
||||||
)}
|
|
||||||
key={index}
|
if (event.key === 'ArrowDown') {
|
||||||
onClick={() => selectItem(index)}
|
downHandler();
|
||||||
>
|
return true;
|
||||||
<MentionItem profile={item} />
|
}
|
||||||
</button>
|
|
||||||
))
|
if (event.key === 'Enter') {
|
||||||
) : (
|
enterHandler();
|
||||||
<div>No result</div>
|
return true;
|
||||||
)}
|
}
|
||||||
</div>
|
|
||||||
);
|
return false;
|
||||||
});
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-[250px] flex-col rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
{props.items.length ? (
|
||||||
|
props.items.map((item: NDKUserProfile, index: number) => (
|
||||||
|
<button
|
||||||
|
className={twMerge(
|
||||||
|
'h-11 w-full rounded-lg px-2 text-start text-sm font-medium hover:bg-white/10',
|
||||||
|
`${index === selectedIndex ? 'is-selected' : ''}`
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
onClick={() => selectItem(index)}
|
||||||
|
>
|
||||||
|
<MentionItem profile={item} />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div>No result</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
MentionList.displayName = 'MentionList';
|
MentionList.displayName = 'MentionList';
|
||||||
|
@ -19,13 +19,13 @@ export function NIP05({
|
|||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
[nip05],
|
['nip05', nip05],
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const username = nip05.split('@')[0];
|
const localPath = nip05.split('@')[0];
|
||||||
const service = nip05.split('@')[1];
|
const service = nip05.split('@')[1];
|
||||||
// #TODO: use tauri native fetch to avoid CORS
|
// #TODO: use tauri native fetch to avoid CORS
|
||||||
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
|
const verifyURL = `https://${service}/.well-known/nostr.json?name=${localPath}`;
|
||||||
|
|
||||||
const res = await fetch(verifyURL, {
|
const res = await fetch(verifyURL, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -37,11 +37,11 @@ export function NIP05({
|
|||||||
if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`);
|
if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`);
|
||||||
|
|
||||||
const data: NIP05 = await res.json();
|
const data: NIP05 = await res.json();
|
||||||
|
|
||||||
if (data.names) {
|
if (data.names) {
|
||||||
if (data.names.username !== pubkey) return false;
|
if (data.names.username !== pubkey) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to verify NIP-05, error: ${e}`);
|
throw new Error(`Failed to verify NIP-05, error: ${e}`);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ArticleNote,
|
ArticleNote,
|
||||||
@ -15,9 +16,25 @@ import { User } from '@shared/user';
|
|||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
|
|
||||||
export function Repost({ event }: { event: NDKEvent }) {
|
export function Repost({ event }: { event: NDKEvent }) {
|
||||||
const repostID = event.tags.find((el) => el[0] === 'e')?.[1];
|
const repostID = event.tags.find((el) => el[0] === 'e')[1] ?? '';
|
||||||
const { status, data } = useEvent(repostID, event.content as unknown as string);
|
const { status, data } = useEvent(repostID, event.content as unknown as string);
|
||||||
|
|
||||||
|
const renderKind = useCallback(
|
||||||
|
(event: NDKEvent) => {
|
||||||
|
switch (event.kind) {
|
||||||
|
case NDKKind.Text:
|
||||||
|
return <TextNote event={event} />;
|
||||||
|
case NDKKind.Article:
|
||||||
|
return <ArticleNote event={event} />;
|
||||||
|
case 1063:
|
||||||
|
return <FileNote event={event} />;
|
||||||
|
default:
|
||||||
|
return <UnknownNote event={event} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[event]
|
||||||
|
);
|
||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full px-3 pb-3">
|
<div className="h-min w-full px-3 pb-3">
|
||||||
@ -43,19 +60,6 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderKind = (event: NDKEvent) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return <TextNote event={event} />;
|
|
||||||
case NDKKind.Article:
|
|
||||||
return <ArticleNote event={event} />;
|
|
||||||
case 1063:
|
|
||||||
return <FileNote event={event} />;
|
|
||||||
default:
|
|
||||||
return <UnknownNote event={event} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full px-3 pb-3">
|
<div className="h-min w-full px-3 pb-3">
|
||||||
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl">
|
<div className="relative overflow-hidden rounded-xl bg-white/10 px-3 pt-3 backdrop-blur-xl">
|
||||||
@ -64,7 +68,7 @@ export function Repost({ event }: { event: NDKEvent }) {
|
|||||||
<RepostUser pubkey={event.pubkey} />
|
<RepostUser pubkey={event.pubkey} />
|
||||||
<User pubkey={data.pubkey} time={data.created_at} isRepost={true} />
|
<User pubkey={data.pubkey} time={data.created_at} isRepost={true} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="-mt-2 flex items-start gap-3">
|
||||||
<div className="w-11 shrink-0" />
|
<div className="w-11 shrink-0" />
|
||||||
<div className="relative z-20 flex-1">
|
<div className="relative z-20 flex-1">
|
||||||
{renderKind(data)}
|
{renderKind(data)}
|
||||||
|
@ -2,7 +2,7 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
|
|||||||
|
|
||||||
export function UnknownNote({ event }: { event: NDKEvent }) {
|
export function UnknownNote({ event }: { event: NDKEvent }) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 mt-3 flex w-full flex-col gap-2">
|
<div className="flex w-full flex-col gap-2">
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2 backdrop-blur-xl">
|
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2 backdrop-blur-xl">
|
||||||
<span className="text-sm font-medium leading-none text-white/50">
|
<span className="text-sm font-medium leading-none text-white/50">
|
||||||
Unknown kind: {event.kind}
|
Unknown kind: {event.kind}
|
||||||
|
@ -23,8 +23,8 @@ export function User({
|
|||||||
isChat?: boolean;
|
isChat?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { status, user } = useProfile(pubkey);
|
const { status, user } = useProfile(pubkey);
|
||||||
const createdAt = formatCreatedAt(time, isChat);
|
|
||||||
|
|
||||||
|
const createdAt = formatCreatedAt(time, isChat);
|
||||||
const avatarWidth = size === 'small' ? 'w-6' : 'w-11';
|
const avatarWidth = size === 'small' ? 'w-6' : 'w-11';
|
||||||
const avatarHeight = size === 'small' ? 'h-6' : 'h-11';
|
const avatarHeight = size === 'small' ? 'h-6' : 'h-11';
|
||||||
|
|
||||||
@ -102,10 +102,10 @@ export function User({
|
|||||||
<h5 className="text-sm font-semibold leading-none">
|
<h5 className="text-sm font-semibold leading-none">
|
||||||
{user?.display_name || user?.name || user?.username}
|
{user?.display_name || user?.name || user?.username}
|
||||||
</h5>
|
</h5>
|
||||||
{user?.nip05 ? (
|
{user.nip05 ? (
|
||||||
<NIP05
|
<NIP05
|
||||||
pubkey={pubkey}
|
pubkey={pubkey}
|
||||||
nip05={user?.nip05}
|
nip05={user.nip05}
|
||||||
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
|
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -19,36 +19,34 @@ export function useEvent(id: string, embed?: string) {
|
|||||||
}
|
}
|
||||||
// get event from db
|
// get event from db
|
||||||
const dbEvent = await db.getEventByID(id);
|
const dbEvent = await db.getEventByID(id);
|
||||||
if (dbEvent) {
|
if (dbEvent) return dbEvent;
|
||||||
return dbEvent;
|
|
||||||
|
// get event from relay if event in db not present
|
||||||
|
const event = await ndk.fetchEvent(id);
|
||||||
|
if (!event) throw new Error(`Event not found: ${id}`);
|
||||||
|
|
||||||
|
let root: string;
|
||||||
|
let reply: string;
|
||||||
|
|
||||||
|
if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) {
|
||||||
|
root = event.tags[0][1];
|
||||||
} else {
|
} else {
|
||||||
// get event from relay if event in db not present
|
root = event.tags.find((el) => el[3] === 'root')?.[1];
|
||||||
const event = await ndk.fetchEvent(id);
|
reply = event.tags.find((el) => el[3] === 'reply')?.[1];
|
||||||
if (!event) throw new Error(`Event not found: ${id}`);
|
|
||||||
|
|
||||||
let root: string;
|
|
||||||
let reply: string;
|
|
||||||
|
|
||||||
if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) {
|
|
||||||
root = event.tags[0][1];
|
|
||||||
} else {
|
|
||||||
root = event.tags.find((el) => el[3] === 'root')?.[1];
|
|
||||||
reply = event.tags.find((el) => el[3] === 'reply')?.[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawEvent = toRawEvent(event);
|
|
||||||
await db.createEvent(
|
|
||||||
event.id,
|
|
||||||
JSON.stringify(rawEvent),
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
root,
|
|
||||||
reply,
|
|
||||||
event.created_at
|
|
||||||
);
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawEvent = toRawEvent(event);
|
||||||
|
await db.createEvent(
|
||||||
|
event.id,
|
||||||
|
JSON.stringify(rawEvent),
|
||||||
|
event.pubkey,
|
||||||
|
event.kind,
|
||||||
|
root,
|
||||||
|
reply,
|
||||||
|
event.created_at
|
||||||
|
);
|
||||||
|
|
||||||
|
return event;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!ndk,
|
enabled: !!ndk,
|
||||||
|
@ -5,8 +5,6 @@ import { Event, parseReferences } from 'nostr-tools';
|
|||||||
import { RichContent } from '@utils/types';
|
import { RichContent } from '@utils/types';
|
||||||
|
|
||||||
export function parser(event: NDKEvent) {
|
export function parser(event: NDKEvent) {
|
||||||
if (event.kind !== 1) return;
|
|
||||||
|
|
||||||
const references = parseReferences(event as unknown as Event);
|
const references = parseReferences(event as unknown as Event);
|
||||||
const urls = getUrls(event.content as unknown as string);
|
const urls = getUrls(event.content as unknown as string);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user