Merge pull request #36494 from overleaf/em-remove-visual-options

Remove the Options object from the visual editor extensions

GitOrigin-RevId: f06e60b88ef77d3a670d7d956d1a86c116d71873
This commit is contained in:
Eric Mc Sween
2026-09-17 11:55:18 +00:00
committed by Copybot
parent 33e2a1e282
commit 2d3474fbdb
9 changed files with 1114 additions and 1109 deletions
@@ -0,0 +1,30 @@
import {
Compartment,
Extension,
Facet,
TransactionSpec,
} from '@codemirror/state'
import { PreviewPath } from '../../../../../types/preview-path'
export type PreviewByPath = (path: string) => PreviewPath | null
const previewByPathConf = new Compartment()
/**
* Resolves a path in the project to a preview URL. The value comes from the
* file tree, so it changes whenever the file tree does. Read it from the state
* at the point of use rather than capturing it, so decorations and widgets
* don't hold a resolver for a stale file tree.
*/
export const previewByPathFacet = Facet.define<PreviewByPath, PreviewByPath>({
combine: values => values[0] ?? (() => null),
})
export const filePreview = (previewByPath: PreviewByPath): Extension =>
previewByPathConf.of(previewByPathFacet.of(previewByPath))
export const setFilePreview = (
previewByPath: PreviewByPath
): TransactionSpec => ({
effects: previewByPathConf.reconfigure(previewByPathFacet.of(previewByPath)),
})
@@ -48,6 +48,7 @@ import { toolbarPanel } from './toolbar/toolbar-panel'
import { breadcrumbPanel } from './breadcrumbs-panel'
import { geometryChangeEvent } from './geometry-change-event'
import { docName } from './doc-name'
import { filePreview } from './file-preview'
import { fileTreeItemDrop } from './file-tree-item-drop'
import { mathPreview } from './math-preview'
import { ranges } from './ranges'
@@ -66,7 +67,7 @@ const moduleExtensions: Array<(options: Record<string, any>) => Extension> =
export const createExtensions = (options: Record<string, any>): Extension[] => [
lineNumbers(),
highlightSpecialChars(options.visual.visual),
highlightSpecialChars(options.showVisual),
// The built-in extension that manages the history stack,
// configured to increase the maximum delay between adjacent grouped edits
history({ newGroupDelay: 250 }),
@@ -85,13 +86,13 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
// A built-in extension that enables soft line wrapping.
EditorView.lineWrapping,
sourceOnly(
options.visual.visual,
options.showVisual,
EditorView.contentAttributes.of({ 'aria-label': 'Source Editor editing' })
),
// A built-in extension that re-indents input if the language defines an indentOnInput field in its language data.
indentOnInput(),
lineWrappingIndentation(options.visual.visual),
indentationMarkers(options.visual.visual),
lineWrappingIndentation(options.showVisual),
indentationMarkers(options.showVisual),
bracketMatching(),
bracketSelection(),
// A built-in extension that enables rectangular selections, created by dragging a new selection while holding down Alt.
@@ -133,6 +134,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
keybindings(),
docName(options.docName),
filePreview(options.previewByPath),
// NOTE: `annotations` needs to be before `language`
annotations(),
@@ -141,7 +143,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
theme(options.theme),
realtime(options.currentDoc, options.handleError),
cursorPosition(options.currentDoc),
scrollPosition(options.currentDoc, options.visual),
scrollPosition(options.currentDoc, options.showVisual),
cursorHighlights(),
autoPair(options.settings),
editable(),
@@ -157,17 +159,17 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
? historyOT(options.currentDoc.currentDocument)
: ranges(),
trackDetachedComments(options.currentDoc),
visual(options.docName, options.visual),
visual(options.docName, options.showVisual),
mathPreview(options.settings.mathPreview),
reviewTooltip(options.settings.floatingMenu),
contextMenu(),
toolbarPanel(),
breadcrumbPanel(),
verticalOverflow(),
highlightActiveLine(options.visual.visual),
highlightActiveLine(options.showVisual),
// The built-in extension that highlights the active line in the gutter.
highlightActiveLineGutter(),
inlineBackground(options.visual.visual),
inlineBackground(options.showVisual),
codemirrorDevTools(),
// Send exceptions to Sentry
EditorView.exceptionSink.of(options.handleException),
@@ -29,7 +29,7 @@ export const scrollPosition = (
}: {
currentDoc: { doc_id: string }
},
{ visual }: { visual: boolean }
visual: boolean
) => {
// store lineInfo for use on unload, when the DOM has already been unmounted
let lineInfo: LineInfo
File diff suppressed because it is too large Load Diff
@@ -3,8 +3,8 @@ import { placeSelectionInsideBlock } from '../selection'
import { isEqual } from 'lodash'
import { FigureData } from '../../figure-modal'
import { debugConsole } from '@/utils/debugging'
import { PreviewPath } from '../../../../../../../types/preview-path'
import type { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api'
import { previewByPathFacet } from '../../file-preview'
import { PdfDestroyLock } from '../utils/pdf-destroy-lock'
// Module level to synchronize across all GraphicsWidgets
@@ -25,7 +25,6 @@ export class GraphicsWidget extends WidgetType {
constructor(
public filePath: string,
public previewByPath: (path: string) => PreviewPath | null,
public centered: boolean,
public figureData: FigureData | null
) {
@@ -107,7 +106,7 @@ export class GraphicsWidget extends WidgetType {
renderGraphic(element: HTMLElement, view: EditorView) {
element.textContent = '' // ensure the element is empty
const preview = this.previewByPath(this.filePath)
const preview = view.state.facet(previewByPathFacet)(this.filePath)
element.dataset.filepath = this.filePath
element.dataset.width = this.figureData?.width?.toString()
@@ -21,14 +21,8 @@ import { pasteHtml } from './paste-html'
import { commandTooltip } from '../command-tooltip'
import { tableGeneratorTheme } from './table-generator'
import { debugConsole } from '@/utils/debugging'
import { PreviewPath } from '../../../../../../types/preview-path'
import { getFileExtension } from '../../utils/file'
type Options = {
visual: boolean
previewByPath: (path: string) => PreviewPath | null
}
// Module-provided visual editors registered via the
// `sourceEditorVisualExtensions` hook. Module exposes `getExtensions(ext)`,
// returning the visual-mode extensions for that file extension. A module match takes precedence
@@ -55,17 +49,16 @@ const visualState = StateField.define<boolean>({
},
})
const configureVisualExtensions = (options: Options) =>
options.visual ? sharedVisualExtensions() : []
const configureVisualExtensions = (showVisual: boolean) =>
showVisual ? sharedVisualExtensions() : []
export const visual = (docName: string, options: Options): Extension => {
const extensions =
visualModuleExtensions(docName) ?? latexVisualExtensions(options)
export const visual = (docName: string, showVisual: boolean): Extension => {
const extensions = visualModuleExtensions(docName) ?? latexVisualExtensions()
return [
visualState.init(() => options.visual),
visualConf.of(configureVisualExtensions(options)),
visualOnly(options.visual, extensions),
visualState.init(() => showVisual),
visualConf.of(configureVisualExtensions(showVisual)),
visualOnly(showVisual, extensions),
]
}
@@ -73,11 +66,11 @@ export const isVisual = (view: EditorView) => {
return view.state.field(visualState, false) || false
}
export const setVisual = (options: Options): TransactionSpec => {
export const setVisual = (showVisual: boolean): TransactionSpec => {
return {
effects: [
toggleVisualEffect.of(options.visual),
visualConf.reconfigure(configureVisualExtensions(options)),
toggleVisualEffect.of(showVisual),
visualConf.reconfigure(configureVisualExtensions(showVisual)),
],
}
}
@@ -202,9 +195,9 @@ const sharedVisualExtensions = () => [
EditorView.contentAttributes.of({ 'aria-label': 'Visual Editor editing' }),
]
const latexVisualExtensions = (options: Options): Extension => [
const latexVisualExtensions = (): Extension => [
listItemMarker,
atomicDecorations(options),
atomicDecorations,
markDecorations, // NOTE: must be after atomicDecorations, so that mark decorations wrap inline widgets
visualKeymap,
commandTooltip,