mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-23 15:04:09 +02:00
add image upload button to reply form
This commit is contained in:
parent
8dda939ca2
commit
d0e58de8a5
5
.changeset/twelve-bananas-begin.md
Normal file
5
.changeset/twelve-bananas-begin.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add image upload button to reply form
|
@ -57,10 +57,10 @@ export default function PostModal({
|
|||||||
watch("nsfwReason");
|
watch("nsfwReason");
|
||||||
|
|
||||||
const textAreaRef = useRef<RefType | null>(null);
|
const textAreaRef = useRef<RefType | null>(null);
|
||||||
|
|
||||||
const imageUploadRef = useRef<HTMLInputElement | null>(null);
|
const imageUploadRef = useRef<HTMLInputElement | null>(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const uploadImage = async (imageFile: File) => {
|
const uploadImage = useCallback(
|
||||||
|
async (imageFile: File) => {
|
||||||
try {
|
try {
|
||||||
if (!imageFile.type.includes("image")) throw new Error("Only images are supported");
|
if (!imageFile.type.includes("image")) throw new Error("Only images are supported");
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
@ -77,7 +77,9 @@ export default function PostModal({
|
|||||||
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
if (e instanceof Error) toast({ description: e.message, status: "error" });
|
||||||
}
|
}
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
};
|
},
|
||||||
|
[setValue, getValues],
|
||||||
|
);
|
||||||
|
|
||||||
const getDraft = useCallback(() => {
|
const getDraft = useCallback(() => {
|
||||||
const { content, nsfw, nsfwReason } = getValues();
|
const { content, nsfw, nsfwReason } = getValues();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { Box, Button, ButtonGroup, Flex, useToast } from "@chakra-ui/react";
|
import { Box, Button, ButtonGroup, Flex, IconButton, VisuallyHiddenInput, useToast } from "@chakra-ui/react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Kind } from "nostr-tools";
|
import { Kind } from "nostr-tools";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@ -20,10 +20,12 @@ import { useSigningContext } from "../../../providers/signing-provider";
|
|||||||
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
import { useWriteRelayUrls } from "../../../hooks/use-client-relays";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import { unique } from "../../../helpers/array";
|
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 { useContextEmojis } from "../../../providers/emoji-provider";
|
||||||
import UserDirectoryProvider from "../../../providers/user-directory-provider";
|
import UserDirectoryProvider from "../../../providers/user-directory-provider";
|
||||||
import { TrustProvider } from "../../../providers/trust";
|
import { TrustProvider } from "../../../providers/trust";
|
||||||
|
import { nostrBuildUploadImage } from "../../../helpers/nostr-build";
|
||||||
|
import { ImageIcon } from "../../../components/icons";
|
||||||
|
|
||||||
export type ReplyFormProps = {
|
export type ReplyFormProps = {
|
||||||
item: ThreadItem;
|
item: ThreadItem;
|
||||||
@ -49,6 +51,31 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
|
|||||||
|
|
||||||
watch("content");
|
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(() => {
|
const draft = useMemo(() => {
|
||||||
let updated = finalizeNote({ kind: Kind.Text, content: getValues().content, created_at: dayjs().unix(), tags: [] });
|
let updated = finalizeNote({ kind: Kind.Text, content: getValues().content, created_at: dayjs().unix(), tags: [] });
|
||||||
updated = createEmojiTags(updated, emojis);
|
updated = createEmojiTags(updated, emojis);
|
||||||
@ -70,16 +97,47 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserDirectoryProvider getDirectory={() => threadMembers}>
|
<UserDirectoryProvider getDirectory={() => threadMembers}>
|
||||||
<Flex as="form" direction="column" gap="2" onSubmit={submit}>
|
<Flex as="form" direction="column" gap="2" pb="4" onSubmit={submit}>
|
||||||
<MagicTextArea
|
<MagicTextArea
|
||||||
placeholder="Reply"
|
placeholder="Reply"
|
||||||
autoFocus
|
autoFocus
|
||||||
mb="2"
|
mb="2"
|
||||||
rows={5}
|
rows={4}
|
||||||
isRequired
|
isRequired
|
||||||
value={getValues().content}
|
value={getValues().content}
|
||||||
onChange={(e) => setValue("content", e.target.value)}
|
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 && (
|
{getValues().content.length > 0 && (
|
||||||
<Box p="2" borderWidth={1} borderRadius="md" mb="2">
|
<Box p="2" borderWidth={1} borderRadius="md" mb="2">
|
||||||
<TrustProvider trust>
|
<TrustProvider trust>
|
||||||
@ -87,15 +145,6 @@ export default function ReplyForm({ item, onCancel, onSubmitted }: ReplyFormProp
|
|||||||
</TrustProvider>
|
</TrustProvider>
|
||||||
</Box>
|
</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>
|
</Flex>
|
||||||
</UserDirectoryProvider>
|
</UserDirectoryProvider>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user