mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
📝 feat: Improved Textarea Functionality (#1942)
* feat: paste plain text from apps with rich paste data, improved edit message textarea, improved height resizing for long text * feat(EditMessage): autofocus * chore: retain user text color when entering edit mode
This commit is contained in:
parent
de0cee3f56
commit
c52ea9490b
3 changed files with 91 additions and 36 deletions
|
@ -1,7 +1,8 @@
|
|||
import debounce from 'lodash/debounce';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TEndpointOption } from 'librechat-data-provider';
|
||||
import type { SetterOrUpdater } from 'recoil';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
|
||||
import useGetSender from '~/hooks/Conversations/useGetSender';
|
||||
|
@ -25,12 +26,20 @@ const getAssistantName = ({
|
|||
}
|
||||
};
|
||||
|
||||
export default function useTextarea({ setText, submitMessage, disabled = false }) {
|
||||
export default function useTextarea({
|
||||
setText,
|
||||
submitMessage,
|
||||
disabled = false,
|
||||
}: {
|
||||
setText: SetterOrUpdater<string>;
|
||||
submitMessage: () => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { conversation, isSubmitting, latestMessage, setShowBingToneSetting, setFilesLoading } =
|
||||
useChatContext();
|
||||
const isComposing = useRef(false);
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const { handleFiles } = useFileHandling();
|
||||
const getSender = useGetSender();
|
||||
const localize = useLocalize();
|
||||
|
@ -54,7 +63,7 @@ export default function useTextarea({ setText, submitMessage, disabled = false }
|
|||
}
|
||||
|
||||
if (conversationId !== 'search') {
|
||||
inputRef.current?.focus();
|
||||
textAreaRef.current?.focus();
|
||||
}
|
||||
// setShowBingToneSetting is a recoil setter, so it doesn't need to be in the dependency array
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -62,14 +71,14 @@ export default function useTextarea({ setText, submitMessage, disabled = false }
|
|||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
textAreaRef.current?.focus();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [isSubmitting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current?.value) {
|
||||
if (textAreaRef.current?.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -91,15 +100,15 @@ export default function useTextarea({ setText, submitMessage, disabled = false }
|
|||
|
||||
const placeholder = getPlaceholderText();
|
||||
|
||||
if (inputRef.current?.getAttribute('placeholder') === placeholder) {
|
||||
if (textAreaRef.current?.getAttribute('placeholder') === placeholder) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setPlaceholder = () => {
|
||||
const placeholder = getPlaceholderText();
|
||||
|
||||
if (inputRef.current?.getAttribute('placeholder') !== placeholder) {
|
||||
inputRef.current?.setAttribute('placeholder', placeholder);
|
||||
if (textAreaRef.current?.getAttribute('placeholder') !== placeholder) {
|
||||
textAreaRef.current?.setAttribute('placeholder', placeholder);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -147,16 +156,33 @@ export default function useTextarea({ setText, submitMessage, disabled = false }
|
|||
isComposing.current = false;
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
||||
e.preventDefault();
|
||||
setFilesLoading(true);
|
||||
handleFiles(e.clipboardData.files);
|
||||
}
|
||||
};
|
||||
const handlePaste = useCallback(
|
||||
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
const pastedData = e.clipboardData.getData('text/plain');
|
||||
const textArea = textAreaRef.current;
|
||||
|
||||
if (!textArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = textArea.selectionStart;
|
||||
const end = textArea.selectionEnd;
|
||||
|
||||
const newValue =
|
||||
textArea.value.substring(0, start) + pastedData + textArea.value.substring(end);
|
||||
setText(newValue);
|
||||
|
||||
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
||||
e.preventDefault();
|
||||
setFilesLoading(true);
|
||||
handleFiles(e.clipboardData.files);
|
||||
}
|
||||
},
|
||||
[handleFiles, setFilesLoading, setText],
|
||||
);
|
||||
|
||||
return {
|
||||
inputRef,
|
||||
textAreaRef,
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handlePaste,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue