Skip to content

Commit

Permalink
fix: input scrolls to the bottom when set the caret to the top (#4399)
Browse files Browse the repository at this point in the history
* fix: input scrolls to the bottom on long inputs

* test: fix failed tests
  • Loading branch information
louis-menlo authored Jan 3, 2025
1 parent 9fb6b9f commit d17c2ea
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 19 deletions.
2 changes: 1 addition & 1 deletion extensions/inference-cortex-extension/download.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set BIN_PATH=./bin
set SHARED_PATH=./../../electron/shared
set /p CORTEX_VERSION=<./bin/version.txt
set ENGINE_VERSION=0.1.42
set ENGINE_VERSION=0.1.43

@REM Download cortex.llamacpp binaries
set DOWNLOAD_URL=https://github.com/janhq/cortex.llamacpp/releases/download/v%ENGINE_VERSION%/cortex.llamacpp-%ENGINE_VERSION%-windows-amd64
Expand Down
2 changes: 1 addition & 1 deletion extensions/inference-cortex-extension/download.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Read CORTEX_VERSION
CORTEX_VERSION=$(cat ./bin/version.txt)
ENGINE_VERSION=0.1.42
ENGINE_VERSION=0.1.43
CORTEX_RELEASE_URL="https://github.com/janhq/cortex.cpp/releases/download"
ENGINE_DOWNLOAD_URL="https://github.com/janhq/cortex.llamacpp/releases/download/v${ENGINE_VERSION}/cortex.llamacpp-${ENGINE_VERSION}"
CUDA_DOWNLOAD_URL="https://github.com/janhq/cortex.llamacpp/releases/download/v${ENGINE_VERSION}"
Expand Down
84 changes: 79 additions & 5 deletions web/screens/Thread/ThreadCenterPanel/ChatInput/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, ClipboardEvent } from 'react'
import { MessageStatus } from '@janhq/core'
import { useAtom, useAtomValue } from 'jotai'

import { BaseEditor, createEditor, Editor, Transforms } from 'slate'
import { BaseEditor, createEditor, Editor, Range, Transforms } from 'slate'
import { withHistory } from 'slate-history' // Import withHistory
import {
Editable,
Expand Down Expand Up @@ -186,10 +186,6 @@ const RichTextEditor = ({
: '40px'
textareaRef.current.style.height =
textareaRef.current.scrollHeight + 2 + 'px'
textareaRef.current?.scrollTo({
top: textareaRef.current.scrollHeight,
behavior: 'instant',
})
textareaRef.current.style.overflow =
textareaRef.current.clientHeight >= 390 ? 'auto' : 'hidden'
}
Expand Down Expand Up @@ -302,6 +298,7 @@ const RichTextEditor = ({
return decorate(entry)
}}
renderLeaf={renderLeaf} // Pass the renderLeaf function
scrollSelectionIntoView={scrollSelectionIntoView}
onKeyDown={handleKeyDown}
onPaste={handlePaste} // Add the custom paste handler
className={twMerge(
Expand All @@ -317,6 +314,83 @@ const RichTextEditor = ({
/>
</Slate>
)

function scrollSelectionIntoView(
editor: ReactEditor,
domRange: globalThis.Range
) {
// This was affecting the selection of multiple blocks and dragging behavior,
// so enabled only if the selection has been collapsed.
if (editor.selection && Range.isExpanded(editor.selection)) return

const minTop = 80 // sticky header height

const leafEl = domRange.startContainer.parentElement
const scrollParent = getScrollParent(leafEl)

// Check if browser supports getBoundingClientRect
if (typeof domRange.getBoundingClientRect !== 'function') return

const { top: elementTop, height: elementHeight } =
domRange.getBoundingClientRect()
const { height: parentHeight } = scrollParent.getBoundingClientRect()

const isChildAboveViewport = elementTop < minTop
const isChildBelowViewport = elementTop + elementHeight > parentHeight

if (isChildAboveViewport && isChildBelowViewport) {
// Child spans through all visible area which means it's already in view.
return
}

if (isChildAboveViewport) {
const y = scrollParent.scrollTop + elementTop - minTop
scrollParent.scroll({ left: scrollParent.scrollLeft, top: y })
return
}

if (isChildBelowViewport) {
const y = Math.min(
scrollParent.scrollTop + elementTop - minTop,
scrollParent.scrollTop + elementTop + elementHeight - parentHeight
)
scrollParent.scroll({ left: scrollParent.scrollLeft, top: y })
}
}

function getScrollParent(element: any) {
const elementStyle = window.getComputedStyle(element)
const excludeStaticParent = elementStyle.position === 'absolute'

if (elementStyle.position === 'fixed') {
return document.body
}

let parent = element

while (parent) {
const parentStyle = window.getComputedStyle(parent)

if (parentStyle.position !== 'static' || !excludeStaticParent) {
const overflowAttributes = [
parentStyle.overflow,
parentStyle.overflowY,
parentStyle.overflowX,
]

if (
overflowAttributes.includes('auto') ||
overflowAttributes.includes('hidden')
) {
return parent
}
}

parent = parent.parentElement
}

return document.documentElement
}
}

export default RichTextEditor
14 changes: 8 additions & 6 deletions web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const ChatInput = () => {

const activeThreadId = useAtomValue(getActiveThreadIdAtom)
const [fileUpload, setFileUpload] = useAtom(fileUploadAtom)
const [showAttacmentMenus, setShowAttacmentMenus] = useState(false)
const [showAttachmentMenus, setShowAttachmentMenus] = useState(false)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
const imageInputRef = useRef<HTMLInputElement>(null)
Expand All @@ -73,7 +73,9 @@ const ChatInput = () => {
activeTabThreadRightPanelAtom
)

const refAttachmentMenus = useClickOutside(() => setShowAttacmentMenus(false))
const refAttachmentMenus = useClickOutside(() =>
setShowAttachmentMenus(false)
)
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)

useEffect(() => {
Expand Down Expand Up @@ -169,7 +171,7 @@ const ChatInput = () => {
) {
e.stopPropagation()
} else {
setShowAttacmentMenus(!showAttacmentMenus)
setShowAttachmentMenus(!showAttachmentMenus)
}
}}
>
Expand Down Expand Up @@ -214,7 +216,7 @@ const ChatInput = () => {
/>
)}

{showAttacmentMenus && (
{showAttachmentMenus && (
<div
ref={refAttachmentMenus}
className={twMerge(
Expand All @@ -234,7 +236,7 @@ const ChatInput = () => {
onClick={() => {
if (activeAssistant?.model.settings?.vision_model) {
imageInputRef.current?.click()
setShowAttacmentMenus(false)
setShowAttachmentMenus(false)
}
}}
>
Expand All @@ -254,7 +256,7 @@ const ChatInput = () => {
onClick={() => {
if (isModelSupportRagAndTools) {
fileInputRef.current?.click()
setShowAttacmentMenus(false)
setShowAttachmentMenus(false)
}
}}
>
Expand Down
12 changes: 6 additions & 6 deletions web/screens/Thread/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import { render } from '@testing-library/react'
import ThreadScreen from './index'
import { useStarterScreen } from '../../hooks/useStarterScreen'
import '@testing-library/jest-dom'

global.ResizeObserver = class {
observe() { }
unobserve() { }
disconnect() { }
observe() {}
unobserve() {}
disconnect() {}
}
// Mock the useStarterScreen hook
jest.mock('@/hooks/useStarterScreen')
Expand All @@ -17,7 +17,7 @@ global.API_BASE_URL = 'http://localhost:3000'

describe('ThreadScreen', () => {
it('renders OnDeviceStarterScreen when isShowStarterScreen is true', () => {
; (useStarterScreen as jest.Mock).mockReturnValue({
;(useStarterScreen as jest.Mock).mockReturnValue({
isShowStarterScreen: true,
extensionHasSettings: false,
})
Expand All @@ -27,7 +27,7 @@ describe('ThreadScreen', () => {
})

it('renders Thread panels when isShowStarterScreen is false', () => {
; (useStarterScreen as jest.Mock).mockReturnValue({
;(useStarterScreen as jest.Mock).mockReturnValue({
isShowStarterScreen: false,
extensionHasSettings: false,
})
Expand Down

0 comments on commit d17c2ea

Please sign in to comment.