mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 18:39:06 +08:00
fix: notes content search next scroll (#10908)
* fix: topic branch incomplete copy - split ID mapping into two passes Fix the bug where topic branching would not copy all message relationships completely.The issue was that askId mapping lookup happened in the same loop as ID generation, causing later messages' askIds to fail mapping when they referenced messages that hadn't been processed yet. Solution: Split into two passes: 1. First pass: Generate new IDs for all messages and build complete mapping 2. Second pass: Clone messages and blocks using the complete ID mapping This ensures all message relationships (especially assistant message askId references)are properly maintained in the new topic. * fix(notes): 保持 Ctrl+F ‘下一个’在编辑器容器内滚动,避免索引提前回到第一条 - 使用传入的滚动容器计算相对偏移并 target.scrollTo 居中 - 容器不可滚动时回退到 scrollIntoView,兼容其他页面 - 将 target 纳入依赖,确保引用最新容器 受影响文件: - src/renderer/src/components/ContentSearch.tsx:165 * fix(search): improve notes content search next-scroll behavior * Update dom.ts --------- Co-authored-by: Pleasurecruise <3196812536@qq.com>
This commit is contained in:
parent
35aa9d7355
commit
75fcf8fbb5
@ -1,5 +1,6 @@
|
||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||
import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout'
|
||||
import { scrollElementIntoView } from '@renderer/utils'
|
||||
import { Tooltip } from 'antd'
|
||||
import { debounce } from 'lodash'
|
||||
import { CaseSensitive, ChevronDown, ChevronUp, User, WholeWord, X } from 'lucide-react'
|
||||
@ -181,17 +182,14 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
// 3. 将当前项滚动到视图中
|
||||
// 获取第一个文本节点的父元素来进行滚动
|
||||
const parentElement = currentMatchRange.startContainer.parentElement
|
||||
if (shouldScroll) {
|
||||
parentElement?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
})
|
||||
if (shouldScroll && parentElement) {
|
||||
// 优先在指定的滚动容器内滚动,避免滚动整个页面导致索引错乱/看起来"跳到第一条"
|
||||
scrollElementIntoView(parentElement, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[allRanges, currentIndex]
|
||||
[allRanges, currentIndex, target]
|
||||
)
|
||||
|
||||
const search = useCallback(
|
||||
|
||||
55
src/renderer/src/utils/dom.ts
Normal file
55
src/renderer/src/utils/dom.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Simple wrapper for scrollIntoView with common default options.
|
||||
* Provides a unified interface with sensible defaults.
|
||||
*
|
||||
* @param element - The target element to scroll into view
|
||||
* @param options - Scroll options. If not provided, uses { behavior: 'smooth', block: 'center', inline: 'nearest' }
|
||||
*/
|
||||
export function scrollIntoView(element: HTMLElement, options?: ScrollIntoViewOptions): void {
|
||||
const defaultOptions: ScrollIntoViewOptions = {
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
}
|
||||
element.scrollIntoView(options ?? defaultOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligently scrolls an element into view at the center position.
|
||||
* Prioritizes scrolling within the specified container to avoid scrolling the entire page.
|
||||
*
|
||||
* @param element - The target element to scroll into view
|
||||
* @param scrollContainer - Optional scroll container. If provided and scrollable, scrolling happens within it; otherwise uses browser default scrolling
|
||||
* @param behavior - Scroll behavior, defaults to 'smooth'
|
||||
*/
|
||||
export function scrollElementIntoView(
|
||||
element: HTMLElement,
|
||||
scrollContainer?: HTMLElement | null,
|
||||
behavior: ScrollBehavior = 'smooth'
|
||||
): void {
|
||||
if (!scrollContainer) {
|
||||
// No container specified, use browser default scrolling
|
||||
scrollIntoView(element, { behavior, block: 'center', inline: 'nearest' })
|
||||
return
|
||||
}
|
||||
|
||||
// Check if container is scrollable
|
||||
const canScroll =
|
||||
scrollContainer.scrollHeight > scrollContainer.clientHeight ||
|
||||
scrollContainer.scrollWidth > scrollContainer.clientWidth
|
||||
|
||||
if (canScroll) {
|
||||
// Container is scrollable, scroll within the container
|
||||
const containerRect = scrollContainer.getBoundingClientRect()
|
||||
const elRect = element.getBoundingClientRect()
|
||||
|
||||
// Calculate element's scrollable offset position relative to the container
|
||||
const elementTopWithinContainer = elRect.top - containerRect.top + scrollContainer.scrollTop
|
||||
const desiredTop = elementTopWithinContainer - Math.max(0, scrollContainer.clientHeight - elRect.height) / 2
|
||||
|
||||
scrollContainer.scrollTo({ top: Math.max(0, desiredTop), behavior })
|
||||
} else {
|
||||
// Container is not scrollable, fallback to browser default scrolling
|
||||
scrollIntoView(element, { behavior, block: 'center', inline: 'nearest' })
|
||||
}
|
||||
}
|
||||
@ -214,6 +214,7 @@ export function uniqueObjectArray<T>(array: T[]): T[] {
|
||||
export * from './api'
|
||||
export * from './collection'
|
||||
export * from './dataLimit'
|
||||
export * from './dom'
|
||||
export * from './file'
|
||||
export * from './image'
|
||||
export * from './json'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user