Browse Source

[web] Add editor commands to command registry and toolbar (#24538)

* [web] Add editor commands to command registry and toolbar

* [web] Omit empty groups

* [web] Editor redesign: Move toolbar commands to custom hook

* [web] Disable editor commands when editor is not visible

GitOrigin-RevId: be9f4060fc44e51223e16860fdcf6698c927998c
Mathias Jakobsen 1 year ago
parent
commit
bdf0194fc8

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

@@ -180,7 +180,9 @@
   "blank_project": "",
   "blocked_filename": "",
   "blog": "",
+  "bold": "",
   "browser": "",
+  "bullet_list": "",
   "buy_licenses": "",
   "buy_more_licenses": "",
   "by_subscribing_you_agree_to_our_terms_of_service": "",
@@ -241,6 +243,7 @@
   "choose_from_group_members": "",
   "choose_how_you_search_your_references": "",
   "choose_which_experiments": "",
+  "citation": "",
   "clear_cached_files": "",
   "clear_search": "",
   "click_here_to_view_sl_in_lng": "",
@@ -334,6 +337,7 @@
   "create_project_in_github": "",
   "created": "",
   "created_at": "",
+  "cross_reference": "",
   "current_file": "",
   "current_password": "",
   "currently_seeing_only_24_hrs_history": "",
@@ -347,6 +351,7 @@
   "dark_mode": "",
   "date_and_owner": "",
   "dealing_with_errors": "",
+  "decrease_indent": "",
   "delete": "",
   "delete_account": "",
   "delete_account_confirmation_label": "",
@@ -401,6 +406,7 @@
   "discover_the_fastest_way_to_search_and_cite": "",
   "dismiss_error_popup": "",
   "display_deleted_user": "",
+  "display_math": "",
   "do_you_need_edit_access": "",
   "do_you_want_to_change_your_primary_email_address_to": "",
   "do_you_want_to_overwrite_it": "",
@@ -537,6 +543,7 @@
   "fast": "",
   "fast_draft": "",
   "features_like_track_changes": "",
+  "figure": "",
   "file": "",
   "file_action_created": "",
   "file_action_deleted": "",
@@ -557,6 +564,7 @@
   "files_cannot_include_invalid_characters": "",
   "files_selected": "",
   "filter_projects": "",
+  "find": "",
   "find_out_more": "",
   "find_out_more_about_institution_login": "",
   "find_out_more_about_the_file_outline": "",
@@ -776,7 +784,9 @@
   "include_results_from_your_reference_manager": "",
   "include_results_from_your_x_account": "",
   "include_the_error_message_and_ai_response": "",
+  "increase_indent": "",
   "increased_compile_timeout": "",
+  "inline_math": "",
   "inr_discount_modal_info": "",
   "inr_discount_modal_title": "",
   "insert": "",
@@ -827,6 +837,7 @@
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "",
   "it_looks_like_your_account_is_billed_manually": "",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "",
+  "italics": "",
   "join_beta_program": "",
   "join_now": "",
   "join_overleaf_labs": "",
@@ -974,6 +985,7 @@
   "managers_management": "",
   "managing_your_subscription": "",
   "marked_as_resolved": "",
+  "math": "",
   "math_display": "",
   "math_inline": "",
   "maximum_files_uploaded_together": "",
@@ -1075,6 +1087,7 @@
   "notification_project_invite_accepted_message": "",
   "notification_project_invite_message": "",
   "number_of_users": "",
+  "numbered_list": "",
   "oauth_orcid_description": "",
   "of": "",
   "off": "",
@@ -1137,6 +1150,7 @@
   "papers_sync_description": "",
   "papers_upgrade_prompt_content": "",
   "papers_upgrade_prompt_title": "",
+  "paragraph_styles": "",
   "partial_outline_warning": "",
   "password": "",
   "password_managed_externally": "",
@@ -1300,6 +1314,7 @@
   "recurly_email_updated": "",
   "redirect_to_editor": "",
   "redirect_url": "",
+  "redo": "",
   "reduce_costs_group_licenses": "",
   "reference_error_relink_hint": "",
   "reference_manager_searched_groups": "",
@@ -1637,6 +1652,7 @@
   "switch_to_old_editor": "",
   "switch_to_pdf": "",
   "switch_to_standard_plan": "",
+  "symbol": "",
   "symbol_palette": "",
   "sync": "",
   "sync_dropbox_github": "",
@@ -1647,6 +1663,7 @@
   "syntax_validation": "",
   "tab_connecting": "",
   "tab_no_longer_connected": "",
+  "table": "",
   "table_generator": "",
   "tag_color": "",
   "tag_name_cannot_exceed_characters": "",
@@ -1852,6 +1869,7 @@
   "unconfirmed": "",
   "undelete": "",
   "understanding_labels": "",
+  "undo": "",
   "unfold_line": "",
   "unique_identifier_attribute": "",
   "university": "",

+ 6 - 1
services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx

@@ -107,7 +107,12 @@ function populateSectionOrGroup<
     children: children
       .map(child => {
         if (typeof child !== 'string') {
-          return populateSectionOrGroup(child, registry)
+          const populatedChild = populateSectionOrGroup(child, registry)
+          if (populatedChild.children.length === 0) {
+            // Skip empty groups
+            return undefined
+          }
+          return populatedChild
         }
         const command = registry.get(child)
         if (command) {

+ 91 - 64
services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx

@@ -3,10 +3,7 @@ import {
   DropdownHeader,
 } from '@/features/ui/components/bootstrap-5/dropdown-menu'
 import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
-import {
-  MenuBarDropdown,
-  NestedMenuBarDropdown,
-} from '@/shared/components/menu-bar/menu-bar-dropdown'
+import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
 import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
 import { useTranslation } from 'react-i18next'
 import ChangeLayoutOptions from './change-layout-options'
@@ -30,6 +27,7 @@ export const ToolbarMenuBar = () => {
   useCommandProvider(
     () => [
       {
+        type: 'command',
         label: t('show_version_history'),
         handler: () => {
           setView(view === 'history' ? 'editor' : 'history')
@@ -54,28 +52,98 @@ export const ToolbarMenuBar = () => {
     []
   )
 
+  const editMenuStructure: MenuStructure = useMemo(
+    () => [
+      {
+        id: 'edit-undo-redo',
+        children: ['undo', 'redo'],
+      },
+      {
+        id: 'edit-search',
+        children: ['find', 'select-all'],
+      },
+    ],
+    []
+  )
+
+  const insertMenuStructure: MenuStructure = useMemo(
+    () => [
+      {
+        id: 'insert-latex',
+        children: [
+          {
+            id: 'insert-math-group',
+            title: t('math'),
+            children: ['insert-inline-math', 'insert-display-math'],
+          },
+          'insert-symbol',
+          {
+            id: 'insert-figure-group',
+            title: t('figure'),
+            children: [
+              'insert-figure-from-computer',
+              'insert-figure-from-project-files',
+              'insert-figure-from-another-project',
+              'insert-figure-from-url',
+            ],
+          },
+          'insert-table',
+          'insert-citation',
+          'insert-link',
+          'insert-cross-reference',
+        ],
+      },
+      {
+        id: 'insert-comment',
+        children: ['comment'],
+      },
+    ],
+    [t]
+  )
+
+  const formatMenuStructure: MenuStructure = useMemo(
+    () => [
+      {
+        id: 'format-text',
+        children: ['format-bold', 'format-italics'],
+      },
+      {
+        id: 'format-list',
+        children: [
+          'format-bullet-list',
+          'format-numbered-list',
+          'format-increase-indentation',
+          'format-decrease-indentation',
+        ],
+      },
+      {
+        id: 'format-paragraph',
+        title: t('paragraph_styles'),
+        children: [
+          'format-style-normal',
+          'format-style-section',
+          'format-style-subsection',
+          'format-style-subsubsection',
+          'format-style-paragraph',
+          'format-style-subparagraph',
+        ],
+      },
+    ],
+    [t]
+  )
+
   return (
     <MenuBar
       className="ide-redesign-toolbar-menu-bar"
       id="toolbar-menu-bar-item"
     >
       <CommandDropdown menu={fileMenuStructure} title={t('file')} id="file" />
-      <MenuBarDropdown
-        title={t('edit')}
-        id="edit"
-        className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued"
-      >
-        <MenuBarOption title="Undo" />
-        <MenuBarOption title="Redo" />
-        <DropdownDivider />
-        <MenuBarOption title="Cut" />
-        <MenuBarOption title="Copy" />
-        <MenuBarOption title="Paste" />
-        <MenuBarOption title="Paste without formatting" />
-        <DropdownDivider />
-        <MenuBarOption title="Find" />
-        <MenuBarOption title="Select all" />
-      </MenuBarDropdown>
+      <CommandDropdown menu={editMenuStructure} title={t('edit')} id="edit" />
+      <CommandDropdown
+        menu={insertMenuStructure}
+        title={t('insert')}
+        id="insert"
+      />
       <MenuBarDropdown
         title={t('view')}
         id="view"
@@ -92,52 +160,11 @@ export const ToolbarMenuBar = () => {
         <MenuBarOption title="Fit to width" />
         <MenuBarOption title="Fit to height" />
       </MenuBarDropdown>
-      <MenuBarDropdown
-        title={t('insert')}
-        id="insert"
-        className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued"
-      >
-        <NestedMenuBarDropdown title="Math" id="math">
-          <MenuBarOption title="Generate from text or image" />
-          <DropdownDivider />
-          <MenuBarOption title="Inline math" />
-          <MenuBarOption title="Display math" />
-        </NestedMenuBarDropdown>
-        <MenuBarOption title="Symbol" />
-        <NestedMenuBarDropdown title="Figure" id="figure">
-          <MenuBarOption title="Upload from computer" />
-          <MenuBarOption title="From project files" />
-          <MenuBarOption title="From another project" />
-          <MenuBarOption title="From URL" />
-        </NestedMenuBarDropdown>
-        <MenuBarOption title="Table" />
-        <MenuBarOption title="Citation" />
-        <MenuBarOption title="Link" />
-        <MenuBarOption title="Cross-reference" />
-        <DropdownDivider />
-        <MenuBarOption title="Comment" />
-      </MenuBarDropdown>
-      <MenuBarDropdown
+      <CommandDropdown
+        menu={formatMenuStructure}
         title={t('format')}
         id="format"
-        className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued"
-      >
-        <MenuBarOption title="Bold" />
-        <MenuBarOption title="Italics" />
-        <DropdownDivider />
-        <MenuBarOption title="Bullet list" />
-        <MenuBarOption title="Numbered list" />
-        <MenuBarOption title="Increase indentation" />
-        <MenuBarOption title="Decrease indentation" />
-        <DropdownDivider />
-        <DropdownHeader>Paragraph styles</DropdownHeader>
-        <MenuBarOption title="Normal text" />
-        <MenuBarOption title="Section" />
-        <MenuBarOption title="Subsection" />
-        <MenuBarOption title="Subsubsection" />
-        <MenuBarOption title="Paragraph" />
-        <MenuBarOption title="Subparagraph" />
-      </MenuBarDropdown>
+      />
       <MenuBarDropdown
         title={t('help')}
         id="help"

+ 304 - 0
services/web/frontend/js/features/ide-redesign/hooks/use-toolbar-menu-editor-commands.tsx

@@ -0,0 +1,304 @@
+import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
+import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context'
+import { FigureModalSource } from '@/features/source-editor/components/figure-modal/figure-modal-context'
+import * as commands from '@/features/source-editor/extensions/toolbar/commands'
+import { setSectionHeadingLevel } from '@/features/source-editor/extensions/toolbar/sections'
+import { useEditorContext } from '@/shared/context/editor-context'
+import { useLayoutContext } from '@/shared/context/layout-context'
+import getMeta from '@/utils/meta'
+import { redo, selectAll, undo } from '@codemirror/commands'
+import { openSearchPanel } from '@codemirror/search'
+import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useIsNewEditorEnabled } from '../utils/new-editor-utils'
+
+export const useToolbarMenuBarEditorCommands = () => {
+  const view = useCodeMirrorViewContext()
+  const { t } = useTranslation()
+  const { view: layoutView } = useLayoutContext()
+  const editorIsVisible = layoutView === 'editor'
+
+  const openFigureModal = useCallback((source: FigureModalSource) => {
+    window.dispatchEvent(
+      new CustomEvent('figure-modal:open', {
+        detail: { source },
+      })
+    )
+  }, [])
+
+  const newEditor = useIsNewEditorEnabled()
+
+  useCommandProvider(() => {
+    if (!newEditor) {
+      return
+    }
+
+    return [
+      /************************************
+       *          Edit menu
+       ************************************/
+      {
+        id: 'undo',
+        label: t('undo'),
+        handler: () => {
+          undo(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'redo',
+        label: t('redo'),
+        handler: () => {
+          redo(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'find',
+        label: t('find'),
+        handler: () => {
+          openSearchPanel(view)
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'select-all',
+        label: t('select_all'),
+        handler: () => {
+          selectAll(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      /************************************
+       *         Insert menu
+       ************************************/
+      {
+        id: 'insert-inline-math',
+        label: t('inline_math'),
+        handler: () => {
+          commands.wrapInInlineMath(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'insert-display-math',
+        label: t('display_math'),
+        handler: () => {
+          commands.wrapInDisplayMath(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        label: t('upload_from_computer'),
+        id: 'insert-figure-from-computer',
+        handler: () => {
+          openFigureModal(FigureModalSource.FILE_UPLOAD)
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        label: t('from_project_files'),
+        id: 'insert-figure-from-project-files',
+        handler: () => {
+          openFigureModal(FigureModalSource.FILE_TREE)
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        label: t('from_another_project'),
+        id: 'insert-figure-from-another-project',
+        handler: () => {
+          openFigureModal(FigureModalSource.OTHER_PROJECT)
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        label: t('from_url'),
+        id: 'insert-figure-from-url',
+        handler: () => {
+          openFigureModal(FigureModalSource.FROM_URL)
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'insert-table',
+        label: t('table'),
+        handler: () => {
+          commands.insertTable(view, 3, 3)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'insert-citation',
+        label: t('citation'),
+        handler: () => {
+          commands.insertCite(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'insert-link',
+        label: t('link'),
+        handler: () => {
+          commands.wrapInHref(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'insert-cross-reference',
+        label: t('cross_reference'),
+        handler: () => {
+          commands.insertRef(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'comment',
+        label: t('comment'),
+        handler: () => {
+          commands.addComment()
+        },
+        disabled: !editorIsVisible,
+      },
+      /************************************
+       *         Format menu
+       ************************************/
+      {
+        id: 'format-bold',
+        label: t('bold'),
+        handler: () => {
+          commands.toggleBold(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-italics',
+        label: t('italics'),
+        handler: () => {
+          commands.toggleItalic(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-bullet-list',
+        label: t('bullet_list'),
+        handler: () => {
+          commands.toggleBulletList(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-numbered-list',
+        label: t('numbered_list'),
+        handler: () => {
+          commands.toggleNumberedList(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-increase-indentation',
+        label: t('increase_indent'),
+        handler: () => {
+          commands.indentIncrease(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-decrease-indentation',
+        label: t('decrease_indent'),
+        handler: () => {
+          commands.indentDecrease(view)
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-normal',
+        label: t('normal'),
+        handler: () => {
+          setSectionHeadingLevel(view, 'text')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-section',
+        label: 'Section',
+        handler: () => {
+          setSectionHeadingLevel(view, 'section')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-subsection',
+        label: 'Subsection',
+        handler: () => {
+          setSectionHeadingLevel(view, 'subsection')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-subsubsection',
+        label: 'Subsubsection',
+        handler: () => {
+          setSectionHeadingLevel(view, 'subsubsection')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-paragraph',
+        label: 'Paragraph',
+        handler: () => {
+          setSectionHeadingLevel(view, 'paragraph')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+      {
+        id: 'format-style-subparagraph',
+        label: 'Subparagraph',
+        handler: () => {
+          setSectionHeadingLevel(view, 'subparagraph')
+          view.focus()
+        },
+        disabled: !editorIsVisible,
+      },
+    ]
+  }, [view, t, editorIsVisible, openFigureModal, newEditor])
+
+  const { toggleSymbolPalette } = useEditorContext()
+  const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable')
+  useCommandProvider(() => {
+    if (!symbolPaletteAvailable) {
+      return
+    }
+
+    return [
+      {
+        id: 'insert-symbol',
+        label: t('symbol'),
+        handler: () => {
+          toggleSymbolPalette?.()
+        },
+        disabled: !editorIsVisible,
+      },
+    ]
+  }, [symbolPaletteAvailable, t, toggleSymbolPalette, editorIsVisible])
+}

+ 34 - 26
services/web/frontend/js/features/source-editor/components/codemirror-editor.tsx

@@ -19,6 +19,7 @@ import {
 import MathPreviewTooltip from './math-preview-tooltip'
 import Breadcrumbs from '@/features/ide-redesign/components/breadcrumbs'
 import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
+import { useToolbarMenuBarEditorCommands } from '@/features/ide-redesign/hooks/use-toolbar-menu-editor-commands'
 
 // TODO: remove this when definitely no longer used
 export * from './codemirror-context'
@@ -39,8 +40,6 @@ function CodeMirrorEditor() {
 
   const isMounted = useIsMounted()
 
-  const newEditor = useIsNewEditorEnabled()
-
   // create the view using the initial state and intercept transactions
   const viewRef = useRef<EditorView | null>(null)
   if (viewRef.current === null) {
@@ -62,33 +61,42 @@ function CodeMirrorEditor() {
   return (
     <CodeMirrorStateContext.Provider value={state}>
       <CodeMirrorViewContext.Provider value={viewRef.current}>
-        <ReviewPanelProviders>
-          <CodemirrorOutline />
-          <CodeMirrorView />
-          <FigureModal />
-          <CodeMirrorSearch />
-          <CodeMirrorToolbar />
-          {sourceEditorToolbarComponents.map(
-            ({ import: { default: Component }, path }) => (
-              <Component key={path} />
-            )
-          )}
-          {newEditor && <Breadcrumbs />}
-          <CodeMirrorCommandTooltip />
-
-          <MathPreviewTooltip />
-          <ReviewTooltipMenu />
-          <ReviewPanelNew />
-
-          {sourceEditorComponents.map(
-            ({ import: { default: Component }, path }) => (
-              <Component key={path} />
-            )
-          )}
-        </ReviewPanelProviders>
+        <CodeMirrorEditorComponents />
       </CodeMirrorViewContext.Provider>
     </CodeMirrorStateContext.Provider>
   )
 }
 
+function CodeMirrorEditorComponents() {
+  const newEditor = useIsNewEditorEnabled()
+  useToolbarMenuBarEditorCommands()
+
+  return (
+    <ReviewPanelProviders>
+      <CodemirrorOutline />
+      <CodeMirrorView />
+      <FigureModal />
+      <CodeMirrorSearch />
+      <CodeMirrorToolbar />
+      {sourceEditorToolbarComponents.map(
+        ({ import: { default: Component }, path }) => (
+          <Component key={path} />
+        )
+      )}
+      {newEditor && <Breadcrumbs />}
+      <CodeMirrorCommandTooltip />
+
+      <MathPreviewTooltip />
+      <ReviewTooltipMenu />
+      <ReviewPanelNew />
+
+      {sourceEditorComponents.map(
+        ({ import: { default: Component }, path }) => (
+          <Component key={path} />
+        )
+      )}
+    </ReviewPanelProviders>
+  )
+}
+
 export default memo(CodeMirrorEditor)

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

@@ -235,9 +235,11 @@
   "blank_project": "Blank Project",
   "blocked_filename": "This file name is blocked.",
   "blog": "Blog",
+  "bold": "Bold",
   "brl_discount_offer_plans_page_banner": "__flag__ <b>Great news!</b> We’ve applied a 50% discount to premium plans on this page for our users in Brazil. Check out the new lower prices.",
   "browser": "Browser",
   "built_in": "Built-In",
+  "bullet_list": "Bullet list",
   "buy_licenses": "Buy licenses",
   "buy_more_licenses": "Buy more licenses",
   "buy_now_no_exclamation_mark": "Buy now",
@@ -311,6 +313,7 @@
   "choose_how_you_search_your_references": "Choose how you search your references",
   "choose_which_experiments": "Choose which experiments you’d like to try.",
   "choose_your_plan": "Choose your plan",
+  "citation": "Citation",
   "city": "City",
   "clear_cached_files": "Clear cached files",
   "clear_search": "clear search",
@@ -442,6 +445,7 @@
   "created_at": "Created at",
   "creating": "Creating",
   "credit_card": "Credit Card",
+  "cross_reference": "Cross reference",
   "cs": "Czech",
   "currency": "Currency",
   "current_file": "Current file",
@@ -466,6 +470,7 @@
   "de": "German",
   "dealing_with_errors": "Dealing with errors",
   "december": "December",
+  "decrease_indent": "Decrease indentation",
   "dedicated_account_manager": "Dedicated account manager",
   "default": "Default",
   "delete": "Delete",
@@ -525,6 +530,7 @@
   "discover_why_over_people_worldwide_trust_overleaf": "Discover why over __count__ million people worldwide trust Overleaf with their work.",
   "dismiss_error_popup": "Dismiss first error alert",
   "display_deleted_user": "Display deleted users",
+  "display_math": "Display math",
   "do_not_have_acct_or_do_not_want_to_link": "If you don’t have an <b>__appName__</b> account, or if you don’t want to link to your <b>__institutionName__</b> account, please click <b>__clickText__</b>.",
   "do_not_link_accounts": "Don’t link accounts",
   "do_you_need_edit_access": "Do you need edit access?",
@@ -712,6 +718,7 @@
   "features_and_benefits": "Features & Benefits",
   "features_like_track_changes": "Features like real-time track changes",
   "february": "February",
+  "figure": "Figure",
   "file": "File",
   "file_action_created": "Created",
   "file_action_deleted": "Deleted",
@@ -734,6 +741,7 @@
   "files_selected": "files selected.",
   "filter_projects": "Filter projects",
   "filters": "Filters",
+  "find": "Find",
   "find_out_more": "Find out More",
   "find_out_more_about_institution_login": "Find out more about institutional login",
   "find_out_more_about_the_file_outline": "Find out more about the file outline",
@@ -1004,9 +1012,11 @@
   "include_results_from_your_reference_manager": "Include results from your reference manager",
   "include_results_from_your_x_account": "Include results from your __provider__ account",
   "include_the_error_message_and_ai_response": "Include the error message and AI response",
+  "increase_indent": "Increase indentation",
   "increased_compile_timeout": "Increased compile timeout",
   "individuals": "Individuals",
   "info": "Info",
+  "inline_math": "Inline math",
   "inr_discount_modal_info": "Get document history, track changes, additional collaborators, and more at Purchasing Power Parity prices.",
   "inr_discount_modal_title": "70% off all Overleaf premium plans for users in India",
   "inr_discount_offer_plans_page_banner": "__flag__ <b>Great news!</b> We’ve applied a <b>70% discount</b> to premium plans for our users in India. Check out the new lower prices below.",
@@ -1083,6 +1093,7 @@
   "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "It looks like that didn’t work. You can try again or <0>get in touch</0> with our Support team for more help.",
   "it_looks_like_your_account_is_billed_manually": "It looks like your account is being billed manually - adding seats or upgrading your subscription can only be done by the Support team. Please <0>get in touch</0> for help.",
   "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "It looks like your payment details are missing. Please <0>update your billing information</0>, or <1>get in touch</1> with our Support team for more help.",
+  "italics": "Italics",
   "ja": "Japanese",
   "january": "January",
   "join_beta_program": "Join beta program",
@@ -1286,6 +1297,7 @@
   "managing_your_subscription": "Managing your subscription",
   "march": "March",
   "marked_as_resolved": "Marked as resolved",
+  "math": "Math",
   "math_display": "Math Display",
   "math_inline": "Math Inline",
   "maximum_files_uploaded_together": "Maximum __max__ files uploaded together",
@@ -1426,6 +1438,7 @@
   "number_collab_info": "The number of people you can invite to work on a project with you. The limit is per project, so you can invite different people to each project.",
   "number_of_projects": "Number of projects",
   "number_of_users": "Number of users",
+  "numbered_list": "Numbered list",
   "oauth_orcid_description": " <a href=\"__link__\">Securely establish your identity by linking your ORCID iD to your __appName__ account</a>. Submissions to participating publishers will automatically include your ORCID iD for improved workflow and visibility. ",
   "october": "October",
   "off": "Off",
@@ -1512,6 +1525,7 @@
   "papers_sync_description": "With the Papers integration you can import your references from Papers into your __appName__ projects.",
   "papers_upgrade_prompt_content": "Link your Papers account to search and add your references from Papers directly in your project—they’ll automatically be added to your .bib file. Or import them as a file into your __appName__ projects.",
   "papers_upgrade_prompt_title": "Cite from Papers",
+  "paragraph_styles": "Paragraph styles",
   "partial_outline_warning": "The File outline is out of date. It will update itself as you edit the document",
   "password": "Password",
   "password_cant_be_the_same_as_current_one": "Password can’t be the same as current one",
@@ -1723,6 +1737,7 @@
   "redirect_to_editor": "Redirect to editor",
   "redirect_url": "Redirect URL",
   "redirecting": "Redirecting",
+  "redo": "Redo",
   "reduce_costs_group_licenses": "You can cut down on paperwork and reduce costs with our discounted group licenses.",
   "reference_error_relink_hint": "If this error persists, try re-linking your account here:",
   "reference_manager_searched_groups": "__provider__ search groups",
@@ -2135,6 +2150,7 @@
   "switch_to_old_editor": "Switch to old editor",
   "switch_to_pdf": "Switch to PDF",
   "switch_to_standard_plan": "Switch to Standard plan",
+  "symbol": "Symbol",
   "symbol_palette": "Symbol palette",
   "symbol_palette_highlighted": "<0>Symbol</0> palette",
   "symbol_palette_info_new": "Insert math symbols into your document with the click of a button.",
@@ -2147,6 +2163,7 @@
   "syntax_validation": "Code check",
   "tab_connecting": "Connecting with the editor",
   "tab_no_longer_connected": "This tab is no longer connected with the editor",
+  "table": "Table",
   "table_generator": "Table Generator",
   "tag_color": "Tag color",
   "tag_name_cannot_exceed_characters": "Tag name cannot exceed __maxLength__ characters",
@@ -2385,6 +2402,7 @@
   "undelete": "Undelete",
   "undeleting": "Undeleting",
   "understanding_labels": "Understanding labels",
+  "undo": "Undo",
   "unfold_line": "Unfold line",
   "unique_identifier_attribute": "Unique identifier attribute",
   "university": "University",