[web] Recreate workbench with Overleaf styles (#29651)

GitOrigin-RevId: 52ca336f70b29edf6e39cf95aa164f3ae32c0a79
This commit is contained in:
Mathias Jakobsen
2025-11-18 10:19:26 +00:00
committed by Copybot
parent b1009d3b1f
commit 7b331b0222
16 changed files with 460 additions and 3743 deletions
+360 -3706
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -2,7 +2,6 @@
.eslint*
.prettier*
libraries/access-token-encryptor/**
libraries/ai/**
libraries/eslint-plugin/**
libraries/fetch-utils/**
libraries/logger/**
-3
View File
@@ -24,7 +24,6 @@ FROM base AS deps-prod
COPY package.json package-lock.json /overleaf/
COPY libraries/access-token-encryptor/package.json /overleaf/libraries/access-token-encryptor/package.json
COPY libraries/ai/package.json /overleaf/libraries/ai/package.json
COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
@@ -59,7 +58,6 @@ FROM deps AS dev
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
COPY libraries/access-token-encryptor/ /overleaf/libraries/access-token-encryptor/
COPY libraries/ai/ /overleaf/libraries/ai/
COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
COPY libraries/logger/ /overleaf/libraries/logger/
@@ -97,7 +95,6 @@ RUN nice find /overleaf/services/web/public -name '*.js.map' -delete
# copy source code and precompile pug images
FROM deps-prod AS pug
COPY libraries/access-token-encryptor/ /overleaf/libraries/access-token-encryptor/
COPY libraries/ai/ /overleaf/libraries/ai/
COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
COPY libraries/logger/ /overleaf/libraries/logger/
-1
View File
@@ -570,7 +570,6 @@ IMAGE_SCRATCH ?= $(IMAGE_REPO):do-not-use-this-tag-for-deploys--it-is-used-for-e
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
$(MONOREPO)/libraries/ai/package.json \
$(MONOREPO)/libraries/access-token-encryptor/package.json \
$(MONOREPO)/libraries/eslint-plugin/package.json \
$(MONOREPO)/libraries/fetch-utils/package.json \
@@ -37,6 +37,7 @@ export default /** @type {const} */ ([
'integration_instructions',
'lightbulb',
'more_vert',
'neurology',
'note_add',
'open_in_new',
'password',
@@ -55,7 +55,7 @@ export type EditorManager = {
getCurrentDocumentId: () => DocId | null
setIgnoringExternalUpdates: (value: boolean) => void
openDocWithId: (docId: string, options?: OpenDocOptions) => void
openDoc: (document: Doc, options?: OpenDocOptions) => void
openDoc: (document: Doc, options?: OpenDocOptions) => Promise<Doc | undefined>
openDocs: OpenDocuments
openFileWithId: (fileId: string) => void
openInitialDoc: (docId?: string) => void
@@ -30,6 +30,7 @@ import { UserFeaturesProvider } from '@/shared/context/user-features-context'
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context'
import { CommandRegistryProvider } from './command-registry-context'
import { EditorSelectionProvider } from '@/shared/context/editor-selection-context'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
const rootContextProviders = importOverleafModules('rootContextProviders') as {
@@ -74,6 +75,7 @@ export const ReactContextRoot: FC<
IdeRedesignSwitcherProvider,
CommandRegistryProvider,
UserFeaturesProvider,
EditorSelectionProvider,
...providers,
}
@@ -121,9 +123,11 @@ export const ReactContextRoot: FC<
<Providers.OutlineProvider>
<Providers.IdeRedesignSwitcherProvider>
<Providers.CommandRegistryProvider>
{
childrenWrappedWithDynamicProviders
}
<Providers.EditorSelectionProvider>
{
childrenWrappedWithDynamicProviders
}
</Providers.EditorSelectionProvider>
</Providers.CommandRegistryProvider>
</Providers.IdeRedesignSwitcherProvider>
</Providers.OutlineProvider>
@@ -54,6 +54,7 @@ import { historyOT } from './history-ot'
import { trackDetachedComments } from './track-detached-comments'
import { reviewTooltip } from './review-tooltip'
import { tooltipsReposition } from './tooltips-reposition'
import { selectionListener } from '@/features/source-editor/extensions/selection-listener'
const moduleExtensions: Array<(options: Record<string, any>) => Extension> =
importOverleafModules('sourceEditorExtensions').map(
@@ -172,4 +173,5 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
geometryChangeEvent(),
fileTreeItemDrop(),
tooltipsReposition(),
selectionListener(options.setEditorSelection),
]
@@ -0,0 +1,22 @@
import { ViewPlugin } from '@codemirror/view'
import { EditorSelection } from '@codemirror/state'
import { debounce } from 'lodash'
export const selectionListener = (
setEditorSelection: (value: EditorSelection | undefined) => void
) => {
const debouncedSetEditorSelection = debounce(setEditorSelection, 250)
return ViewPlugin.define(() => {
return {
update(update) {
if (update.selectionSet) {
debouncedSetEditorSelection(update.state.selection)
}
},
destroy() {
setEditorSelection(undefined)
},
}
})
}
@@ -60,6 +60,7 @@ import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-
import { SearchQuery } from '@codemirror/search'
import { beforeChangeDocEffect } from '@/features/source-editor/extensions/before-change-doc'
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
import { useEditorSelectionContext } from '@/shared/context/editor-selection-context'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -106,6 +107,8 @@ function useCodeMirrorScope(view: EditorView) {
const { referenceKeys, searchLocalReferences } = useReferencesContext()
const { setEditorSelection } = useEditorSelectionContext()
const ranges = useRangesContext()
const threads = useThreadsContext()
@@ -335,6 +338,7 @@ function useCodeMirrorScope(view: EditorView) {
initialSearchQuery: searchQueryRef.current,
showBoundary,
handleException,
setEditorSelection,
}),
})
view.setState(state)
@@ -364,7 +368,7 @@ function useCodeMirrorScope(view: EditorView) {
}
// IMPORTANT: This effect must not depend on anything variable apart from currentDocument,
// as the editor state is recreated when the effect runs.
}, [view, currentDocument, showBoundary, handleException])
}, [view, currentDocument, showBoundary, handleException, setEditorSelection])
useEffect(() => {
if (openDocName) {
@@ -3,7 +3,7 @@ import { ButtonProps } from './button-props'
type BaseIconButtonProps = ButtonProps & {
accessibilityLabel?: string
type?: 'button' | 'submit'
type?: 'button' | 'submit' | 'reset'
}
type FilledIconButtonProps = BaseIconButtonProps & {
@@ -0,0 +1,47 @@
import {
createContext,
type Dispatch,
type FC,
type PropsWithChildren,
type SetStateAction,
useContext,
useMemo,
useState,
} from 'react'
import type { EditorSelection } from '@codemirror/state'
export const EditorSelectionContext = createContext<
| {
editorSelection: EditorSelection | undefined
setEditorSelection: Dispatch<SetStateAction<EditorSelection | undefined>>
}
| undefined
>(undefined)
export const EditorSelectionProvider: FC<PropsWithChildren> = ({
children,
}) => {
const [editorSelection, setEditorSelection] = useState<EditorSelection>()
const value = useMemo(() => {
return { editorSelection, setEditorSelection }
}, [editorSelection])
return (
<EditorSelectionContext.Provider value={value}>
{children}
</EditorSelectionContext.Provider>
)
}
export const useEditorSelectionContext = () => {
const context = useContext(EditorSelectionContext)
if (!context) {
throw new Error(
'useEditorSelectionContext is only available inside EditorSelectionProvider'
)
}
return context
}
+12 -4
View File
@@ -81,8 +81,8 @@
"safari > 14"
],
"dependencies": {
"@ai-sdk/mcp": "^1.0.0-beta.13",
"@ai-sdk/openai": "^3.0.0-beta.44",
"@ai-sdk/mcp": "^1.0.0-beta.15",
"@ai-sdk/openai": "^3.0.0-beta.59",
"@aws-sdk/client-ses": "^3.864.0",
"@contentful/rich-text-html-renderer": "^16.0.2",
"@contentful/rich-text-types": "^16.0.2",
@@ -109,7 +109,7 @@
"@stripe/stripe-js": "^7.7.0",
"@xmldom/xmldom": "^0.7.13",
"accepts": "^1.3.7",
"ai": "^6.0.0-beta.84",
"ai": "^6.0.0-beta.99",
"ajv": "^8.12.0",
"archiver": "^5.3.0",
"async": "^3.2.5",
@@ -199,6 +199,7 @@
},
"devDependencies": {
"5to6-codemod": "^1.8.0",
"@ai-sdk/react": "^3.0.0-beta.99",
"@babel/cli": "^7.27.0",
"@babel/core": "^7.26.10",
"@babel/plugin-proposal-decorators": "^7.27.0",
@@ -221,7 +222,6 @@
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"@lezer/markdown": "^1.4.3",
"@overleaf/ai": "^1.0.0",
"@overleaf/codemirror-tree-view": "^0.1.3",
"@overleaf/dictionaries": "https://github.com/overleaf/dictionaries/archive/refs/tags/v0.0.3.tar.gz",
"@overleaf/eslint-plugin": "*",
@@ -258,6 +258,7 @@
"@types/chai": "^4.3.0",
"@types/dateformat": "^5.0.3",
"@types/diff": "^5.2.3",
"@types/dom-speech-recognition": "^0.0.7",
"@types/events": "^3.0.3",
"@types/express": "^4.17.23",
"@types/mocha": "^9.1.0",
@@ -345,6 +346,7 @@
"jscodeshift": "^17.0.0",
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
"katex": "^0.16.25",
"knip": "^5.64.1",
"match-sorter": "^6.2.0",
"mathjax": "^3.2.2",
@@ -378,10 +380,15 @@
"react-google-recaptcha": "^3.1.0",
"react-i18next": "^13.3.1",
"react-linkify": "^1.0.0-alpha",
"react-markdown": "^10.1.0",
"react-refresh": "^0.14.0",
"react-resizable-panels": "^2.1.1",
"react-syntax-highlighter": "^15.6.6",
"reflect-metadata": "^0.2.2",
"rehype-external-links": "^3.0.0",
"rehype-katex": "^7.0.1",
"rehype-sanitize": "^6.0.0",
"remark-math": "^6.0.0",
"resolve-url-loader": "^5.0.0",
"samlp": "^7.0.2",
"sandboxed-module": "overleaf/node-sandboxed-module#cafa2d60f17ce75cc023e6f296eb8de79d92d35d",
@@ -401,6 +408,7 @@
"ts-loader": "^9.5.2",
"tty-browserify": "^0.0.1",
"typescript": "^5.8.3",
"use-stick-to-bottom": "^1.1.1",
"uuid": "^9.0.1",
"vitest": "^3.1.2",
"w3c-keyname": "^2.2.8",
+2 -1
View File
@@ -29,7 +29,8 @@
"@testing-library/cypress",
"reflect-metadata",
"vitest/globals",
"@testing-library/jest-dom"
"@testing-library/jest-dom",
"@types/dom-speech-recognition"
]
},
"include": [
-21
View File
@@ -10,7 +10,6 @@ const {
const PackageVersions = require('./app/src/infrastructure/PackageVersions.js')
const invalidateBabelCacheIfNeeded = require('./frontend/macros/invalidate-babel-cache-if-needed')
const { dirname } = require('node:path')
// Make sure that babel-macros are re-evaluated after changing the modules config
invalidateBabelCacheIfNeeded()
@@ -259,26 +258,6 @@ module.exports = {
},
],
},
{
// CSS from AI module
include: dirname(require.resolve('@overleaf/ai')),
use: [
{
loader: 'css-loader',
options: {
exportType: 'css-style-sheet',
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: require.resolve('@overleaf/ai/postcss.config.js'),
},
},
},
],
},
{
// Standard CSS processing (extracted into separate file)
use: [MiniCssExtractPlugin.loader, 'css-loader'],