|
|
|
|
@ -1,7 +1,9 @@
@@ -1,7 +1,9 @@
|
|
|
|
|
import { Command, CommandInput, CommandItem, CommandList } from '@/components/ui/command' |
|
|
|
|
import { Textarea } from '@/components/ui/textarea' |
|
|
|
|
import { useToast } from '@/hooks' |
|
|
|
|
import { pubkeyToNpub } from '@/lib/pubkey' |
|
|
|
|
import { cn } from '@/lib/utils' |
|
|
|
|
import { useMediaUploadService } from '@/providers/MediaUploadServiceProvider' |
|
|
|
|
import client from '@/services/client.service' |
|
|
|
|
import { TProfile } from '@/types' |
|
|
|
|
import React, { |
|
|
|
|
@ -18,22 +20,27 @@ import { SimpleUserAvatar } from '../UserAvatar'
@@ -18,22 +20,27 @@ import { SimpleUserAvatar } from '../UserAvatar'
|
|
|
|
|
import { SimpleUsername } from '../Username' |
|
|
|
|
import { getCurrentWord, replaceWord } from './utils' |
|
|
|
|
|
|
|
|
|
export default function TextareaWithMentions({ |
|
|
|
|
export default function PostTextarea({ |
|
|
|
|
textValue, |
|
|
|
|
setTextValue, |
|
|
|
|
cursorOffset = 0, |
|
|
|
|
onUploadImage, |
|
|
|
|
...props |
|
|
|
|
}: ComponentProps<'textarea'> & { |
|
|
|
|
textValue: string |
|
|
|
|
setTextValue: Dispatch<SetStateAction<string>> |
|
|
|
|
cursorOffset?: number |
|
|
|
|
onUploadImage?: ({ url, tags }: { url: string; tags: string[][] }) => void |
|
|
|
|
}) { |
|
|
|
|
const { toast } = useToast() |
|
|
|
|
const textareaRef = useRef<HTMLTextAreaElement>(null) |
|
|
|
|
const dropdownRef = useRef<HTMLDivElement>(null) |
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null) |
|
|
|
|
const { upload } = useMediaUploadService() |
|
|
|
|
const [commandValue, setCommandValue] = useState('') |
|
|
|
|
const [debouncedCommandValue, setDebouncedCommandValue] = useState(commandValue) |
|
|
|
|
const [profiles, setProfiles] = useState<TProfile[]>([]) |
|
|
|
|
const [dragover, setDragover] = useState(false) |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (textareaRef.current && cursorOffset !== 0) { |
|
|
|
|
@ -132,7 +139,7 @@ export default function TextareaWithMentions({
@@ -132,7 +139,7 @@ export default function TextareaWithMentions({
|
|
|
|
|
const textarea = textareaRef.current |
|
|
|
|
const dropdown = dropdownRef.current |
|
|
|
|
if (textarea && dropdown) { |
|
|
|
|
replaceWord(textarea, `${value}`) |
|
|
|
|
replaceWord(textarea, `${value} `) |
|
|
|
|
setCommandValue('') |
|
|
|
|
dropdown.classList.add('hidden') |
|
|
|
|
} |
|
|
|
|
@ -170,9 +177,73 @@ export default function TextareaWithMentions({
@@ -170,9 +177,73 @@ export default function TextareaWithMentions({
|
|
|
|
|
} |
|
|
|
|
}, [handleBlur, handleKeyDown, handleMouseDown, handleSectionChange]) |
|
|
|
|
|
|
|
|
|
const uploadImages = async (files: File[]) => { |
|
|
|
|
for (const file of files) { |
|
|
|
|
if (file.type.startsWith('image/') || file.type.startsWith('video/')) { |
|
|
|
|
const placeholder = `[Uploading "${file.name}"...]` |
|
|
|
|
if (textValue.includes(placeholder)) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
setTextValue((prev) => (prev === '' ? placeholder : `${prev}\n${placeholder}`)) |
|
|
|
|
try { |
|
|
|
|
const result = await upload(file) |
|
|
|
|
setTextValue((prev) => { |
|
|
|
|
if (prev.includes(placeholder)) { |
|
|
|
|
return prev.replace(placeholder, result.url) |
|
|
|
|
} else { |
|
|
|
|
return prev + `\n${result.url}` |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
onUploadImage?.(result) |
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('Error uploading file', error) |
|
|
|
|
toast({ |
|
|
|
|
variant: 'destructive', |
|
|
|
|
title: 'Failed to upload file', |
|
|
|
|
description: (error as Error).message |
|
|
|
|
}) |
|
|
|
|
setTextValue((prev) => prev.replace(placeholder, '')) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handlePast = async (e: React.ClipboardEvent<HTMLTextAreaElement>) => { |
|
|
|
|
await uploadImages( |
|
|
|
|
Array.from(e.clipboardData.items) |
|
|
|
|
.map((item) => item.getAsFile()) |
|
|
|
|
.filter(Boolean) as File[] |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleDrop = async (e: React.DragEvent<HTMLTextAreaElement>) => { |
|
|
|
|
e.preventDefault() |
|
|
|
|
setDragover(false) |
|
|
|
|
await uploadImages(Array.from(e.dataTransfer.files)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className="relative w-full"> |
|
|
|
|
<Textarea {...props} ref={textareaRef} value={textValue} onChange={onTextValueChange} /> |
|
|
|
|
<div |
|
|
|
|
className={cn( |
|
|
|
|
'relative w-full', |
|
|
|
|
dragover && 'outline-2 outline-offset-4 outline-dashed outline-border rounded-md' |
|
|
|
|
)} |
|
|
|
|
> |
|
|
|
|
<Textarea |
|
|
|
|
{...props} |
|
|
|
|
ref={textareaRef} |
|
|
|
|
value={textValue} |
|
|
|
|
onChange={onTextValueChange} |
|
|
|
|
onPaste={handlePast} |
|
|
|
|
onDrop={handleDrop} |
|
|
|
|
onDragOver={(e) => { |
|
|
|
|
e.preventDefault() |
|
|
|
|
setDragover(true) |
|
|
|
|
}} |
|
|
|
|
onDragLeave={() => { |
|
|
|
|
setDragover(false) |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<Command |
|
|
|
|
ref={dropdownRef} |
|
|
|
|
className={cn( |