From 6794af0edea064a3bac8fd397ff44c8b6ae52f51 Mon Sep 17 00:00:00 2001 From: Malik Glossop Date: Wed, 10 Jun 2026 11:32:59 +0200 Subject: [PATCH] Merge pull request #34110 from overleaf/mg-toolbar-language-suggestions Add language suggestion controls to editor toolbar GitOrigin-RevId: fe52b909b44ef8ccfc1449366bbf62f2d9d7ee4e --- .../Features/Project/ProjectController.mjs | 1 + services/web/config/settings.defaults.js | 1 + services/web/cypress/support/component.ts | 4 +++ .../web/frontend/extracted-translations.json | 5 ++++ .../components/codemirror-toolbar.tsx | 27 ++++++++++--------- .../components/toolbar/toolbar-items.tsx | 14 +++++++++- services/web/locales/en.json | 5 ++++ 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index c954aebfb2..b98cd8004b 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -470,6 +470,7 @@ const _ProjectController = { 'ai-workbench-release', 'compile-timeout-target-plans', 'writefull-figure-generator', + 'writefull-toolbar-migration', 'wf-citations-checker', 'wf-citations-checker-on-selection', 'writefull-asymetric-queue-size-per-model', diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index caf594b870..fe4753192e 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -1036,6 +1036,7 @@ module.exports = { diagnosticActions: [], sourceEditorCompletionSources: [], sourceEditorSymbolPalette: [], + sourceEditorToolbarButtonGroups: [], sourceEditorToolbarComponents: [], sourceEditorToolbarEndButtons: [], rootContextProviders: [], diff --git a/services/web/cypress/support/component.ts b/services/web/cypress/support/component.ts index abac9c50c8..32c6bb01ad 100644 --- a/services/web/cypress/support/component.ts +++ b/services/web/cypress/support/component.ts @@ -1,3 +1,7 @@ +// Prime the writefull IoC container before other imports to avoid a circular +// dependency TDZ error (command.ts → ioc.ts → handler.ts → command.ts). +import '@wf/infrastructure/ioc' + import 'cypress-plugin-tab' import { resetMeta } from './ct/window' // needs to be before i18n import localesPromise from '@/i18n' diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 4c280eba15..f36203ad7a 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -44,6 +44,7 @@ "accept_invitation": "", "accept_or_reject_individual_edits": "", "accept_selected_changes": "", + "accept_suggestion": "", "accept_terms_and_conditions": "", "accepted_invite": "", "accepting": "", @@ -1075,6 +1076,7 @@ "language_model_gpt_custom_prompt_description": "", "language_model_writefull_english": "", "language_model_writefull_english_description": "", + "language_suggestion_actions": "", "language_suggestions": "", "last_active": "", "last_active_description": "", @@ -1297,6 +1299,7 @@ "next": "", "next_page": "", "next_payment_of_x_collectected_on_y": "", + "next_suggestion": "", "no_actions": "", "no_add_on_purchase_while_paused": "", "no_audit_logs_found": "", @@ -1505,6 +1508,7 @@ "preview": "", "preview_editor_tabs": "", "previous_page": "", + "previous_suggestion": "", "price": "", "primarily_work_study_question": "", "primarily_work_study_question_company": "", @@ -1630,6 +1634,7 @@ "reject": "", "reject_change": "", "reject_selected_changes": "", + "reject_suggestion": "", "relink_your_account": "", "reload_editor": "", "remind_before_trial_ends": "", diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx index f1f325ae6d..23fa79994d 100644 --- a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx +++ b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx @@ -125,12 +125,20 @@ const Toolbar = memo(function Toolbar() { if (resizeRef.current) { buildOverflow(resizeRef.current.element) } - }, [buildOverflow, languageName, resizeRef, visual]) + }, [buildOverflow, languageName, listDepth, resizeRef, visual]) - // calculate overflow when buttons change + // calculate overflow when toolbar content changes const observerRef = useRef(null) - const handleButtons = useCallback( + const handleToolbar = useCallback( (node: HTMLDivElement) => { + // register the resize observer on the toolbar node + elementRef(node) + + if (observerRef.current) { + observerRef.current.disconnect() + observerRef.current = null + } + if (!('MutationObserver' in window)) { return } @@ -142,12 +150,10 @@ const Toolbar = memo(function Toolbar() { } }) - observerRef.current.observe(node, { childList: true }) - } else if (observerRef.current) { - observerRef.current.disconnect() + observerRef.current.observe(node, { childList: true, subtree: true }) } }, - [buildOverflow, resizeRef] + [buildOverflow, elementRef, resizeRef] ) // calculate overflow when active element changes to/from inside a table @@ -181,7 +187,7 @@ const Toolbar = memo(function Toolbar() { role="toolbar" aria-label={t('toolbar_editor')} className="ol-cm-toolbar toolbar-editor" - ref={elementRef} + ref={handleToolbar} > {showActions && ( -
+
{!visualPreviewEnabled && } {sourceEditorToolbarEndButtons.map( ({ import: { default: Component }, path }) => ( diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx index 857c525eaa..035de4e4fa 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx @@ -1,4 +1,4 @@ -import { FC, memo } from 'react' +import { ComponentType, FC, memo } from 'react' import { EditorState } from '@codemirror/state' import { useEditorContext } from '../../../../shared/context/editor-context' import { ToolbarButton } from './toolbar-button' @@ -18,6 +18,14 @@ import { useProjectContext } from '@/shared/context/project-context' import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context' import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' import { isCursorOnEmptyLine } from '@/features/source-editor/utils/is-cursor-on-empty-line' +import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' + +const sourceEditorToolbarButtonGroups = importOverleafModules( + 'sourceEditorToolbarButtonGroups' +) as { + import: { default: ComponentType; overflowGroupId: string } + path: string +}[] const addCommentFromToolbar = () => commands.addComment('toolbar') @@ -70,6 +78,10 @@ export const ToolbarItems: FC<{ />
)} + {sourceEditorToolbarButtonGroups.map( + ({ import: { default: Component, overflowGroupId }, path }) => + showGroup(overflowGroupId) && + )} {languageName === 'latex' && ( <> {showGroup('group-section') && ( diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 12559eb00b..63fbd1fc74 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -49,6 +49,7 @@ "accept_invitation": "Accept invitation", "accept_or_reject_individual_edits": "Accept or reject individual edits", "accept_selected_changes": "Accept selected changes", + "accept_suggestion": "Accept suggestion", "accept_terms_and_conditions": "Accept terms and conditions", "accepted_invite": "Accepted invite", "accepting_invite_as": "You are accepting this invite as", @@ -1394,6 +1395,7 @@ "language_model_gpt_custom_prompt_description": "Customize your edits with a prompt, no matter your language, text type, or editing needs.", "language_model_writefull_english": "Writefull", "language_model_writefull_english_description": "Our custom model for research writing. It doesn’t store your data or train on your texts.", + "language_suggestion_actions": "Language suggestion actions", "language_suggestions": "Language suggestions", "larger_discounts_available": "Larger discounts available", "larger_discounts_for_groups_of_20_plus": "Larger discounts for groups of 20+", @@ -1689,6 +1691,7 @@ "next": "Next", "next_page": "Next page", "next_payment_of_x_collectected_on_y": "The next payment of <0>__paymentAmmount__ will be collected on <1>__collectionDate__.", + "next_suggestion": "Next suggestion", "nl": "Dutch", "no": "Norwegian", "no_actions": "No actions", @@ -1984,6 +1987,7 @@ "preview_editor_tabs": "Preview editor tabs", "previous_24_hours_only": "previous 24 hours only", "previous_page": "Previous page", + "previous_suggestion": "Previous suggestion", "price": "Price", "pricing": "Pricing", "primarily_work_study_question": "Where do you primarily work or study?", @@ -2137,6 +2141,7 @@ "reject": "Reject", "reject_change": "Reject change", "reject_selected_changes": "Reject selected changes", + "reject_suggestion": "Reject suggestion", "related_tags": "Related Tags", "relink_your_account": "Re-link your account", "reload_editor": "Reload editor",