diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index f0c492800c..009135b840 100644 --- a/services/web/frontend/extracted-translations.json +++ b/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": "", diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx index 9d0deea0c5..8293d2878e 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx +++ b/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) { diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx index fc24161c6b..6d8f999aed 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx +++ b/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 ( - - - - - - - - - - - - + + { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Paragraph styles - - - - - - - + /> { + 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]) +} 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 2c6e08a536..1b0aaf0cbd 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 @@ -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(null) if (viewRef.current === null) { @@ -62,33 +61,42 @@ function CodeMirrorEditor() { return ( - - - - - - - {sourceEditorToolbarComponents.map( - ({ import: { default: Component }, path }) => ( - - ) - )} - {newEditor && } - - - - - - - {sourceEditorComponents.map( - ({ import: { default: Component }, path }) => ( - - ) - )} - + ) } +function CodeMirrorEditorComponents() { + const newEditor = useIsNewEditorEnabled() + useToolbarMenuBarEditorCommands() + + return ( + + + + + + + {sourceEditorToolbarComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} + {newEditor && } + + + + + + + {sourceEditorComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} + + ) +} + export default memo(CodeMirrorEditor) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index be05835080..dca1bf2a21 100644 --- a/services/web/locales/en.json +++ b/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__ Great news! 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 __appName__ account, or if you don’t want to link to your __institutionName__ account, please click __clickText__.", "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__ Great news! We’ve applied a 70% discount 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 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 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, or <1>get in touch 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": " Securely establish your identity by linking your ORCID iD to your __appName__ account. 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 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",