From dfd448cd85f54c4e637a86de143d8d0eed7f5e3e Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Thu, 23 Jan 2025 09:34:58 +0000 Subject: [PATCH] Merge pull request #22964 from overleaf/ae-project-search-flush Ensure that open docs are flushed before running full project search GitOrigin-RevId: 6707cf982018908a37957503add73a085c749f61 --- .../ide-react/editor/open-documents.ts | 31 ++++++++++++ .../js/features/pdf-preview/util/compiler.js | 48 +++++-------------- .../shared/context/local-compile-context.tsx | 3 +- .../pdf-preview/pdf-logs-entries.spec.tsx | 3 ++ 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/services/web/frontend/js/features/ide-react/editor/open-documents.ts b/services/web/frontend/js/features/ide-react/editor/open-documents.ts index cedd96b1f0..68373863ff 100644 --- a/services/web/frontend/js/features/ide-react/editor/open-documents.ts +++ b/services/web/frontend/js/features/ide-react/editor/open-documents.ts @@ -95,4 +95,35 @@ export class OpenDocuments { } return ids } + + async awaitBufferedOps(signal: AbortSignal) { + if (this.hasUnsavedChanges()) { + const { promise, resolve } = Promise.withResolvers() + + let resolved = false + + const listener = () => { + if (!this.hasUnsavedChanges()) { + debugConsole.log('saved') + window.removeEventListener('doc:saved', listener) + resolved = true + resolve() + } + } + + window.addEventListener('doc:saved', listener) + + signal.addEventListener('abort', () => { + if (!resolved) { + debugConsole.log('aborted') + window.removeEventListener('doc:saved', listener) + resolve() + } + }) + + this.flushAll() + + await promise + } + } } diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.js b/services/web/frontend/js/features/pdf-preview/util/compiler.js index 846991f56d..0e69f60fa0 100644 --- a/services/web/frontend/js/features/pdf-preview/util/compiler.js +++ b/services/web/frontend/js/features/pdf-preview/util/compiler.js @@ -30,6 +30,7 @@ export default class DocumentCompiler { setError, cleanupCompileResult, signal, + openDocs, }) { this.compilingRef = compilingRef this.projectId = projectId @@ -41,6 +42,7 @@ export default class DocumentCompiler { this.setError = setError this.cleanupCompileResult = cleanupCompileResult this.signal = signal + this.openDocs = openDocs this.projectRootDocId = null this.clsiServerId = null @@ -61,40 +63,6 @@ export default class DocumentCompiler { maxWait: AUTO_COMPILE_MAX_WAIT, } ) - - this._onDocSavedCallback = null - } - - async _awaitBufferedOps() { - const removeEventListener = () => { - clearTimeout(this.pendingOpTimeout) - if (this._onDocSavedCallback) { - window.removeEventListener('doc:saved', this._onDocSavedCallback) - this._onDocSavedCallback = null - } - } - - removeEventListener() - return new Promise(resolve => { - if (!this.currentDoc?.hasBufferedOps?.()) { - return resolve() - } - - this._onDocSavedCallback = () => { - // TODO: it's possible that there's more than one doc open with buffered ops, and ideally we'd wait for all docs to be flushed - removeEventListener() - resolve() - } - - clearTimeout(this.pendingOpTimeout) - this.pendingOpTimeout = setTimeout(() => { - removeEventListener() - resolve() - }, PENDING_OP_MAX_WAIT) - - window.addEventListener('doc:saved', this._onDocSavedCallback) - window.dispatchEvent(new CustomEvent('flush-changes')) - }) } // The main "compile" function. @@ -118,7 +86,17 @@ export default class DocumentCompiler { } try { - await this._awaitBufferedOps() + if ( + typeof AbortSignal.any === 'function' && + typeof AbortSignal.timeout === 'function' + ) { + await this.openDocs.awaitBufferedOps( + AbortSignal.any([ + this.signal, + AbortSignal.timeout(PENDING_OP_MAX_WAIT), + ]) + ) + } // reset values this.setChangedAt(0) // TODO: wait for doc:saved? diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index ef12cbec00..4924160804 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -115,7 +115,7 @@ export const LocalCompileProvider: FC = ({ children }) => { const ide = useIdeContext() const { hasPremiumCompile, isProjectOwner } = useEditorContext() - const { openDocId } = useEditorManagerContext() + const { openDocId, openDocs } = useEditorManagerContext() const { _id: projectId, rootDocId } = useProjectContext() @@ -299,6 +299,7 @@ export const LocalCompileProvider: FC = ({ children }) => { cleanupCompileResult, compilingRef, signal, + openDocs, }) }) diff --git a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx index 5e8ca5c4b3..df3dd89fa7 100644 --- a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx +++ b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx @@ -10,6 +10,7 @@ import { EditorManagerContext, } from '@/features/ide-react/context/editor-manager-context' import { EditorView } from '@codemirror/view' +import { OpenDocuments } from '@/features/ide-react/editor/open-documents' describe('', function () { const fakeFindEntityResult: FindResult = { @@ -36,6 +37,8 @@ describe('', function () { const EditorManagerProvider: FC = ({ children }) => { const value = { openDocId: cy.spy().as('openDocId'), + // @ts-ignore + openDocs: new OpenDocuments(), } as unknown as EditorManager return (