From 1d2d964a2eb159914a5ac62dd6a8d5423fe2a6ed Mon Sep 17 00:00:00 2001
From: Borja <158476064+borja-writefull@users.noreply.github.com>
Date: Wed, 15 Oct 2025 07:43:47 +0200
Subject: [PATCH] Refactor Writefull toolbar (#28911)
GitOrigin-RevId: 1d8a3addc9046dc67c0cca20d5cf4fba35d132d1
---
package-lock.json | 30 +++++++++----------
.../web/frontend/extracted-translations.json | 3 --
.../components/codemirror-editor.tsx | 9 ------
.../components/codemirror-toolbar.tsx | 19 +++++++++++-
.../components/toolbar/button-menu.tsx | 7 +++++
.../components/toolbar/overflow.tsx | 16 ++++++++--
.../context/types/writefull-instance.ts | 2 +-
services/web/locales/da.json | 3 --
services/web/locales/en.json | 3 --
services/web/locales/zh-CN.json | 3 --
services/web/package.json | 6 ++--
services/web/webpack.config.js | 24 +++++++++++++++
12 files changed, 82 insertions(+), 43 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 1fa7295c8d..2d837588f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21735,17 +21735,17 @@
}
},
"node_modules/@writefull/core": {
- "version": "1.27.26",
- "resolved": "https://registry.npmjs.org/@writefull/core/-/core-1.27.26.tgz",
- "integrity": "sha512-EI8te62cSuxTLT6tv9tOuk0ddkVIcciD/a/HdTaCsEAF+vpAJHEtD4fkLHeZt+U4P5cJhQPNt6lG/Ei0O2AR9g==",
+ "version": "1.27.27",
+ "resolved": "https://registry.npmjs.org/@writefull/core/-/core-1.27.27.tgz",
+ "integrity": "sha512-q5UWTq7zRQb6y/n8J4k1c2DzuhGnJin6VdY/GeGd7QW7/XRoVlq2pknKQXXdJJCx7mlMahsaZcAj38iqvKii0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bugsnag/js": "^7.23.0",
"@bugsnag/plugin-react": "^7.24.0",
"@growthbook/growthbook": "^1.4.1",
- "@writefull/ui": "^1.27.26",
- "@writefull/utils": "^1.27.26",
+ "@writefull/ui": "^1.27.27",
+ "@writefull/utils": "^1.27.27",
"axios": "^1.8.3",
"idb": "^8.0.2",
"inversify": "^6.0.2",
@@ -21757,14 +21757,14 @@
}
},
"node_modules/@writefull/ui": {
- "version": "1.27.26",
- "resolved": "https://registry.npmjs.org/@writefull/ui/-/ui-1.27.26.tgz",
- "integrity": "sha512-I9hcCKz6VE8bpmo3/MDAZPNX01TkBj63FcBpKcPQ/bkvNAwQvjJ1zaB1K65GBPIZS1FvFN6fXEO8+LPj/0Z+Kg==",
+ "version": "1.27.27",
+ "resolved": "https://registry.npmjs.org/@writefull/ui/-/ui-1.27.27.tgz",
+ "integrity": "sha512-hM3y2oy0leorke+dWMyBl7ur6lRR1l6qICZlwqBCJouqiZnffHTxeDipdzDlbFzyfC4qgozgAcVVEmPtbKfSGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.27.5",
- "@writefull/utils": "^1.27.26"
+ "@writefull/utils": "^1.27.27"
},
"peerDependencies": {
"react": ">= 18",
@@ -21772,9 +21772,9 @@
}
},
"node_modules/@writefull/utils": {
- "version": "1.27.26",
- "resolved": "https://registry.npmjs.org/@writefull/utils/-/utils-1.27.26.tgz",
- "integrity": "sha512-cb1nGLP0RBKSvwzGfEGj8xZN9jy15JPoPbNiijHlILiR2+KQ0ICu2uWSos2K2OaKO8mK/2P0nFU4rJOZ/9jc8w==",
+ "version": "1.27.27",
+ "resolved": "https://registry.npmjs.org/@writefull/utils/-/utils-1.27.27.tgz",
+ "integrity": "sha512-yv2135in+NQbYfr+0rmGAKFY55u1OIkOXPQyniRXPjdYwiRVC77tt5XJwqrThVPYd1mruZcFKJ9qDSK4RfAtOg==",
"dev": true,
"license": "MIT"
},
@@ -53334,9 +53334,9 @@
"@uppy/utils": "^5.7.0",
"@uppy/xhr-upload": "^3.6.0",
"@vitest/eslint-plugin": "1.1.44",
- "@writefull/core": "^1.27.26",
- "@writefull/ui": "^1.27.26",
- "@writefull/utils": "^1.27.26",
+ "@writefull/core": "^1.27.27",
+ "@writefull/ui": "^1.27.27",
+ "@writefull/utils": "^1.27.27",
"5to6-codemod": "^1.8.0",
"abort-controller": "^3.0.0",
"acorn": "^7.1.1",
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index ba9e16e4d3..1087bddde9 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -991,7 +991,6 @@
"loading_github_repositories": "",
"loading_prices": "",
"loading_recent_github_commits": "",
- "loading_writefull": "",
"log_entry_description": "",
"log_entry_maximum_entries": "",
"log_entry_maximum_entries_enable_stop_on_first_error": "",
@@ -2170,8 +2169,6 @@
"work_with_non_overleaf_users": "",
"work_with_other_github_users": "",
"write_faster_smarter_with_overleaf_and_writefull_ai_tools": "",
- "writefull_loading_error_body": "",
- "writefull_loading_error_title": "",
"x_changes_in": "",
"x_changes_in_plural": "",
"x_libraries_accessed_in_this_project": "",
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 da7c808817..062652c784 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
@@ -27,10 +27,6 @@ const sourceEditorComponents = importOverleafModules(
'sourceEditorComponents'
) as { import: { default: ElementType }; path: string }[]
-const sourceEditorToolbarComponents = importOverleafModules(
- 'sourceEditorToolbarComponents'
-) as { import: { default: ElementType }; path: string }[]
-
function CodeMirrorEditor() {
// create the initial state
const [state, setState] = useState(() => {
@@ -77,11 +73,6 @@ function CodeMirrorEditorComponents() {
- {sourceEditorToolbarComponents.map(
- ({ import: { default: Component }, path }) => (
-
- )
- )}
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 9fe0c0b3e5..a6265ecc93 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
@@ -1,4 +1,11 @@
-import { memo, useCallback, useEffect, useRef, useState } from 'react'
+import {
+ ElementType,
+ memo,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react'
import { createPortal } from 'react-dom'
import {
useCodeMirrorStateContext,
@@ -27,6 +34,11 @@ import Breadcrumbs from '@/features/ide-redesign/components/breadcrumbs'
import classNames from 'classnames'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import { useFeatureFlag } from '@/shared/context/split-test-context'
+import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
+
+const sourceEditorToolbarComponents = importOverleafModules(
+ 'sourceEditorToolbarComponents'
+) as { import: { default: ElementType }; path: string }[]
export const CodeMirrorToolbar = () => {
const view = useCodeMirrorViewContext()
@@ -198,6 +210,11 @@ const Toolbar = memo(function Toolbar() {
+ {sourceEditorToolbarComponents.map(
+ ({ import: { default: Component }, path }) => (
+
+ )
+ )}
{newEditor && breadcrumbs && }
>
diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx
index ca27ed7588..cc534d6bf1 100644
--- a/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx
+++ b/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx
@@ -13,6 +13,7 @@ export const ToolbarButtonMenu: FC<
id: string
label: string
icon: React.ReactNode
+ disabled?: boolean
disablePopover?: boolean
altCommand?: (view: EditorView) => void
}>
@@ -21,6 +22,7 @@ export const ToolbarButtonMenu: FC<
id,
label,
altCommand,
+ disabled,
disablePopover,
children,
}) {
@@ -39,11 +41,16 @@ export const ToolbarButtonMenu: FC<
type="button"
className="ol-cm-toolbar-button"
aria-label={label}
+ aria-disabled={disabled}
onMouseDown={event => {
event.preventDefault()
event.stopPropagation()
}}
onClick={event => {
+ if (disabled) {
+ event.preventDefault()
+ return
+ }
if (event.altKey && altCommand && open === false) {
emitToolbarEvent(view, id)
event.preventDefault()
diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx
index 089868e962..dc5eb24b72 100644
--- a/services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx
+++ b/services/web/frontend/js/features/source-editor/components/toolbar/overflow.tsx
@@ -12,8 +12,16 @@ export const ToolbarOverflow: FC<
overflowOpen: boolean
setOverflowOpen: (open: boolean) => void
overflowRef?: React.Ref
+ popoverClassName?: string
}>
-> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
+> = ({
+ overflowed,
+ overflowOpen,
+ setOverflowOpen,
+ overflowRef,
+ popoverClassName,
+ children,
+}) => {
const { t } = useTranslation()
const buttonRef = useRef(null)
const keyboardInputRef = useRef(false)
@@ -104,7 +112,11 @@ export const ToolbarOverflow: FC<
ref={overflowRef}
role="toolbar"
>
- {children}
+
+ {children}
+
>
diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts
index 694016ca1e..8a11a4a20b 100644
--- a/services/web/frontend/js/shared/context/types/writefull-instance.ts
+++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts
@@ -9,7 +9,7 @@ export interface WritefullEvents {
}
export interface WritefullAPI {
- init(): Promise
+ init(): void
addEventListener(
name: eventName,
callback: (detail: WritefullEvents[eventName]) => void
diff --git a/services/web/locales/da.json b/services/web/locales/da.json
index 5c11884a4b..8c4d10d48f 100644
--- a/services/web/locales/da.json
+++ b/services/web/locales/da.json
@@ -1002,7 +1002,6 @@
"loading_github_repositories": "Indlæser dit GitHub repository",
"loading_prices": "Indlæser priser",
"loading_recent_github_commits": "Indlæs nylige commits",
- "loading_writefull": "Indlæser Writefull",
"log_entry_description": "Logoptegnelse med niveau: __level__",
"log_entry_maximum_entries": "Grænsen for elementer i loggen er nået",
"log_entry_maximum_entries_enable_stop_on_first_error": "Prøv at fikse den første fejl og genkompilere. Ofte kan en fejl være skyld i mange efterfølgende fejlmeddelelser. Du kan Slå <0>“Stop ved første fejl”0> til for at fokusere på at fikse fejl. Vi anbefaler, at du fikser fejl så hurtigt som muligt; hvis de får lov at hobe sig op kan de føre til fejl, som er svære at fejlrette, og fatale fejl. <1>Lære mere1>",
@@ -2124,8 +2123,6 @@
"work_or_university_sso": "Arbejds/universitets single sign-on",
"work_with_non_overleaf_users": "Arbejd sammen med ikke-Overleaf-brugere",
"writefull": "Writefull",
- "writefull_loading_error_body": "Prøv at genindlæse siden. Hvis det ikke virker, prøv at deaktivere alle aktive browserudvidelser for at tjekket at de ikke blokerer Writefull for at indlæse.",
- "writefull_loading_error_title": "Writefull blev ikke indlæst korrekt",
"x_changes_in": "__count__ ændring i",
"x_changes_in_plural": "__count__ ændringer i",
"x_libraries_accessed_in_this_project": "__provider__ biblioteker tilgået i dette projekt",
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 0ba3689e87..10b4d43858 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1274,7 +1274,6 @@
"loading_github_repositories": "Loading your GitHub repositories",
"loading_prices": "loading prices",
"loading_recent_github_commits": "Loading recent commits",
- "loading_writefull": "Loading Writefull",
"log_entry_description": "Log entry with level: __level__",
"log_entry_maximum_entries": "Maximum log entries limit hit",
"log_entry_maximum_entries_enable_stop_on_first_error": "Try to fix the first error and recompile. Often one error causes many later error messages. You can <0>Enable “Stop on first error”0> to focus on fixing errors. We recommend fixing errors as soon as possible; letting them accumulate may lead to hard-to-debug and fatal errors. <1>Learn more1>",
@@ -2720,8 +2719,6 @@
"work_with_other_github_users": "Work with other GitHub users",
"write_faster_smarter_with_overleaf_and_writefull_ai_tools": "Write faster, smarter, and with confidence with Overleaf and Writefull AI tools",
"writefull": "Writefull",
- "writefull_loading_error_body": "Try refreshing the page. If this doesn’t work, try disabling any active browser extensions to check they aren’t blocking Writefull from loading.",
- "writefull_loading_error_title": "Writefull didn’t load correctly",
"x_changes_in": "__count__ change in",
"x_changes_in_plural": "__count__ changes in",
"x_libraries_accessed_in_this_project": "__provider__ libraries accessed in this project",
diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json
index 35adb92b6f..73f0710398 100644
--- a/services/web/locales/zh-CN.json
+++ b/services/web/locales/zh-CN.json
@@ -1200,7 +1200,6 @@
"loading_github_repositories": "正在读取您的GitHub存储库",
"loading_prices": "加载价格",
"loading_recent_github_commits": "正在装载最近的提交",
- "loading_writefull": "加载 Writefull",
"log_entry_description": "级别为__level__的日志条目",
"log_entry_maximum_entries": "最大日志条目限制已达到",
"log_entry_maximum_entries_enable_stop_on_first_error": "尝试修复第一个错误并重新编译。通常一个错误会导致许多后续的错误消息。您可以<0>启用“第一次出现错误时停止”0>以专注于修复错误。我们建议尽快修复错误;让它们积累起来可能会导致难以调试和致命的错误<1> 了解更多信息1>",
@@ -2553,8 +2552,6 @@
"work_with_non_overleaf_users": "和非Overleaf用户一起工作",
"work_with_other_github_users": "与其他 GitHub 用户合作",
"writefull": "Writefull",
- "writefull_loading_error_body": "尝试刷新页面,如果无效,尝试禁用所有的浏览器拓展,以便检查是否他们阻止了 Writefull 的加载。",
- "writefull_loading_error_title": "Writefull 加载失败",
"x_changes_in": "__count__ 处变化在",
"x_changes_in_plural": "__count__ 处变化在",
"x_libraries_accessed_in_this_project": "本项目中访问的 __provider__ 库",
diff --git a/services/web/package.json b/services/web/package.json
index be6d5c54ca..1873edd580 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -275,9 +275,9 @@
"@uppy/utils": "^5.7.0",
"@uppy/xhr-upload": "^3.6.0",
"@vitest/eslint-plugin": "1.1.44",
- "@writefull/core": "^1.27.26",
- "@writefull/ui": "^1.27.26",
- "@writefull/utils": "^1.27.26",
+ "@writefull/core": "^1.27.27",
+ "@writefull/ui": "^1.27.27",
+ "@writefull/utils": "^1.27.27",
"5to6-codemod": "^1.8.0",
"abort-controller": "^3.0.0",
"acorn": "^7.1.1",
diff --git a/services/web/webpack.config.js b/services/web/webpack.config.js
index 21a08f8df9..8cad9ea47c 100644
--- a/services/web/webpack.config.js
+++ b/services/web/webpack.config.js
@@ -236,6 +236,30 @@ module.exports = {
},
],
},
+ {
+ // CSS from writefull module - inject directly into DOM
+ include: path.resolve(__dirname, 'modules/writefull/'),
+ use: [
+ 'style-loader',
+ {
+ loader: 'css-loader',
+ options: {
+ importLoaders: 1,
+ },
+ },
+ {
+ loader: 'postcss-loader',
+ options: {
+ postcssOptions: {
+ config: path.resolve(
+ __dirname,
+ 'modules/writefull/frontend/js/integration/postcss.config.js'
+ ),
+ },
+ },
+ },
+ ],
+ },
{
// Standard CSS processing (extracted into separate file)
use: [MiniCssExtractPlugin.loader, 'css-loader'],