Add Markdown visual editor support

GitOrigin-RevId: 4ec2ffb276c729a58f82ccb26ed571f4187a4178
This commit is contained in:
Domagoj Kriskovic
2026-06-04 08:06:04 +00:00
committed by Copybot
parent e38f4e18e4
commit 5cf1b43ce7
10 changed files with 45 additions and 9 deletions
@@ -100,7 +100,7 @@ const RichTextToggle: FC<{
if (disabled) {
return (
<OLTooltip
description={t('visual_editor_is_only_available_for_tex_files')}
description={t('visual_editor_does_not_support_this_file_type')}
id="rich-text-toggle-tooltip"
overlayProps={{ placement: 'bottom' }}
tooltipProps={{ className: 'tooltip-wide' }}
@@ -9,6 +9,7 @@ import {
import { visualHighlightStyle, visualTheme } from './visual-theme'
import { atomicDecorations } from './atomic-decorations'
import { markDecorations } from './mark-decorations'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import { EditorView, ViewPlugin } from '@codemirror/view'
import { visualKeymap } from './visual-keymap'
import { mousedown, mouseDownEffect } from './selection'
@@ -27,6 +28,15 @@ type Options = {
previewByPath: (path: string) => PreviewPath | null
}
// Language-specific visual editor extensions provided by modules (e.g. the
// markdown visual editor). They live in the visual bundle so they are only
// active in visual mode and react to switching editor modes.
const visualEditorExtensions: Array<(options: Options) => Extension> =
importOverleafModules('sourceEditorVisualExtensions').map(
(item: { import: { extension: (options: Options) => Extension } }) =>
item.import.extension
)
const visualConf = new Compartment()
export const toggleVisualEffect = StateEffect.define<boolean>()
@@ -171,6 +181,7 @@ const extension = (options: Options) => [
mousedown,
listItemMarker,
atomicDecorations(options),
visualEditorExtensions.map(extension => extension(options)),
markDecorations, // NOTE: must be after atomicDecorations, so that mark decorations wrap inline widgets
visualKeymap,
commandTooltip,
@@ -63,7 +63,7 @@ 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 { useFeatureFlag } from '@/shared/context/split-test-context'
import { isValidTeXFile } from '@/main/is-valid-tex-file'
import { isCmVisualEditorAvailable } from '../utils/visual-editor'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -277,7 +277,8 @@ function useCodeMirrorScope(view: EditorView) {
const { previewByPath } = useFileTreePathContext()
const showVisual = visual && !!openDocName && isValidTeXFile(openDocName)
const showVisual =
visual && !!openDocName && isCmVisualEditorAvailable(openDocName)
const visualRef = useRef({
previewByPath,
@@ -2,13 +2,37 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac
import { isValidTeXFile } from '../../../main/is-valid-tex-file'
const visualEditorProviders = importOverleafModules('visualEditorProviders')
const cmVisualEditorProviders = importOverleafModules(
'sourceEditorVisualExtensions'
)
export function isVisualEditorAvailable(filename: string): boolean {
// Core LaTeX visual editor
/**
* This currently covers LaTeX and Markdown. Other file
* types (e.g. .bib) use module-provided visual editors that render their own
* component instead, so they must NOT enable the CodeMirror visual extensions.
*/
export function isCmVisualEditorAvailable(filename: string): boolean {
if (isValidTeXFile(filename)) {
return true
}
for (const provider of cmVisualEditorProviders) {
if (provider.import.isCmVisualEditorFile?.(filename)) {
return true
}
}
return false
}
/**
* Whether any visual editor exists for the file, including module-provided
* ones. Drives the editor toggle UI and the default editor mode for a file.
*/
export function isVisualEditorAvailable(filename: string): boolean {
if (isCmVisualEditorAvailable(filename)) {
return true
}
// Visual editors provided by modules
for (const provider of visualEditorProviders) {
if (provider.import.isVisualEditorAvailable(filename)) {