Merge pull request #25808 from overleaf/mj-ide-full-project-search

[web] Editor redesign: Add full project search

GitOrigin-RevId: b4327c4ba0ddd7387ec8d6640e31200ca0fe4a6e
This commit is contained in:
Mathias Jakobsen
2025-06-03 08:06:52 +00:00
committed by Copybot
parent 3a96df4623
commit 48337b2e2c
11 changed files with 58 additions and 10 deletions
@@ -1292,6 +1292,7 @@
"project_ownership_transfer_confirmation_2": "",
"project_renamed_or_deleted": "",
"project_renamed_or_deleted_detail": "",
"project_search": "",
"project_search_file_count": "",
"project_search_file_count_plural": "",
"project_search_result_count": "",
@@ -10,6 +10,7 @@ export default /** @type {const} */ ([
'create_new_folder',
'delete',
'description',
'error',
'experiment',
'forum',
'help',
@@ -20,10 +21,10 @@ export default /** @type {const} */ ([
'picture_as_pdf',
'rate_review',
'report',
'search',
'settings',
'space_dashboard',
'table_chart',
'upload_file',
'web_asset',
'error',
])
@@ -6,7 +6,7 @@ type SearchEventSegmentation = {
searchType: 'full-project'
} & (
| { method: 'keyboard' }
| { method: 'button'; location: 'toolbar' | 'search-form' }
| { method: 'button'; location: 'toolbar' | 'search-form' | 'rail' }
))
| ({
searchType: 'document'
@@ -0,0 +1,19 @@
import { ElementType } from 'react'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
const componentModule = importOverleafModules('fullProjectSearchPanel')[0] as
| {
import: { default: ElementType }
path: string
}
| undefined
export const FullProjectSearchPanel = () => {
if (!componentModule) {
return null
}
const FullProjectSearch = componentModule.import.default
return <FullProjectSearch />
}
export const hasFullProjectSearch = Boolean(componentModule)
@@ -34,6 +34,11 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
import { useChatContext } from '@/features/chat/context/chat-context'
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
import {
FullProjectSearchPanel,
hasFullProjectSearch,
} from './full-project-search-panel'
import { sendSearchEvent } from '@/features/event-tracking/search-events'
type RailElement = {
icon: AvailableUnfilledIcon
@@ -106,6 +111,13 @@ export const RailLayout = () => {
title: t('file_tree'),
component: <FileTreeOutlinePanel />,
},
{
key: 'full-project-search',
icon: 'search',
title: t('project_search'),
component: <FullProjectSearchPanel />,
hide: !hasFullProjectSearch,
},
{
key: 'integrations',
icon: 'integration_instructions',
@@ -170,10 +182,17 @@ export const RailLayout = () => {
// Attempting to open a non-existent tab
return
}
const keyOrDefault = key ?? 'file-tree'
const keyOrDefault = (key ?? 'file-tree') as RailTabKey
// Change the selected tab and make sure it's open
openTab(keyOrDefault as RailTabKey)
openTab(keyOrDefault)
sendEvent('rail-click', { tab: keyOrDefault })
if (keyOrDefault === 'full-project-search') {
sendSearchEvent('search-open', {
searchType: 'full-project',
method: 'button',
location: 'rail',
})
}
if (key === 'chat') {
markMessagesAsRead()
@@ -19,6 +19,7 @@ export type RailTabKey =
| 'review-panel'
| 'chat'
| 'errors'
| 'full-project-search'
export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary'
@@ -36,7 +36,6 @@ import { getStoredSelection, setStoredSelection } from '../extensions/search'
import { debounce } from 'lodash'
import { EditorSelection, EditorState } from '@codemirror/state'
import { sendSearchEvent } from '@/features/event-tracking/search-events'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import { FullProjectSearchButton } from './full-project-search-button'
const MATCH_COUNT_DEBOUNCE_WAIT = 100 // the amount of ms to wait before counting matches
@@ -82,8 +81,6 @@ const CodeMirrorSearchForm: FC<React.PropsWithChildren> = () => {
const inputRef = useRef<HTMLInputElement | null>(null)
const replaceRef = useRef<HTMLInputElement | null>(null)
const newEditor = useIsNewEditorEnabled()
const handleInputRef = useCallback((node: HTMLInputElement) => {
inputRef.current = node
@@ -443,7 +440,7 @@ const CodeMirrorSearchForm: FC<React.PropsWithChildren> = () => {
</OLButton>
</OLButtonGroup>
{!newEditor && <FullProjectSearchButton query={query} />}
<FullProjectSearchButton query={query} />
{position !== null && (
<div className="ol-cm-search-form-position">
@@ -12,6 +12,8 @@ import Close from '@/shared/components/close'
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
import { useEditorContext } from '@/shared/context/editor-context'
import getMeta from '@/utils/meta'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
const PROMOTION_SIGNUP_CUT_OFF_DATE = new Date('2025-04-22T00:00:00Z')
@@ -19,6 +21,8 @@ export const FullProjectSearchButton = ({ query }: { query: SearchQuery }) => {
const view = useCodeMirrorViewContext()
const { t } = useTranslation()
const { setProjectSearchIsOpen } = useLayoutContext()
const newEditor = useIsNewEditorEnabled()
const { openTab } = useRailContext()
const ref = useRef<HTMLButtonElement>(null)
const { inactiveTutorials } = useEditorContext()
@@ -44,14 +48,18 @@ export const FullProjectSearchButton = ({ query }: { query: SearchQuery }) => {
}
const openFullProjectSearch = useCallback(() => {
setProjectSearchIsOpen(true)
if (newEditor) {
openTab('full-project-search')
} else {
setProjectSearchIsOpen(true)
}
closeSearchPanel(view)
window.setTimeout(() => {
window.dispatchEvent(
new CustomEvent('editor:full-project-search', { detail: query })
)
}, 200)
}, [setProjectSearchIsOpen, query, view])
}, [setProjectSearchIsOpen, query, view, newEditor, openTab])
const onClick = useCallback(() => {
sendSearchEvent('search-open', {