mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 12:49:29 +02:00
save reply and DM drafts
This commit is contained in:
parent
8f963ec55e
commit
5b842081a3
@ -78,7 +78,7 @@
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-force-graph-2d": "^1.25.4",
|
||||
"react-force-graph-3d": "^1.24.2",
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-hook-form": "^7.51.5",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-mosaic-component": "^6.1.0",
|
||||
"react-photo-album": "^2.3.0",
|
||||
|
@ -111,7 +111,7 @@ export default function PostModal({
|
||||
watch("difficulty");
|
||||
|
||||
// cache form to localStorage
|
||||
useCacheForm<FormValues>(cacheFormKey, getValues, setValue, formState);
|
||||
useCacheForm<FormValues>(cacheFormKey, getValues, reset, formState);
|
||||
|
||||
const imageUploadRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
|
@ -1,63 +1,78 @@
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { FieldValues, UseFormGetValues, UseFormSetValue, UseFormStateReturn } from "react-hook-form";
|
||||
import { useMount, useUnmount } from "react-use";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { useTimeout } from "@chakra-ui/react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { FieldValues, UseFormGetValues, UseFormReset, UseFormStateReturn } from "react-hook-form";
|
||||
import { useBeforeUnload } from "react-router-dom";
|
||||
|
||||
import { logger } from "../helpers/debug";
|
||||
|
||||
// TODO: make these caches expire
|
||||
export default function useCacheForm<TFieldValues extends FieldValues = FieldValues>(
|
||||
key: string | null,
|
||||
getValues: UseFormGetValues<TFieldValues>,
|
||||
setValue: UseFormSetValue<TFieldValues>,
|
||||
reset: UseFormReset<TFieldValues>,
|
||||
state: UseFormStateReturn<TFieldValues>,
|
||||
opts?: { clearOnKeyChange: boolean },
|
||||
) {
|
||||
const log = useMemo(() => (key ? logger.extend(`CachedForm:${key}`) : () => {}), [key]);
|
||||
const storageKey = key && "cached-form-" + key;
|
||||
|
||||
useMount(() => {
|
||||
if (storageKey === null) return;
|
||||
const stateRef = useRef<UseFormStateReturn<TFieldValues>>(state);
|
||||
stateRef.current = state;
|
||||
|
||||
// NOTE: this watches the dirty state
|
||||
state.isDirty;
|
||||
state.isSubmitted;
|
||||
|
||||
useEffect(() => {
|
||||
if (!storageKey) return;
|
||||
|
||||
// restore form on key change or mount
|
||||
try {
|
||||
const cached = localStorage.getItem(storageKey);
|
||||
|
||||
// remove the item and keep it in memory
|
||||
localStorage.removeItem(storageKey);
|
||||
|
||||
if (cached) {
|
||||
log("Restoring form");
|
||||
const values = JSON.parse(cached) as TFieldValues;
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
// @ts-ignore
|
||||
setValue(key, value, { shouldDirty: true });
|
||||
}
|
||||
|
||||
log("Restoring form");
|
||||
reset(values, { keepDefaultValues: true });
|
||||
} else if (opts?.clearOnKeyChange) {
|
||||
log("Clearing form");
|
||||
reset();
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
const stateRef = useRef<UseFormStateReturn<TFieldValues>>(state);
|
||||
stateRef.current = state;
|
||||
useUnmount(() => {
|
||||
if (storageKey === null) return;
|
||||
if (!stateRef.current.isDirty) return;
|
||||
// save previous key on change or unmount
|
||||
return () => {
|
||||
if (stateRef.current.isSubmitted) {
|
||||
log("Removing because submitted");
|
||||
localStorage.removeItem(storageKey);
|
||||
} else if (stateRef.current.isDirty) {
|
||||
const values = getValues();
|
||||
log("Saving form", values);
|
||||
localStorage.setItem(storageKey, JSON.stringify(values));
|
||||
}
|
||||
};
|
||||
}, [storageKey, log, opts?.clearOnKeyChange]);
|
||||
|
||||
if (!stateRef.current.isSubmitted) {
|
||||
log("Saving form", getValues());
|
||||
localStorage.setItem(storageKey, JSON.stringify(getValues()));
|
||||
} else if (localStorage.getItem(storageKey)) {
|
||||
log("Removing cache because form was submitted");
|
||||
const saveOnClose = useCallback(() => {
|
||||
if (!storageKey) return;
|
||||
|
||||
if (stateRef.current.isSubmitted) {
|
||||
log("Removing because submitted");
|
||||
localStorage.removeItem(storageKey);
|
||||
} else if (stateRef.current.isDirty) {
|
||||
const values = getValues();
|
||||
log("Saving form", values);
|
||||
localStorage.setItem(storageKey, JSON.stringify(values));
|
||||
}
|
||||
});
|
||||
}, [log, getValues, storageKey]);
|
||||
|
||||
const autoSave = useCallback(() => {
|
||||
if (storageKey === null) return;
|
||||
if (!stateRef.current.isSubmitted) {
|
||||
log("Autosave", getValues());
|
||||
localStorage.setItem(storageKey, JSON.stringify(getValues()));
|
||||
}
|
||||
}, [storageKey]);
|
||||
|
||||
useTimeout(autoSave, 5_000);
|
||||
useBeforeUnload(saveOnClose);
|
||||
|
||||
return useCallback(() => {
|
||||
if (storageKey === null) return;
|
||||
if (!storageKey) return;
|
||||
|
||||
localStorage.removeItem(storageKey);
|
||||
}, [storageKey]);
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ export default function ChannelMessageForm({
|
||||
|
||||
setLoadingMessage("Signing...");
|
||||
await publish("Send DM", draft, undefined, false);
|
||||
reset();
|
||||
reset({ content: "" });
|
||||
|
||||
// refocus input
|
||||
setTimeout(() => textAreaRef.current?.focus(), 50);
|
||||
|
@ -11,6 +11,7 @@ import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import { useDecryptionContext } from "../../../providers/global/decryption-provider";
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import useCacheForm from "../../../hooks/use-cache-form";
|
||||
|
||||
export default function SendMessageForm({
|
||||
pubkey,
|
||||
@ -30,6 +31,10 @@ export default function SendMessageForm({
|
||||
});
|
||||
watch("content");
|
||||
|
||||
const clearCache = useCacheForm<{ content: string }>(`dm-${pubkey}`, getValues, reset, formState, {
|
||||
clearOnKeyChange: true,
|
||||
});
|
||||
|
||||
const autocompleteRef = useRef<RefType | null>(null);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const { onPaste } = useTextAreaUploadFileWithForm(autocompleteRef, getValues, setValue);
|
||||
@ -55,7 +60,8 @@ export default function SendMessageForm({
|
||||
const pub = await publish("Send DM", draft, userMailboxes?.inbox);
|
||||
|
||||
if (pub) {
|
||||
reset();
|
||||
clearCache();
|
||||
reset({ content: "" });
|
||||
|
||||
// add plaintext to decryption context
|
||||
getOrCreateContainer(pubkey, encrypted).plaintext.next(values.content);
|
||||
@ -79,7 +85,7 @@ export default function SendMessageForm({
|
||||
<MagicTextArea
|
||||
mb="2"
|
||||
value={getValues().content}
|
||||
onChange={(e) => setValue("content", e.target.value, { shouldDirty: true })}
|
||||
onChange={(e) => setValue("content", e.target.value, { shouldDirty: true, shouldTouch: true })}
|
||||
rows={2}
|
||||
isRequired
|
||||
instanceRef={(inst) => (autocompleteRef.current = inst)}
|
||||
|
@ -25,6 +25,7 @@ import { UploadImageIcon } from "../../../components/icons";
|
||||
import { unique } from "../../../helpers/array";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import { TextNoteContents } from "../../../components/note/timeline-note/text-note-contents";
|
||||
import useCacheForm from "../../../hooks/use-cache-form";
|
||||
|
||||
export type ReplyFormProps = {
|
||||
item: ThreadItem;
|
||||
@ -41,11 +42,14 @@ export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kin
|
||||
const { requestSignature } = useSigningContext();
|
||||
|
||||
const threadMembers = useMemo(() => getThreadMembers(item, account?.pubkey), [item, account?.pubkey]);
|
||||
const { setValue, getValues, watch, handleSubmit } = useForm({
|
||||
const { setValue, getValues, watch, handleSubmit, formState, reset } = useForm({
|
||||
defaultValues: {
|
||||
content: "",
|
||||
},
|
||||
mode: "all",
|
||||
});
|
||||
const clearCache = useCacheForm<{ content: string }>(`reply-${item.event.id}`, getValues, reset, formState);
|
||||
|
||||
const contentMentions = getPubkeysMentionedInContent(getValues().content);
|
||||
const notifyPubkeys = unique([...threadMembers, ...contentMentions]);
|
||||
|
||||
@ -88,6 +92,7 @@ export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kin
|
||||
const pub = await publish("Reply", { ...draft, created_at: dayjs().unix() });
|
||||
|
||||
if (pub && onSubmitted) onSubmitted(pub.event);
|
||||
clearCache();
|
||||
});
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
|
@ -61,11 +61,13 @@ export default function CreateWikiPageView() {
|
||||
useEffect(() => {
|
||||
if (!fork) return;
|
||||
|
||||
setValue("topic", getPageTopic(fork));
|
||||
setValue("title", getPageTitle(fork) ?? "");
|
||||
setValue("summary", getPageSummary(fork));
|
||||
setValue("content", fork.content);
|
||||
}, [fork, setValue]);
|
||||
reset({
|
||||
topic: getPageTopic(fork),
|
||||
title: getPageTitle(fork) ?? "",
|
||||
summary: getPageSummary(fork, false) ?? "",
|
||||
content: fork.content,
|
||||
});
|
||||
}, [fork, reset]);
|
||||
|
||||
const cacheKey = forkAddress
|
||||
? "wiki-" + forkAddress.identifier + ":" + forkAddress.pubkey + "-fork"
|
||||
@ -77,7 +79,7 @@ export default function CreateWikiPageView() {
|
||||
cacheKey,
|
||||
// @ts-expect-error
|
||||
getValues,
|
||||
setValue,
|
||||
reset,
|
||||
formState,
|
||||
);
|
||||
|
||||
|
@ -44,7 +44,7 @@ function EditWikiPagePage({ page }: { page: NostrEvent }) {
|
||||
"wiki-" + topic,
|
||||
// @ts-expect-error
|
||||
getValues,
|
||||
setValue,
|
||||
reset,
|
||||
formState,
|
||||
);
|
||||
|
||||
|
@ -6702,10 +6702,10 @@ react-force-graph-3d@^1.24.2:
|
||||
prop-types "15"
|
||||
react-kapsule "2"
|
||||
|
||||
react-hook-form@^7.45.4:
|
||||
version "7.50.1"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.50.1.tgz#f6aeb17a863327e5a0252de8b35b4fc8990377ed"
|
||||
integrity sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==
|
||||
react-hook-form@^7.51.5:
|
||||
version "7.51.5"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.5.tgz#4afbfb819312db9fea23e8237a3a0d097e128b43"
|
||||
integrity sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user