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 {