Sfoglia il codice sorgente

Merge pull request #34110 from overleaf/mg-toolbar-language-suggestions

Add language suggestion controls to editor toolbar

GitOrigin-RevId: fe52b909b44ef8ccfc1449366bbf62f2d9d7ee4e
Malik Glossop 2 mesi fa
parent
commit
6794af0ede

+ 1 - 0
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',

+ 1 - 0
services/web/config/settings.defaults.js

@@ -1036,6 +1036,7 @@ module.exports = {
     diagnosticActions: [],
     sourceEditorCompletionSources: [],
     sourceEditorSymbolPalette: [],
+    sourceEditorToolbarButtonGroups: [],
     sourceEditorToolbarComponents: [],
     sourceEditorToolbarEndButtons: [],
     rootContextProviders: [],

+ 4 - 0
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'

+ 5 - 0
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": "",

+ 15 - 12
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<MutationObserver | null>(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 && (
             <ToolbarItems
@@ -211,10 +217,7 @@ const Toolbar = memo(function Toolbar() {
             )}
           </div>
 
-          <div
-            className="ol-cm-toolbar-button-group ol-cm-toolbar-end"
-            ref={handleButtons}
-          >
+          <div className="ol-cm-toolbar-button-group ol-cm-toolbar-end">
             {!visualPreviewEnabled && <EditorSwitch />}
             {sourceEditorToolbarEndButtons.map(
               ({ import: { default: Component }, path }) => (

+ 13 - 1
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<{
           />
         </div>
       )}
+      {sourceEditorToolbarButtonGroups.map(
+        ({ import: { default: Component, overflowGroupId }, path }) =>
+          showGroup(overflowGroupId) && <Component key={path} />
+      )}
       {languageName === 'latex' && (
         <>
           {showGroup('group-section') && (

+ 5 - 0
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__</0> will be collected on <1>__collectionDate__</1>.",
+  "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",