diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index ad8bd618eb..0e0bf3a800 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -333,6 +333,7 @@ const _ProjectController = { const splitTests = [ !anonymous && 'bib-file-tpr-prompt', 'compile-log-events', + 'full-project-search', 'math-preview', 'null-test-share-modal', 'paywall-cta', diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index ddd0d4cd23..c1dd664245 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -983,6 +983,8 @@ module.exports = { autoCompleteExtensions: [], sectionTitleGenerators: [], toastGenerators: [], + editorSidebarComponents: [], + fileTreeToolbarComponents: [], }, moduleImportSequence: [ diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-toolbar.tsx b/services/web/frontend/js/features/file-tree/components/file-tree-toolbar.tsx index 8c7a739245..0f82a232da 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-toolbar.tsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-toolbar.tsx @@ -7,6 +7,12 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' import MaterialIcon from '@/shared/components/material-icon' import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' +import React, { ElementType } from 'react' + +const fileTreeToolbarComponents = importOverleafModules( + 'fileTreeToolbarComponents' +) as { import: { default: ElementType }; path: string }[] function FileTreeToolbar() { const { fileTreeReadOnly } = useFileTreeData() @@ -105,12 +111,14 @@ function FileTreeToolbarRight() { const { canRename, canDelete, startRenaming, startDeleting } = useFileTreeActionable() - if (!canRename && !canDelete) { - return null - } - return (
+ {fileTreeToolbarComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} + {canRename ? ( ) : null} + {canDelete ? ( + {editorSidebarComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} pdfLayout: IdeLayout pdfPreviewOpen: boolean + projectSearchIsOpen: boolean + setProjectSearchIsOpen: Dispatch> } +const isMac = /Mac/.test(window.navigator?.platform) + const debugPdfDetach = getMeta('ol-debugPdfDetach') export const LayoutContext = createContext( @@ -107,6 +113,9 @@ export const LayoutProvider: FC = ({ children }) => { const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown') + // whether the project search is open + const [projectSearchIsOpen, setProjectSearchIsOpen] = useState(false) + useEventListener( 'ui.toggle-left-menu', useCallback( @@ -124,6 +133,21 @@ export const LayoutProvider: FC = ({ children }) => { }, [setReviewPanelOpen]) ) + useEventListener( + 'keydown', + useCallback((event: KeyboardEvent) => { + if ( + (isMac ? event.metaKey : event.ctrlKey) && + event.shiftKey && + event.key === 'f' + ) { + if (isSplitTestEnabled('full-project-search')) { + setProjectSearchIsOpen(true) + } + } + }, []) + ) + // whether to display the editor and preview side-by-side or full-width ("flat") const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout') @@ -194,6 +218,8 @@ export const LayoutProvider: FC = ({ children }) => { leftMenuShown, pdfLayout, pdfPreviewOpen, + projectSearchIsOpen, + setProjectSearchIsOpen, reviewPanelOpen, miniReviewPanelVisible, loadingStyleSheet, @@ -216,6 +242,8 @@ export const LayoutProvider: FC = ({ children }) => { leftMenuShown, pdfLayout, pdfPreviewOpen, + projectSearchIsOpen, + setProjectSearchIsOpen, reviewPanelOpen, miniReviewPanelVisible, loadingStyleSheet, diff --git a/services/web/frontend/js/shared/context/project-context.tsx b/services/web/frontend/js/shared/context/project-context.tsx index 7577886328..4e54acaeed 100644 --- a/services/web/frontend/js/shared/context/project-context.tsx +++ b/services/web/frontend/js/shared/context/project-context.tsx @@ -1,7 +1,8 @@ -import { FC, createContext, useContext, useMemo } from 'react' +import { FC, createContext, useContext, useMemo, useState } from 'react' import useScopeValue from '../hooks/use-scope-value' import getMeta from '@/utils/meta' import { ProjectContextValue } from './types/project-context' +import { ProjectSnapshot } from '@/infrastructure/project-snapshot' const ProjectContext = createContext(undefined) @@ -43,6 +44,8 @@ export const ProjectProvider: FC = ({ children }) => { mainBibliographyDoc_id: mainBibliographyDocId, } = project || projectFallback + const [projectSnapshot] = useState(() => new ProjectSnapshot(_id)) + const tags = useMemo( () => (getMeta('ol-projectTags') || []) @@ -65,6 +68,7 @@ export const ProjectProvider: FC = ({ children }) => { tags, trackChangesState, mainBibliographyDocId, + projectSnapshot, } }, [ _id, @@ -79,6 +83,7 @@ export const ProjectProvider: FC = ({ children }) => { tags, trackChangesState, mainBibliographyDocId, + projectSnapshot, ]) return ( diff --git a/services/web/frontend/js/shared/context/types/project-context.tsx b/services/web/frontend/js/shared/context/types/project-context.tsx index 7eedd18654..ce609d01d1 100644 --- a/services/web/frontend/js/shared/context/types/project-context.tsx +++ b/services/web/frontend/js/shared/context/types/project-context.tsx @@ -1,5 +1,6 @@ import { UserId } from '../../../../../types/user' import { PublicAccessLevel } from '../../../../../types/public-access-level' +import { ProjectSnapshot } from '@/infrastructure/project-snapshot' export type ProjectContextMember = { _id: UserId @@ -46,6 +47,7 @@ export type ProjectContextValue = { color?: string }[] trackChangesState: boolean | Record + projectSnapshot: ProjectSnapshot } export type ProjectContextUpdateValue = Partial diff --git a/services/web/frontend/stylesheets/app/editor/ide-react.less b/services/web/frontend/stylesheets/app/editor/ide-react.less index f6bcfd626d..412796ba0b 100644 --- a/services/web/frontend/stylesheets/app/editor/ide-react.less +++ b/services/web/frontend/stylesheets/app/editor/ide-react.less @@ -51,12 +51,12 @@ // Enable ::before and ::after pseudo-elements to position themselves correctly position: relative; - background-color: @editor-resizer-bg-color; .custom-toggler { padding: 0; border-width: 0; + // Override react-resizable-panels which sets a global * { cursor: ew-resize } cursor: pointer !important; } @@ -74,9 +74,11 @@ width: 7px; height: 18px; } + &::before { top: 25%; } + &::after { top: 75%; } @@ -89,6 +91,7 @@ .synctex-controls { left: -8px; margin: 0; + // Ensure that SyncTex controls appear in front of PDF viewer controls and logs pane z-index: 12; @@ -128,6 +131,7 @@ height: 100%; background-color: @file-tree-bg; color: var(--neutral-20); + position: relative; } .ide-react-symbol-palette { diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss index 1fe67be188..2fa76cfb77 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss @@ -60,6 +60,7 @@ $editor-toggler-bg-dark-color: color.adjust( background-color: var(--file-tree-bg); height: 100%; color: var(--content-secondary-dark); + position: relative; } .ide-react-body {