mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 20:57:21 +08:00
feat(dataflow-result): scroll to and highlight newly created chunk (#16937)
This commit is contained in:
@@ -13,6 +13,7 @@ const FormatPreserveEditor = ({
|
||||
textMode,
|
||||
clickChunk,
|
||||
isReadonly,
|
||||
newChunkIndex,
|
||||
}: FormatPreserveEditorProps) => {
|
||||
console.log('initialValue', initialValue);
|
||||
|
||||
@@ -41,6 +42,7 @@ const FormatPreserveEditor = ({
|
||||
textMode={textMode}
|
||||
isChunck={isChunck}
|
||||
clickChunk={clickChunk}
|
||||
newChunkIndex={newChunkIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface FormatPreserveEditorProps {
|
||||
textMode?: ChunkTextMode;
|
||||
clickChunk: (chunk: IChunk) => void;
|
||||
isReadonly: boolean;
|
||||
newChunkIndex?: number;
|
||||
}
|
||||
|
||||
export type IJsonContainerProps = {
|
||||
@@ -47,6 +48,7 @@ export type IJsonContainerProps = {
|
||||
textMode?: ChunkTextMode;
|
||||
clickChunk: (chunk: IChunk) => void;
|
||||
isReadonly: boolean;
|
||||
newChunkIndex?: number;
|
||||
};
|
||||
|
||||
export type IObjContainerProps = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { isArray } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { ChunkTextMode } from '../../constant';
|
||||
import styles from '../../index.module.less';
|
||||
import { IChunk } from '../../interface';
|
||||
@@ -23,11 +23,14 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
|
||||
textMode,
|
||||
clickChunk,
|
||||
isReadonly,
|
||||
newChunkIndex,
|
||||
} = props;
|
||||
|
||||
const { content, activeEditIndex, setActiveEditIndex, editDivRef } =
|
||||
useParserInit({ initialValue });
|
||||
|
||||
const sectionRefs = useRef<(HTMLElement | null)[]>([]);
|
||||
|
||||
const parserKey = useMemo(() => {
|
||||
const key =
|
||||
content.key === 'chunks' && content.params.field_name
|
||||
@@ -76,6 +79,32 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
|
||||
}
|
||||
}, [editDivRef, activeEditIndex, content, parserKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (newChunkIndex === undefined) return;
|
||||
const target = sectionRefs.current[newChunkIndex];
|
||||
if (!target) return;
|
||||
|
||||
// Scroll only the nearest scrollable ancestor so the whole page doesn't shift
|
||||
let scrollContainer: HTMLElement | null = target.parentElement;
|
||||
while (scrollContainer) {
|
||||
const { overflowY } = window.getComputedStyle(scrollContainer);
|
||||
if (overflowY === 'auto' || overflowY === 'scroll') break;
|
||||
scrollContainer = scrollContainer.parentElement;
|
||||
}
|
||||
|
||||
if (scrollContainer) {
|
||||
const containerRect = scrollContainer.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const offsetTop =
|
||||
targetRect.top - containerRect.top + scrollContainer.scrollTop;
|
||||
const targetCenter = offsetTop + target.clientHeight / 2;
|
||||
scrollContainer.scrollTo({
|
||||
top: Math.max(0, targetCenter - scrollContainer.clientHeight / 2),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, [newChunkIndex, content]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isArray(content.value) &&
|
||||
@@ -90,11 +119,17 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
|
||||
return (
|
||||
<section
|
||||
key={index}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[index] = el;
|
||||
}}
|
||||
className={cn(
|
||||
isChunck
|
||||
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start'
|
||||
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start transition-shadow duration-300'
|
||||
: '',
|
||||
activeEditIndex === index && isChunck ? 'bg-bg-title' : '',
|
||||
newChunkIndex === index && isChunck
|
||||
? 'ring-2 ring-accent-primary'
|
||||
: '',
|
||||
)}
|
||||
>
|
||||
{isChunck && !isReadonly && (
|
||||
|
||||
@@ -36,6 +36,7 @@ const ParserContainer = (props: IProps) => {
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
|
||||
const [newChunkIndex, setNewChunkIndex] = useState<number | undefined>();
|
||||
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
|
||||
const initialValue = useMemo(() => {
|
||||
const outputs = data?.value?.obj?.params?.outputs;
|
||||
@@ -136,6 +137,7 @@ const ParserContainer = (props: IProps) => {
|
||||
const handleCreateChunk = useCallback(
|
||||
(text: string) => {
|
||||
const newText = [...initialText.value, { text: text || ' ' }];
|
||||
setNewChunkIndex(newText.length - 1);
|
||||
setInitialText({
|
||||
...initialText,
|
||||
value: newText as any,
|
||||
@@ -144,6 +146,12 @@ const ParserContainer = (props: IProps) => {
|
||||
[initialText],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (newChunkIndex === undefined) return;
|
||||
const timer = setTimeout(() => setNewChunkIndex(undefined), 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [newChunkIndex]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isChange && !isReadonly && (
|
||||
@@ -220,6 +228,7 @@ const ParserContainer = (props: IProps) => {
|
||||
clickChunk={clickChunk}
|
||||
handleCheckboxClick={handleCheckboxClick}
|
||||
selectedChunkIds={selectedChunkIds}
|
||||
newChunkIndex={newChunkIndex}
|
||||
/>
|
||||
)}
|
||||
<Spotlight opcity={0.6} coverage={60} />
|
||||
|
||||
Reference in New Issue
Block a user