wip
This commit is contained in:
@@ -40,7 +40,7 @@
|
|||||||
"@tiptap/react": "^2.1.11",
|
"@tiptap/react": "^2.1.11",
|
||||||
"@tiptap/starter-kit": "^2.1.11",
|
"@tiptap/starter-kit": "^2.1.11",
|
||||||
"@tiptap/suggestion": "^2.1.11",
|
"@tiptap/suggestion": "^2.1.11",
|
||||||
"@vidstack/react": "^1.1.5",
|
"@vidstack/react": "^1.1.7",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"destr": "^2.0.1",
|
"destr": "^2.0.1",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
"react-hook-form": "^7.47.0",
|
"react-hook-form": "^7.47.0",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
|
||||||
"reactflow": "^11.9.2",
|
"reactflow": "^11.9.2",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||||
|
|||||||
60
pnpm-lock.yaml
generated
60
pnpm-lock.yaml
generated
@@ -72,8 +72,8 @@ dependencies:
|
|||||||
specifier: ^2.1.11
|
specifier: ^2.1.11
|
||||||
version: 2.1.11(@tiptap/core@2.1.11)(@tiptap/pm@2.1.11)
|
version: 2.1.11(@tiptap/core@2.1.11)(@tiptap/pm@2.1.11)
|
||||||
'@vidstack/react':
|
'@vidstack/react':
|
||||||
specifier: ^1.1.5
|
specifier: ^1.1.7
|
||||||
version: 1.1.5(@types/react@18.2.24)(react@18.2.0)
|
version: 1.1.7(@types/react@18.2.24)(react@18.2.0)
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.10
|
specifier: ^1.11.10
|
||||||
version: 1.11.10
|
version: 1.11.10
|
||||||
@@ -122,9 +122,6 @@ dependencies:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.16.0
|
specifier: ^6.16.0
|
||||||
version: 6.16.0(react-dom@18.2.0)(react@18.2.0)
|
version: 6.16.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
react-textarea-autosize:
|
|
||||||
specifier: ^8.5.3
|
|
||||||
version: 8.5.3(@types/react@18.2.24)(react@18.2.0)
|
|
||||||
reactflow:
|
reactflow:
|
||||||
specifier: ^11.9.2
|
specifier: ^11.9.2
|
||||||
version: 11.9.2(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.9.2(@types/react@18.2.24)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -2703,8 +2700,8 @@ packages:
|
|||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@vidstack/react@1.1.5(@types/react@18.2.24)(react@18.2.0):
|
/@vidstack/react@1.1.7(@types/react@18.2.24)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-EutVuOU2b6XTryEmPU23T2Ftu3j4BuJGdGbyHoDtc15m6iL/DtpkBJCLqRVI7PaiuQtqneKcvKUNcBNogVz5KQ==}
|
resolution: {integrity: sha512-qNd3+hLBdtWqFuPe1OZetVi/F/qOKXb1ByTPc4JdCmiGaheNhpMMLf8HM/r26YnP5v86WXvKgCFDuiiMyWXGbg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^18.0.0
|
'@types/react': ^18.0.0
|
||||||
@@ -5732,20 +5729,6 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-textarea-autosize@8.5.3(@types/react@18.2.24)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
'@babel/runtime': 7.23.1
|
|
||||||
react: 18.2.0
|
|
||||||
use-composed-ref: 1.3.0(react@18.2.0)
|
|
||||||
use-latest: 1.2.1(@types/react@18.2.24)(react@18.2.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@types/react'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react@18.2.0:
|
/react@18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -6428,41 +6411,6 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-composed-ref@1.3.0(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.2.24)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.24
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-latest@1.2.1(@types/react@18.2.24)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': '*'
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.2.24
|
|
||||||
react: 18.2.0
|
|
||||||
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.24)(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-sidecar@1.1.2(@types/react@18.2.24)(react@18.2.0):
|
/use-sidecar@1.1.2(@types/react@18.2.24)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useRef } from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { VList, VListHandle } from 'virtua';
|
import { VList, VListHandle } from 'virtua';
|
||||||
|
|
||||||
import { ChatMessageForm } from '@app/chats/components/messages/form';
|
import { ChatForm } from '@app/chats/components/chatForm';
|
||||||
import { ChatMessageItem } from '@app/chats/components/messages/item';
|
import { ChatMessage } from '@app/chats/components/message';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
@@ -31,7 +31,7 @@ export function ChatScreen() {
|
|||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(message: NDKEvent) => {
|
(message: NDKEvent) => {
|
||||||
return (
|
return (
|
||||||
<ChatMessageItem
|
<ChatMessage
|
||||||
message={message}
|
message={message}
|
||||||
userPubkey={db.account.pubkey}
|
userPubkey={db.account.pubkey}
|
||||||
userPrivkey={userPrivkey}
|
userPrivkey={userPrivkey}
|
||||||
@@ -42,7 +42,7 @@ export function ChatScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.length > 0) listRef.current?.scrollToIndex(data.length);
|
if (data && data.length > 0) listRef.current?.scrollToIndex(data.length);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -68,10 +68,9 @@ export function ChatScreen() {
|
|||||||
}, [pubkey]);
|
}, [pubkey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full grid-cols-3 bg-white/10 backdrop-blur-xl">
|
<div className="h-full w-full p-3">
|
||||||
<div className="col-span-2 border-r border-white/5">
|
<div className="rounded-lg border-t border-white/5 bg-white/10 backdrop-blur-xl">
|
||||||
<div className="h-full w-full flex-1 p-3">
|
<div className="flex h-full flex-col justify-between overflow-hidden">
|
||||||
<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">
|
<div className="h-full w-full flex-1">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
@@ -93,8 +92,8 @@ export function ChatScreen() {
|
|||||||
</VList>
|
</VList>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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">
|
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 backdrop-blur-xl">
|
||||||
<ChatMessageForm
|
<ChatForm
|
||||||
receiverPubkey={pubkey}
|
receiverPubkey={pubkey}
|
||||||
userPubkey={db.account.pubkey}
|
userPubkey={db.account.pubkey}
|
||||||
userPrivkey={userPrivkey}
|
userPrivkey={userPrivkey}
|
||||||
@@ -103,6 +102,5 @@ export function ChatScreen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const ChatListItem = memo(function ChatListItem({ event }: { event: NDKEv
|
|||||||
preventScrollReset={true}
|
preventScrollReset={true}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
twMerge(
|
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
|
isActive
|
||||||
? 'border-fuchsia-500 bg-white/5 text-white'
|
? 'border-fuchsia-500 bg-white/5 text-white'
|
||||||
: 'border-transparent text-white/70'
|
: 'border-transparent text-white/70'
|
||||||
@@ -55,14 +55,10 @@ export const ChatListItem = memo(function ChatListItem({ event }: { event: NDKEv
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
style={{ contentVisibility: 'auto' }}
|
style={{ contentVisibility: 'auto' }}
|
||||||
className="h-10 w-10 rounded-lg"
|
className="h-9 w-9 rounded-lg"
|
||||||
/>
|
/>
|
||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={300}>
|
||||||
<img
|
<img src={svgURI} alt={event.pubkey} className="h-9 w-9 rounded-lg bg-white" />
|
||||||
src={svgURI}
|
|
||||||
alt={event.pubkey}
|
|
||||||
className="h-10 w-10 rounded-lg border border-white/5 bg-black"
|
|
||||||
/>
|
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { nip04 } from 'nostr-tools';
|
import { nip04 } from 'nostr-tools';
|
||||||
import { useCallback, useState } from 'react';
|
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 { EnterIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
export function ChatMessageForm({
|
export function ChatForm({
|
||||||
receiverPubkey,
|
receiverPubkey,
|
||||||
userPrivkey,
|
userPrivkey,
|
||||||
}: {
|
}: {
|
||||||
@@ -46,8 +45,10 @@ export function ChatMessageForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center justify-between rounded-md bg-white/20 px-3">
|
<div className="flex items-center gap-2">
|
||||||
<TextareaAutosize
|
<MediaUploader setState={setValue} />
|
||||||
|
<div className="flex w-full items-center justify-between rounded-full bg-white/20 px-3">
|
||||||
|
<input
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onKeyDown={handleEnterPress}
|
onKeyDown={handleEnterPress}
|
||||||
@@ -56,14 +57,12 @@ export function ChatMessageForm({
|
|||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
placeholder="Message"
|
placeholder="Message"
|
||||||
className="min-h-[44px] flex-1 resize-none bg-transparent py-3 text-white !outline-none placeholder:text-white"
|
className="h-10 flex-1 resize-none bg-transparent px-3 text-white placeholder:text-white/80 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
<div className="inline-flex items-center gap-2">
|
|
||||||
<MediaUploader setState={setValue} />
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={submit}
|
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" />
|
<EnterIcon className="h-5 w-5" />
|
||||||
Send
|
Send
|
||||||
@@ -29,12 +29,12 @@ export function MediaUploader({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => uploadMedia()}
|
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 ? (
|
{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>
|
</button>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
54
src/app/chats/components/message.tsx
Normal file
54
src/app/chats/components/message.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -3,16 +3,13 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import { ChatListItem } from '@app/chats/components/item';
|
import { ChatListItem } from '@app/chats/components/chaListItem';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
export function ChatsScreen() {
|
export function ChatsScreen() {
|
||||||
const { db } = useStorage();
|
|
||||||
const { getAllNIP04Chats } = useNostr();
|
const { getAllNIP04Chats } = useNostr();
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['nip04-chats'],
|
['nip04-chats'],
|
||||||
@@ -24,9 +21,7 @@ export function ChatsScreen() {
|
|||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(event: NDKEvent) => {
|
(event: NDKEvent) => {
|
||||||
if (db.account.pubkey !== event.pubkey) {
|
|
||||||
return <ChatListItem key={event.id} event={event} />;
|
return <ChatListItem key={event.id} event={event} />;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
@@ -34,7 +29,10 @@ export function ChatsScreen() {
|
|||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full grid-cols-3">
|
<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="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">
|
<div className="flex h-full flex-col gap-1 py-2">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center pb-16">
|
<div className="flex h-full w-full items-center justify-center pb-16">
|
||||||
|
|||||||
@@ -50,15 +50,15 @@ input::-ms-clear {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
@apply prose prose-white max-w-none select-text whitespace-pre-line text-white prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-0 prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
@apply prose prose-white max-w-none select-text text-white prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-0 prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-article {
|
.markdown-article {
|
||||||
@apply prose prose-white max-w-none select-text whitespace-pre-line text-white/80 prose-headings:mb-1 prose-headings:mt-3 prose-headings:text-white prose-p:mb-2 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
@apply prose prose-white max-w-none select-text text-white/80 prose-headings:mb-1 prose-headings:mt-3 prose-headings:text-white prose-p:mb-2 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-simple {
|
.markdown-simple {
|
||||||
@apply prose prose-white max-w-none select-text hyphens-auto whitespace-pre-line text-white/70 prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-p:last:mb-0 prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
@apply prose prose-white max-w-none select-text hyphens-auto text-white/70 prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-p:last:mb-0 prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror p.is-empty::before {
|
.ProseMirror p.is-empty::before {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { message } from '@tauri-apps/api/dialog';
|
|||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import CurrencyInput from 'react-currency-input-field';
|
import CurrencyInput from 'react-currency-input-field';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
|
||||||
|
|
||||||
import { CancelIcon, ZapIcon } from '@shared/icons';
|
import { CancelIcon, ZapIcon } from '@shared/icons';
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
<ZapIcon className="h-5 w-5 text-white/80 group-hover:text-orange-400" />
|
<ZapIcon className="h-5 w-5 text-white/80 group-hover:text-orange-400" />
|
||||||
</button>
|
</button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal className="relative z-10">
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-2xl" />
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-2xl" />
|
||||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-white/10 backdrop-blur-xl">
|
<div className="relative h-min w-full max-w-xl rounded-xl bg-white/10 backdrop-blur-xl">
|
||||||
@@ -171,7 +170,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex w-full flex-col gap-2">
|
<div className="mt-4 flex w-full flex-col gap-2">
|
||||||
<TextareaAutosize
|
<input
|
||||||
name="zapMessage"
|
name="zapMessage"
|
||||||
value={zapMessage}
|
value={zapMessage}
|
||||||
onChange={(e) => setZapMessage(e.target.value)}
|
onChange={(e) => setZapMessage(e.target.value)}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { MediaPlayer, MediaProvider } from '@vidstack/react';
|
import { MediaPlayer, MediaProvider, Poster } from '@vidstack/react';
|
||||||
|
import {
|
||||||
|
DefaultAudioLayout,
|
||||||
|
DefaultVideoLayout,
|
||||||
|
defaultLayoutIcons,
|
||||||
|
} from '@vidstack/react/player/layouts/default';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Image } from '@shared/image';
|
import { Image } from '@shared/image';
|
||||||
@@ -31,9 +36,27 @@ export function FileNote(props: { event?: NDKEvent }) {
|
|||||||
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
|
poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`}
|
||||||
load="visible"
|
load="visible"
|
||||||
aspectRatio="16/9"
|
aspectRatio="16/9"
|
||||||
|
muted={true}
|
||||||
crossorigin=""
|
crossorigin=""
|
||||||
|
className="player"
|
||||||
>
|
>
|
||||||
<MediaProvider />
|
<MediaProvider>
|
||||||
|
<Poster
|
||||||
|
className="vds-poster"
|
||||||
|
src="https://thumbnail.video/api/get?url=${url}&seconds=1"
|
||||||
|
alt={url}
|
||||||
|
/>
|
||||||
|
</MediaProvider>
|
||||||
|
<DefaultAudioLayout
|
||||||
|
icons={defaultLayoutIcons}
|
||||||
|
smallLayoutWhen="(width < 500) or (height < 380)"
|
||||||
|
noModal={true}
|
||||||
|
/>
|
||||||
|
<DefaultVideoLayout
|
||||||
|
icons={defaultLayoutIcons}
|
||||||
|
smallLayoutWhen="(width < 500) or (height < 380)"
|
||||||
|
noModal={true}
|
||||||
|
/>
|
||||||
</MediaPlayer>
|
</MediaPlayer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
const setWidget = useWidgets((state) => state.setWidget);
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@@ -24,9 +24,9 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
content: tag.replace('#', ''),
|
content: tag.replace('#', ''),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="break-words text-fuchsia-400 hover:text-fuchsia-500"
|
className="break-all text-fuchsia-400 hover:text-fuchsia-500"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export function VideoPreview({ urls }: { urls: string[] }) {
|
|||||||
load="visible"
|
load="visible"
|
||||||
aspectRatio="16/9"
|
aspectRatio="16/9"
|
||||||
crossorigin=""
|
crossorigin=""
|
||||||
|
muted={true}
|
||||||
className="player"
|
className="player"
|
||||||
>
|
>
|
||||||
<MediaProvider>
|
<MediaProvider>
|
||||||
@@ -24,8 +25,16 @@ export function VideoPreview({ urls }: { urls: string[] }) {
|
|||||||
alt={url}
|
alt={url}
|
||||||
/>
|
/>
|
||||||
</MediaProvider>
|
</MediaProvider>
|
||||||
<DefaultAudioLayout icons={defaultLayoutIcons} />
|
<DefaultAudioLayout
|
||||||
<DefaultVideoLayout icons={defaultLayoutIcons} />
|
icons={defaultLayoutIcons}
|
||||||
|
smallLayoutWhen="(width < 500) or (height < 380)"
|
||||||
|
noModal={true}
|
||||||
|
/>
|
||||||
|
<DefaultVideoLayout
|
||||||
|
icons={defaultLayoutIcons}
|
||||||
|
smallLayoutWhen="(width < 500) or (height < 380)"
|
||||||
|
noModal={true}
|
||||||
|
/>
|
||||||
</MediaPlayer>
|
</MediaPlayer>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user