feat: support media files upload via paste and drop
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Note from '@/components/Note'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { createCommentDraftEvent, createShortTextNoteDraftEvent } from '@/lib/draft-event'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
@@ -9,7 +10,7 @@ import { ChevronDown, ImageUp, LoaderCircle } from 'lucide-react'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TextareaWithMentions from '../TextareaWithMentions'
|
||||
import PostTextarea from '../PostTextarea'
|
||||
import Mentions from './Mentions'
|
||||
import PostOptions from './PostOptions'
|
||||
import Preview from './Preview'
|
||||
@@ -123,14 +124,29 @@ export default function NormalPostContent({
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
<TextareaWithMentions
|
||||
className="h-32"
|
||||
setTextValue={setContent}
|
||||
textValue={content}
|
||||
placeholder={t('Write something...')}
|
||||
cursorOffset={cursorOffset}
|
||||
/>
|
||||
{processedContent && <Preview content={processedContent} />}
|
||||
<Tabs defaultValue="edit" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="edit">{t('Edit')}</TabsTrigger>
|
||||
<TabsTrigger value="preview">{t('Preview')}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="edit">
|
||||
<PostTextarea
|
||||
className="h-52"
|
||||
setTextValue={setContent}
|
||||
textValue={content}
|
||||
placeholder={
|
||||
t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')'
|
||||
}
|
||||
cursorOffset={cursorOffset}
|
||||
onUploadImage={({ url, tags }) => {
|
||||
setPictureInfos((prev) => [...prev, { url, tags }])
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="preview">
|
||||
<Preview content={processedContent} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<SendOnlyToSwitch
|
||||
parentEvent={parentEvent}
|
||||
specifiedRelayUrls={specifiedRelayUrls}
|
||||
@@ -141,7 +157,7 @@ export default function NormalPostContent({
|
||||
<Uploader
|
||||
onUploadSuccess={({ url, tags }) => {
|
||||
setPictureInfos((prev) => [...prev, { url, tags }])
|
||||
setContent((prev) => `${prev}\n${url}`)
|
||||
setContent((prev) => (prev === '' ? url : `${prev}\n${url}`))
|
||||
}}
|
||||
onUploadingChange={setUploadingPicture}
|
||||
accept="image/*,video/*,audio/*"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ChevronDown, Loader, LoaderCircle, Plus, X } from 'lucide-react'
|
||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Image from '../Image'
|
||||
import TextareaWithMentions from '../TextareaWithMentions'
|
||||
import PostTextarea from '../PostTextarea'
|
||||
import Mentions from './Mentions'
|
||||
import PostOptions from './PostOptions'
|
||||
import SendOnlyToSwitch from './SendOnlyToSwitch'
|
||||
@@ -105,7 +105,7 @@ export default function PicturePostContent({ close }: { close: () => void }) {
|
||||
{t('A special note for picture-first clients like Olas')}
|
||||
</div>
|
||||
<PictureUploader pictureInfos={pictureInfos} setPictureInfos={setPictureInfos} />
|
||||
<TextareaWithMentions
|
||||
<PostTextarea
|
||||
className="h-32"
|
||||
setTextValue={setContent}
|
||||
textValue={content}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Content from '../Content'
|
||||
|
||||
export default function Preview({ content }: { content: string }) {
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<Card className="p-3 min-h-52">
|
||||
<Content
|
||||
event={{
|
||||
content,
|
||||
@@ -15,7 +15,7 @@ export default function Preview({ content }: { content: string }) {
|
||||
pubkey: '',
|
||||
sig: ''
|
||||
}}
|
||||
className="pointer-events-none"
|
||||
className="pointer-events-none h-full"
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -20,14 +20,15 @@ export default function Uploader({
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
if (!event.target.files) return
|
||||
|
||||
onUploadingChange?.(true)
|
||||
try {
|
||||
onUploadingChange?.(true)
|
||||
const result = await upload(file)
|
||||
console.log('File uploaded successfully', result)
|
||||
onUploadSuccess(result)
|
||||
for (const file of event.target.files) {
|
||||
const result = await upload(file)
|
||||
console.log('File uploaded successfully', result)
|
||||
onUploadSuccess(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading file', error)
|
||||
toast({
|
||||
@@ -59,6 +60,7 @@ export default function Uploader({
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileChange}
|
||||
accept={accept}
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,14 +6,17 @@ import {
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle
|
||||
} from '@/components/ui/sheet'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { Dispatch, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../ui/sheet'
|
||||
import NormalPostContent from './NormalPostContent'
|
||||
import PicturePostContent from './PicturePostContent'
|
||||
import Title from './Title'
|
||||
|
||||
export default function PostEditor({
|
||||
@@ -27,35 +30,17 @@ export default function PostEditor({
|
||||
open: boolean
|
||||
setOpen: Dispatch<boolean>
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
|
||||
const content = useMemo(() => {
|
||||
return parentEvent || defaultContent ? (
|
||||
return (
|
||||
<NormalPostContent
|
||||
defaultContent={defaultContent}
|
||||
parentEvent={parentEvent}
|
||||
close={() => setOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
<Tabs defaultValue="normal" className="space-y-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="normal">{t('Normal Note')}</TabsTrigger>
|
||||
<TabsTrigger value="picture">{t('Picture Note')}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="normal">
|
||||
<NormalPostContent
|
||||
defaultContent={defaultContent}
|
||||
parentEvent={parentEvent}
|
||||
close={() => setOpen(false)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="picture">
|
||||
<PicturePostContent close={() => setOpen(false)} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
}, [parentEvent, defaultContent])
|
||||
}, [])
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user