From 73fb4e1f818c021caa634d982d9e7b90d7b8c528 Mon Sep 17 00:00:00 2001 From: anastasiia Date: Fri, 12 Jan 2024 08:28:46 -0500 Subject: [PATCH] feedback fixes --- apps/desktop/src/TextSearch.tsx | 2 +- client/src/App.tsx | 2 + client/src/CommandBar/Header/index.tsx | 10 +- .../CommandBar/steps/ManageRepos/index.tsx | 1 + .../CommandBar/steps/PrivateRepos/index.tsx | 1 + client/src/CommandBar/steps/SeachFiles.tsx | 1 + .../ChatTab/ChatPersistentState.tsx | 65 ++- .../ChatTab/Input/InputCore.tsx | 4 +- .../Project/CurrentTabContent/EmptyTab.tsx | 2 +- .../CurrentTabContent/FileTab/index.tsx | 1 + .../LeftSidebar/NavPanel/RepoDropdown.tsx | 60 +- .../LeftSidebar/RegexSearchPanel/index.tsx | 11 +- .../Code/CodeFull/VirtualizedCode.tsx | 3 + client/src/components/Code/CodeFull/index.tsx | 49 +- client/src/components/Code/CodeLine.tsx | 30 +- client/src/components/SearchOnPage/index.tsx | 6 +- client/src/components/TextInput/index.tsx | 2 +- .../UpgradeRequiredPopup/BranchesSvg.tsx | 542 ++++++++++++++++++ .../components/UpgradeRequiredPopup/index.tsx | 78 +++ .../providers/RepositoriesContextProvider.tsx | 2 +- .../context/providers/UIContextProvider.tsx | 20 +- client/src/context/uiContext.ts | 4 + client/src/hooks/useCodeSearch.ts | 100 ++++ client/src/index.css | 4 +- 24 files changed, 925 insertions(+), 75 deletions(-) create mode 100644 client/src/components/UpgradeRequiredPopup/BranchesSvg.tsx create mode 100644 client/src/components/UpgradeRequiredPopup/index.tsx create mode 100644 client/src/hooks/useCodeSearch.ts diff --git a/apps/desktop/src/TextSearch.tsx b/apps/desktop/src/TextSearch.tsx index 8f9f687fd7..d08e3a3625 100644 --- a/apps/desktop/src/TextSearch.tsx +++ b/apps/desktop/src/TextSearch.tsx @@ -142,7 +142,7 @@ const TextSearch = ({ currentResult={currentResult} setCurrentResult={setCurrentResult} searchValue={searchValue} - containerClassName="fixed top-[100px] right-[5px]" + containerClassName="fixed top-[100px] right-[5px] w-80" /> ); }; diff --git a/client/src/App.tsx b/client/src/App.tsx index 93da87a850..dead9e8902 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -16,6 +16,7 @@ import ProjectSettings from './ProjectSettings'; import TabsContextProvider from './context/providers/TabsContextProvider'; import { FileHighlightsContextProvider } from './context/providers/FileHighlightsContextProvider'; import RepositoriesContextProvider from './context/providers/RepositoriesContextProvider'; +import UpgradeRequiredPopup from './components/UpgradeRequiredPopup'; const toastOptions = { unStyled: true, @@ -44,6 +45,7 @@ const App = () => { + diff --git a/client/src/CommandBar/Header/index.tsx b/client/src/CommandBar/Header/index.tsx index 585d7699b1..b7656573d0 100644 --- a/client/src/CommandBar/Header/index.tsx +++ b/client/src/CommandBar/Header/index.tsx @@ -32,6 +32,7 @@ type GeneralProps = { handleBack?: () => void; breadcrumbs?: string[]; customRightComponent?: ReactElement; + disableKeyNav?: boolean; }; type Props = GeneralProps & (PropsWithInput | PropsWithoutInput); @@ -45,6 +46,7 @@ const CommandBarHeader = ({ value, placeholder, noInput, + disableKeyNav, }: Props) => { const { t } = useTranslation(); const { isVisible } = useContext(CommandBarContext.General); @@ -62,7 +64,10 @@ const CommandBarHeader = ({ const handleKeyEvent = useCallback( (e: KeyboardEvent) => { - if (e.key === 'Escape') { + if ( + e.key === 'Escape' || + (e.key === 'Backspace' && !value && !isComposing) + ) { e.stopPropagation(); e.preventDefault(); if (handleBack) { @@ -78,7 +83,7 @@ const CommandBarHeader = ({ }, [setIsVisible, handleBack, customSubmitHandler, value, isComposing], ); - useKeyboardNavigation(handleKeyEvent, !isVisible); + useKeyboardNavigation(handleKeyEvent, !isVisible || disableKeyNav); return (
@@ -111,6 +116,7 @@ const CommandBarHeader = ({ autoFocus onCompositionStart={onCompositionStart} onCompositionEnd={onCompositionEnd} + disabled={disableKeyNav} /> )}
diff --git a/client/src/CommandBar/steps/ManageRepos/index.tsx b/client/src/CommandBar/steps/ManageRepos/index.tsx index 0b7a7c5e73..6d46dd782b 100644 --- a/client/src/CommandBar/steps/ManageRepos/index.tsx +++ b/client/src/CommandBar/steps/ManageRepos/index.tsx @@ -196,6 +196,7 @@ const ManageRepos = ({}: Props) => { onChange={handleInputChange} handleBack={handleBack} placeholder={t('')} + disableKeyNav={isDropdownVisible} /> {sectionsToShow.length ? ( diff --git a/client/src/CommandBar/steps/PrivateRepos/index.tsx b/client/src/CommandBar/steps/PrivateRepos/index.tsx index c8c370ce8d..0c4eaf6059 100644 --- a/client/src/CommandBar/steps/PrivateRepos/index.tsx +++ b/client/src/CommandBar/steps/PrivateRepos/index.tsx @@ -104,6 +104,7 @@ const PrivateReposStep = ({}: Props) => { value={inputValue} onChange={handleInputChange} placeholder={t('Search private repos...')} + disableKeyNav={isDropdownVisible} /> {sectionsToShow.length ? ( diff --git a/client/src/CommandBar/steps/SeachFiles.tsx b/client/src/CommandBar/steps/SeachFiles.tsx index ecab43a98c..0db638b670 100644 --- a/client/src/CommandBar/steps/SeachFiles.tsx +++ b/client/src/CommandBar/steps/SeachFiles.tsx @@ -66,6 +66,7 @@ const SearchFiles = ({}: Props) => { onClick: () => { openNewTab({ type: TabTypesEnum.FILE, path, repoRef: repo }); setIsVisible(false); + setChosenStep({ id: CommandBarStepEnum.INITIAL }); }, label: path, footerHint: t('Open'), diff --git a/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx b/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx index e6c5a3a30f..2ffe8661f8 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx @@ -58,7 +58,7 @@ const ChatPersistentState = ({ const { setChats } = useContext(ChatsContext); const { openNewTab, updateTabProperty } = useContext(TabsContext.Handlers); - const prevEventSource = useRef(null); + const eventSource = useRef(null); const [conversation, setConversation] = useState([]); useEffect(() => { @@ -232,11 +232,11 @@ const ChatPersistentState = ({ }, [setInputValueImperatively]); const makeSearch = useCallback( - (query: string, options?: Options) => { + async (query: string, options?: Options) => { if (!query) { return; } - prevEventSource.current?.close(); + eventSource.current?.close(); setInputValue({ plain: '', parsed: [] }); setInputImperativeValue(null); setLoading(true); @@ -270,11 +270,10 @@ const ChatPersistentState = ({ } const fullUrl = url + '?' + new URLSearchParams(queryParams).toString(); console.log(fullUrl); - const eventSource = new EventSource(fullUrl); - prevEventSource.current = eventSource; + eventSource.current = new EventSource(fullUrl); setSelectedLines(null); let firstResultCame: boolean; - eventSource.onerror = (err) => { + eventSource.current.onerror = (err) => { console.log('SSE error', err); firstResultCame = false; stopGenerating(); @@ -303,26 +302,30 @@ const ChatPersistentState = ({ }); }; let conversation_id = ''; - setConversation((prev) => [ - ...prev, - { - author: ChatMessageAuthor.Server, - isLoading: true, - loadingSteps: [], - text: '', - conclusion: '', - queryId: '', - responseTimestamp: '', - }, - ]); - eventSource.onmessage = (ev) => { + setConversation((prev) => + prev[prev.length - 1].author === ChatMessageAuthor.Server && + (prev[prev.length - 1] as ChatMessageServer).isLoading + ? prev + : [ + ...prev, + { + author: ChatMessageAuthor.Server, + isLoading: true, + loadingSteps: [], + text: '', + conclusion: '', + queryId: '', + responseTimestamp: '', + }, + ], + ); + eventSource.current.onmessage = (ev) => { console.log(ev.data); if ( ev.data === '{"Err":"incompatible client"}' || ev.data === '{"Err":"failed to check compatibility"}' ) { - eventSource.close(); - prevEventSource.current?.close(); + eventSource.current?.close(); if (ev.data === '{"Err":"incompatible client"}') { setDeprecatedModalOpen(true); } else { @@ -408,8 +411,8 @@ const ChatPersistentState = ({ side, ); } - eventSource.close(); - prevEventSource.current = null; + eventSource.current?.close(); + eventSource.current = null; setLoading(false); setConversation((prev) => { const newConversation = prev.slice(0, -1); @@ -468,13 +471,16 @@ const ChatPersistentState = ({ console.log('failed to parse response', err); } }; - return () => { - eventSource.close(); - }; }, [conversationId, t, queryIdToEdit, preferredAnswerSpeed, openNewTab, side], ); + useEffect(() => { + return () => { + eventSource.current?.close(); + }; + }, []); + useEffect(() => { if (!submittedQuery.plain) { return; @@ -497,7 +503,10 @@ const ChatPersistentState = ({ userQueryParsed = [{ type: ParsedQueryTypeEnum.TEXT, text: userQuery }]; } setConversation((prev) => { - return prev.length === 1 && submittedQuery.options + return (prev.length === 1 && submittedQuery.options) || + (prev.length === 2 && + submittedQuery.options?.lines && + submittedQuery.options === initialQuery) ? prev : [ ...prev, @@ -524,7 +533,7 @@ const ChatPersistentState = ({ }, [conversation, tabKey, side, tabTitle]); const stopGenerating = useCallback(() => { - prevEventSource.current?.close(); + eventSource.current?.close(); setLoading(false); setConversation((prev) => { const newConversation = prev.slice(0, -1); diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx index d2b9ce0275..2706719de3 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx @@ -145,7 +145,7 @@ const InputCore = ({ if (key && state[key]?.active) { return false; } - const parts = state.toJSON().doc.content[0]?.content; + const parts = state.toJSON().doc?.content?.[0]?.content; // trying to submit with no text if (!parts) { return false; @@ -193,7 +193,7 @@ const InputCore = ({ ); useEffect(() => { - const newValue = state.toJSON().doc.content[0]?.content; + const newValue = state.toJSON().doc?.content?.[0]?.content || ''; onChange(newValue || []); }, [state]); diff --git a/client/src/Project/CurrentTabContent/EmptyTab.tsx b/client/src/Project/CurrentTabContent/EmptyTab.tsx index 526967038c..b9dc87d1ef 100644 --- a/client/src/Project/CurrentTabContent/EmptyTab.tsx +++ b/client/src/Project/CurrentTabContent/EmptyTab.tsx @@ -20,7 +20,7 @@ const EmptyTab = ({}: Props) => { Select a file or open a new tab to display it here.{' '} Press{' '} - + cmdKey {' '} diff --git a/client/src/Project/CurrentTabContent/FileTab/index.tsx b/client/src/Project/CurrentTabContent/FileTab/index.tsx index 5ab6dd929e..2942e376d0 100644 --- a/client/src/Project/CurrentTabContent/FileTab/index.tsx +++ b/client/src/Project/CurrentTabContent/FileTab/index.tsx @@ -254,6 +254,7 @@ const FileTab = ({ {({ width, height }) => ( void; }; const RepoDropdown = ({ @@ -48,6 +50,7 @@ const RepoDropdown = ({ allBranches, projectId, indexingStatus, + handleClose, }: Props) => { const { t } = useTranslation(); const [isBranchesOpen, setIsBranchesOpen] = useState(false); @@ -57,6 +60,9 @@ const RepoDropdown = ({ const { isSelfServe } = useContext(DeviceContext); const { refreshCurrentProjectRepos } = useContext(ProjectContext.Current); const { isSubscribed } = useContext(PersonalQuotaContext.Values); + const { setIsUpgradeRequiredPopupOpen } = useContext( + UIContext.UpgradeRequiredPopup, + ); const onRepoSync = useCallback( async (e?: MouseEvent) => { @@ -142,10 +148,15 @@ const RepoDropdown = ({ const switchToBranch = useCallback( async (branch: string, e?: MouseEvent) => { e?.stopPropagation(); - await changeRepoBranch(projectId, repoRef, branch); - refreshCurrentProjectRepos(); + if (isSubscribed || isSelfServe) { + await changeRepoBranch(projectId, repoRef, branch); + refreshCurrentProjectRepos(); + } else { + setIsUpgradeRequiredPopupOpen(true); + handleClose(); + } }, - [projectId, repoRef], + [projectId, repoRef, isSubscribed, isSelfServe], ); return ( @@ -162,24 +173,22 @@ const RepoDropdown = ({ ) } /> - {(isSelfServe || isSubscribed) && ( - } - customRightElement={ - - {selectedBranch} - - - } - /> - )} + } + customRightElement={ + + {selectedBranch} + + + } + />
{ - setBranchesToSync((prev) => [...prev, b]); - await indexRepoBranch(repoRef, b); + if (isSubscribed || isSelfServe) { + setBranchesToSync((prev) => [...prev, b]); + await indexRepoBranch(repoRef, b); + } else { + setIsUpgradeRequiredPopupOpen(true); + handleClose(); + } }} > {t('Sync')} diff --git a/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx b/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx index ac6de34ec2..6f2103f069 100644 --- a/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx +++ b/client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx @@ -4,6 +4,7 @@ import { memo, useCallback, useContext, + useEffect, useRef, useState, } from 'react'; @@ -59,7 +60,9 @@ const RegexSearchPanel = ({ projectId, isRegexEnabled }: Props) => { const inputRef = useRef(null); const { setIsRegexSearchEnabled } = useContext(ProjectContext.RegexSearch); const { isVisible } = useContext(CommandBarContext.General); - const { isLeftSidebarFocused } = useContext(UIContext.Focus); + const { isLeftSidebarFocused, setIsLeftSidebarFocused } = useContext( + UIContext.Focus, + ); const [focusedIndex, setFocusedIndex] = useState(-1); const onChange = useCallback((e: ChangeEvent) => { @@ -187,6 +190,12 @@ const RegexSearchPanel = ({ projectId, isRegexEnabled }: Props) => { isVisible || !isRegexEnabled || !isLeftSidebarFocused, ); + useEffect(() => { + if (isRegexEnabled) { + setIsLeftSidebarFocused(true); + } + }, [isRegexEnabled]); + return !isRegexEnabled ? null : (
{ const ref = useRef(null); const listProps = useMemo( @@ -89,6 +91,7 @@ const VirtualizedCode = ({ showLineNumbers hoverEffect style={style} + searchTerm={searchTerm} shouldHighlight={ (!!scrollToIndex && index >= scrollToIndex[0] && diff --git a/client/src/components/Code/CodeFull/index.tsx b/client/src/components/Code/CodeFull/index.tsx index 00a824f369..f6fc3f3e69 100644 --- a/client/src/components/Code/CodeFull/index.tsx +++ b/client/src/components/Code/CodeFull/index.tsx @@ -17,6 +17,8 @@ import { TabTypesEnum } from '../../../types/general'; import { useOnClickOutside } from '../../../hooks/useOnClickOutsideHook'; import RefsDefsPopup from '../../RefsDefsPopup'; import { copyToClipboard, getSelectionLines } from '../../../utils'; +import SearchOnPage from '../../SearchOnPage'; +import { useCodeSearch } from '../../../hooks/useCodeSearch'; import SelectionPopup from './SelectionPopup'; import VirtualizedCode from './VirtualizedCode'; @@ -38,6 +40,7 @@ type Props = { side: 'left' | 'right'; width: number; height: number; + isSearchDisabled?: boolean; }; const CodeFull = ({ @@ -55,6 +58,7 @@ const CodeFull = ({ side, width, height, + isSearchDisabled, }: Props) => { const { openNewTab } = useContext(TabsContext.Handlers); const [tokenInfo, setTokenInfo] = useState({ @@ -76,6 +80,34 @@ const CodeFull = ({ right?: number; } | null>(null); useOnClickOutside(popupRef, () => setPopupVisible(false)); + const scrollLineNumber = useMemo( + () => + scrollToLine?.split('_').map((s) => Number(s)) as + | [number, number] + | undefined, + [scrollToLine], + ); + const [scrollToIndex, setScrollToIndex] = useState( + scrollLineNumber || undefined, + ); + useEffect(() => { + setScrollToIndex(scrollLineNumber || undefined); + }, [scrollLineNumber]); + + const { + handleSearchCancel, + isSearchActive, + setSearchTerm, + searchTerm, + searchResults, + setCurrentResult, + currentResult, + deferredSearchTerm, + } = useCodeSearch({ + code, + setScrollToIndex, + isDisabled: isSearchDisabled, + }); const lang = useMemo( () => getPrismLanguage(language) || 'plaintext', @@ -83,12 +115,6 @@ const CodeFull = ({ ); const tokens = useMemo(() => tokenizeCode(code, lang), [code, lang]); - const scrollToIndex = useMemo(() => { - return scrollToLine?.split('_').map((s) => Number(s)) as - | [number, number] - | undefined; - }, [scrollToLine]); - const getHoverableContent = useCallback( (hoverableRange: Range, tokenRange: Range, lineNumber?: number) => { if (hoverableRange && relativePath) { @@ -256,6 +282,16 @@ const CodeFull = ({ return (
+
         
       
diff --git a/client/src/components/Code/CodeLine.tsx b/client/src/components/Code/CodeLine.tsx index 980c3708ba..cdb2e5419d 100644 --- a/client/src/components/Code/CodeLine.tsx +++ b/client/src/components/Code/CodeLine.tsx @@ -1,4 +1,12 @@ -import React, { ReactNode, useRef, memo, useMemo, CSSProperties } from 'react'; +import React, { + ReactNode, + useRef, + memo, + useMemo, + CSSProperties, + useEffect, +} from 'react'; +import { markNode, unmark } from '../../utils/textSearch'; type Props = { lineNumber: number; @@ -13,6 +21,7 @@ type Props = { hoveredBackground?: boolean; highlightColor?: string | null; style?: CSSProperties; + searchTerm?: string; }; const CodeLine = ({ @@ -28,8 +37,27 @@ const CodeLine = ({ highlightColor, hoveredBackground, style, + searchTerm, }: Props) => { const codeRef = useRef(null); + + useEffect(() => { + if (codeRef.current && searchTerm) { + markNode( + codeRef.current, + new RegExp( + searchTerm.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), + 'gi', + ), + ); + } + return () => { + if (codeRef.current) { + unmark(codeRef.current); + } + }; + }, [searchTerm]); + const styleCombined = useMemo( () => ({ ...style, diff --git a/client/src/components/SearchOnPage/index.tsx b/client/src/components/SearchOnPage/index.tsx index a16d698822..f1a00448f5 100644 --- a/client/src/components/SearchOnPage/index.tsx +++ b/client/src/components/SearchOnPage/index.tsx @@ -55,14 +55,14 @@ const SearchOnPage = ({ inputClassName="pr-24" onEscape={onCancel} /> -
+
{resultNum ? ( {currentResult}/{resultNum} ) : null} {' '} + + to seamlessly explore code across all branches in your GitHub + repositories, maximizing your code discovery capabilities. + +

+
+ +
+
+ +
+
+ + ); +}; + +export default memo(UpgradeRequiredPopup); diff --git a/client/src/context/providers/RepositoriesContextProvider.tsx b/client/src/context/providers/RepositoriesContextProvider.tsx index 347fdfaa9c..0f1b608c27 100644 --- a/client/src/context/providers/RepositoriesContextProvider.tsx +++ b/client/src/context/providers/RepositoriesContextProvider.tsx @@ -41,7 +41,7 @@ const RepositoriesContextProvider = ({ const data = JSON.parse(ev.data); console.log('data', data); if (data.ev?.status_change === SyncStatus.Done) { - if (!data.ev?.rsync) { + if (!data.rsync) { toast(t('Repository indexed'), { id: `${data.ref}-indexed`, description: ( diff --git a/client/src/context/providers/UIContextProvider.tsx b/client/src/context/providers/UIContextProvider.tsx index 0d5693b6f5..c008e22939 100644 --- a/client/src/context/providers/UIContextProvider.tsx +++ b/client/src/context/providers/UIContextProvider.tsx @@ -54,6 +54,8 @@ export const UIContextProvider = memo( (getPlainFromStorage(THEME) as 'system' | null) || 'system', ); const [isLeftSidebarFocused, setIsLeftSidebarFocused] = useState(false); + const [isUpgradeRequiredPopupOpen, setIsUpgradeRequiredPopupOpen] = + useState(false); const refreshToken = useCallback(async (refToken: string) => { if (refToken) { @@ -172,6 +174,14 @@ export const UIContextProvider = memo( [isLeftSidebarFocused], ); + const upgradePopupContextValue = useMemo( + () => ({ + isUpgradeRequiredPopupOpen, + setIsUpgradeRequiredPopupOpen, + }), + [isUpgradeRequiredPopupOpen], + ); + return ( @@ -179,9 +189,13 @@ export const UIContextProvider = memo( - - {children} - + + + {children} + + diff --git a/client/src/context/uiContext.ts b/client/src/context/uiContext.ts index 2cd0afb867..158cc0f270 100644 --- a/client/src/context/uiContext.ts +++ b/client/src/context/uiContext.ts @@ -39,4 +39,8 @@ export const UIContext = { isLeftSidebarFocused: false, setIsLeftSidebarFocused: (b: boolean) => {}, }), + UpgradeRequiredPopup: createContext({ + isUpgradeRequiredPopupOpen: false, + setIsUpgradeRequiredPopupOpen: (b: boolean) => {}, + }), }; diff --git a/client/src/hooks/useCodeSearch.ts b/client/src/hooks/useCodeSearch.ts new file mode 100644 index 0000000000..a7631d2e6f --- /dev/null +++ b/client/src/hooks/useCodeSearch.ts @@ -0,0 +1,100 @@ +import { useCallback, useDeferredValue, useEffect, useState } from 'react'; +import { checkEventKeys } from '../utils/keyboardUtils'; +import useKeyboardNavigation from './useKeyboardNavigation'; + +type Props = { + setScrollToIndex: (i?: [number, number]) => void; + isDisabled?: boolean; + code: string; +}; +export const useCodeSearch = ({ + setScrollToIndex, + isDisabled, + code, +}: Props) => { + const [isSearchActive, setSearchActive] = useState(false); + const [searchResults, setSearchResults] = useState([]); + const [currentResult, setCurrentResult] = useState(0); + const [searchTerm, setSearchTerm] = useState(''); + const deferredSearchTerm = useDeferredValue(searchTerm); + + const handleKeyEvent = useCallback( + (e: KeyboardEvent) => { + if (checkEventKeys(e, ['cmd', 'F'])) { + e.preventDefault(); + e.stopPropagation(); + setSearchActive((prev) => !prev); + return false; + } else if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + setSearchActive((prev) => { + if (prev) { + e.preventDefault(); + } + return false; + }); + setScrollToIndex(undefined); + setSearchTerm(''); + } else if (e.key === 'Enter') { + const isNext = !e.shiftKey; + setCurrentResult((prev) => + isNext + ? prev < searchResults.length + ? prev + 1 + : 1 + : prev > 1 + ? prev - 1 + : searchResults.length, + ); + } + }, + [searchResults], + ); + useKeyboardNavigation(handleKeyEvent, isDisabled); + + useEffect(() => { + if (deferredSearchTerm === '') { + setSearchResults([]); + setCurrentResult(0); + return; + } + const lines = code.split('\n'); + const results = lines.reduce(function (prev: number[], cur, i) { + if (cur.toLowerCase().includes(deferredSearchTerm.toLowerCase())) { + prev.push(i); + } + return prev; + }, []); + const currentlyHighlightedLine = searchResults[currentResult - 1]; + const indexInNewResults = results.indexOf(currentlyHighlightedLine); + setSearchResults(results); + setCurrentResult(indexInNewResults >= 0 ? indexInNewResults + 1 : 1); + }, [deferredSearchTerm]); + + useEffect(() => { + if (searchResults[currentResult - 1]) { + setScrollToIndex([ + searchResults[currentResult - 1], + searchResults[currentResult - 1], + ]); + } + }, [currentResult, searchResults]); + + const handleSearchCancel = useCallback(() => { + setSearchTerm(''); + setSearchActive(false); + setScrollToIndex(undefined); + }, []); + + return { + setSearchTerm, + handleSearchCancel, + isSearchActive, + searchResults, + currentResult, + setCurrentResult, + searchTerm, + deferredSearchTerm, + }; +}; diff --git a/client/src/index.css b/client/src/index.css index bad8dc85a6..81999480e2 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1143,11 +1143,11 @@ h5, .h5 { } .search-highlight { - background-color: rgba(var(--bg-highlight), 0.25); + background-color: rgba(var(--yellow), 0.25); } .search-highlight-active { - background-color: rgba(var(--bg-highlight), 0.8); + background-color: rgba(var(--yellow), 0.5); } .onboarding-chats-img {