diff --git a/package.json b/package.json index fa225e38..80ee335a 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@tiptap/react": "^2.1.11", "@tiptap/starter-kit": "^2.1.11", "@tiptap/suggestion": "^2.1.11", - "@vidstack/react": "^1.1.5", + "@vidstack/react": "^1.1.7", "dayjs": "^1.11.10", "destr": "^2.0.1", "html-to-text": "^9.0.5", @@ -57,7 +57,6 @@ "react-hook-form": "^7.47.0", "react-markdown": "^8.0.7", "react-router-dom": "^6.16.0", - "react-textarea-autosize": "^8.5.3", "reactflow": "^11.9.2", "remark-gfm": "^3.0.1", "tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03f698a9..f904528b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ dependencies: specifier: ^2.1.11 version: 2.1.11(@tiptap/core@2.1.11)(@tiptap/pm@2.1.11) '@vidstack/react': - specifier: ^1.1.5 - version: 1.1.5(@types/react@18.2.24)(react@18.2.0) + specifier: ^1.1.7 + version: 1.1.7(@types/react@18.2.24)(react@18.2.0) dayjs: specifier: ^1.11.10 version: 1.11.10 @@ -122,9 +122,6 @@ dependencies: react-router-dom: specifier: ^6.16.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: specifier: ^11.9.2 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 dev: true - /@vidstack/react@1.1.5(@types/react@18.2.24)(react@18.2.0): - resolution: {integrity: sha512-EutVuOU2b6XTryEmPU23T2Ftu3j4BuJGdGbyHoDtc15m6iL/DtpkBJCLqRVI7PaiuQtqneKcvKUNcBNogVz5KQ==} + /@vidstack/react@1.1.7(@types/react@18.2.24)(react@18.2.0): + resolution: {integrity: sha512-qNd3+hLBdtWqFuPe1OZetVi/F/qOKXb1ByTPc4JdCmiGaheNhpMMLf8HM/r26YnP5v86WXvKgCFDuiiMyWXGbg==} engines: {node: '>=18'} peerDependencies: '@types/react': ^18.0.0 @@ -5732,20 +5729,6 @@ packages: tslib: 2.6.2 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: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -6428,41 +6411,6 @@ packages: tslib: 2.6.2 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): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/src/app/chats/chat.tsx b/src/app/chats/chat.tsx index 8d9d504c..f9d15696 100644 --- a/src/app/chats/chat.tsx +++ b/src/app/chats/chat.tsx @@ -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 ( - { - 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 ( -
-
-
-
-
- {status === 'loading' ? ( -
-
- -

Loading messages

-
+
+
+
+
+ {status === 'loading' ? ( +
+
+ +

Loading messages

- ) : data.length === 0 ? ( -
-

🙌

-

- You two didn't talk yet, let's send first message -

-
- ) : ( - - {data.map((message) => renderItem(message))} - - )} -
-
- -
+
+ ) : data.length === 0 ? ( +
+

🙌

+

+ You two didn't talk yet, let's send first message +

+
+ ) : ( + + {data.map((message) => renderItem(message))} + + )} +
+
+
diff --git a/src/app/chats/components/item.tsx b/src/app/chats/components/chaListItem.tsx similarity index 90% rename from src/app/chats/components/item.tsx rename to src/app/chats/components/chaListItem.tsx index db1c7227..e818cb4f 100644 --- a/src/app/chats/components/item.tsx +++ b/src/app/chats/components/chaListItem.tsx @@ -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" /> - {event.pubkey} + {event.pubkey}
diff --git a/src/app/chats/components/messages/form.tsx b/src/app/chats/components/chatForm.tsx similarity index 57% rename from src/app/chats/components/messages/form.tsx rename to src/app/chats/components/chatForm.tsx index cdeb9844..89838e4c 100644 --- a/src/app/chats/components/messages/form.tsx +++ b/src/app/chats/components/chatForm.tsx @@ -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 ( -
- 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" - /> -
- +
+ +
+ 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" + /> diff --git a/src/app/chats/components/message.tsx b/src/app/chats/components/message.tsx new file mode 100644 index 00000000..58e1f071 --- /dev/null +++ b/src/app/chats/components/message.tsx @@ -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 ( +
+
+ +
+
+ {!richContent ? ( +

Decrypting...

+ ) : ( +
+

+ {richContent.parsed} +

+
+ {richContent.images.length > 0 && ( + + )} + {richContent.videos.length > 0 && ( + + )} + {richContent.links.length > 0 && } + {richContent.notes.length > 0 && + richContent.notes.map((note: string) => ( + + ))} +
+
+ )} +
+
+
+ ); +} diff --git a/src/app/chats/components/messages/item.tsx b/src/app/chats/components/messages/item.tsx deleted file mode 100644 index 94abb01f..00000000 --- a/src/app/chats/components/messages/item.tsx +++ /dev/null @@ -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 ( -
-
- -
-
- -
-
-
- ); -} diff --git a/src/app/chats/index.tsx b/src/app/chats/index.tsx index 8f3690f1..d2316e57 100644 --- a/src/app/chats/index.tsx +++ b/src/app/chats/index.tsx @@ -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 ; - } + return ; }, [data] ); @@ -34,7 +29,10 @@ export function ChatsScreen() { return (
-
+
{status === 'loading' ? (
diff --git a/src/index.css b/src/index.css index ec134231..6d57156e 100644 --- a/src/index.css +++ b/src/index.css @@ -50,15 +50,15 @@ input::-ms-clear { } .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 { - @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 { - @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 { diff --git a/src/shared/notes/actions/zap.tsx b/src/shared/notes/actions/zap.tsx index c4c219b7..76d52877 100644 --- a/src/shared/notes/actions/zap.tsx +++ b/src/shared/notes/actions/zap.tsx @@ -5,7 +5,6 @@ import { message } from '@tauri-apps/api/dialog'; import { QRCodeSVG } from 'qrcode.react'; import { useEffect, useRef, useState } from 'react'; import CurrencyInput from 'react-currency-input-field'; -import TextareaAutosize from 'react-textarea-autosize'; import { CancelIcon, ZapIcon } from '@shared/icons'; @@ -99,7 +98,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) { - +
@@ -171,7 +170,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
- setZapMessage(e.target.value)} diff --git a/src/shared/notes/kinds/file.tsx b/src/shared/notes/kinds/file.tsx index 200fed88..866ae0cf 100644 --- a/src/shared/notes/kinds/file.tsx +++ b/src/shared/notes/kinds/file.tsx @@ -1,5 +1,10 @@ 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 { Image } from '@shared/image'; @@ -31,9 +36,27 @@ export function FileNote(props: { event?: NDKEvent }) { poster={`https://thumbnail.video/api/get?url=${url}&seconds=1`} load="visible" aspectRatio="16/9" + muted={true} crossorigin="" + className="player" > - + + + + +
); diff --git a/src/shared/notes/mentions/hashtag.tsx b/src/shared/notes/mentions/hashtag.tsx index 5c95bd9e..29422466 100644 --- a/src/shared/notes/mentions/hashtag.tsx +++ b/src/shared/notes/mentions/hashtag.tsx @@ -7,7 +7,7 @@ export function Hashtag({ tag }: { tag: string }) { const setWidget = useWidgets((state) => state.setWidget); return ( - @@ -24,9 +24,9 @@ export function Hashtag({ tag }: { tag: string }) { content: tag.replace('#', ''), }) } - className="break-words text-fuchsia-400 hover:text-fuchsia-500" + className="break-all text-fuchsia-400 hover:text-fuchsia-500" > {tag} - +
); } diff --git a/src/shared/notes/preview/video.tsx b/src/shared/notes/preview/video.tsx index 089820f7..c99ecf46 100644 --- a/src/shared/notes/preview/video.tsx +++ b/src/shared/notes/preview/video.tsx @@ -15,6 +15,7 @@ export function VideoPreview({ urls }: { urls: string[] }) { load="visible" aspectRatio="16/9" crossorigin="" + muted={true} className="player" > @@ -24,8 +25,16 @@ export function VideoPreview({ urls }: { urls: string[] }) { alt={url} /> - - + + ))}