From 4e2ea44b61d356f5d43ec56e95616cb1313a4adc Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Wed, 14 Jan 2026 12:41:41 +0100 Subject: [PATCH] [web] add BibTeX visual editor module (#30726) * [web] add BibTeX visual editor module * Make the visual editor hook open to extension Instead of looking specifically for the bibtex visual editor, allow any extension to provide a visual editor. * Fix stylelint error --------- Co-authored-by: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> GitOrigin-RevId: c85c27a2b119c826e3d00cbd475a2a21f1508091 --- .../Features/Project/ProjectController.mjs | 1 + services/web/config/settings.defaults.js | 1 + .../components/codemirror-editor.tsx | 12 ++++++++ .../components/editor-switch.tsx | 6 ++-- .../hooks/use-codemirror-scope.ts | 5 ++-- .../source-editor/utils/visual-editor.ts | 29 +++++++++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 services/web/frontend/js/features/source-editor/utils/visual-editor.ts 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 +}