This commit is contained in:
Ren Amamiya
2023-10-04 14:11:45 +07:00
parent 480580890e
commit f80dd78a8e
14 changed files with 165 additions and 176 deletions

View File

@@ -4,8 +4,8 @@ import { useCallback, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import { VList, VListHandle } from 'virtua';
import { ChatMessageForm } from '@app/chats/components/messages/form';
import { ChatMessageItem } from '@app/chats/components/messages/item';
import { ChatForm } from '@app/chats/components/chatForm';
import { ChatMessage } from '@app/chats/components/message';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
@@ -31,7 +31,7 @@ export function ChatScreen() {
const renderItem = useCallback(
(message: NDKEvent) => {
return (
<ChatMessageItem
<ChatMessage
message={message}
userPubkey={db.account.pubkey}
userPrivkey={userPrivkey}
@@ -42,7 +42,7 @@ export function ChatScreen() {
);
useEffect(() => {
if (data.length > 0) listRef.current?.scrollToIndex(data.length);
if (data && data.length > 0) listRef.current?.scrollToIndex(data.length);
}, [data]);
useEffect(() => {
@@ -68,38 +68,36 @@ export function ChatScreen() {
}, [pubkey]);
return (
<div className="grid h-full w-full grid-cols-3 bg-white/10 backdrop-blur-xl">
<div className="col-span-2 border-r border-white/5">
<div className="h-full w-full flex-1 p-3">
<div className="flex h-full flex-col justify-between overflow-hidden rounded-xl bg-white/10 backdrop-blur-xl">
<div className="h-full w-full flex-1">
{status === 'loading' ? (
<div className="flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center gap-1.5">
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
<p className="text-sm font-medium text-white/50">Loading messages</p>
</div>
<div className="h-full w-full p-3">
<div className="rounded-lg border-t border-white/5 bg-white/10 backdrop-blur-xl">
<div className="flex h-full flex-col justify-between overflow-hidden">
<div className="h-full w-full flex-1">
{status === 'loading' ? (
<div className="flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center gap-1.5">
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
<p className="text-sm font-medium text-white/50">Loading messages</p>
</div>
) : data.length === 0 ? (
<div className="absolute left-1/2 top-1/2 flex w-full -translate-x-1/2 -translate-y-1/2 transform flex-col gap-1 text-center">
<h3 className="mb-2 text-4xl">🙌</h3>
<p className="leading-none text-white/50">
You two didn&apos;t talk yet, let&apos;s send first message
</p>
</div>
) : (
<VList ref={listRef} className="scrollbar-hide h-full" mode="reverse">
{data.map((message) => renderItem(message))}
</VList>
)}
</div>
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 px-5 backdrop-blur-xl">
<ChatMessageForm
receiverPubkey={pubkey}
userPubkey={db.account.pubkey}
userPrivkey={userPrivkey}
/>
</div>
</div>
) : data.length === 0 ? (
<div className="absolute left-1/2 top-1/2 flex w-full -translate-x-1/2 -translate-y-1/2 transform flex-col gap-1 text-center">
<h3 className="mb-2 text-4xl">🙌</h3>
<p className="leading-none text-white/50">
You two didn&apos;t talk yet, let&apos;s send first message
</p>
</div>
) : (
<VList ref={listRef} className="scrollbar-hide h-full" mode="reverse">
{data.map((message) => renderItem(message))}
</VList>
)}
</div>
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 backdrop-blur-xl">
<ChatForm
receiverPubkey={pubkey}
userPubkey={db.account.pubkey}
userPrivkey={userPrivkey}
/>
</div>
</div>
</div>

View File

@@ -41,7 +41,7 @@ export const ChatListItem = memo(function ChatListItem({ event }: { event: NDKEv
preventScrollReset={true}
className={({ isActive }) =>
twMerge(
'flex items-center gap-2.5 px-3 py-2 hover:bg-white/10',
'flex items-center gap-2.5 px-3 py-1.5 hover:bg-white/10',
isActive
? 'border-fuchsia-500 bg-white/5 text-white'
: 'border-transparent text-white/70'
@@ -55,14 +55,10 @@ export const ChatListItem = memo(function ChatListItem({ event }: { event: NDKEv
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
className="h-10 w-10 rounded-lg"
className="h-9 w-9 rounded-lg"
/>
<Avatar.Fallback delayMs={300}>
<img
src={svgURI}
alt={event.pubkey}
className="h-10 w-10 rounded-lg border border-white/5 bg-black"
/>
<img src={svgURI} alt={event.pubkey} className="h-9 w-9 rounded-lg bg-white" />
</Avatar.Fallback>
</Avatar.Root>
<div className="flex w-full flex-col">

View File

@@ -1,14 +1,13 @@
import { nip04 } from 'nostr-tools';
import { useCallback, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
import { MediaUploader } from '@app/chats/components/messages/mediaUploader';
import { MediaUploader } from '@app/chats/components/mediaUploader';
import { EnterIcon } from '@shared/icons';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatMessageForm({
export function ChatForm({
receiverPubkey,
userPrivkey,
}: {
@@ -46,24 +45,24 @@ export function ChatMessageForm({
};
return (
<div className="flex w-full items-center justify-between rounded-md bg-white/20 px-3">
<TextareaAutosize
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleEnterPress}
spellCheck={false}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="Message"
className="min-h-[44px] flex-1 resize-none bg-transparent py-3 text-white !outline-none placeholder:text-white"
/>
<div className="inline-flex items-center gap-2">
<MediaUploader setState={setValue} />
<div className="flex items-center gap-2">
<MediaUploader setState={setValue} />
<div className="flex w-full items-center justify-between rounded-full bg-white/20 px-3">
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleEnterPress}
spellCheck={false}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="Message"
className="h-10 flex-1 resize-none bg-transparent px-3 text-white placeholder:text-white/80 focus:outline-none"
/>
<button
type="button"
onClick={submit}
className="inline-flex items-center gap-1.5 text-sm font-medium leading-none text-white/50"
className="inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-white/80"
>
<EnterIcon className="h-5 w-5" />
Send

View File

@@ -29,12 +29,12 @@ export function MediaUploader({
<button
type="button"
onClick={() => uploadMedia()}
className="group inline-flex h-8 w-8 items-center justify-center rounded backdrop-blur-xl hover:bg-white/10"
className="group inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl hover:bg-white/20"
>
{loading ? (
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
<LoaderIcon className="h-4 w-4 animate-spin" />
) : (
<MediaIcon className="h-5 w-5 text-white" />
<MediaIcon className="h-4 w-4" />
)}
</button>
</Tooltip.Trigger>

View File

@@ -0,0 +1,54 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
import { ImagePreview, LinkPreview, MentionNote, VideoPreview } from '@shared/notes';
import { User } from '@shared/user';
import { parser } from '@utils/parser';
export function ChatMessage({
message,
userPubkey,
userPrivkey,
}: {
message: NDKEvent;
userPubkey: string;
userPrivkey: string;
}) {
const decryptedContent = useDecryptMessage(message, userPubkey, userPrivkey);
const richContent = parser(decryptedContent) ?? null;
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={message.pubkey} time={message.created_at} variant="chat" />
<div className="-mt-6 flex items-start gap-3">
<div className="w-10 shrink-0" />
{!richContent ? (
<p>Decrypting...</p>
) : (
<div>
<p className="select-text whitespace-pre-line text-white">
{richContent.parsed}
</p>
<div>
{richContent.images.length > 0 && (
<ImagePreview urls={richContent.images} />
)}
{richContent.videos.length > 0 && (
<VideoPreview urls={richContent.videos} />
)}
{richContent.links.length > 0 && <LinkPreview urls={richContent.links} />}
{richContent.notes.length > 0 &&
richContent.notes.map((note: string) => (
<MentionNote key={note} id={note} />
))}
</div>
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -1,34 +0,0 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
import { TextNote } from '@shared/notes';
import { User } from '@shared/user';
export function ChatMessageItem({
message,
userPubkey,
userPrivkey,
}: {
message: NDKEvent;
userPubkey: string;
userPrivkey: string;
}) {
const decryptedContent = useDecryptMessage(message, userPubkey, userPrivkey);
// if we have decrypted content, use it instead of the encrypted content
if (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={message.pubkey} time={message.created_at} variant="chat" />
<div className="-mt-5 flex items-start gap-3">
<div className="w-10 shrink-0" />
<TextNote content={message.content} />
</div>
</div>
</div>
);
}

View File

@@ -3,16 +3,13 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
import { Outlet } from 'react-router-dom';
import { ChatListItem } from '@app/chats/components/item';
import { useStorage } from '@libs/storage/provider';
import { ChatListItem } from '@app/chats/components/chaListItem';
import { LoaderIcon } from '@shared/icons';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatsScreen() {
const { db } = useStorage();
const { getAllNIP04Chats } = useNostr();
const { status, data } = useQuery(
['nip04-chats'],
@@ -24,9 +21,7 @@ export function ChatsScreen() {
const renderItem = useCallback(
(event: NDKEvent) => {
if (db.account.pubkey !== event.pubkey) {
return <ChatListItem key={event.id} event={event} />;
}
return <ChatListItem key={event.id} event={event} />;
},
[data]
);
@@ -34,7 +29,10 @@ export function ChatsScreen() {
return (
<div className="grid h-full w-full grid-cols-3">
<div className="scrollbar-hide col-span-1 h-full overflow-y-auto border-r border-white/5">
<div className="h-16 w-full shrink-0 border-b border-white/5" />
<div
data-tauri-drag-region
className="h-16 w-full shrink-0 border-b border-white/5"
/>
<div className="flex h-full flex-col gap-1 py-2">
{status === 'loading' ? (
<div className="flex h-full w-full items-center justify-center pb-16">