re-enable nip-04 with more polish, prepare for nip-44
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
53
src/app/chats/components/messages/mediaUploader.tsx
Normal file
53
src/app/chats/components/messages/mediaUploader.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user