diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index a7ff970ef0..d8892e70ff 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -996,6 +996,7 @@ module.exports = { toastGenerators: [], editorSidebarComponents: [], fileTreeToolbarComponents: [], + fullProjectSearchPanel: [], integrationPanelComponents: [], referenceSearchSetting: [], }, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 09c2ba90dc..20459e0ed6 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -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": "", diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 index df942df176..8e72799b07 100644 Binary files a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 and b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 differ diff --git a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs index baefac05aa..1c41421910 100644 --- a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs +++ b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs @@ -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', ]) diff --git a/services/web/frontend/js/features/event-tracking/search-events.ts b/services/web/frontend/js/features/event-tracking/search-events.ts index cd9ff4b8ba..630d07aeaa 100644 --- a/services/web/frontend/js/features/event-tracking/search-events.ts +++ b/services/web/frontend/js/features/event-tracking/search-events.ts @@ -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' diff --git a/services/web/frontend/js/features/ide-redesign/components/full-project-search-panel.tsx b/services/web/frontend/js/features/ide-redesign/components/full-project-search-panel.tsx new file mode 100644 index 0000000000..926341ce89 --- /dev/null +++ b/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 +} + +export const hasFullProjectSearch = Boolean(componentModule) diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index d6e1112536..9bd70ac4bb 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/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 { 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: , }, + { + key: 'full-project-search', + icon: 'search', + title: t('project_search'), + component: , + 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() diff --git a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx index c02d17fb9b..85ec482fc0 100644 --- a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx +++ b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx @@ -19,6 +19,7 @@ export type RailTabKey = | 'review-panel' | 'chat' | 'errors' + | 'full-project-search' export type RailModalKey = 'keyboard-shortcuts' | 'contact-us' | 'dictionary' diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx index 90a968add6..a65232f94d 100644 --- a/services/web/frontend/js/features/source-editor/components/codemirror-search-form.tsx +++ b/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 { 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 = () => { const inputRef = useRef(null) const replaceRef = useRef(null) - const newEditor = useIsNewEditorEnabled() - const handleInputRef = useCallback((node: HTMLInputElement) => { inputRef.current = node @@ -443,7 +440,7 @@ const CodeMirrorSearchForm: FC = () => { - {!newEditor && } + {position !== null && (
diff --git a/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx b/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx index 698204d89c..be02fdbe3c 100644 --- a/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx +++ b/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 { 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(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', { diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 445fb62c8b..2efd23fd9f 100644 --- a/services/web/locales/en.json +++ b/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_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_search": "Project search", "project_search_file_count": "in __count__ file", "project_search_file_count_plural": "in __count__ files", "project_search_result_count": "__count__ result",