Просмотр исходного кода

[cm6] Add toolbar to Source Mode for Beta users (#13429)

* [cm6] toolbar for source mode

* top:0 for new toolbar

* empty div for extensions

* fix legacy css top pos

* show source toolbar split test

* prettier

* show beta icon in source editor

* dropdown toolbar wip

* fix wrong conflict resolve

* math dropdown, chrome extension fixes

* math dropdown cleanup

* sort en.json

* fix sort en.json

* using isVisual

* getMeta in component, pug update

* using flex grow

* toolbar beta badge

* remove extra whitespace

* has-legacy-toolbar class

* Increase container size

* fix tests

* prettier

* styling fixes, using SplitTestBadge

* only show source toolbar if flag is set

* fix typo

---------

Co-authored-by: Alf Eaton <alf.eaton@overleaf.com>
GitOrigin-RevId: 34b01a9421f4a0d6defc40925c5092901575946e
Domagoj Kriskovic 3 лет назад
Родитель
Сommit
17452b51d7
21 измененных файлов с 357 добавлено и 88 удалено
  1. 19 0
      services/web/app/src/Features/Project/ProjectController.js
  2. 1 0
      services/web/app/views/project/editor/editor-pane.pug
  3. 1 0
      services/web/app/views/project/editor/meta.pug
  4. 1 0
      services/web/frontend/extracted-translations.json
  5. 30 2
      services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx
  6. 197 0
      services/web/frontend/js/features/source-editor/components/editor-switch-legacy.tsx
  7. 0 54
      services/web/frontend/js/features/source-editor/components/editor-switch.tsx
  8. 11 2
      services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx
  9. 49 0
      services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx
  10. 3 17
      services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx
  11. 1 1
      services/web/frontend/js/features/source-editor/controllers/editor-switch-controller.js
  12. 3 0
      services/web/frontend/js/features/source-editor/extensions/index.ts
  13. 12 1
      services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts
  14. 3 2
      services/web/frontend/js/features/source-editor/extensions/visual/visual.ts
  15. 1 1
      services/web/frontend/stories/editor-switch.stories.js
  16. 6 0
      services/web/frontend/stylesheets/app/editor.less
  17. 9 6
      services/web/frontend/stylesheets/app/editor/compile-button.less
  18. 5 0
      services/web/frontend/stylesheets/app/editor/toolbar.less
  19. 1 0
      services/web/locales/en.json
  20. 3 1
      services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx
  21. 1 1
      services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx

+ 19 - 0
services/web/app/src/Features/Project/ProjectController.js

@@ -683,6 +683,21 @@ const ProjectController = {
             cb()
           })
         },
+        sourceEditorToolbarAssigment(cb) {
+          SplitTestHandler.getAssignment(
+            req,
+            res,
+            'source-editor-toolbar',
+            (error, assignment) => {
+              // do not fail editor load if assignment fails
+              if (error) {
+                cb(null, { variant: 'default' })
+              } else {
+                cb(null, assignment)
+              }
+            }
+          )
+        },
         historyViewAssignment(cb) {
           SplitTestHandler.getAssignment(
             req,
@@ -729,6 +744,7 @@ const ProjectController = {
           pdfjsAssignment,
           editorLeftMenuAssignment,
           richTextAssignment,
+          sourceEditorToolbarAssigment,
           historyViewAssignment,
           reviewPanelAssignment,
         }
@@ -919,6 +935,9 @@ const ProjectController = {
               pdfjsVariant: pdfjsAssignment.variant,
               debugPdfDetach,
               showLegacySourceEditor,
+              showSourceToolbar:
+                !showLegacySourceEditor &&
+                sourceEditorToolbarAssigment.variant === 'enabled',
               showSymbolPalette,
               galileoEnabled,
               galileoFeatures,

+ 1 - 0
services/web/app/views/project/editor/editor-pane.pug

@@ -22,6 +22,7 @@
 		include ./file-view
 
 		.editor-container.full-size(
+			class={"has-source-toolbar" : showSourceToolbar},
 			ng-show="ui.view == 'editor' && editor.multiSelectedCount === 0"
 			vertical-resizable-panes="south-pane-resizer"
 			vertical-resizable-panes-hidden-externally-on="south-pane-toggled"

+ 1 - 0
services/web/app/views/project/editor/meta.pug

@@ -22,6 +22,7 @@ meta(name="ol-wsRetryHandshake" data-type="json" content=settings.wsRetryHandsha
 meta(name="ol-pdfjsVariant" content=pdfjsVariant)
 meta(name="ol-debugPdfDetach" data-type="boolean" content=debugPdfDetach)
 meta(name="ol-showLegacySourceEditor", data-type="boolean" content=showLegacySourceEditor)
+meta(name="ol-showSourceToolbar", data-type="boolean" content=showSourceToolbar)
 meta(name="ol-showSymbolPalette" data-type="boolean" content=showSymbolPalette)
 meta(name="ol-galileoEnabled" data-type="string" content=galileoEnabled)
 meta(name="ol-galileoPromptWords" data-type="string" content=galileoPromptWords)

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -1016,6 +1016,7 @@
   "toolbar_insert_figure": "",
   "toolbar_insert_inline_math": "",
   "toolbar_insert_link": "",
+  "toolbar_insert_math": "",
   "toolbar_insert_table": "",
   "toolbar_numbered_list": "",
   "toolbar_redo": "",

+ 30 - 2
services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx

@@ -13,6 +13,13 @@ import { ToolbarOverflow } from './toolbar/overflow'
 import useDropdown from '../../../shared/hooks/use-dropdown'
 import { getPanel } from '@codemirror/view'
 import { createToolbarPanel } from '../extensions/toolbar/toolbar-panel'
+import EditorSwitch from './editor-switch'
+import SwitchToPDFButton from './switch-to-pdf-button'
+import { DetacherSynctexControl } from '../../pdf-preview/components/detach-synctex-control'
+import DetachCompileButtonWrapper from '../../pdf-preview/components/detach-compile-button-wrapper'
+import getMeta from '../../../utils/meta'
+import { isVisual } from '../extensions/visual/visual'
+import SplitTestBadge from '../../../shared/components/split-test-badge'
 
 export const CodeMirrorToolbar = () => {
   const view = useCodeMirrorViewContext()
@@ -26,7 +33,10 @@ export const CodeMirrorToolbar = () => {
 }
 
 const Toolbar = memo(function Toolbar() {
+  const showSourceToolbar: boolean = getMeta('ol-showSourceToolbar')
+
   const state = useCodeMirrorStateContext()
+  const view = useCodeMirrorViewContext()
 
   const [overflowed, setOverflowed] = useState(false)
   const [collapsed, setCollapsed] = useState(false)
@@ -85,9 +95,13 @@ const Toolbar = memo(function Toolbar() {
   }
 
   return (
-    <div className="ol-cm-toolbar" ref={resizeRef}>
+    <div className="ol-cm-toolbar toolbar-editor" ref={resizeRef}>
+      {showSourceToolbar && <EditorSwitch />}
       <ToolbarItems state={state} />
-      <div className="ol-cm-toolbar-button-group" ref={overflowBeforeRef}>
+      <div
+        className="ol-cm-toolbar-button-group ol-cm-toolbar-stretch"
+        ref={overflowBeforeRef}
+      >
         <ToolbarOverflow
           overflowed={overflowed}
           target={overflowBeforeRef.current ?? undefined}
@@ -97,6 +111,7 @@ const Toolbar = memo(function Toolbar() {
         >
           <ToolbarItems state={state} overflowed={overflowedItemsRef.current} />
         </ToolbarOverflow>
+        <div className="formatting-buttons-wrapper" />
       </div>
       <div className="ol-cm-toolbar-button-group ol-cm-toolbar-end">
         <ToolbarButton
@@ -106,6 +121,19 @@ const Toolbar = memo(function Toolbar() {
           active={searchPanelOpen(state)}
           icon="search"
         />
+        {!isVisual(view) && (
+          <SplitTestBadge
+            splitTestName="source-editor-toolbar"
+            displayOnVariants={['enabled']}
+          />
+        )}
+        {showSourceToolbar && (
+          <>
+            <SwitchToPDFButton />
+            <DetacherSynctexControl />
+            <DetachCompileButtonWrapper />
+          </>
+        )}
       </div>
       <div className="ol-cm-toolbar-button-group hidden">
         <ToolbarButton

+ 197 - 0
services/web/frontend/js/features/source-editor/components/editor-switch-legacy.tsx

@@ -0,0 +1,197 @@
+import { ChangeEvent, FC, memo, useCallback } from 'react'
+import useScopeValue from '../../../shared/hooks/use-scope-value'
+import Tooltip from '../../../shared/components/tooltip'
+import { sendMB } from '../../../infrastructure/event-tracking'
+import getMeta from '../../../utils/meta'
+import SplitTestBadge from '../../../shared/components/split-test-badge'
+import isValidTeXFile from '../../../main/is-valid-tex-file'
+import { useTranslation } from 'react-i18next'
+
+function Badge() {
+  const content = (
+    <>
+      Overleaf has upgraded the source editor. You can still use the old editor
+      by selecting "Source (legacy)".
+      <br />
+      <br />
+      Click to learn more and give feedback
+    </>
+  )
+
+  return (
+    <Tooltip
+      id="editor-switch"
+      description={content}
+      overlayProps={{
+        placement: 'bottom',
+        delayHide: 100,
+      }}
+      tooltipProps={{ className: 'tooltip-wide' }}
+    >
+      <a
+        href="https://forms.gle/GmSs6odZRKRp3VX98"
+        target="_blank"
+        rel="noopener noreferrer"
+        className="info-badge"
+      >
+        <span className="sr-only">{content}</span>
+      </a>
+    </Tooltip>
+  )
+}
+
+const showLegacySourceEditor: boolean = getMeta('ol-showLegacySourceEditor')
+const visualEditorNameVariant: string = getMeta('ol-visualEditorNameVariant')
+const isParticipatingInVisualEditorNamingTest: boolean = getMeta(
+  'ol-isParticipatingInVisualEditorNamingTest'
+)
+
+function EditorSwitch() {
+  const [newSourceEditor, setNewSourceEditor] = useScopeValue(
+    'editor.newSourceEditor'
+  )
+  const [richText, setRichText] = useScopeValue('editor.showRichText')
+  const sourceName =
+    visualEditorNameVariant === 'code-visual'
+      ? 'Code Editor'
+      : visualEditorNameVariant === 'source-visual'
+      ? 'Source Editor'
+      : 'Source'
+
+  const [visual, setVisual] = useScopeValue('editor.showVisual')
+
+  const [docName] = useScopeValue('editor.open_doc_name')
+  const richTextAvailable = isValidTeXFile(docName)
+  const richTextOrVisual = richText || (richTextAvailable && visual)
+
+  const handleChange = useCallback(
+    event => {
+      const editorType = event.target.value
+
+      switch (editorType) {
+        case 'ace':
+          setRichText(false)
+          setVisual(false)
+          setNewSourceEditor(false)
+          break
+
+        case 'cm6':
+          setRichText(false)
+          setVisual(false)
+          setNewSourceEditor(true)
+          break
+
+        case 'rich-text':
+          if (getMeta('ol-richTextVariant') === 'cm6') {
+            setRichText(false)
+            setVisual(true)
+            setNewSourceEditor(true)
+          } else {
+            setRichText(true)
+            setVisual(false)
+          }
+
+          break
+      }
+
+      sendMB('editor-switch-change', { editorType })
+    },
+    [setRichText, setVisual, setNewSourceEditor]
+  )
+
+  return (
+    <div className="editor-toggle-switch">
+      {showLegacySourceEditor ? <Badge /> : null}
+
+      <fieldset className="toggle-switch">
+        <legend className="sr-only">Editor mode.</legend>
+
+        <input
+          type="radio"
+          name="editor"
+          value="cm6"
+          id="editor-switch-cm6"
+          className="toggle-switch-input"
+          checked={!richTextOrVisual && !!newSourceEditor}
+          onChange={handleChange}
+        />
+        <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
+          <span>{sourceName}</span>
+        </label>
+
+        {showLegacySourceEditor ? (
+          <>
+            <input
+              type="radio"
+              name="editor"
+              value="ace"
+              id="editor-switch-ace"
+              className="toggle-switch-input"
+              checked={!richTextOrVisual && !newSourceEditor}
+              onChange={handleChange}
+            />
+            <label htmlFor="editor-switch-ace" className="toggle-switch-label">
+              <span>Source (legacy)</span>
+            </label>
+          </>
+        ) : null}
+
+        <RichTextToggle
+          checked={!!richTextOrVisual}
+          disabled={!richTextAvailable}
+          handleChange={handleChange}
+        />
+      </fieldset>
+
+      {!!richTextOrVisual && !isParticipatingInVisualEditorNamingTest && (
+        <SplitTestBadge splitTestName="rich-text" displayOnVariants={['cm6']} />
+      )}
+    </div>
+  )
+}
+
+const RichTextToggle: FC<{
+  checked: boolean
+  disabled: boolean
+  handleChange: (event: ChangeEvent<HTMLInputElement>) => void
+}> = ({ checked, disabled, handleChange }) => {
+  const { t } = useTranslation()
+
+  const richTextName =
+    visualEditorNameVariant === 'default' ? 'Rich Text' : 'Visual Editor'
+
+  const toggle = (
+    <span>
+      <input
+        type="radio"
+        name="editor"
+        value="rich-text"
+        id="editor-switch-rich-text"
+        className="toggle-switch-input"
+        checked={checked}
+        onChange={handleChange}
+        disabled={disabled}
+      />
+      <label htmlFor="editor-switch-rich-text" className="toggle-switch-label">
+        <span>{richTextName}</span>
+      </label>
+    </span>
+  )
+
+  if (disabled) {
+    return (
+      <Tooltip
+        description={t('rich_text_is_only_available_for_tex_files')}
+        id="rich-text-toggle-tooltip"
+        overlayProps={{ placement: 'bottom' }}
+        tooltipProps={{ className: 'tooltip-wide' }}
+      >
+        {toggle}
+      </Tooltip>
+    )
+  }
+
+  return toggle
+}
+
+export default memo(EditorSwitch)

+ 0 - 54
services/web/frontend/js/features/source-editor/components/editor-switch.tsx

@@ -7,41 +7,6 @@ import isValidTeXFile from '../../../main/is-valid-tex-file'
 import { useTranslation } from 'react-i18next'
 import SplitTestBadge from '../../../shared/components/split-test-badge'
 
-function Badge() {
-  const content = (
-    <>
-      Overleaf has upgraded the source editor. You can still use the old editor
-      by selecting "Source (legacy)".
-      <br />
-      <br />
-      Click to learn more and give feedback
-    </>
-  )
-
-  return (
-    <Tooltip
-      id="editor-switch"
-      description={content}
-      overlayProps={{
-        placement: 'bottom',
-        delayHide: 100,
-      }}
-      tooltipProps={{ className: 'tooltip-wide' }}
-    >
-      <a
-        href="https://forms.gle/GmSs6odZRKRp3VX98"
-        target="_blank"
-        rel="noopener noreferrer"
-        className="info-badge"
-      >
-        <span className="sr-only">{content}</span>
-      </a>
-    </Tooltip>
-  )
-}
-
-const showLegacySourceEditor: boolean = getMeta('ol-showLegacySourceEditor')
-
 function EditorSwitch() {
   const { t } = useTranslation()
   const [newSourceEditor, setNewSourceEditor] = useScopeValue(
@@ -91,8 +56,6 @@ function EditorSwitch() {
 
   return (
     <div className="editor-toggle-switch">
-      {showLegacySourceEditor ? <Badge /> : null}
-
       <fieldset className="toggle-switch">
         <legend className="sr-only">Editor mode.</legend>
 
@@ -109,23 +72,6 @@ function EditorSwitch() {
           <span>{t('code_editor')}</span>
         </label>
 
-        {showLegacySourceEditor ? (
-          <>
-            <input
-              type="radio"
-              name="editor"
-              value="ace"
-              id="editor-switch-ace"
-              className="toggle-switch-input"
-              checked={!richTextOrVisual && !newSourceEditor}
-              onChange={handleChange}
-            />
-            <label htmlFor="editor-switch-ace" className="toggle-switch-label">
-              <span>Source (legacy)</span>
-            </label>
-          </>
-        ) : null}
-
         <RichTextToggle
           checked={!!richTextOrVisual}
           disabled={!richTextAvailable}

+ 11 - 2
services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx

@@ -6,13 +6,22 @@ import Tooltip from '../../../../shared/components/tooltip'
 import { EditorView } from '@codemirror/view'
 import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
 import { useCodeMirrorViewContext } from '../codemirror-editor'
+import MaterialIcon from '../../../../shared/components/material-icon'
 
 export const ToolbarButtonMenu: FC<{
   id: string
   label: string
   icon: string
+  materialIcon?: boolean
   altCommand?: (view: EditorView) => void
-}> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
+}> = memo(function ButtonMenu({
+  icon,
+  id,
+  label,
+  materialIcon,
+  altCommand,
+  children,
+}) {
   const target = useRef<any>(null)
   const { open, onToggle, ref } = useDropdown()
   const view = useCodeMirrorViewContext()
@@ -39,7 +48,7 @@ export const ToolbarButtonMenu: FC<{
       }}
       ref={target}
     >
-      <Icon type={icon} fw />
+      {materialIcon ? <MaterialIcon type={icon} /> : <Icon type={icon} fw />}
     </Button>
   )
 

+ 49 - 0
services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx

@@ -0,0 +1,49 @@
+import { ListGroupItem } from 'react-bootstrap'
+import { ToolbarButtonMenu } from './button-menu'
+import { emitCommandEvent } from '../../extensions/toolbar/utils/analytics'
+import MaterialIcon from '../../../../shared/components/material-icon'
+import { useTranslation } from 'react-i18next'
+import { useCodeMirrorViewContext } from '../codemirror-editor'
+import {
+  wrapInDisplayMath,
+  wrapInInlineMath,
+} from '../../extensions/toolbar/commands'
+
+export function MathDropdown() {
+  const { t } = useTranslation()
+  const view = useCodeMirrorViewContext()
+
+  return (
+    <ToolbarButtonMenu
+      id="toolbar-math"
+      label={t('toolbar_insert_math')}
+      icon="calculate"
+      materialIcon
+    >
+      <ListGroupItem
+        aria-label={t('toolbar_insert_inline_math')}
+        onClick={event => {
+          emitCommandEvent(view, 'toolbar-inline-math')
+          event.preventDefault()
+          wrapInInlineMath(view)
+          view.focus()
+        }}
+      >
+        <MaterialIcon type="123" />
+        <span>{t('toolbar_insert_inline_math')}</span>
+      </ListGroupItem>
+      <ListGroupItem
+        aria-label={t('toolbar_insert_display_math')}
+        onClick={event => {
+          emitCommandEvent(view, 'toolbar-display-math')
+          event.preventDefault()
+          wrapInDisplayMath(view)
+          view.focus()
+        }}
+      >
+        <MaterialIcon type="view_day" />
+        <span>{t('toolbar_insert_display_math')}</span>
+      </ListGroupItem>
+    </ToolbarButtonMenu>
+  )
+}

+ 3 - 17
services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx

@@ -13,9 +13,10 @@ import { redo, undo } from '@codemirror/commands'
 import * as commands from '../../extensions/toolbar/commands'
 import { SectionHeadingDropdown } from './section-heading-dropdown'
 import { canAddComment } from '../../extensions/toolbar/comments'
-import { useTranslation } from 'react-i18next'
 import getMeta from '../../../../utils/meta'
 import { InsertFigureDropdown } from './insert-figure-dropdown'
+import { useTranslation } from 'react-i18next'
+import { MathDropdown } from './math-dropdown'
 
 const isMac = /Mac/.test(window.navigator?.platform)
 
@@ -98,22 +99,7 @@ export const ToolbarItems: FC<{
       )}
       {showGroup('group-math') && (
         <div className="ol-cm-toolbar-button-group" data-overflow="group-math">
-          <ToolbarButton
-            id="toolbar-inline-math"
-            label={t('toolbar_insert_inline_math')}
-            command={commands.wrapInInlineMath}
-            icon="π"
-            textIcon
-            className="ol-cm-toolbar-button-math"
-          />
-          <ToolbarButton
-            id="toolbar-display-math"
-            label={t('toolbar_insert_display_math')}
-            command={commands.wrapInDisplayMath}
-            icon="Σ"
-            textIcon
-            className="ol-cm-toolbar-button-math"
-          />
+          <MathDropdown />
           <ToolbarButton
             id="toolbar-toggle-symbol-palette"
             label={t('toolbar_toggle_symbol_palette')}

+ 1 - 1
services/web/frontend/js/features/source-editor/controllers/editor-switch-controller.js

@@ -1,6 +1,6 @@
 import App from '../../../base'
 import { react2angular } from 'react2angular'
-import EditorSwitch from '../components/editor-switch'
+import EditorSwitch from '../components/editor-switch-legacy'
 import { rootContext } from '../../../shared/context/root-context'
 
 App.component('editorSwitch', react2angular(rootContext.use(EditorSwitch)))

+ 3 - 0
services/web/frontend/js/features/source-editor/extensions/index.ts

@@ -45,7 +45,9 @@ import { keymaps } from './keymaps'
 import { shortcuts } from './shortcuts'
 import { effectListeners } from './effect-listeners'
 import { highlightSpecialChars } from './highlight-special-chars'
+import { toolbarPanel } from './toolbar/toolbar-panel'
 import { geometryChangeEvent } from './geometry-change-event'
+import { isSplitTestEnabled } from '../../../utils/splitTestUtils'
 
 const moduleExtensions: Array<() => Extension> = importOverleafModules(
   'sourceEditorExtensions'
@@ -120,6 +122,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
   emptyLineFiller(),
   trackChanges(options.currentDoc, options.changeManager),
   visual(options.currentDoc, options.visual),
+  isSplitTestEnabled('source-editor-toolbar') ? toolbarPanel() : [],
   verticalOverflow(),
   highlightActiveLine(options.visual.visual),
   // The built-in extension that highlights the active line in the gutter.

+ 12 - 1
services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts

@@ -75,6 +75,9 @@ export const toolbarPanel = () => [
       '& .list-group-item': {
         width: '100%',
         textAlign: 'start',
+        display: 'flex',
+        alignItems: 'center',
+        gap: '5px',
       },
     },
     '.ol-cm-toolbar-button-group': {
@@ -90,6 +93,9 @@ export const toolbarPanel = () => [
         '&.ol-cm-toolbar-end': {
           borderLeft: 'none',
         },
+        '&.ol-cm-toolbar-stretch': {
+          flex: 1,
+        },
         '&.overflow-hidden': {
           borderLeft: 'none',
         },
@@ -99,6 +105,9 @@ export const toolbarPanel = () => [
         padding: 0,
       },
     },
+    '.formatting-buttons-wrapper': {
+      flex: 1,
+    },
     '.ol-cm-toolbar-button': {
       display: 'inline-flex',
       alignItems: 'center',
@@ -148,8 +157,10 @@ export const toolbarPanel = () => [
       },
     },
     '.ol-cm-toolbar-end': {
-      flex: 1,
       justifyContent: 'flex-end',
+      '& .badge': {
+        marginRight: '5px',
+      },
     },
     '.ol-cm-toolbar-overflow-toggle': {
       display: 'none',

+ 3 - 2
services/web/frontend/js/features/source-editor/extensions/visual/visual.ts

@@ -17,11 +17,12 @@ import { findEffect } from '../../utils/effects'
 import { forceParsing, syntaxTree } from '@codemirror/language'
 import { hasLanguageLoadedEffect } from '../language'
 import { restoreScrollPosition } from '../scroll-position'
-import { toolbarPanel } from '../toolbar/toolbar-panel'
 import { CurrentDoc } from '../../../../../../types/current-doc'
 import isValidTeXFile from '../../../../main/is-valid-tex-file'
 import { listItemMarker } from './list-item-marker'
 import { figureModalPasteHandler } from '../figure-modal'
+import { isSplitTestEnabled } from '../../../../utils/splitTestUtils'
+import { toolbarPanel } from '../toolbar/toolbar-panel'
 
 type Options = {
   visual: boolean
@@ -197,8 +198,8 @@ const extension = (options: Options) => [
   atomicDecorations(options),
   skipPreambleWithCursor,
   visualKeymap,
-  toolbarPanel(),
   scrollJumpAdjuster,
+  isSplitTestEnabled('source-editor-toolbar') ? [] : toolbarPanel(),
   showContentWhenParsed,
   figureModalPasteHandler(),
 ]

+ 1 - 1
services/web/frontend/stories/editor-switch.stories.js

@@ -1,4 +1,4 @@
-import EditorSwitch from '../js/features/source-editor/components/editor-switch'
+import EditorSwitch from '../js/features/source-editor/components/editor-switch-legacy'
 import { ScopeDecorator } from './decorators/scope'
 
 export default {

+ 6 - 0
services/web/frontend/stylesheets/app/editor.less

@@ -95,8 +95,14 @@
 #editor,
 #editor-rich-text {
   .full-size;
+}
+
+.editor-container #editor {
   top: @editor-toolbar-height;
 }
+.editor-container.has-source-toolbar #editor {
+  top: 0;
+}
 
 .pdf-empty,
 .no-history-available,

+ 9 - 6
services/web/frontend/stylesheets/app/editor/compile-button.less

@@ -13,13 +13,16 @@
   margin-left: 6px;
 }
 
-.detach-compile-button-container when (@is-new-css = false) {
-  margin-right: -5px;
-}
+// only apply for legacy editor
+.toolbar-pdf-right {
+  .detach-compile-button-container when (@is-new-css = false) {
+    margin-right: -5px;
+  }
 
-// because 2px border on :active state
-.detach-compile-button-container when (@is-new-css = true) {
-  margin-right: -3px;
+  // because 2px border on :active state
+  .detach-compile-button-container when (@is-new-css = true) {
+    margin-right: -3px;
+  }
 }
 
 .btn-striped-animated {

+ 5 - 0
services/web/frontend/stylesheets/app/editor/toolbar.less

@@ -299,6 +299,7 @@
 .editor-toggle-switch {
   display: flex;
   align-items: center;
+  white-space: nowrap;
 
   .toggle-switch {
     margin-left: 5px;
@@ -317,6 +318,10 @@
     padding-right: 8px;
     border-right: none;
   }
+
+  .badge {
+    margin-right: 5px;
+  }
 }
 
 /**************************************

+ 1 - 0
services/web/locales/en.json

@@ -1636,6 +1636,7 @@
   "toolbar_insert_figure": "Insert Figure",
   "toolbar_insert_inline_math": "Insert Inline Math",
   "toolbar_insert_link": "Insert Link",
+  "toolbar_insert_math": "Insert Math",
   "toolbar_insert_table": "Insert Table",
   "toolbar_numbered_list": "Numbered List",
   "toolbar_redo": "Redo",

+ 3 - 1
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx

@@ -18,7 +18,7 @@ const clickToolbarButton = (text: string) => {
 }
 
 const Container: FC = ({ children }) => (
-  <div style={{ width: 785, height: 785 }}>{children}</div>
+  <div style={{ width: 1500, height: 785 }}>{children}</div>
 )
 
 const mountEditor = (content: string) => {
@@ -97,6 +97,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
     mountEditor('2+3=5')
     selectAll()
 
+    clickToolbarButton('Insert Math')
     clickToolbarButton('Insert Inline Math')
     cy.get('.cm-content').should('have.text', '\\(2+3=5\\)')
   })
@@ -105,6 +106,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
     mountEditor('2+3=5')
     selectAll()
 
+    clickToolbarButton('Insert Math')
     clickToolbarButton('Insert Display Math')
     cy.get('.cm-content').should('have.text', '\\[2+3=5\\]')
   })

+ 1 - 1
services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx

@@ -4,7 +4,7 @@ import { mockScope, rootFolderId } from '../helpers/mock-scope'
 import { FC } from 'react'
 
 const Container: FC = ({ children }) => (
-  <div style={{ width: 785, height: 785 }}>{children}</div>
+  <div style={{ width: 1500, height: 785 }}>{children}</div>
 )
 
 const clickToolbarButton = (text: string) => {