Merge pull request #26583 from overleaf/td-editor-scope-values-to-context

Move scope values starting with `editor.` to contexts

GitOrigin-RevId: 7ca349ceff002228cf4e931c644c8c386eb6c597
This commit is contained in:
Tim Down
2025-07-09 08:05:15 +00:00
committed by Copybot
parent b2fb70c2b6
commit 132ccbc4cc
40 changed files with 593 additions and 292 deletions
@@ -1,5 +1,8 @@
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import { mockScope, rootFolderId } from '../helpers/mock-scope'
import { FC } from 'react'
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
@@ -41,7 +44,6 @@ describe('<FigureModal />', function () {
function mount() {
const content = ''
const scope = mockScope(content)
scope.editor.showVisual = true
const FileTreePathProvider: FC<React.PropsWithChildren> = ({
children,
@@ -63,7 +65,16 @@ describe('<FigureModal />', function () {
cy.mount(
<TestContainer>
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
<EditorProviders
scope={scope}
providers={{
FileTreePathProvider,
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,6 +1,9 @@
// Needed since eslint gets confused by mocha-each
/* eslint-disable mocha/prefer-arrow-callback */
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import forEach from 'mocha-each'
@@ -14,11 +17,18 @@ const mountEditor = (content: string | string[]) => {
content = '\n' + content
}
const scope = mockScope(content)
scope.editor.showVisual = true
cy.viewport(1000, 800)
cy.mount(
<TestContainer style={{ width: 1000, height: 800 }}>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,15 +1,25 @@
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import { TestContainer } from '../helpers/test-container'
const mountEditor = (content: string) => {
const scope = mockScope(content)
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,15 +1,25 @@
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import { TestContainer } from '../helpers/test-container'
const mountEditor = (content: string) => {
const scope = mockScope(content)
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,4 +1,7 @@
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import { TestContainer } from '../helpers/test-container'
@@ -6,11 +9,18 @@ import { isMac } from '@/shared/utils/os'
const mountEditor = (content: string) => {
const scope = mockScope(content)
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,4 +1,7 @@
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import { TestContainer } from '../helpers/test-container'
@@ -7,11 +10,18 @@ const menuIconsText = 'content_copyexpand_more'
const mountEditor = (content = '') => {
const scope = mockScope(content)
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,5 +1,8 @@
import { mockScope } from '../helpers/mock-scope'
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { FC } from 'react'
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
@@ -43,13 +46,19 @@ const mountEditor = (content: string) => {
const scope = mockScope(content)
scope.permissions.write = false
scope.permissions.trackedWrite = false
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders
scope={scope}
providers={{ FileTreePathProvider, PermissionsProvider }}
providers={{
FileTreePathProvider,
PermissionsProvider,
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
@@ -1,4 +1,7 @@
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import { TestContainer } from '../helpers/test-container'
@@ -18,11 +21,18 @@ const clickToolbarButton = (name: string) => {
const mountEditor = (content: string) => {
const scope = mockScope(content)
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,5 +1,8 @@
import { mockScope } from '../helpers/mock-scope'
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { TestContainer } from '../helpers/test-container'
@@ -11,11 +14,18 @@ describe('<CodeMirrorEditor/> tooltips in Visual mode', function () {
cy.interceptEvents()
const scope = mockScope('\n\n\n')
scope.editor.showVisual = true
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
providers={{
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -1,7 +1,10 @@
// Needed since eslint gets confused by mocha-each
/* eslint-disable mocha/prefer-arrow-callback */
import { FC } from 'react'
import { EditorProviders } from '../../../helpers/editor-providers'
import {
EditorProviders,
makeEditorPropertiesProvider,
} from '../../../helpers/editor-providers'
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { mockScope } from '../helpers/mock-scope'
import forEach from 'mocha-each'
@@ -19,7 +22,6 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
const content = '\n'.repeat(3)
const scope = mockScope(content)
scope.editor.showVisual = true
const FileTreePathProvider: FC<React.PropsWithChildren> = ({
children,
@@ -41,7 +43,16 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
cy.mount(
<TestContainer>
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
<EditorProviders
scope={scope}
providers={{
FileTreePathProvider,
EditorPropertiesProvider: makeEditorPropertiesProvider({
showVisual: true,
showSymbolPalette: false,
}),
}}
>
<CodemirrorEditor />
</EditorProviders>
</TestContainer>
@@ -622,7 +622,7 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
const rect = selection.getRangeAt(0).getBoundingClientRect()
expect(Math.round(rect.top)).to.be.gte(100)
expect(Math.round(rect.left)).to.be.gte(90)
expect(Math.round(rect.left)).to.be.gte(80)
})
})
})
@@ -19,7 +19,6 @@ export const mockScope = (
sharejs_doc: mockDoc(content, docOptions),
openDocName: 'test.tex',
currentDocumentId: docId,
showVisual: false,
wantTrackChanges: false,
},
project: {
@@ -27,6 +27,10 @@ import { ReactContextRoot } from '@/features/ide-react/context/react-context-roo
import useEventListener from '@/shared/hooks/use-event-listener'
import useDetachLayout from '@/shared/hooks/use-detach-layout'
import useExposedState from '@/shared/hooks/use-exposed-state'
import {
EditorPropertiesContext,
EditorPropertiesContextValue,
} from '@/features/ide-react/context/editor-properties-context'
import {
type IdeLayout,
type IdeView,
@@ -160,6 +164,7 @@ export function EditorProviders({
} as any as DocumentContainer,
openDocName: null,
currentDocumentId: null,
wantTrackChanges: false,
},
project: {
_id: projectId,
@@ -190,6 +195,9 @@ export function EditorProviders({
openDocName: scope.editor.openDocName,
currentDocument: scope.editor.sharejs_doc,
}),
EditorPropertiesProvider: makeEditorPropertiesProvider({
wantTrackChanges: scope.editor.wantTrackChanges,
}),
LayoutProvider: makeLayoutProvider(layoutContext),
...providers,
}}
@@ -251,7 +259,7 @@ const makeIdeReactProvider = (
}))
const [ideContextValue] = useState(() => {
const scopeStore = createReactScopeValueStore(PROJECT_ID)
const scopeStore = createReactScopeValueStore()
for (const [key, value] of Object.entries(scope)) {
// TODO: path for nested entries
scopeStore.set(key, value)
@@ -438,3 +446,60 @@ const makeLayoutProvider = (
}
return LayoutProvider
}
export function makeEditorPropertiesProvider(
initialValues: Partial<
Pick<
EditorPropertiesContextValue,
'showVisual' | 'showSymbolPalette' | 'wantTrackChanges'
>
>
) {
const EditorPropertiesProvider: FC<PropsWithChildren> = ({ children }) => {
const {
showVisual: initialShowVisual,
showSymbolPalette: initialShowSymbolPalette,
wantTrackChanges: initialWantTrackChanges,
} = initialValues
const [showVisual, setShowVisual] = useState(initialShowVisual || false)
const [showSymbolPalette, setShowSymbolPalette] = useState(
initialShowSymbolPalette || false
)
function toggleSymbolPalette() {
setShowSymbolPalette(show => !show)
}
const [opening, setOpening] = useState(true)
const [trackChanges, setTrackChanges] = useState(false)
const [wantTrackChanges, setWantTrackChanges] = useState(
initialWantTrackChanges || false
)
const [errorState, setErrorState] = useState(false)
const value = {
showVisual,
setShowVisual,
showSymbolPalette,
setShowSymbolPalette,
toggleSymbolPalette,
opening,
setOpening,
trackChanges,
setTrackChanges,
wantTrackChanges,
setWantTrackChanges,
errorState,
setErrorState,
}
return (
<EditorPropertiesContext.Provider value={value}>
{children}
</EditorPropertiesContext.Provider>
)
}
return EditorPropertiesProvider
}
@@ -22,7 +22,7 @@ describe('usePersistedState', function () {
expect(window.Storage.prototype.setItem).to.have.callCount(1)
const Test = () => {
const [value] = usePersistedState(key)
const [value] = usePersistedState<string>(key)
return <div>{value}</div>
}
@@ -139,6 +139,35 @@ describe('usePersistedState', function () {
expect(localStorage.getItem(key)).to.equal('foobar')
})
it('converts persisted value (string to boolean)', function () {
const key = 'test:convert'
localStorage.setItem(key, 'yep')
const Test = () => {
const [value, setValue] = usePersistedState(key, true, {
converter: {
toPersisted(value) {
return value ? 'yep' : 'nope'
},
fromPersisted(persistedValue) {
return persistedValue === 'yep'
},
},
})
useEffect(() => {
setValue(false)
}, [setValue])
return <div>{String(value)}</div>
}
render(<Test />)
screen.getByText('false')
expect(localStorage.getItem(key)).to.equal('nope')
})
it('handles syncing values via storage event', async function () {
const key = 'test:sync'
localStorage.setItem(key, 'foo')
@@ -149,7 +178,7 @@ describe('usePersistedState', function () {
window.addEventListener('storage', storageEventListener)
const Test = () => {
const [value, setValue] = usePersistedState(key, 'bar', true)
const [value, setValue] = usePersistedState(key, 'bar', { listen: true })
useEffect(() => {
setValue('baz')