diff --git a/services/web/.storybook/preview.tsx b/services/web/.storybook/preview.tsx index 320caac144..fa6905d7d7 100644 --- a/services/web/.storybook/preview.tsx +++ b/services/web/.storybook/preview.tsx @@ -13,6 +13,7 @@ import en from '../../../services/web/locales/en.json' function resetMeta() { window.metaAttributesCache = new Map() window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' }) + window.metaAttributesCache.set('ol-chatEnabled', true) window.metaAttributesCache.set('ol-ExposedSettings', { adminEmail: 'placeholder@example.com', appName: 'Overleaf', diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx index 2ffd591032..909e1a1962 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx @@ -176,7 +176,6 @@ export default function FileTreeUploadDoc() { // close the modal when all the uploads completed successfully .on('complete', result => { if (!result.failed.length) { - // $scope.$emit('done', { name: name }) cancel() } }) diff --git a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx index bb3d0c1a3c..25ef9cc0d6 100644 --- a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx @@ -16,7 +16,6 @@ import { } from '@/features/ide-react/create-ide-event-emitter' import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload' import { useConnectionContext } from '@/features/ide-react/context/connection-context' -import { getMockIde } from '@/shared/context/mock/mock-ide' import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter' import { postJSON } from '@/infrastructure/fetch-json' import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter' @@ -157,11 +156,11 @@ export const IdeReactProvider: FC = ({ children }) => { const ide = useMemo(() => { return { - ...getMockIde(), + _id: projectId, socket, reportError, } - }, [socket, reportError]) + }, [projectId, socket, reportError]) const value = useMemo( () => ({ diff --git a/services/web/frontend/js/shared/context/ide-context.tsx b/services/web/frontend/js/shared/context/ide-context.tsx index 3eef2bbdbb..1b4d83d56c 100644 --- a/services/web/frontend/js/shared/context/ide-context.tsx +++ b/services/web/frontend/js/shared/context/ide-context.tsx @@ -4,7 +4,6 @@ import { ScopeEventEmitter } from '../../../../types/ide/scope-event-emitter' import { Socket } from '@/features/ide-react/connection/types/socket' export type Ide = { - $scope: Record socket: Socket } diff --git a/services/web/frontend/js/shared/context/mock/mock-ide.js b/services/web/frontend/js/shared/context/mock/mock-ide.js deleted file mode 100644 index 822d3b165c..0000000000 --- a/services/web/frontend/js/shared/context/mock/mock-ide.js +++ /dev/null @@ -1,56 +0,0 @@ -import getMeta from '../../../utils/meta' - -// When rendered without Angular, ide isn't defined. In that case we use -// a mock object that only has the required properties to pass proptypes -// checks and the values needed for the app. In the longer term, the mock -// object will replace ide completely. -export const getMockIde = () => { - return { - _id: getMeta('ol-project_id'), - $scope: { - $on: () => {}, - $watch: () => {}, - $applyAsync: () => {}, - user: {}, - project: { - _id: getMeta('ol-project_id'), - name: getMeta('ol-projectName'), - rootDocId: '', - members: [], - invites: [], - features: { - collaborators: 0, - compileGroup: 'standard', - trackChangesVisible: false, - references: false, - mendeley: false, - zotero: false, - }, - publicAccessLevel: '', - owner: { - _id: '', - email: '', - }, - }, - permissionsLevel: 'readOnly', - editor: { - sharejs_doc: null, - showSymbolPalette: false, - toggleSymbolPalette: () => {}, - }, - ui: { - view: 'pdf', - chatOpen: false, - reviewPanelOpen: false, - leftMenuShown: false, - pdfLayout: 'flat', - }, - pdf: { - uncompiled: true, - logEntryAnnotations: {}, - }, - settings: { syntaxValidation: false, pdfViewer: 'pdfjs' }, - hasLintingError: false, - }, - } -} diff --git a/services/web/frontend/stories/decorators/scope.tsx b/services/web/frontend/stories/decorators/scope.tsx index ae6e366eb8..3be1db9de5 100644 --- a/services/web/frontend/stories/decorators/scope.tsx +++ b/services/web/frontend/stories/decorators/scope.tsx @@ -1,5 +1,4 @@ import React, { FC, useEffect, useState } from 'react' -import { get } from 'lodash' import { User, UserId } from '../../../types/user' import { Project } from '../../../types/project' import { @@ -24,86 +23,68 @@ import { ReactContextRoot } from '@/features/ide-react/context/react-context-roo const scopeWatchers: [string, (value: any) => void][] = [] -const initialize = () => { - const user: User = { - id: 'story-user' as UserId, - email: 'story-user@example.com', - allowedFreeTrial: true, - features: { dropbox: true, symbolPalette: true }, - } +const user: User = { + id: 'story-user' as UserId, + email: 'story-user@example.com', + allowedFreeTrial: true, + features: { dropbox: true, symbolPalette: true }, +} - const project: Project = { - _id: '63e21c07946dd8c76505f85a', - name: 'A Project', - features: { mendeley: true, zotero: true, referencesSearch: true }, - tokens: {}, - owner: { - _id: 'a-user', - email: 'stories@overleaf.com', +const project: Project = { + _id: '63e21c07946dd8c76505f85a', + name: 'A Project', + features: { mendeley: true, zotero: true, referencesSearch: true }, + tokens: {}, + owner: { + _id: 'a-user', + email: 'stories@overleaf.com', + }, + members: [], + invites: [], + rootDoc_id: '5e74f1a7ce17ae0041dfd056', + rootFolder: [ + { + _id: 'root-folder-id', + name: 'rootFolder', + docs: [ + { _id: 'test-file-id', name: 'testfile.tex' }, + { _id: 'test-bib-file-id', name: 'testsources.bib' }, + ], + fileRefs: [{ _id: 'test-image-id', name: 'frog.jpg', hash: '42' }], + folders: [], }, - members: [], - invites: [], - rootDoc_id: '5e74f1a7ce17ae0041dfd056', - rootFolder: [ - { - _id: 'root-folder-id', - name: 'rootFolder', - docs: [ - { _id: 'test-file-id', name: 'testfile.tex' }, - { _id: 'test-bib-file-id', name: 'testsources.bib' }, - ], - fileRefs: [{ _id: 'test-image-id', name: 'frog.jpg', hash: '42' }], - folders: [], - }, - ], - } + ], +} - const scope = { - user, - project, - $watch: (key: string, callback: () => void) => { - scopeWatchers.push([key, callback]) +const initialScope = { + user, + project, + ui: { + chatOpen: true, + pdfLayout: 'flat', + }, + settings: { + pdfViewer: 'js', + syntaxValidation: true, + }, + editor: { + richText: false, + sharejs_doc: { + doc_id: 'test-doc', + getSnapshot: () => 'some doc content', + hasBufferedOps: () => false, }, - $applyAsync: (callback: () => void) => { - window.setTimeout(() => { - callback() - for (const [key, watcher] of scopeWatchers) { - watcher(get(ide.$scope, key)) - } - }, 0) - }, - $on: () => { - // - }, - $broadcast: () => {}, - ui: { - chatOpen: true, - pdfLayout: 'flat', - }, - settings: { - pdfViewer: 'js', - syntaxValidation: true, - }, - editor: { - richText: false, - sharejs_doc: { - doc_id: 'test-doc', - getSnapshot: () => 'some doc content', - hasBufferedOps: () => false, - }, - open_doc_name: 'testfile.tex', - }, - hasLintingError: false, - permissionsLevel: 'owner', - } + open_doc_name: 'testfile.tex', + }, + hasLintingError: false, + permissionsLevel: 'owner', +} - const ide = { - $scope: scope, - socket: new SocketIOShim.SocketShimNoop( - new SocketIOMock() - ) as unknown as Socket, - } +const socket = new SocketIOShim.SocketShimNoop( + new SocketIOMock() +) as unknown as Socket +const initializeMetaTags = () => { // window.metaAttributesCache is reset in preview.tsx window.metaAttributesCache.set('ol-user', user) window.metaAttributesCache.set('ol-project_id', project._id) @@ -111,8 +92,6 @@ const initialize = () => { 'ol-gitBridgePublicBaseUrl', 'https://git.stories.com' ) - - window._ide = ide } type ScopeDecoratorOptions = { @@ -125,7 +104,7 @@ export const ScopeDecorator = ( opts: ScopeDecoratorOptions = { mockCompileOnLoad: true }, meta: Record = {} ) => { - initialize() + initializeMetaTags() // mock compile on load useFetchMock(fetchMock => { @@ -171,7 +150,7 @@ const ConnectionProvider: FC = ({ children }) => { error: '', } return { - socket: window._ide.socket as Socket, + socket, connectionState, isConnected: true, isStillReconnecting: false, @@ -215,9 +194,8 @@ const IdeReactProvider: FC = ({ children }) => { })) const [ideContextValue] = useState(() => { - const ide = window._ide const scopeStore = createReactScopeValueStore(projectId) - for (const [key, value] of Object.entries(ide.$scope)) { + for (const [key, value] of Object.entries(initialScope)) { scopeStore.set(key, value) } const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter()) @@ -231,7 +209,7 @@ const IdeReactProvider: FC = ({ children }) => { } return { - ...ide, + socket, scopeStore, scopeEventEmitter, } diff --git a/services/web/test/frontend/features/chat/components/chat-pane.test.jsx b/services/web/test/frontend/features/chat/components/chat-pane.test.jsx index 5a59b9b19f..ee7c391c33 100644 --- a/services/web/test/frontend/features/chat/components/chat-pane.test.jsx +++ b/services/web/test/frontend/features/chat/components/chat-pane.test.jsx @@ -7,10 +7,7 @@ import { import fetchMock from 'fetch-mock' import ChatPane from '../../../../../frontend/js/features/chat/components/chat-pane' -import { - cleanUpContext, - renderWithEditorContext, -} from '../../../helpers/render-with-context' +import { renderWithEditorContext } from '../../../helpers/render-with-context' import { stubMathJax, tearDownMathJaxStubs } from './stubs' describe('', function () { @@ -47,8 +44,6 @@ describe('', function () { beforeEach(function () { fetchMock.removeRoutes().clearHistory() - cleanUpContext() - stubMathJax() }) diff --git a/services/web/test/frontend/features/chat/context/chat-context.test.jsx b/services/web/test/frontend/features/chat/context/chat-context.test.jsx index ddb69d3025..36d5846555 100644 --- a/services/web/test/frontend/features/chat/context/chat-context.test.jsx +++ b/services/web/test/frontend/features/chat/context/chat-context.test.jsx @@ -9,7 +9,6 @@ import { useChatContext, chatClientIdGenerator, } from '@/features/chat/context/chat-context' -import { cleanUpContext } from '../../../helpers/render-with-context' import { stubMathJax, tearDownMathJaxStubs } from '../components/stubs' import { SocketIOMock } from '@/ide/connection/SocketIoShim' import { EditorProviders } from '../../../helpers/editor-providers' @@ -24,7 +23,6 @@ describe('ChatContext', function () { beforeEach(function () { fetchMock.removeRoutes().clearHistory() - cleanUpContext() stubMathJax() diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx index 6c43e548ea..b86207fb0f 100644 --- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx +++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx @@ -5,10 +5,7 @@ import fetchMock from 'fetch-mock' import userEvent from '@testing-library/user-event' import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal' -import { - renderWithEditorContext, - cleanUpContext, -} from '../../../helpers/render-with-context' +import { renderWithEditorContext } from '../../../helpers/render-with-context' import { EditorProviders, USER_EMAIL, @@ -100,7 +97,6 @@ describe('', function () { afterEach(function () { this.locationWrapperSandbox.restore() fetchMock.removeRoutes().clearHistory() - cleanUpContext() }) it('renders the modal', async function () { diff --git a/services/web/test/frontend/features/word-count-modal/components/word-count-modal.spec.tsx b/services/web/test/frontend/features/word-count-modal/components/word-count-modal.spec.tsx index 2ed1dc9448..9fc5887535 100644 --- a/services/web/test/frontend/features/word-count-modal/components/word-count-modal.spec.tsx +++ b/services/web/test/frontend/features/word-count-modal/components/word-count-modal.spec.tsx @@ -12,7 +12,7 @@ describe('', function () { }) cy.mount( - + ) @@ -30,7 +30,7 @@ describe('', function () { }) cy.mount( - + ) @@ -48,7 +48,7 @@ describe('', function () { }) cy.mount( - + ) @@ -64,7 +64,7 @@ describe('', function () { }) cy.mount( - + ) @@ -87,7 +87,7 @@ describe('', function () { }) cy.mount( - + ) diff --git a/services/web/test/frontend/helpers/editor-providers.jsx b/services/web/test/frontend/helpers/editor-providers.jsx index a6bc9c32c6..bdff189975 100644 --- a/services/web/test/frontend/helpers/editor-providers.jsx +++ b/services/web/test/frontend/helpers/editor-providers.jsx @@ -1,7 +1,6 @@ // Disable prop type checks for test harnesses /* eslint-disable react/prop-types */ -import sinon from 'sinon' -import { get, merge } from 'lodash' +import { merge } from 'lodash' import { SocketIOMock } from '@/ide/connection/SocketIoShim' import { IdeContext } from '@/shared/context/ide-context' import React, { useEffect, useState } from 'react' @@ -48,8 +47,7 @@ export function EditorProviders({ compiler = 'pdflatex', socket = new SocketIOMock(), isRestrictedTokenMember = false, - clsiServerId = '1234', - scope = {}, + scope: defaultScope = {}, features = { referencesSearch: true, }, @@ -71,18 +69,6 @@ export function EditorProviders({ }, ], ui = { view: 'editor', pdfLayout: 'sideBySide', chatOpen: true }, - fileTreeManager = { - findEntityById: () => null, - findEntityByPath: () => null, - getEntityPath: () => '', - getRootDocDirname: () => '', - getPreviewByPath: path => ({ url: path, extension: 'png' }), - }, - editorManager = { - getCurrentDocumentId: () => 'foo', - getCurrentDocValue: () => {}, - openDoc: sinon.stub(), - }, userSettings = {}, providers = {}, }) { @@ -99,7 +85,7 @@ export function EditorProviders({ merge({}, defaultUserSettings, userSettings) ) - const $scope = merge( + const scope = merge( { user, editor: { @@ -123,25 +109,11 @@ export function EditorProviders({ compiler, }, ui, - $watch: (path, callback) => { - callback(get($scope, path)) - return () => null - }, - $on: sinon.stub(), - $applyAsync: sinon.stub(), permissionsLevel, }, - scope + defaultScope ) - window._ide = { - $scope, - socket, - clsiServerId, - editorManager, - fileTreeManager, - } - // Add details for useUserContext window.metaAttributesCache.set('ol-user', { ...user, features }) window.metaAttributesCache.set('ol-project_id', projectId) @@ -149,8 +121,8 @@ export function EditorProviders({ return ( @@ -159,79 +131,85 @@ export function EditorProviders({ ) } -const ConnectionProvider = ({ children }) => { - const [value] = useState(() => ({ - socket: window._ide.socket, - connectionState: { - readyState: WebSocket.OPEN, - forceDisconnected: false, - inactiveDisconnect: false, - reconnectAt: null, - forcedDisconnectDelay: 0, - lastConnectionAttempt: 0, - error: '', - }, - isConnected: true, - isStillReconnecting: false, - secondsUntilReconnect: () => 0, - tryReconnectNow: () => {}, - registerUserActivity: () => {}, - disconnect: () => {}, - })) - - return ( - - {children} - - ) -} - -const IdeReactProvider = ({ children }) => { - const [startedFreeTrial, setStartedFreeTrial] = useState(false) - - const [ideReactContextValue] = useState(() => ({ - projectId: PROJECT_ID, - eventEmitter: new IdeEventEmitter(), - startedFreeTrial, - setStartedFreeTrial, - reportError: () => {}, - projectJoined: true, - })) - - const [ideContextValue] = useState(() => { - const ide = window._ide - - const scopeStore = createReactScopeValueStore(PROJECT_ID) - for (const [key, value] of Object.entries(ide.$scope)) { - // TODO: path for nested entries - scopeStore.set(key, value) - } - scopeStore.set('editor.sharejs_doc', ide.$scope.editor.sharejs_doc) - scopeStore.set('ui.chatOpen', ide.$scope.ui.chatOpen) - const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter()) - - return { - ...ide, - scopeStore, - scopeEventEmitter, - } - }) - - useEffect(() => { - window.overleaf = { - ...window.overleaf, - unstable: { - ...window.overleaf?.unstable, - store: ideContextValue.scopeStore, +const makeConnectionProvider = socket => { + const ConnectionProvider = ({ children }) => { + const [value] = useState(() => ({ + socket, + connectionState: { + readyState: WebSocket.OPEN, + forceDisconnected: false, + inactiveDisconnect: false, + reconnectAt: null, + forcedDisconnectDelay: 0, + lastConnectionAttempt: 0, + error: '', }, - } - }, [ideContextValue.scopeStore]) + isConnected: true, + isStillReconnecting: false, + secondsUntilReconnect: () => 0, + tryReconnectNow: () => {}, + registerUserActivity: () => {}, + disconnect: () => {}, + })) - return ( - - + return ( + {children} - - - ) + + ) + } + return ConnectionProvider +} + +const makeIdeReactProvider = (scope, socket) => { + const IdeReactProvider = ({ children }) => { + const [startedFreeTrial, setStartedFreeTrial] = useState(false) + + const [ideReactContextValue] = useState(() => ({ + projectId: PROJECT_ID, + eventEmitter: new IdeEventEmitter(), + startedFreeTrial, + setStartedFreeTrial, + reportError: () => {}, + projectJoined: true, + })) + + const [ideContextValue] = useState(() => { + const scopeStore = createReactScopeValueStore(PROJECT_ID) + for (const [key, value] of Object.entries(scope)) { + // TODO: path for nested entries + scopeStore.set(key, value) + } + scopeStore.set('editor.sharejs_doc', scope.editor.sharejs_doc) + scopeStore.set('ui.chatOpen', scope.ui.chatOpen) + const scopeEventEmitter = new ReactScopeEventEmitter( + new IdeEventEmitter() + ) + + return { + socket, + scopeStore, + scopeEventEmitter, + } + }) + + useEffect(() => { + window.overleaf = { + ...window.overleaf, + unstable: { + ...window.overleaf?.unstable, + store: ideContextValue.scopeStore, + }, + } + }, [ideContextValue.scopeStore]) + + return ( + + + {children} + + + ) + } + return IdeReactProvider } diff --git a/services/web/test/frontend/helpers/render-with-context.jsx b/services/web/test/frontend/helpers/render-with-context.jsx index e3aba6264d..31ee64d5be 100644 --- a/services/web/test/frontend/helpers/render-with-context.jsx +++ b/services/web/test/frontend/helpers/render-with-context.jsx @@ -18,7 +18,3 @@ export function renderWithEditorContext( ...renderOptions, }) } - -export function cleanUpContext() { - delete window._ide -} diff --git a/services/web/types/window.ts b/services/web/types/window.ts index 1150bf1e50..d2856e7179 100644 --- a/services/web/types/window.ts +++ b/services/web/types/window.ts @@ -1,20 +1,11 @@ import 'recurly__recurly-js' import { ScopeValueStore } from './ide/scope-value-store' import { MetaAttributesCache } from '@/utils/meta' -import { Socket } from '@/features/ide-react/connection/types/socket' declare global { // eslint-disable-next-line no-unused-vars interface Window { metaAttributesCache: MetaAttributesCache - _ide: Record & { - $scope: Record & { - pdf?: { - logEntryAnnotations: Record - } - } - socket: Socket - } MathJax: Record // For react-google-recaptcha recaptchaOptions?: {