re-enable nip-04 with more polish, prepare for nip-44

This commit is contained in:
Ren Amamiya
2023-08-24 09:05:34 +07:00
parent 3455eb701f
commit 4893ebd932
19 changed files with 376 additions and 242 deletions

View File

@@ -5,10 +5,9 @@ import { Image } from '@shared/image';
import { useProfile } from '@utils/hooks/useProfile';
import { displayNpub } from '@utils/shortenKey';
import { Chats } from '@utils/types';
export function ChatsListItem({ data }: { data: Chats }) {
const { status, user } = useProfile(data.sender_pubkey);
export function ChatsListItem({ pubkey }: { pubkey: string }) {
const { status, user } = useProfile(pubkey);
if (status === 'loading') {
return (
@@ -21,7 +20,7 @@ export function ChatsListItem({ data }: { data: Chats }) {
return (
<NavLink
to={`/chats/${data.sender_pubkey}`}
to={`/chats/${pubkey}`}
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
@@ -32,23 +31,13 @@ export function ChatsListItem({ data }: { data: Chats }) {
>
<Image
src={user?.picture || user?.image}
alt={data.sender_pubkey}
alt={pubkey}
className="h-6 w-6 shrink-0 rounded object-cover"
/>
<div className="inline-flex w-full flex-1 items-center justify-between">
<h5 className="max-w-[10rem] truncate">
{user?.nip05 ||
user?.name ||
user?.display_name ||
displayNpub(data.sender_pubkey, 16)}
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
</h5>
<div className="flex items-center">
{data.new_messages > 0 && (
<span className="inline-flex w-8 items-center justify-center rounded bg-fuchsia-400/10 px-1 py-1 text-xs font-medium text-fuchsia-500 ring-1 ring-inset ring-fuchsia-400/20">
{data.new_messages}
</span>
)}
</div>
</div>
</NavLink>
);

View File

@@ -8,18 +8,23 @@ import { UnknownsModal } from '@app/chats/components/unknowns';
import { useStorage } from '@libs/storage/provider';
import { Chats } from '@utils/types';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatsList() {
const { db } = useStorage();
const { status, data: chats } = useQuery(['chats'], async () => {
return { follows: [], unknowns: [] };
});
const { fetchNIP04Chats } = useNostr();
const { status, data: chats } = useQuery(
['nip04-chats'],
async () => {
return await fetchNIP04Chats();
},
{ refetchOnWindowFocus: false }
);
const renderItem = useCallback(
(item: Chats) => {
if (db.account.pubkey !== item.sender_pubkey) {
return <ChatsListItem key={item.sender_pubkey} data={item} />;
(item: string) => {
if (db.account.pubkey !== item) {
return <ChatsListItem key={item} pubkey={item} />;
}
},
[chats]

View File

@@ -1,8 +1,9 @@
import { nip04 } from 'nostr-tools';
import { useCallback, useState } from 'react';
import { MediaUploader } from '@app/chats/components/messages/mediaUploader';
import { EnterIcon } from '@shared/icons';
import { MediaUploader } from '@shared/mediaUploader';
import { useNostr } from '@utils/hooks/useNostr';
@@ -34,7 +35,7 @@ export function ChatMessageForm({
const handleEnterPress = (e: {
key: string;
shiftKey: any;
shiftKey: KeyboardEvent['shiftKey'];
preventDefault: () => void;
}) => {
if (e.key === 'Enter' && !e.shiftKey) {

View File

@@ -1,32 +1,31 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
import { NoteContent } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
export function ChatMessageItem({
data,
message,
userPubkey,
userPrivkey,
}: {
data: any;
message: NDKEvent;
userPubkey: string;
userPrivkey: string;
}) {
const decryptedContent = useDecryptMessage(data, userPubkey, userPrivkey);
const decryptedContent = useDecryptMessage(message, userPubkey, userPrivkey);
// if we have decrypted content, use it instead of the encrypted content
if (decryptedContent) {
data['content'] = decryptedContent;
message['content'] = decryptedContent;
}
return (
<div className="flex h-min min-h-min w-full select-text flex-col px-5 py-3 hover:bg-white/10">
<div className="flex flex-col">
<User pubkey={data.sender_pubkey} time={data.created_at} isChat={true} />
<User pubkey={message.pubkey} time={message.created_at} isChat={true} />
<div className="-mt-[20px] pl-[49px]">
<p className="select-text whitespace-pre-line break-words text-base text-white">
{data.content}
{message.content}
</p>
</div>
</div>

View File

@@ -0,0 +1,53 @@
import * as Tooltip from '@radix-ui/react-tooltip';
import { Dispatch, SetStateAction, useState } from 'react';
import { LoaderIcon, MediaIcon } from '@shared/icons';
import { useImageUploader } from '@utils/hooks/useUploader';
export function MediaUploader({
setState,
}: {
setState: Dispatch<SetStateAction<string>>;
}) {
const upload = useImageUploader();
const [loading, setLoading] = useState(false);
const uploadMedia = async () => {
setLoading(true);
const image = await upload(null);
if (image.url) {
setState((prev: string) => `${prev}\n${image.url}`);
}
setLoading(false);
};
return (
<Tooltip.Provider>
<Tooltip.Root delayDuration={150}>
<Tooltip.Trigger asChild>
<button
type="button"
onClick={() => uploadMedia()}
className="group inline-flex h-8 w-8 items-center justify-center rounded hover:bg-white/10"
>
{loading ? (
<LoaderIcon className="h-5 w-5 animate-spin text-black dark:text-white" />
) : (
<MediaIcon className="h-5 w-5 text-white/50 group-hover:text-white" />
)}
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none text-white will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade"
sideOffset={5}
>
Upload media
<Tooltip.Arrow className="fill-black" />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}

View File

@@ -7,9 +7,8 @@ import { User } from '@app/auth/components/user';
import { CancelIcon, PlusIcon } from '@shared/icons';
import { compactNumber } from '@utils/number';
import { Chats } from '@utils/types';
export function UnknownsModal({ data }: { data: Chats[] }) {
export function UnknownsModal({ data }: { data: string[] }) {
const navigate = useNavigate();
const [open, setOpen] = useState(false);
@@ -55,16 +54,16 @@ export function UnknownsModal({ data }: { data: Chats[] }) {
</div>
</div>
<div className="flex h-[500px] flex-col overflow-y-auto overflow-x-hidden pb-2 pt-2">
{data.map((user) => (
{data.map((pubkey) => (
<div
key={user.sender_pubkey}
key={pubkey}
className="group flex items-center justify-between px-4 py-2 hover:bg-white/10"
>
<User pubkey={user.sender_pubkey} />
<User pubkey={pubkey} />
<div>
<button
type="button"
onClick={() => openChat(user.sender_pubkey)}
onClick={() => openChat(pubkey)}
className="hidden w-max rounded bg-white/10 px-3 py-1 text-sm font-medium hover:bg-fuchsia-500 group-hover:inline-flex"
>
Chat

View File

@@ -1,16 +1,21 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { nip04 } from 'nostr-tools';
import { useEffect, useState } from 'react';
import { Chats } from '@utils/types';
export function useDecryptMessage(data: Chats, userPubkey: string, userPriv: string) {
const [content, setContent] = useState(data.content);
export function useDecryptMessage(
message: NDKEvent,
userPubkey: string,
userPriv: string
) {
const [content, setContent] = useState(message.content);
useEffect(() => {
async function decrypt() {
const pubkey =
userPubkey === data.sender_pubkey ? data.receiver_pubkey : data.sender_pubkey;
const result = await nip04.decrypt(userPriv, pubkey, data.content);
userPubkey === message.pubkey
? message.tags.find((el) => el[0] === 'p')[1]
: message.pubkey;
const result = await nip04.decrypt(userPriv, pubkey, message.content);
setContent(result);
}

View File

@@ -13,23 +13,27 @@ import { useStorage } from '@libs/storage/provider';
import { useStronghold } from '@stores/stronghold';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatScreen() {
const virtuosoRef = useRef(null);
const { ndk } = useNDK();
const { db } = useStorage();
const { pubkey } = useParams();
const { status, data } = useQuery(['chat', pubkey], async () => {
return [];
});
const userPrivkey = useStronghold((state) => state.privkey);
const { db } = useStorage();
const { ndk } = useNDK();
const { pubkey } = useParams();
const { fetchNIP04Messages } = useNostr();
const { status, data } = useQuery(['nip04-dm', pubkey], async () => {
return await fetchNIP04Messages(pubkey);
});
const itemContent = useCallback(
(index: string | number) => {
const message = data[index];
if (!message) return;
return (
<ChatMessageItem
data={data[index]}
message={message}
userPubkey={db.account.pubkey}
userPrivkey={userPrivkey}
/>