Просмотр исходного кода

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

[web] Editor redesign: Add full project search

GitOrigin-RevId: b4327c4ba0ddd7387ec8d6640e31200ca0fe4a6e
Mathias Jakobsen 1 год назад
Родитель
Сommit
48337b2e2c

+ 1 - 0
services/web/config/settings.defaults.js

@@ -996,6 +996,7 @@ module.exports = {
     toastGenerators: [],
     toastGenerators: [],
     editorSidebarComponents: [],
     editorSidebarComponents: [],
     fileTreeToolbarComponents: [],
     fileTreeToolbarComponents: [],
+    fullProjectSearchPanel: [],
     integrationPanelComponents: [],
     integrationPanelComponents: [],
     referenceSearchSetting: [],
     referenceSearchSetting: [],
   },
   },

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -1292,6 +1292,7 @@
   "project_ownership_transfer_confirmation_2": "",
   "project_ownership_transfer_confirmation_2": "",
   "project_renamed_or_deleted": "",
   "project_renamed_or_deleted": "",
   "project_renamed_or_deleted_detail": "",
   "project_renamed_or_deleted_detail": "",
+  "project_search": "",
   "project_search_file_count": "",
   "project_search_file_count": "",
   "project_search_file_count_plural": "",
   "project_search_file_count_plural": "",
   "project_search_result_count": "",
   "project_search_result_count": "",

BIN
services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2


+ 2 - 1
services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs

@@ -10,6 +10,7 @@ export default /** @type {const} */ ([
   'create_new_folder',
   'create_new_folder',
   'delete',
   'delete',
   'description',
   'description',
+  'error',
   'experiment',
   'experiment',
   'forum',
   'forum',
   'help',
   'help',
@@ -20,10 +21,10 @@ export default /** @type {const} */ ([
   'picture_as_pdf',
   'picture_as_pdf',
   'rate_review',
   'rate_review',
   'report',
   'report',
+  'search',
   'settings',
   'settings',
   'space_dashboard',
   'space_dashboard',
   'table_chart',
   'table_chart',
   'upload_file',
   'upload_file',
   'web_asset',
   'web_asset',
-  'error',
 ])
 ])

+ 1 - 1
services/web/frontend/js/features/event-tracking/search-events.ts

@@ -6,7 +6,7 @@ type SearchEventSegmentation = {
         searchType: 'full-project'
         searchType: 'full-project'
       } & (
       } & (
         | { method: 'keyboard' }
         | { method: 'keyboard' }
-        | { method: 'button'; location: 'toolbar' | 'search-form' }
+        | { method: 'button'; location: 'toolbar' | 'search-form' | 'rail' }
       ))
       ))
     | ({
     | ({
         searchType: 'document'
         searchType: 'document'

+ 19 - 0
services/web/frontend/js/features/ide-redesign/components/full-project-search-panel.tsx

@@ -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)

+ 21 - 2
services/web/frontend/js/features/ide-redesign/components/rail.tsx

@@ -34,6 +34,11 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
 import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
 import { useChatContext } from '@/features/chat/context/chat-context'
 import { useChatContext } from '@/features/chat/context/chat-context'
 import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
 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 = {
 type RailElement = {
   icon: AvailableUnfilledIcon
   icon: AvailableUnfilledIcon
@@ -106,6 +111,13 @@ export const RailLayout = () => {
         title: t('file_tree'),
         title: t('file_tree'),
         component: <FileTreeOutlinePanel />,
         component: <FileTreeOutlinePanel />,
       },
       },
+      {
+        key: 'full-project-search',
+        icon: 'search',
+        title: t('project_search'),
+        component: <FullProjectSearchPanel />,
+        hide: !hasFullProjectSearch,
+      },
       {
       {
         key: 'integrations',
         key: 'integrations',
         icon: 'integration_instructions',
         icon: 'integration_instructions',
@@ -170,10 +182,17 @@ export const RailLayout = () => {
           // Attempting to open a non-existent tab
           // Attempting to open a non-existent tab
           return
           return
         }
         }
-        const keyOrDefault = key ?? 'file-tree'
+        const keyOrDefault = (key ?? 'file-tree') as RailTabKey
         // Change the selected tab and make sure it's open
         // Change the selected tab and make sure it's open
-        openTab(keyOrDefault as RailTabKey)
+        openTab(keyOrDefault)
         sendEvent('rail-click', { tab: keyOrDefault })
         sendEvent('rail-click', { tab: keyOrDefault })
+        if (keyOrDefault === 'full-project-search') {
+          sendSearchEvent('search-open', {
+            searchType: 'full-project',
+            method: 'button',
+            location: 'rail',
+          })
+        }
 
 
         if (key === 'chat') {
         if (key === 'chat') {
           markMessagesAsRead()
           markMessagesAsRead()

+ 1 - 0
services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx

@@ -19,6 +19,7 @@ export type RailTabKey =
   | 'review-panel'
   | 'review-panel'
   | 'chat'
   | 'chat'
   | 'errors'
   | 'errors'
+  | 'full-project-search'
 
 
 export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary'
 export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary'
 
 

+ 1 - 4
services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx

@@ -36,7 +36,6 @@ import { getStoredSelection, setStoredSelection } from '../extensions/search'
 import { debounce } from 'lodash'
 import { debounce } from 'lodash'
 import { EditorSelection, EditorState } from '@codemirror/state'
 import { EditorSelection, EditorState } from '@codemirror/state'
 import { sendSearchEvent } from '@/features/event-tracking/search-events'
 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'
 import { FullProjectSearchButton } from './full-project-search-button'
 
 
 const MATCH_COUNT_DEBOUNCE_WAIT = 100 // the amount of ms to wait before counting matches
 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 inputRef = useRef<HTMLInputElement | null>(null)
   const replaceRef = useRef<HTMLInputElement | null>(null)
   const replaceRef = useRef<HTMLInputElement | null>(null)
 
 
-  const newEditor = useIsNewEditorEnabled()
-
   const handleInputRef = useCallback((node: HTMLInputElement) => {
   const handleInputRef = useCallback((node: HTMLInputElement) => {
     inputRef.current = node
     inputRef.current = node
 
 
@@ -443,7 +440,7 @@ const CodeMirrorSearchForm: FC<React.PropsWithChildren> = () => {
             </OLButton>
             </OLButton>
           </OLButtonGroup>
           </OLButtonGroup>
 
 
-          {!newEditor && <FullProjectSearchButton query={query} />}
+          <FullProjectSearchButton query={query} />
 
 
           {position !== null && (
           {position !== null && (
             <div className="ol-cm-search-form-position">
             <div className="ol-cm-search-form-position">

+ 10 - 2
services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx

@@ -12,6 +12,8 @@ import Close from '@/shared/components/close'
 import useTutorial from '@/shared/hooks/promotions/use-tutorial'
 import useTutorial from '@/shared/hooks/promotions/use-tutorial'
 import { useEditorContext } from '@/shared/context/editor-context'
 import { useEditorContext } from '@/shared/context/editor-context'
 import getMeta from '@/utils/meta'
 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')
 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 view = useCodeMirrorViewContext()
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { setProjectSearchIsOpen } = useLayoutContext()
   const { setProjectSearchIsOpen } = useLayoutContext()
+  const newEditor = useIsNewEditorEnabled()
+  const { openTab } = useRailContext()
   const ref = useRef<HTMLButtonElement>(null)
   const ref = useRef<HTMLButtonElement>(null)
 
 
   const { inactiveTutorials } = useEditorContext()
   const { inactiveTutorials } = useEditorContext()
@@ -44,14 +48,18 @@ export const FullProjectSearchButton = ({ query }: { query: SearchQuery }) => {
   }
   }
 
 
   const openFullProjectSearch = useCallback(() => {
   const openFullProjectSearch = useCallback(() => {
-    setProjectSearchIsOpen(true)
+    if (newEditor) {
+      openTab('full-project-search')
+    } else {
+      setProjectSearchIsOpen(true)
+    }
     closeSearchPanel(view)
     closeSearchPanel(view)
     window.setTimeout(() => {
     window.setTimeout(() => {
       window.dispatchEvent(
       window.dispatchEvent(
         new CustomEvent('editor:full-project-search', { detail: query })
         new CustomEvent('editor:full-project-search', { detail: query })
       )
       )
     }, 200)
     }, 200)
-  }, [setProjectSearchIsOpen, query, view])
+  }, [setProjectSearchIsOpen, query, view, newEditor, openTab])
 
 
   const onClick = useCallback(() => {
   const onClick = useCallback(() => {
     sendSearchEvent('search-open', {
     sendSearchEvent('search-open', {

+ 1 - 0
services/web/locales/en.json

@@ -1708,6 +1708,7 @@
   "project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
   "project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
   "project_renamed_or_deleted": "Project Renamed or Deleted",
   "project_renamed_or_deleted": "Project Renamed or Deleted",
   "project_renamed_or_deleted_detail": "This project has either been renamed or deleted by an external data source such as Dropbox. We don’t want to delete your data on Overleaf, so this project still contains your history and collaborators. If the project has been renamed please look in your project list for a new project under the new name.",
   "project_renamed_or_deleted_detail": "This project has either been renamed or deleted by an external data source such as Dropbox. We don’t want to delete your data on Overleaf, so this project still contains your history and collaborators. If the project has been renamed please look in your project list for a new project under the new name.",
+  "project_search": "Project search",
   "project_search_file_count": "in __count__ file",
   "project_search_file_count": "in __count__ file",
   "project_search_file_count_plural": "in __count__ files",
   "project_search_file_count_plural": "in __count__ files",
   "project_search_result_count": "__count__ result",
   "project_search_result_count": "__count__ result",