diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs
index ecbc0136c8..81803bc7d9 100644
--- a/services/web/app/src/Features/Project/ProjectController.mjs
+++ b/services/web/app/src/Features/Project/ProjectController.mjs
@@ -432,6 +432,7 @@ const _ProjectController = {
}
const splitTests = [
+ 'bibtex-visual-editor',
'compile-log-events',
'visual-preview',
'external-socket-heartbeat',
diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js
index 41f463f1cc..797e7cc4eb 100644
--- a/services/web/config/settings.defaults.js
+++ b/services/web/config/settings.defaults.js
@@ -1015,6 +1015,7 @@ module.exports = {
ssoCertificateInfo: [],
v1ImportDataScreen: [],
snapshotUtils: [],
+ visualEditorProviders: [],
usGovBanner: [],
rollingBuildsUpdatedAlert: [],
offlineModeToolbarButtons: [],
diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
index 2031a03c2d..ad92971c51 100644
--- a/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
+++ b/services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx
@@ -17,9 +17,12 @@ import {
CodeMirrorViewContext,
} from './codemirror-context'
import MathPreviewTooltip from './math-preview-tooltip'
+import { getVisualEditorComponent } from '../utils/visual-editor'
import { useToolbarMenuBarEditorCommands } from '@/features/ide-redesign/hooks/use-toolbar-menu-editor-commands'
import { useProjectContext } from '@/shared/context/project-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
+import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
+import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
// TODO: remove this when definitely no longer used
export * from './codemirror-context'
@@ -69,6 +72,13 @@ function CodeMirrorEditor() {
function CodeMirrorEditorComponents() {
useToolbarMenuBarEditorCommands()
const { features } = useProjectContext()
+ const { openDocName } = useEditorOpenDocContext()
+ const { showVisual } = useEditorPropertiesContext()
+
+ const VisualEditor =
+ showVisual && openDocName != null
+ ? getVisualEditorComponent(openDocName)
+ : null
return (
@@ -88,6 +98,8 @@ function CodeMirrorEditorComponents() {
)
)}
+
+ {VisualEditor && }
)
}
diff --git a/services/web/frontend/js/features/source-editor/components/editor-switch.tsx b/services/web/frontend/js/features/source-editor/components/editor-switch.tsx
index 64297eeeb6..012a785ae3 100644
--- a/services/web/frontend/js/features/source-editor/components/editor-switch.tsx
+++ b/services/web/frontend/js/features/source-editor/components/editor-switch.tsx
@@ -1,10 +1,10 @@
import { ChangeEvent, FC, memo, useCallback } from 'react'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
import { sendMB } from '../../../infrastructure/event-tracking'
-import { isValidTeXFile } from '../../../main/is-valid-tex-file'
import { useTranslation } from 'react-i18next'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
+import { isVisualEditorAvailable } from '../utils/visual-editor'
function EditorSwitch() {
const { t } = useTranslation()
@@ -12,7 +12,9 @@ function EditorSwitch() {
useEditorPropertiesContext()
const { openDocName } = useEditorOpenDocContext()
- const richTextAvailable = openDocName ? isValidTeXFile(openDocName) : false
+ const richTextAvailable = openDocName
+ ? isVisualEditorAvailable(openDocName)
+ : false
const handleChange = useCallback(
(event: ChangeEvent) => {
diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
index d96740fb98..af271e04f1 100644
--- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
+++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
@@ -37,7 +37,6 @@ import { setVisual } from '../extensions/visual/visual'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import { setDocName } from '@/features/source-editor/extensions/doc-name'
-import { isValidTeXFile } from '@/main/is-valid-tex-file'
import { captureException } from '@/infrastructure/error-reporter'
import grammarlyExtensionPresent from '@/shared/utils/grammarly'
import { debugConsole } from '@/utils/debugging'
@@ -62,6 +61,7 @@ import { beforeChangeDocEffect } from '@/features/source-editor/extensions/befor
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
import { useEditorSelectionContext } from '@/shared/context/editor-selection-context'
import { useActiveEditorTheme } from '@/shared/hooks/use-active-editor-theme'
+import { isVisualEditorAvailable } from '../utils/visual-editor'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -273,7 +273,8 @@ function useCodeMirrorScope(view: EditorView) {
const { previewByPath } = useFileTreePathContext()
- const showVisual = visual && !!openDocName && isValidTeXFile(openDocName)
+ const showVisual =
+ visual && !!openDocName && isVisualEditorAvailable(openDocName)
const visualRef = useRef({
previewByPath,
diff --git a/services/web/frontend/js/features/source-editor/utils/visual-editor.ts b/services/web/frontend/js/features/source-editor/utils/visual-editor.ts
new file mode 100644
index 0000000000..02c3e91a9d
--- /dev/null
+++ b/services/web/frontend/js/features/source-editor/utils/visual-editor.ts
@@ -0,0 +1,29 @@
+import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
+import { isValidTeXFile } from '../../../main/is-valid-tex-file'
+
+const visualEditorProviders = importOverleafModules('visualEditorProviders')
+
+export function isVisualEditorAvailable(filename: string): boolean {
+ // Core LaTeX visual editor
+ if (isValidTeXFile(filename)) {
+ return true
+ }
+
+ // Visual editors provided by modules
+ for (const provider of visualEditorProviders) {
+ if (provider.import.isVisualEditorAvailable(filename)) {
+ return true
+ }
+ }
+ return false
+}
+
+export function getVisualEditorComponent(filename: string) {
+ for (const provider of visualEditorProviders) {
+ const component = provider.import.getVisualEditorComponent(filename)
+ if (component != null) {
+ return component
+ }
+ }
+ return null
+}