add image upload button to reply form

This commit is contained in:
hzrd149 2023-09-23 18:11:26 -05:00
parent 8dda939ca2
commit d0e58de8a5
3 changed files with 87 additions and 31 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add image upload button to reply form

View File

@ -57,27 +57,29 @@ export default function PostModal({
watch("nsfwReason");
const textAreaRef = useRef<RefType | null>(null);
const imageUploadRef = useRef<HTMLInputElement | null>(null);
const [uploading, setUploading] = useState(false);
const uploadImage = async (imageFile: File) => {
try {
if (!imageFile.type.includes("image")) throw new Error("Only images are supported");
setUploading(true);
const uploadImage = useCallback(
async (imageFile: File) => {
try {
if (!imageFile.type.includes("image")) throw new Error("Only images are supported");
setUploading(true);
const response = await nostrBuildUploadImage(imageFile, requestSignature);
const imageUrl = response.url;
const response = await nostrBuildUploadImage(imageFile, requestSignature);
const imageUrl = response.url;
const content = getValues().content;
const position = textAreaRef.current?.getCaretPosition();
if (position !== undefined) {
setValue("content", content.slice(0, position) + imageUrl + content.slice(position));
} else setValue("content", content + imageUrl);
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
}
setUploading(false);
};
const content = getValues().content;
const position = textAreaRef.current?.getCaretPosition();
if (position !== undefined) {
setValue("content", content.slice(0, position) + imageUrl + content.slice(position));
} else setValue("content", content + imageUrl);
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
}
setUploading(false);
},
[setValue, getValues],
);
const getDraft = useCallback(() => {
const { content, nsfw, nsfwReason } = getValues();

View File

@ -1,5 +1,5 @@
import { useMemo } from "react";
import { Box, Button, ButtonGroup, Flex, useToast } from "@chakra-ui/react";
import { useCallback, useMemo, useRef, useState } from "react";
import { Box, Button, ButtonGroup, Flex, IconButton, VisuallyHiddenInput, useToast } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { Kind } from "nostr-tools";
import dayjs from "dayjs";
@ -20,10 +20,12 @@ import { useSigningContext } from "../../../providers/signing-provider";
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
import NostrPublishAction from "../../../classes/nostr-publish-action";
import { unique } from "../../../helpers/array";
import MagicTextArea from "../../../components/magic-textarea";
import MagicTextArea, { RefType } from "../../../components/magic-textarea";
import { useContextEmojis } from "../../../providers/emoji-provider";
import UserDirectoryProvider from "../../../providers/user-directory-provider";
import { TrustProvider } from "../../../providers/trust";
import { nostrBuildUploadImage } from "../../../helpers/nostr-build";
import { ImageIcon } from "../../../components/icons";
export type ReplyFormProps = {
item: ThreadItem;
@ -49,6 +51,31 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
watch("content");
const textAreaRef = useRef<RefType | null>(null);
const imageUploadRef = useRef<HTMLInputElement | null>(null);
const [uploading, setUploading] = useState(false);
const uploadImage = useCallback(
async (imageFile: File) => {
try {
if (!imageFile.type.includes("image")) throw new Error("Only images are supported");
setUploading(true);
const response = await nostrBuildUploadImage(imageFile, requestSignature);
const imageUrl = response.url;
const content = getValues().content;
const position = textAreaRef.current?.getCaretPosition();
if (position !== undefined) {
setValue("content", content.slice(0, position) + imageUrl + content.slice(position));
} else setValue("content", content + imageUrl);
} catch (e) {
if (e instanceof Error) toast({ description: e.message, status: "error" });
}
setUploading(false);
},
[setValue, getValues],
);
const draft = useMemo(() => {
let updated = finalizeNote({ kind: Kind.Text, content: getValues().content, created_at: dayjs().unix(), tags: [] });
updated = createEmojiTags(updated, emojis);
@ -70,16 +97,47 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
return (
<UserDirectoryProvider getDirectory={() => threadMembers}>
<Flex as="form" direction="column" gap="2" onSubmit={submit}>
<Flex as="form" direction="column" gap="2" pb="4" onSubmit={submit}>
<MagicTextArea
placeholder="Reply"
autoFocus
mb="2"
rows={5}
rows={4}
isRequired
value={getValues().content}
onChange={(e) => setValue("content", e.target.value)}
instanceRef={(inst) => (textAreaRef.current = inst)}
onPaste={(e) => {
const imageFile = Array.from(e.clipboardData.files).find((f) => f.type.includes("image"));
if (imageFile) uploadImage(imageFile);
}}
/>
<Flex gap="2" alignItems="center">
<VisuallyHiddenInput
type="file"
accept="image/*"
ref={imageUploadRef}
onChange={(e) => {
const img = e.target.files?.[0];
if (img) uploadImage(img);
}}
/>
<IconButton
icon={<ImageIcon />}
aria-label="Upload Image"
title="Upload Image"
onClick={() => imageUploadRef.current?.click()}
isLoading={uploading}
size="sm"
/>
<UserAvatarStack label="Notify" pubkeys={notifyPubkeys} />
<ButtonGroup size="sm" ml="auto">
<Button onClick={onCancel}>Cancel</Button>
<Button type="submit" colorScheme="brand" size="sm">
Submit
</Button>
</ButtonGroup>
</Flex>
{getValues().content.length > 0 && (
<Box p="2" borderWidth={1} borderRadius="md" mb="2">
<TrustProvider trust>
@ -87,15 +145,6 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
</TrustProvider>
</Box>
)}
<Flex gap="2" alignItems="center">
<ButtonGroup size="sm">
<Button onClick={onCancel}>Cancel</Button>
</ButtonGroup>
<UserAvatarStack label="Notify" pubkeys={notifyPubkeys} />
<Button type="submit" colorScheme="brand" size="sm" ml="auto">
Submit
</Button>
</Flex>
</Flex>
</UserDirectoryProvider>
);