diff --git a/.changeset/fresh-jobs-repair.md b/.changeset/fresh-jobs-repair.md new file mode 100644 index 0000000..23bc920 --- /dev/null +++ b/.changeset/fresh-jobs-repair.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix chat text input layout issues diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c7cddb5..57f1ec7 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -15,6 +15,7 @@ import Thumbnails from "../common/Thumbnails" import { vscode } from "../../utils/vscode" import { WebviewMessage } from "../../../../src/shared/WebviewMessage" import { Mode } from "../../../../src/core/prompts/types" +import { CaretIcon } from "../common/CaretIcon" interface ChatTextAreaProps { inputValue: string @@ -50,7 +51,6 @@ const ChatTextArea = forwardRef( ref, ) => { const { filePaths, currentApiConfigName, listApiConfigMeta } = useExtensionState() - const [isTextAreaFocused, setIsTextAreaFocused] = useState(false) const [gitCommits, setGitCommits] = useState([]) const [showDropdown, setShowDropdown] = useState(false) @@ -376,7 +376,6 @@ const ChatTextArea = forwardRef( if (!isMouseDownOnMenu) { setShowContextMenu(false) } - setIsTextAreaFocused(false) }, [isMouseDownOnMenu]) const handlePaste = useCallback( @@ -494,65 +493,97 @@ const ChatTextArea = forwardRef( [updateCursorPosition], ) + const selectStyle = { + fontSize: "11px", + cursor: textAreaDisabled ? "not-allowed" : "pointer", + backgroundColor: "transparent", + border: "none", + color: "var(--vscode-foreground)", + opacity: textAreaDisabled ? 0.5 : 0.8, + outline: "none", + paddingLeft: "20px", + paddingRight: "6px", + WebkitAppearance: "none" as const, + MozAppearance: "none" as const, + appearance: "none" as const + } + + const caretContainerStyle = { + position: "absolute" as const, + left: 6, + top: "50%", + transform: "translateY(-45%)", + pointerEvents: "none" as const, + opacity: textAreaDisabled ? 0.5 : 0.8 + } + return ( -
{ - e.preventDefault() - const files = Array.from(e.dataTransfer.files) - const text = e.dataTransfer.getData("text") - if (text) { - const newValue = - inputValue.slice(0, cursorPosition) + text + inputValue.slice(cursorPosition) - setInputValue(newValue) - const newCursorPosition = cursorPosition + text.length - setCursorPosition(newCursorPosition) - setIntendedCursorPosition(newCursorPosition) - return - } - const acceptedTypes = ["png", "jpeg", "webp"] - const imageFiles = files.filter((file) => { - const [type, subtype] = file.type.split("/") - return type === "image" && acceptedTypes.includes(subtype) - }) - if (!shouldDisableImages && imageFiles.length > 0) { - const imagePromises = imageFiles.map((file) => { - return new Promise((resolve) => { - const reader = new FileReader() - reader.onloadend = () => { - if (reader.error) { - console.error("Error reading file:", reader.error) - resolve(null) - } else { - const result = reader.result - resolve(typeof result === "string" ? result : null) - } - } - reader.readAsDataURL(file) - }) - }) - const imageDataArray = await Promise.all(imagePromises) - const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null) - if (dataUrls.length > 0) { - setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE)) - if (typeof vscode !== 'undefined') { - vscode.postMessage({ - type: 'draggedImages', - dataUrls: dataUrls - }) - } - } else { - console.warn("No valid images were processed") +
{ + e.preventDefault() + const files = Array.from(e.dataTransfer.files) + const text = e.dataTransfer.getData("text") + if (text) { + const newValue = + inputValue.slice(0, cursorPosition) + text + inputValue.slice(cursorPosition) + setInputValue(newValue) + const newCursorPosition = cursorPosition + text.length + setCursorPosition(newCursorPosition) + setIntendedCursorPosition(newCursorPosition) + return } - } - }} - onDragOver={(e) => { - e.preventDefault() - }}> + const acceptedTypes = ["png", "jpeg", "webp"] + const imageFiles = files.filter((file) => { + const [type, subtype] = file.type.split("/") + return type === "image" && acceptedTypes.includes(subtype) + }) + if (!shouldDisableImages && imageFiles.length > 0) { + const imagePromises = imageFiles.map((file) => { + return new Promise((resolve) => { + const reader = new FileReader() + reader.onloadend = () => { + if (reader.error) { + console.error("Error reading file:", reader.error) + resolve(null) + } else { + const result = reader.result + resolve(typeof result === "string" ? result : null) + } + } + reader.readAsDataURL(file) + }) + }) + const imageDataArray = await Promise.all(imagePromises) + const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null) + if (dataUrls.length > 0) { + setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE)) + if (typeof vscode !== 'undefined') { + vscode.postMessage({ + type: 'draggedImages', + dataUrls: dataUrls + }) + } + } else { + console.warn("No valid images were processed") + } + } + }} + onDragOver={(e) => { + e.preventDefault() + }} + > {showContextMenu && (
( />
)} - {!isTextAreaFocused && ( + +
0 ? `${thumbnailsHeight + 16}px` : 0, + zIndex: 1 }} /> - )} -
- { - if (typeof ref === "function") { - ref(el) - } else if (ref) { - ref.current = el - } - textAreaRef.current = el - }} - value={inputValue} - disabled={textAreaDisabled} - onChange={(e) => { - handleInputChange(e) - updateHighlights() - }} - onKeyDown={handleKeyDown} - onKeyUp={handleKeyUp} - onFocus={() => setIsTextAreaFocused(true)} - onBlur={handleBlur} - onPaste={handlePaste} - onSelect={updateCursorPosition} - onMouseUp={updateCursorPosition} - onHeightChange={(height) => { - if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) { - setTextAreaBaseHeight(height) - } - onHeightChange?.(height) - }} - placeholder={placeholderText} - minRows={2} - maxRows={20} - autoFocus={true} - style={{ - width: "100%", - boxSizing: "border-box", - backgroundColor: "transparent", - color: "var(--vscode-input-foreground)", - borderRadius: 2, - fontFamily: "var(--vscode-font-family)", - fontSize: "var(--vscode-editor-font-size)", - lineHeight: "var(--vscode-editor-line-height)", - resize: "none", - overflowX: "hidden", - overflowY: "scroll", - borderLeft: 0, - borderRight: 0, - borderTop: 0, - borderBottom: `${thumbnailsHeight + 6}px solid transparent`, - borderColor: "transparent", - padding: "9px 9px 25px 9px", - marginBottom: "15px", - cursor: textAreaDisabled ? "not-allowed" : undefined, - flex: 1, - zIndex: 1, - }} - onScroll={() => updateHighlights()} - /> + { + if (typeof ref === "function") { + ref(el) + } else if (ref) { + ref.current = el + } + textAreaRef.current = el + }} + value={inputValue} + disabled={textAreaDisabled} + onChange={(e) => { + handleInputChange(e) + updateHighlights() + }} + onKeyDown={handleKeyDown} + onKeyUp={handleKeyUp} + onBlur={handleBlur} + onPaste={handlePaste} + onSelect={updateCursorPosition} + onMouseUp={updateCursorPosition} + onHeightChange={(height) => { + if (textAreaBaseHeight === undefined || height < textAreaBaseHeight) { + setTextAreaBaseHeight(height) + } + onHeightChange?.(height) + }} + placeholder={placeholderText} + minRows={4} + maxRows={20} + autoFocus={true} + style={{ + width: "100%", + boxSizing: "border-box", + backgroundColor: "transparent", + color: "var(--vscode-input-foreground)", + borderRadius: 2, + fontFamily: "var(--vscode-font-family)", + fontSize: "var(--vscode-editor-font-size)", + lineHeight: "var(--vscode-editor-line-height)", + resize: "none", + overflowX: "hidden", + overflowY: "auto", + border: "none", + padding: "8px", + marginBottom: thumbnailsHeight > 0 ? `${thumbnailsHeight + 16}px` : 0, + cursor: textAreaDisabled ? "not-allowed" : undefined, + flex: "0 1 auto", + zIndex: 2 + }} + onScroll={() => updateHighlights()} + /> +
+ {selectedImages.length > 0 && ( ( onHeightChange={handleThumbnailsHeightChange} style={{ position: "absolute", - paddingTop: 4, - bottom: 36, - left: 22, - right: 67, + bottom: "36px", + left: "16px", zIndex: 2, + marginBottom: "8px" }} /> )} -
+
- - { + const newMode = e.target.value as Mode + setMode(newMode) + vscode.postMessage({ + type: "mode", + text: newMode + }) + }} style={{ - backgroundColor: "var(--vscode-dropdown-background)", - color: "var(--vscode-dropdown-foreground)" + ...selectStyle, + minWidth: "70px", + flex: "0 0 auto" }} > - {config.name} - - ))} - -
-
- -
- {isEnhancingPrompt ? ( - - ) : ( - !textAreaDisabled && handleEnhancePrompt()} - style={{ fontSize: 16.5 }} - /> - )} + + + + +
+ +
- !shouldDisableImages && onSelectImages()} style={{ fontSize: 16.5 }} /> - !textAreaDisabled && onSend()} style={{ fontSize: 15 }} /> - + +
+ +
+ +
+
+
+ +
+
+ {isEnhancingPrompt ? ( + + ) : ( + !textAreaDisabled && handleEnhancePrompt()} + style={{ fontSize: 16.5 }} + /> + )} +
+ !shouldDisableImages && onSelectImages()} + style={{ fontSize: 16.5 }} + /> + !textAreaDisabled && onSend()} + style={{ fontSize: 15 }} + /> +
) diff --git a/webview-ui/src/components/common/CaretIcon.tsx b/webview-ui/src/components/common/CaretIcon.tsx new file mode 100644 index 0000000..ab1126d --- /dev/null +++ b/webview-ui/src/components/common/CaretIcon.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +export const CaretIcon = () => ( + + + +) \ No newline at end of file