diff --git a/app/calcTextareaHeight.js b/app/calcTextareaHeight.js deleted file mode 100644 index 2324473dc..000000000 --- a/app/calcTextareaHeight.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * fork from element-ui - * https://github.com/ElemeFE/element/blob/master/packages/input/src/calcTextareaHeight.js - */ - -let hiddenTextarea; - -const HIDDEN_STYLE = ` - height:0 !important; - visibility:hidden !important; - overflow:hidden !important; - position:absolute !important; - z-index:-1000 !important; - top:0 !important; - right:0 !important -`; - -const CONTEXT_STYLE = [ - "letter-spacing", - "line-height", - "padding-top", - "padding-bottom", - "font-family", - "font-weight", - "font-size", - "text-rendering", - "text-transform", - "width", - "text-indent", - "padding-left", - "padding-right", - "border-width", - "box-sizing", -]; - -function calculateNodeStyling(targetElement) { - const style = window.getComputedStyle(targetElement); - - const boxSizing = style.getPropertyValue("box-sizing"); - - const paddingSize = - parseFloat(style.getPropertyValue("padding-bottom")) + - parseFloat(style.getPropertyValue("padding-top")); - - const borderSize = - parseFloat(style.getPropertyValue("border-bottom-width")) + - parseFloat(style.getPropertyValue("border-top-width")); - - const contextStyle = CONTEXT_STYLE.map( - (name) => `${name}:${style.getPropertyValue(name)}`, - ).join(";"); - - return { contextStyle, paddingSize, borderSize, boxSizing }; -} - -export default function calcTextareaHeight( - targetElement, - minRows = 2, - maxRows = 4, -) { - if (!hiddenTextarea) { - hiddenTextarea = document.createElement("textarea"); - document.body.appendChild(hiddenTextarea); - } - - let { paddingSize, borderSize, boxSizing, contextStyle } = - calculateNodeStyling(targetElement); - - hiddenTextarea.setAttribute("style", `${contextStyle};${HIDDEN_STYLE}`); - hiddenTextarea.value = targetElement.value || targetElement.placeholder || ""; - - let height = hiddenTextarea.scrollHeight; - const result = {}; - - if (boxSizing === "border-box") { - height = height + borderSize; - } else if (boxSizing === "content-box") { - height = height - paddingSize; - } - - hiddenTextarea.value = ""; - let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; - - if (minRows !== null) { - let minHeight = singleRowHeight * minRows; - if (boxSizing === "border-box") { - minHeight = minHeight + paddingSize + borderSize; - } - height = Math.max(minHeight, height); - result.minHeight = `${minHeight}px`; - } - if (maxRows !== null) { - let maxHeight = singleRowHeight * maxRows; - if (boxSizing === "border-box") { - maxHeight = maxHeight + paddingSize + borderSize; - } - height = Math.min(maxHeight, height); - } - result.height = `${height}px`; - hiddenTextarea.parentNode && - hiddenTextarea.parentNode.removeChild(hiddenTextarea); - hiddenTextarea = null; - return result; -} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 4ab616444..b631d534e 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -41,6 +41,8 @@ import chatStyle from "./chat.module.scss"; import { Input, Modal, showModal, showToast } from "./ui-lib"; +import calcTextareaHeight from "../calcTextareaHeight"; + const Markdown = dynamic( async () => memo((await import("./markdown")).Markdown), { @@ -331,6 +333,10 @@ function useScrollToBottom() { export function Chat(props: { showSideBar?: () => void; sideBarShowing?: boolean; + autoSize: { + minRows: number; + maxRows?: number; + }; }) { type RenderMessage = Message & { preview?: boolean }; @@ -347,6 +353,7 @@ export function Chat(props: { const { submitKey, shouldSubmit } = useSubmitHandler(); const { scrollRef, setAutoScroll } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); + const [textareaStyle, setTextareaStyle] = useState({}); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -380,6 +387,16 @@ export function Chat(props: { dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; + // textarea has an adaptive height + const resizeTextarea = () => { + const dom = inputRef.current; + if (!dom) return; + const { minRows, maxRows } = props.autoSize; + setTimeout(() => { + setTextareaStyle(calcTextareaHeight(dom, minRows, maxRows)); + }, 50); + }; + // only search prompts when user input is short const SEARCH_TEXT_LIMIT = 30; const onInput = (text: string) => { @@ -504,6 +521,11 @@ export function Chat(props: { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Textarea Adaptive height + useEffect(() => { + resizeTextarea(); + }); + return ( <div className={styles.chat} key={session.id}> <div className={styles["window-header"]}> @@ -659,8 +681,8 @@ export function Chat(props: { <textarea ref={inputRef} className={styles["chat-input"]} + style={textareaStyle} placeholder={Locale.Chat.Input(submitKey)} - rows={2} onInput={(e) => onInput(e.currentTarget.value)} value={userInput} onKeyDown={onInputKeyDown} diff --git a/app/components/home.tsx b/app/components/home.tsx index 7602e5248..1d8501ed8 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -25,8 +25,6 @@ import dynamic from "next/dynamic"; import { REPO_URL } from "../constant"; import { ErrorBoundary } from "./error"; -import calcTextareaHeight from "../calcTextareaHeight"; - export function Loading(props: { noLogo?: boolean }) { return ( <div className={styles["loading-content"]}> diff --git a/app/utils.ts b/app/utils.ts index bb44e072d..3be76cf70 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -50,6 +50,10 @@ export function isMobileScreen() { return window.innerWidth <= 600; } +export function isFirefox() { + return /firefox/i.test(navigator.userAgent); +} + export function selectOrCopy(el: HTMLElement, content: string) { const currentSelection = window.getSelection(); diff --git a/tsconfig.json b/tsconfig.json index 14d189328..c73eef3e8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"], "exclude": ["node_modules"] }