codex-overleaf-link 2.4.1 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +275 -305
- package/README.zh-CN.md +290 -320
- package/extension/bootstrap/background.js +99 -19
- package/extension/bootstrap/manifest.template.json +9 -4
- package/extension/bootstrap/popup.html +11 -16
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/update.js +25 -2
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/runtime-manifest.json +3 -1
- package/extension/src/background.js +234 -56
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +440 -0
- package/extension/src/backgroundUpdateCoordinator.js +541 -381
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +16 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +244 -12
- package/extension/src/content/changeHistoryView.js +175 -0
- package/extension/src/content/composerAttachments.js +117 -67
- package/extension/src/content/composerPanel.js +41 -8
- package/extension/src/content/contentRuntime.js +1084 -1099
- package/extension/src/content/diagnosticsController.js +122 -97
- package/extension/src/content/diagnosticsExport.js +236 -0
- package/extension/src/content/diagnosticsPanel.js +78 -19
- package/extension/src/content/diffReviewPanel.js +8 -5
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +14680 -3606
- package/extension/src/content/generated/content.bundle.meta.json +30 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +25 -1
- package/extension/src/content/localSkillsPanel.js +141 -16
- package/extension/src/content/markdownDomRenderer.js +9 -4
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +8 -5
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +33 -5
- package/extension/src/content/panelMaintenance.js +87 -132
- package/extension/src/content/panelRenderer.js +10 -0
- package/extension/src/content/pendingInputView.js +109 -76
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +63 -14
- package/extension/src/content/providerSettingsDialog.js +724 -113
- package/extension/src/content/recentProjects.js +21 -6
- package/extension/src/content/runActivitySummary.js +665 -0
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +40 -15
- package/extension/src/content/runFailureNotice.js +34 -12
- package/extension/src/content/runGuidanceController.js +76 -30
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +306 -0
- package/extension/src/content/runQueueScheduler.js +150 -43
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +11 -8
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +315 -290
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +300 -0
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/sessionPersistence.js +6 -1
- package/extension/src/content/settingsPanel.js +84 -55
- package/extension/src/content/settingsWorkbench.js +379 -0
- package/extension/src/content/sharedSessionViewSync.js +109 -0
- package/extension/src/content/subagentActivityView.js +195 -0
- package/extension/src/content/trackedChangeCaptureController.js +7 -3
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +179 -56
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +308 -2
- package/extension/src/content/writebackOrchestrator.js +213 -199
- package/extension/src/content/writebackPlan.js +231 -0
- package/extension/src/content/writebackRecovery.js +122 -0
- package/extension/src/content/writingStyleSettings.js +600 -0
- package/extension/src/content/writtenChangesView.js +152 -0
- package/extension/src/page/binaryAssetUploader.js +223 -23
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafProjectSnapshot.js +1 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +149 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +20 -6
- package/extension/src/page/snapshotRouter.js +124 -48
- package/extension/src/page/textCoordinates.js +102 -0
- package/extension/src/page/textFileCreator.js +442 -71
- package/extension/src/page/trackedChangeCapture.js +53 -57
- package/extension/src/page/trackedChangeOwnership.js +216 -0
- package/extension/src/page/trackedChangeReplay.js +184 -0
- package/extension/src/page/trackedChangesLifecycle.js +343 -339
- package/extension/src/page/treeOperations.js +33 -12
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +114 -0
- package/extension/src/page/writebackRouter.js +242 -201
- package/extension/src/pageBridge.js +73 -102
- package/extension/src/popup.js +261 -65
- package/extension/src/shared/agentTranscript.js +41 -4
- package/extension/src/shared/auditRecords.js +73 -0
- package/extension/src/shared/changeHistoryModel.js +94 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +101 -37
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +329 -3
- package/extension/src/shared/globalPreferences.js +31 -14
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +389 -25
- package/extension/src/shared/managedUpdateProjection.js +8 -5
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +9 -2
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +114 -3
- package/extension/src/shared/runActivityModel.js +361 -0
- package/extension/src/shared/runExecutionSnapshot.js +8 -2
- package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
- package/extension/src/shared/selectionContext.js +37 -0
- package/extension/src/shared/sessionState.js +166 -30
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +311 -0
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +77 -78
- package/extension/src/shared/storageMigration.js +74 -111
- package/extension/src/shared/storageRunActions.js +211 -22
- package/extension/src/shared/storageValuePruning.js +40 -0
- package/extension/src/shared/subagentActivityModel.js +174 -0
- package/extension/src/shared/undoOperations.js +216 -6
- package/extension/src/shared/updateConsent.js +42 -11
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +78 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +67 -48
- package/extension/src/shared/writingStyle.js +33 -0
- package/extension/styles/panel.css +1987 -385
- package/extension/styles/popup.css +110 -0
- package/native-host/src/anthropicBridgeRequest.js +6 -3
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexModelRecovery.js +107 -0
- package/native-host/src/codexPromptAssembly.js +33 -12
- package/native-host/src/codexProviderLaunch.js +3 -3
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexRuntimeIdentity.js +1 -0
- package/native-host/src/codexSessionRunner.js +211 -318
- package/native-host/src/codexSessionTiming.js +21 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +115 -39
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeEnvironment.js +70 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeQuotas.js +15 -8
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/overleafWorkspaceRules.js +16 -0
- package/native-host/src/processStopBarrier.js +115 -0
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerModelCatalog.js +4 -3
- package/native-host/src/providerProfile.js +17 -3
- package/native-host/src/providerReasoning.js +10 -11
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +76 -14
- package/native-host/src/releaseTransport.js +14 -5
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +86 -0
- package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
- package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +176 -159
- package/native-host/src/subagentTelemetry.js +325 -0
- package/native-host/src/subagentWorkspace.js +103 -0
- package/native-host/src/subagentWorkspacePath.js +34 -0
- package/native-host/src/taskRunnerRuntime.js +130 -57
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/turnAttachments.js +113 -0
- package/native-host/src/updateManager.js +406 -38
- package/native-host/src/updateNetworkControl.js +75 -0
- package/native-host/src/updateTrust.js +21 -2
- package/native-host/src/vendor/pdfjs/LICENSE +177 -0
- package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
- package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/native-host/src/writingStyleEvidence.js +102 -0
- package/native-host/src/writingStylePdfWorker.js +35 -0
- package/native-host/src/writingStyleProfile.js +165 -0
- package/native-host/src/writingStyleRuntime.js +475 -0
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
(function (root, factory) {
|
|
2
|
-
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
-
else root.CodexOverleafTextFileCreator = factory();
|
|
4
|
-
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
2
|
+
if (typeof module === 'object' && module.exports) module.exports = factory(require('./ownedDirectoryCleanup'));
|
|
3
|
+
else root.CodexOverleafTextFileCreator = factory(root.CodexOverleafOwnedDirectoryCleanup);
|
|
4
|
+
})(typeof window !== 'undefined' ? window : globalThis, function (OwnedDirectoryCleanup) {
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
const MAX_CONFIRMATION_OBSERVATIONS = 8;
|
|
8
|
+
|
|
7
9
|
function create(deps = {}) {
|
|
8
10
|
const { window, document, treeOperations, uploadHelpers, snapshotRouter } = deps;
|
|
9
11
|
const { getProjectId, findFileTreeNode, projectPathExists, collectProjectTextPaths,
|
|
@@ -14,6 +16,162 @@
|
|
|
14
16
|
};
|
|
15
17
|
const normalizeDomText = value => String(value || '').replace(/\s+/g, ' ').trim();
|
|
16
18
|
const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));
|
|
19
|
+
const now = typeof deps.now === 'function' ? deps.now : Date.now;
|
|
20
|
+
const verificationReads = new Set();
|
|
21
|
+
const uploadParentProofs = new WeakMap();
|
|
22
|
+
const verificationBudget = () => ({ remainingMs: 90000 });
|
|
23
|
+
async function pauseVerification(budget, durationMs) {
|
|
24
|
+
const started = now();
|
|
25
|
+
await delay(Math.max(0, Math.min(durationMs, budget.remainingMs)));
|
|
26
|
+
budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started));
|
|
27
|
+
}
|
|
28
|
+
function assertVerificationOwner(projectId, isCurrent) {
|
|
29
|
+
if (!projectId || getProjectId() !== projectId) {
|
|
30
|
+
throw Object.assign(new Error('The project changed during writeback.'), { code: 'aborted_project_changed' });
|
|
31
|
+
}
|
|
32
|
+
if (isCurrent?.() === false) {
|
|
33
|
+
throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function boundedSnapshotRead(params, controller, timeoutMs) {
|
|
37
|
+
const deadline = now() + timeoutMs;
|
|
38
|
+
const expired = diagnostics => Object.assign(new Error('The server snapshot request timed out.'), {
|
|
39
|
+
code: 'source_zip_unavailable', diagnostics,
|
|
40
|
+
technicalMessage: 'The server snapshot request reached its deletion verification deadline.'
|
|
41
|
+
});
|
|
42
|
+
let timer, onAbort;
|
|
43
|
+
try {
|
|
44
|
+
const interruption = new Promise((_, reject) => {
|
|
45
|
+
onAbort = () => reject(Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' }));
|
|
46
|
+
controller?.signal.addEventListener('abort', onAbort, { once: true });
|
|
47
|
+
timer = window.setTimeout(() => { reject(expired(null)); controller?.abort(); }, timeoutMs);
|
|
48
|
+
});
|
|
49
|
+
const result = await Promise.race([snapshotRouter.fetchProjectZipSnapshot(params), interruption]);
|
|
50
|
+
if (now() >= deadline) throw expired(result?.diagnostics);
|
|
51
|
+
return result;
|
|
52
|
+
} finally {
|
|
53
|
+
(window.clearTimeout || globalThis.clearTimeout)(timer);
|
|
54
|
+
controller?.signal.removeEventListener('abort', onAbort);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function readServerSnapshot({ projectId, isCurrent, budget = verificationBudget(), includeBinaryFiles = false,
|
|
58
|
+
singleAttempt = false, deadlineAt = Infinity }) {
|
|
59
|
+
const started = now(), deadline = Math.min(started + Math.max(0, budget.remainingMs), deadlineAt);
|
|
60
|
+
const attemptLimit = singleAttempt ? 1 : 3;
|
|
61
|
+
let last;
|
|
62
|
+
try {
|
|
63
|
+
for (let attempt = 0; attempt < attemptLimit && now() < deadline; attempt++) {
|
|
64
|
+
assertVerificationOwner(projectId, isCurrent);
|
|
65
|
+
const Controller = window.AbortController || globalThis.AbortController;
|
|
66
|
+
const controller = typeof Controller === 'function' ? new Controller() : null;
|
|
67
|
+
if (controller) verificationReads.add(controller);
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
const params = { force: true, maxAgeMs: 0,
|
|
71
|
+
serverOnly: true, writebackVerification: true, includeBinaryFiles, includeContent: true,
|
|
72
|
+
signal: controller?.signal, saveCheckId: String(now()) + '-' + attempt,
|
|
73
|
+
zipTimeoutMs: Math.min(30000, Math.max(1, deadline - now())) };
|
|
74
|
+
result = singleAttempt ? await boundedSnapshotRead(params, controller, params.zipTimeoutMs)
|
|
75
|
+
: await snapshotRouter.fetchProjectZipSnapshot(params);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (singleAttempt) assertVerificationOwner(projectId, isCurrent);
|
|
78
|
+
if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code)) throw error;
|
|
79
|
+
result = { ok: false, reason: error?.message || String(error), ...(singleAttempt ? {
|
|
80
|
+
reason: error?.technicalMessage || error?.message || String(error), diagnostics: error?.diagnostics,
|
|
81
|
+
status: error?.status || error?.statusCode || error?.response?.status, code: error?.code, retryable: error?.retryable
|
|
82
|
+
} : {}) };
|
|
83
|
+
} finally { if (controller) verificationReads.delete(controller); }
|
|
84
|
+
assertVerificationOwner(projectId, isCurrent);
|
|
85
|
+
if (result?.ok === true && Array.isArray(result.files)) {
|
|
86
|
+
const skipped = [...(Array.isArray(result.skipped) ? result.skipped : []),
|
|
87
|
+
...(Array.isArray(result.capabilities?.skipped) ? result.capabilities.skipped : [])];
|
|
88
|
+
if (result.pathIdentityComplete === false || result.capabilities?.pathIdentityComplete === false
|
|
89
|
+
|| skipped.some(file => file?.unknownPath || ['zip_path_invalid', 'zip_path_duplicate'].includes(file?.reason))) {
|
|
90
|
+
throw Object.assign(new Error('The server snapshot contains unresolved file identities; absence cannot be confirmed.'), {
|
|
91
|
+
code: 'source_zip_unavailable', originalCode: 'zip_path_identity_incomplete', retryable: false,
|
|
92
|
+
diagnostics: { ...(result.diagnostics || {}), pathIdentityComplete: false }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
last = Object.assign(new Error('The server project snapshot could not be read.'), {
|
|
98
|
+
code: 'source_zip_unavailable', diagnostics: result?.diagnostics || null,
|
|
99
|
+
technicalMessage: result?.reason || 'No complete server snapshot was returned.',
|
|
100
|
+
...(singleAttempt ? { status: result?.status || result?.statusCode, originalCode: result?.code, retryable: result?.retryable } : {})
|
|
101
|
+
});
|
|
102
|
+
const attempts = result?.diagnostics?.attempts || [];
|
|
103
|
+
if (attempts.length && attempts.every(item => [401, 403].includes(item.status))) break;
|
|
104
|
+
if (attempt < attemptLimit - 1 && now() < deadline) await delay(Math.min(1000 * (2 ** attempt), deadline - now()));
|
|
105
|
+
}
|
|
106
|
+
throw last || Object.assign(new Error('The project verification budget was exhausted.'), { code: 'source_zip_unavailable' });
|
|
107
|
+
} finally { budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started)); }
|
|
108
|
+
}
|
|
109
|
+
function verificationFailure(error, changedDocument, phase) {
|
|
110
|
+
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
111
|
+
const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed' : 'source_zip_unavailable';
|
|
112
|
+
const reason = interrupted ? error.message : changedDocument
|
|
113
|
+
? 'The file was submitted, but its saved content could not be confirmed.'
|
|
114
|
+
: 'The project could not be checked before uploading this file.';
|
|
115
|
+
return { ok: false, code, reason, changedDocument, diagnostics: {
|
|
116
|
+
verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
|
|
117
|
+
}, failure: { code, stage: changedDocument ? 'verify' : 'preflight', severity: changedDocument ? 'warning' : 'blocked',
|
|
118
|
+
retryable: !interrupted, terminalState: interrupted ? 'cancelled' : changedDocument ? 'needs_review' : 'blocked',
|
|
119
|
+
changedDocument, userMessage: reason, nextAction: interrupted ? '' : 'Retry sync to check the file and upload only if it is missing.',
|
|
120
|
+
technicalMessage: error?.technicalMessage || error?.message || '' } };
|
|
121
|
+
}
|
|
122
|
+
async function makeSaveReceipt(projectId, path, content) {
|
|
123
|
+
const cryptoApi = window.crypto || globalThis.crypto;
|
|
124
|
+
if (!cryptoApi?.subtle || typeof TextEncoder !== 'function') return undefined;
|
|
125
|
+
const digest = await cryptoApi.subtle.digest('SHA-256', new TextEncoder().encode(content));
|
|
126
|
+
return { v: 1, source: 'overleaf-zip', projectId, path,
|
|
127
|
+
sha256: Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join(''),
|
|
128
|
+
verifiedAt: new Date().toISOString() };
|
|
129
|
+
}
|
|
130
|
+
async function beginCreateBatch(operations, options = {}) {
|
|
131
|
+
const projectId = getProjectId(), budget = verificationBudget();
|
|
132
|
+
const batch = { projectId, budget, targets: new Set(operations.map(op => op.path)),
|
|
133
|
+
before: new Map(), pending: new Map(), blocked: new Set(), isCurrent: options.isCurrent, error: null };
|
|
134
|
+
try {
|
|
135
|
+
const snapshot = await readServerSnapshot({ projectId, budget, isCurrent: options.isCurrent });
|
|
136
|
+
batch.before = new Map(snapshot.files.map(file => [file.path, file]));
|
|
137
|
+
} catch (error) { batch.error = error; }
|
|
138
|
+
return batch;
|
|
139
|
+
}
|
|
140
|
+
async function finishCreateBatch(batch, entries, appliedEntries = []) {
|
|
141
|
+
let observed = new Map(), lastError = batch.error;
|
|
142
|
+
try {
|
|
143
|
+
for (let attempt = 0; entries.length && attempt < 3 && batch.budget.remainingMs > 0; attempt++) {
|
|
144
|
+
const snapshot = await readServerSnapshot(batch);
|
|
145
|
+
observed = new Map(snapshot.files.map(file => [file.path, file]));
|
|
146
|
+
if (entries.every(entry => observed.get(entry.operation.path)?.content === entry.operation.content)) break;
|
|
147
|
+
if (attempt < 2) await pauseVerification(batch.budget, 500 * (2 ** attempt));
|
|
148
|
+
}
|
|
149
|
+
assertVerificationOwner(batch.projectId, batch.isCurrent);
|
|
150
|
+
} catch (error) { lastError = error; }
|
|
151
|
+
const results = [];
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const operation = entry.operation;
|
|
154
|
+
if (!lastError && observed.get(operation.path)?.content === operation.content) {
|
|
155
|
+
results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
|
|
156
|
+
method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
|
|
157
|
+
createdDirectories: entry.result?.createdDirectories || [], fileUploadAttempted: entry.result?.fileUploadAttempted === true,
|
|
158
|
+
saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
|
|
159
|
+
} else {
|
|
160
|
+
results.push({ operation, result: { ...verificationFailure(lastError || new Error(
|
|
161
|
+
observed.has(operation.path) ? 'Server content differs from the submitted file.' : 'The file is not yet present in the server snapshot.'
|
|
162
|
+
), true, 'post-upload'), createdDirectories: entry.result?.createdDirectories || [],
|
|
163
|
+
fileUploadAttempted: entry.result?.fileUploadAttempted === true } });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// The final fresh snapshot can also prove unrelated edits in this batch.
|
|
167
|
+
if (!lastError) for (const entry of appliedEntries) {
|
|
168
|
+
const content = entry.result?.verifiedContent ?? (entry.operation?.type === 'create' ? entry.operation.content : undefined);
|
|
169
|
+
if (typeof content === 'string' && observed.get(entry.operation.path)?.content === content) {
|
|
170
|
+
entry.result.saveReceipt = await makeSaveReceipt(batch.projectId, entry.operation.path, content);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
17
175
|
const controlName = node => {
|
|
18
176
|
const labelledBy = (node.getAttribute('aria-labelledby') || '').split(/\s+/).filter(Boolean)
|
|
19
177
|
.map(id => document.getElementById?.(id)?.textContent || '').join(' ');
|
|
@@ -47,13 +205,16 @@
|
|
|
47
205
|
return folder;
|
|
48
206
|
};
|
|
49
207
|
|
|
208
|
+
const directoryCleanup = OwnedDirectoryCleanup.create({ document, treeOperations, delay, now,
|
|
209
|
+
readServerSnapshot, assertVerificationOwner, normalizeDomText, normalizeSafeProjectPath });
|
|
210
|
+
const { folderIdentity, cleanupCreatedDirectories } = directoryCleanup;
|
|
211
|
+
|
|
50
212
|
async function createFolderWithDom(entryPath, options = {}) {
|
|
51
213
|
const target = normalizeSafeProjectPath(entryPath);
|
|
52
214
|
const projectId = getProjectId();
|
|
53
215
|
const assertCurrent = () => {
|
|
54
|
-
if (!target
|
|
55
|
-
|
|
56
|
-
}
|
|
216
|
+
if (!target) throw Object.assign(new Error('A valid folder path is required.'), { code: 'invalid_project_path' });
|
|
217
|
+
assertVerificationOwner(projectId, options.isCurrent);
|
|
57
218
|
};
|
|
58
219
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
59
220
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
@@ -87,7 +248,15 @@
|
|
|
87
248
|
toggle.click();
|
|
88
249
|
await waitFor(() => findFolder(parentPath)?.getAttribute('aria-expanded') === 'true');
|
|
89
250
|
};
|
|
90
|
-
let ownedDialog = null;
|
|
251
|
+
let ownedDialog = null, createdProof = null;
|
|
252
|
+
const publishProof = () => {
|
|
253
|
+
if (!createdProof) return;
|
|
254
|
+
if (getProjectId() === projectId) {
|
|
255
|
+
const row = findFolder(target);
|
|
256
|
+
if (row) { createdProof.nativeFolderId = folderIdentity(row); createdProof.observedAfterCreate = true; }
|
|
257
|
+
}
|
|
258
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
259
|
+
};
|
|
91
260
|
try {
|
|
92
261
|
assertCurrent();
|
|
93
262
|
if (findFolder(target)) return;
|
|
@@ -116,12 +285,18 @@
|
|
|
116
285
|
await delay(0);
|
|
117
286
|
assertCurrent();
|
|
118
287
|
if (findFolder(target) || projectPathExists(target)) throw new Error('The target path appeared during creation; no replacement was attempted.');
|
|
288
|
+
const confirm = choose(ownedDialog, /^(?:Create|创建)$/i);
|
|
289
|
+
createdProof = { v: 1, projectId, path: target, nativeFolderId: '', ownershipConfirmed: false, observedAfterCreate: false };
|
|
290
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
119
291
|
options.onMutation?.();
|
|
120
|
-
|
|
292
|
+
confirm.click();
|
|
293
|
+
publishProof();
|
|
121
294
|
await waitFor(() => !dialogs().length);
|
|
122
295
|
await expandParent();
|
|
123
296
|
await waitFor(() => findFolder(target));
|
|
297
|
+
publishProof();
|
|
124
298
|
} finally {
|
|
299
|
+
publishProof();
|
|
125
300
|
if (ownedDialog && getProjectId() === projectId && dialogs().includes(ownedDialog)) {
|
|
126
301
|
const cancel = Array.from(ownedDialog.querySelectorAll('button')).filter(node => visible(node) && /^(?:Cancel|取消)$/i.test(controlName(node)));
|
|
127
302
|
if (cancel.length === 1) cancel[0].click();
|
|
@@ -138,16 +313,20 @@
|
|
|
138
313
|
if (!target || !projectId || typeof operation.content !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
|
|
139
314
|
return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
|
|
140
315
|
}
|
|
141
|
-
let mutationAttempted = false;
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
316
|
+
let mutationAttempted = false, fileUploadAttempted = false;
|
|
317
|
+
const createdDirectories = [];
|
|
318
|
+
const recordDirectory = proof => {
|
|
319
|
+
const index = createdDirectories.findIndex(item => item.path === proof.path);
|
|
320
|
+
if (index < 0) createdDirectories.push(proof); else createdDirectories[index] = proof;
|
|
146
321
|
};
|
|
322
|
+
let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
|
|
323
|
+
const readBudget = options.batch?.budget || verificationBudget();
|
|
324
|
+
let uploadDialog = null;
|
|
325
|
+
const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
|
|
147
326
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
148
327
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
149
|
-
const waitFor = async read => {
|
|
150
|
-
const until = Date.now() +
|
|
328
|
+
const waitFor = async (read, timeoutMs = 5000) => {
|
|
329
|
+
const until = Date.now() + timeoutMs;
|
|
151
330
|
do {
|
|
152
331
|
assertCurrent();
|
|
153
332
|
invalidateDomProjectPathCache();
|
|
@@ -174,13 +353,12 @@
|
|
|
174
353
|
await waitFor(() => findFolder(folderPath)?.getAttribute('aria-expanded') === 'true');
|
|
175
354
|
};
|
|
176
355
|
const readServerFiles = async () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return result.files;
|
|
356
|
+
zipReadCount += 1;
|
|
357
|
+
try {
|
|
358
|
+
const result = await readServerSnapshot({ projectId, budget: readBudget,
|
|
359
|
+
isCurrent: () => { assertCurrent(); return true; } });
|
|
360
|
+
return result.files;
|
|
361
|
+
} catch (error) { lastZipFailure = { reason: error.message, diagnostics: error.diagnostics }; throw error; }
|
|
184
362
|
};
|
|
185
363
|
const uploadText = async () => {
|
|
186
364
|
const parentPath = target.split('/').slice(0, -1).join('/');
|
|
@@ -197,6 +375,7 @@
|
|
|
197
375
|
const tree = document.querySelector('#ide-rail-tabs-tabpane-file-tree, #ide-redesign-file-tree, .ide-redesign-file-tree, .file-tree');
|
|
198
376
|
chooseButton(tree, /^(?:Upload|上传)$/i).click();
|
|
199
377
|
const dialog = await waitFor(() => dialogs().length === 1 && dialogs()[0]);
|
|
378
|
+
uploadDialog = dialog;
|
|
200
379
|
const input = await waitFor(() => uploadHelpers.chooseOverleafFileInput(dialog.querySelectorAll('input[type="file"]')));
|
|
201
380
|
if (projectPathExists(target)) throw new Error('The target appeared before upload; no replacement was attempted.');
|
|
202
381
|
const file = new window.File([operation.content], target.split('/').pop(), { type: 'text/plain' });
|
|
@@ -204,10 +383,23 @@
|
|
|
204
383
|
data.items.add(file);
|
|
205
384
|
assertCurrent();
|
|
206
385
|
uploadHelpers.assignFilesToInput(input, data.files, window);
|
|
207
|
-
mutationAttempted = true;
|
|
386
|
+
mutationAttempted = true; fileUploadAttempted = true;
|
|
387
|
+
options.batch?.pending.set(target, operation);
|
|
208
388
|
input.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
209
389
|
// Never click Replace/Overwrite. A concurrent name conflict must stop.
|
|
210
|
-
|
|
390
|
+
verificationPhase = 'post-upload';
|
|
391
|
+
const deadline = now() + readBudget.remainingMs;
|
|
392
|
+
let observations = 0;
|
|
393
|
+
if (options.batch) {
|
|
394
|
+
await waitFor(() => {
|
|
395
|
+
if (dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''))) {
|
|
396
|
+
options.batch.blocked.add(target);
|
|
397
|
+
throw new Error('Overleaf reported an upload conflict; replacement was not authorized.');
|
|
398
|
+
}
|
|
399
|
+
return projectPathExists(target);
|
|
400
|
+
}, 30000);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
211
403
|
do {
|
|
212
404
|
assertCurrent();
|
|
213
405
|
const conflict = dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''));
|
|
@@ -222,15 +414,24 @@
|
|
|
222
414
|
await waitFor(() => !dialogs().length);
|
|
223
415
|
return;
|
|
224
416
|
}
|
|
225
|
-
|
|
226
|
-
|
|
417
|
+
const remaining = deadline - now();
|
|
418
|
+
if (remaining <= 0) break;
|
|
419
|
+
await pauseVerification(readBudget, Math.min(250 * (2 ** Math.min(observations++, 3)), remaining));
|
|
420
|
+
} while (now() < deadline && observations < MAX_CONFIRMATION_OBSERVATIONS);
|
|
227
421
|
throw new Error('The new file content was not confirmed in the server source ZIP. No editor content was modified.');
|
|
228
422
|
};
|
|
229
423
|
try {
|
|
230
424
|
assertCurrent();
|
|
425
|
+
if (options.batch?.error) throw options.batch.error;
|
|
426
|
+
const existing = options.batch?.before.get(target);
|
|
427
|
+
if (existing && options.allowExistingMatches === true && existing.content === operation.content) {
|
|
428
|
+
return { ok: true, verified: true, changedDocument: false, idempotent: true,
|
|
429
|
+
method: 'overleaf.existing-text-match', verification: 'overleaf-zip',
|
|
430
|
+
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
431
|
+
}
|
|
231
432
|
if (projectPathExists(target)) return { ok: false, code: 'target_file_already_exists', reason: 'The target file already exists.' };
|
|
232
433
|
if (!uploadHelpers?.assignFilesToInput || !snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified text upload support is unavailable.');
|
|
233
|
-
if ((await readServerFiles()).some(file => file.path === target)) {
|
|
434
|
+
if (options.batch ? options.batch.before.has(target) : (await readServerFiles()).some(file => file.path === target)) {
|
|
234
435
|
return { ok: false, code: 'target_file_already_exists', reason: 'The server already contains the target file.' };
|
|
235
436
|
}
|
|
236
437
|
const parts = target.split('/');
|
|
@@ -238,33 +439,93 @@
|
|
|
238
439
|
const folderPath = parts.slice(0, depth).join('/');
|
|
239
440
|
if (!findFolder(folderPath)) await createFolderWithDom(folderPath, {
|
|
240
441
|
isCurrent: () => { assertCurrent(); return true; },
|
|
241
|
-
onMutation: () => { mutationAttempted = true; }
|
|
442
|
+
onMutation: () => { mutationAttempted = true; }, onCreatedDirectory: recordDirectory
|
|
242
443
|
});
|
|
243
444
|
await ensureFolderExpanded(folderPath);
|
|
244
445
|
}
|
|
245
446
|
await uploadText();
|
|
246
|
-
return { ok:
|
|
247
|
-
|
|
447
|
+
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true, createdDirectories, fileUploadAttempted };
|
|
448
|
+
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true, createdDirectories, fileUploadAttempted,
|
|
449
|
+
verified: true, verification: 'overleaf-zip',
|
|
450
|
+
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
248
451
|
} catch (error) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
452
|
+
const directoryCleanup = createdDirectories.length
|
|
453
|
+
? await cleanupCreatedDirectories(createdDirectories, { projectId, isCurrent: options.isCurrent }) : undefined;
|
|
454
|
+
const result = verificationFailure(error, mutationAttempted, verificationPhase);
|
|
455
|
+
if (!fileUploadAttempted && mutationAttempted) {
|
|
456
|
+
const reason = directoryCleanup?.ok ? 'File upload did not start. The new empty folders were removed.'
|
|
457
|
+
: 'File upload did not start. Some new folders remain and need review.';
|
|
458
|
+
result.reason = reason; result.code = 'file_tree_operation_unverified';
|
|
459
|
+
result.failure = { ...result.failure, code: result.code, stage: 'directory-preparation', userMessage: reason,
|
|
460
|
+
nextAction: directoryCleanup?.ok ? 'Retry sync to create the file.' : 'Review the remaining folders before retrying.' };
|
|
461
|
+
}
|
|
462
|
+
return { ...result, createdDirectories: directoryCleanup?.remaining || createdDirectories,
|
|
463
|
+
fileUploadAttempted, directoryCleanup, diagnostics: { verificationPhase, zipReadCount, lastZipFailure } };
|
|
464
|
+
} finally {
|
|
465
|
+
if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
|
|
466
|
+
const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
|
|
467
|
+
&& /^(?:Close dialog|Close|Done|Cancel|关闭|完成|取消)$/i.test(controlName(node)));
|
|
468
|
+
close?.click();
|
|
469
|
+
}
|
|
255
470
|
}
|
|
256
471
|
}
|
|
472
|
+
const rootUploadIdentity = row => row?.querySelector?.('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]')
|
|
473
|
+
?.getAttribute?.('data-file-id') || '';
|
|
474
|
+
function rememberUploadParent(path, node, projectId) {
|
|
475
|
+
if (node && typeof node === 'object') uploadParentProofs.set(node, Object.freeze({
|
|
476
|
+
path, projectId, nativeId: path ? folderIdentity(node) : rootUploadIdentity(node)
|
|
477
|
+
}));
|
|
478
|
+
return node;
|
|
479
|
+
}
|
|
480
|
+
// Synchronous last-moment guard after any uploader await. Snapshot the
|
|
481
|
+
// identity at preparation, since React can reuse a DOM row for a new ID.
|
|
482
|
+
function isUploadParentCurrent(folderPath, parentNode) {
|
|
483
|
+
const target = normalizeSafeProjectPath(folderPath);
|
|
484
|
+
const proof = parentNode && uploadParentProofs.get(parentNode);
|
|
485
|
+
if ((!target && folderPath !== '') || !proof?.projectId || !proof.nativeId
|
|
486
|
+
|| proof.path !== target || getProjectId() !== proof.projectId) return false;
|
|
487
|
+
const root = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
488
|
+
if (!root?.children) return false;
|
|
489
|
+
const selected = Array.from(root.querySelectorAll?.('[role="treeitem"]') || [])
|
|
490
|
+
.filter(row => row.getAttribute('aria-selected') === 'true');
|
|
491
|
+
if (selected.length !== 1 || selected[0] !== parentNode) return false;
|
|
492
|
+
if (!target) return parentNode.closest?.('[role="tree"]') === root
|
|
493
|
+
&& !parentNode.querySelector('[data-file-type="folder"]') && rootUploadIdentity(parentNode) === proof.nativeId;
|
|
494
|
+
return findFolder(target) === parentNode && folderIdentity(parentNode) === proof.nativeId;
|
|
495
|
+
}
|
|
496
|
+
|
|
257
497
|
// Binary uploads use the same folder hierarchy as verified text uploads.
|
|
258
498
|
// A folder is not a text document and must never use basename fallback.
|
|
259
499
|
async function prepareUploadParent(folderPath, options = {}) {
|
|
260
500
|
const target = normalizeSafeProjectPath(folderPath);
|
|
261
501
|
const projectId = getProjectId();
|
|
262
|
-
if (!
|
|
263
|
-
const assertCurrent = () =>
|
|
264
|
-
|
|
265
|
-
|
|
502
|
+
if (!projectId || (!target && folderPath !== '')) throw new Error('A valid upload parent and project are required.');
|
|
503
|
+
const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
|
|
504
|
+
if (!target) {
|
|
505
|
+
// Uppy inherits the selected entity's parent. A previous nested upload
|
|
506
|
+
// must not redirect a root asset into that folder. Use a direct root
|
|
507
|
+
// file row, never the tree adapter's basename fallback.
|
|
508
|
+
assertCurrent();
|
|
509
|
+
const readRoot = () => document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
510
|
+
const root = readRoot();
|
|
511
|
+
const anchor = Array.from(root?.querySelectorAll?.('[role="treeitem"]') || []).find(row =>
|
|
512
|
+
row.closest?.('[role="tree"]') === root
|
|
513
|
+
&& !row.querySelector('[data-file-type="folder"]')
|
|
514
|
+
&& row.querySelector('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]'));
|
|
515
|
+
if (!anchor) throw new Error('A root-level file could not be selected safely for asset upload.');
|
|
516
|
+
if (anchor.getAttribute('aria-selected') !== 'true') {
|
|
517
|
+
(anchor.querySelector('.file-tree-entity-button, .file-tree-entity-details') || anchor).click();
|
|
518
|
+
await delay(100);
|
|
266
519
|
}
|
|
267
|
-
|
|
520
|
+
assertCurrent();
|
|
521
|
+
const currentRoot = readRoot();
|
|
522
|
+
const selected = Array.from(currentRoot?.querySelectorAll?.('[role="treeitem"]') || [])
|
|
523
|
+
.filter(row => row.getAttribute('aria-selected') === 'true');
|
|
524
|
+
if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
|
|
525
|
+
throw new Error('Overleaf did not confirm the selected root upload parent.');
|
|
526
|
+
}
|
|
527
|
+
return rememberUploadParent('', anchor, projectId);
|
|
528
|
+
}
|
|
268
529
|
const expand = async path => {
|
|
269
530
|
assertCurrent();
|
|
270
531
|
const node = findFolder(path);
|
|
@@ -288,7 +549,8 @@
|
|
|
288
549
|
const path = parts.slice(0, depth).join('/');
|
|
289
550
|
assertCurrent();
|
|
290
551
|
if (!findFolder(path) && options.createMissing === true) await createFolderWithDom(path, {
|
|
291
|
-
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation
|
|
552
|
+
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation,
|
|
553
|
+
onCreatedDirectory: options.onCreatedDirectory
|
|
292
554
|
});
|
|
293
555
|
await expand(path);
|
|
294
556
|
}
|
|
@@ -302,7 +564,7 @@
|
|
|
302
564
|
if (selected.getAttribute('aria-selected') !== 'true') {
|
|
303
565
|
throw new Error('Overleaf did not confirm the selected upload parent.');
|
|
304
566
|
}
|
|
305
|
-
return selected;
|
|
567
|
+
return rememberUploadParent(target, selected, projectId);
|
|
306
568
|
}
|
|
307
569
|
|
|
308
570
|
async function prepareEditor(params = {}) {
|
|
@@ -358,40 +620,97 @@
|
|
|
358
620
|
async function deleteFile(operation, options = {}) {
|
|
359
621
|
const target = normalizeSafeProjectPath(operation.path);
|
|
360
622
|
const projectId = getProjectId();
|
|
623
|
+
const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
|
|
624
|
+
const preflightDeadlineAt = deadlineAt - 15000;
|
|
361
625
|
const expected = options.expectedContent;
|
|
626
|
+
const finishCreatedDeletion = result => {
|
|
627
|
+
const proofs = operation.undoCreatedFile?.createdDirectories;
|
|
628
|
+
if (options.undoCreatedFile !== true || !Array.isArray(proofs) || !proofs.length) return result;
|
|
629
|
+
// The ZIP-confirmed file deletion completes its Undo checkpoint.
|
|
630
|
+
// Directory observations are retained separately and grant no deletion authority.
|
|
631
|
+
return { ...result, fileDeleted: true, directoryCleanup: {
|
|
632
|
+
ok: false, removed: [], remaining: proofs, changedDocument: false
|
|
633
|
+
} };
|
|
634
|
+
};
|
|
635
|
+
const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
|
|
636
|
+
&& options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
|
|
637
|
+
const matchesExpected = async file => {
|
|
638
|
+
if (!binary) return file && file.content === expected;
|
|
639
|
+
if (typeof file?.contentBase64 !== 'string') return false;
|
|
640
|
+
const cryptoApi = window.crypto || globalThis.crypto;
|
|
641
|
+
if (!cryptoApi?.subtle) return false;
|
|
642
|
+
const decode = window.atob || globalThis.atob;
|
|
643
|
+
if (typeof decode !== 'function') return false;
|
|
644
|
+
const bytes = Uint8Array.from(decode(file.contentBase64.replace(/\s/g, '')), value => value.charCodeAt(0));
|
|
645
|
+
const hash = await cryptoApi.subtle.digest('SHA-256', bytes);
|
|
646
|
+
return Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')
|
|
647
|
+
=== options.expectedSha256.toLowerCase();
|
|
648
|
+
};
|
|
362
649
|
const canDelete = options.canDelete || (() => typeof deps.readActiveEditorText === 'function'
|
|
363
650
|
&& treeOperations.getActiveFilePath?.() === target && deps.readActiveEditorText() === expected);
|
|
364
651
|
let mutationAttempted = false;
|
|
365
652
|
let stage = 'preflight';
|
|
366
653
|
let ownedDialog = null;
|
|
654
|
+
let zipReadCount = 0;
|
|
655
|
+
const zipFailures = [];
|
|
656
|
+
const remainingMs = () => Math.max(0, deadlineAt - now());
|
|
657
|
+
const budgetExpired = () => Object.assign(new Error('The deletion verification budget was exhausted.'), {
|
|
658
|
+
code: 'delete_confirmation_budget_exhausted',
|
|
659
|
+
technicalMessage: mutationAttempted ? 'No time remains to confirm the deletion in the server snapshot.'
|
|
660
|
+
: 'Deletion was not submitted because at least 15000ms must remain for server confirmation.'
|
|
661
|
+
});
|
|
367
662
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
368
663
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
369
664
|
const assertCurrent = () => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
665
|
+
assertVerificationOwner(projectId, options.isCurrent);
|
|
666
|
+
if (now() >= (mutationAttempted ? deadlineAt : preflightDeadlineAt)) throw budgetExpired();
|
|
373
667
|
};
|
|
374
668
|
const waitFor = async (read, timeoutMs = 5000) => {
|
|
375
|
-
const deadline =
|
|
376
|
-
|
|
669
|
+
const deadline = Math.min(now() + timeoutMs, preflightDeadlineAt);
|
|
670
|
+
while (now() < deadline) {
|
|
377
671
|
assertCurrent();
|
|
378
672
|
invalidateDomProjectPathCache();
|
|
379
673
|
const value = await read();
|
|
674
|
+
assertCurrent();
|
|
380
675
|
if (value) return value;
|
|
381
|
-
await delay(100);
|
|
382
|
-
}
|
|
676
|
+
await delay(Math.max(0, Math.min(100, deadline - now())));
|
|
677
|
+
}
|
|
678
|
+
assertCurrent();
|
|
383
679
|
throw new Error(`Overleaf did not confirm ${stage} before timeout.`);
|
|
384
680
|
};
|
|
681
|
+
const canRetryRead = error => {
|
|
682
|
+
if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code) || error?.retryable === false) return false;
|
|
683
|
+
const diagnostics = error?.diagnostics || {};
|
|
684
|
+
return ![error?.status, diagnostics.status, diagnostics.statusCode,
|
|
685
|
+
...(Array.isArray(diagnostics.attempts) ? diagnostics.attempts.map(item => item?.status) : [])]
|
|
686
|
+
.some(status => [401, 403].includes(Number(status)));
|
|
687
|
+
};
|
|
385
688
|
const readServerFiles = async () => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
689
|
+
const deadline = mutationAttempted ? deadlineAt : preflightDeadlineAt;
|
|
690
|
+
for (let attempt = 0; ; attempt++) {
|
|
691
|
+
assertCurrent();
|
|
692
|
+
snapshotRouter.invalidateCache?.();
|
|
693
|
+
zipReadCount++;
|
|
694
|
+
try {
|
|
695
|
+
const result = await readServerSnapshot({ projectId, includeBinaryFiles: binary, singleAttempt: true,
|
|
696
|
+
deadlineAt: deadline, budget: { remainingMs: Math.max(0, deadline - now()) }, isCurrent: options.isCurrent });
|
|
697
|
+
assertCurrent();
|
|
698
|
+
if ((result.skipped || []).some(file => file?.path === target)) {
|
|
699
|
+
throw Object.assign(new Error('The target was skipped in the server snapshot.'), {
|
|
700
|
+
code: 'source_zip_unavailable', retryable: false, diagnostics: result.diagnostics || null,
|
|
701
|
+
technicalMessage: 'The target was skipped in the server snapshot; absence cannot confirm deletion.'
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return result.files;
|
|
705
|
+
} catch (error) {
|
|
706
|
+
zipFailures.push({ code: error?.originalCode || error?.code, status: error?.status,
|
|
707
|
+
technicalMessage: error?.technicalMessage || error?.message, diagnostics: error?.diagnostics || null,
|
|
708
|
+
remainingMs: remainingMs() });
|
|
709
|
+
if (zipFailures.length > MAX_CONFIRMATION_OBSERVATIONS) zipFailures.shift();
|
|
710
|
+
if (!canRetryRead(error) || now() >= deadline) throw error;
|
|
711
|
+
await delay(Math.min(1000 * (2 ** Math.min(attempt, 2)), deadline - now()));
|
|
712
|
+
}
|
|
393
713
|
}
|
|
394
|
-
return result.files;
|
|
395
714
|
};
|
|
396
715
|
const choose = (scope, selector, pattern) => {
|
|
397
716
|
const matches = Array.from(scope.querySelectorAll(selector)).filter(node => visible(node)
|
|
@@ -406,26 +725,55 @@
|
|
|
406
725
|
// Current Overleaf keeps the stable document ID on the draggable
|
|
407
726
|
// entity inside the row. The menu only mounts on hover/selection.
|
|
408
727
|
const rowIdentity = row => row?.querySelector('[data-file-type="doc"][data-file-id]')?.getAttribute('data-file-id')
|
|
728
|
+
|| row?.querySelector('[data-file-type="file"][data-file-id]')?.getAttribute('data-file-id')
|
|
409
729
|
|| row?.getAttribute('data-entity-id') || row?.getAttribute('data-doc-id')
|
|
410
730
|
|| row?.getAttribute('data-file-id') || row?.querySelector('[id^="menu-button-"]')?.id || '';
|
|
411
731
|
try {
|
|
412
|
-
if (!target || !projectId || typeof
|
|
413
|
-
|| !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
|
|
414
|
-
throw new Error('A checked
|
|
732
|
+
if (!target || !projectId || typeof canDelete !== 'function'
|
|
733
|
+
|| (!binary && (typeof expected !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)))) {
|
|
734
|
+
throw new Error('A checked file pre-image is required for deletion.');
|
|
415
735
|
}
|
|
416
736
|
assertCurrent();
|
|
417
737
|
if (dialogs().length) throw new Error('An existing Overleaf dialog must be closed before deleting files.');
|
|
418
738
|
if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
|
|
419
739
|
const original = (await readServerFiles()).find(file => file.path === target);
|
|
420
|
-
if (!original
|
|
740
|
+
if (!original && options.undoCreatedFile === true && !resolveRow()) {
|
|
741
|
+
return finishCreatedDeletion({ ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
|
|
742
|
+
idempotent: true, verified: true, verification: 'overleaf-zip' });
|
|
743
|
+
}
|
|
744
|
+
const originalMatches = await matchesExpected(original);
|
|
745
|
+
assertCurrent();
|
|
746
|
+
if (!originalMatches) {
|
|
421
747
|
throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
422
748
|
}
|
|
423
749
|
const parentPath = target.split('/').slice(0, -1).join('/');
|
|
424
|
-
|
|
750
|
+
// Folder expansion must share the preflight deadline too. Uploads keep their existing timing.
|
|
751
|
+
const parents = parentPath ? parentPath.split('/') : [];
|
|
752
|
+
for (let depth = 1; depth <= parents.length; depth++) {
|
|
753
|
+
assertCurrent();
|
|
754
|
+
const path = parents.slice(0, depth).join('/'), parent = findFolder(path);
|
|
755
|
+
if (!parent) throw new Error('The target parent folder is unavailable.');
|
|
756
|
+
if (parent.getAttribute('aria-expanded') !== 'false') continue;
|
|
757
|
+
stage = 'parent folder expansion';
|
|
758
|
+
const toggle = parent.querySelector('.folder-expand-collapse-button');
|
|
759
|
+
if (!toggle) throw new Error('The target parent folder cannot be expanded safely.');
|
|
760
|
+
toggle.click();
|
|
761
|
+
await waitFor(() => findFolder(path)?.getAttribute('aria-expanded') === 'true');
|
|
762
|
+
}
|
|
763
|
+
// Collapsed folders can hide an existing file from the tree adapter.
|
|
764
|
+
// Server presence proves existence; prepare its editor only after expansion.
|
|
765
|
+
if (!binary && canDelete() !== true && typeof options.prepareTextPreimage === 'function') {
|
|
766
|
+
const current = await options.prepareTextPreimage();
|
|
767
|
+
assertCurrent();
|
|
768
|
+
if (!current?.ok || current.text !== expected) {
|
|
769
|
+
throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (canDelete() !== true) throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
425
773
|
const row = resolveRow();
|
|
426
774
|
const identity = rowIdentity(row);
|
|
427
775
|
if (!row || !identity || row.querySelector('[data-file-type="folder"]')) {
|
|
428
|
-
throw new Error('An exact Overleaf
|
|
776
|
+
throw new Error('An exact Overleaf file row could not be identified.');
|
|
429
777
|
}
|
|
430
778
|
stage = 'file selection';
|
|
431
779
|
(row.querySelector('.file-tree-entity-details, .file-tree-entity-button') || row).click();
|
|
@@ -459,21 +807,42 @@
|
|
|
459
807
|
};
|
|
460
808
|
if (!verifyDialog()) throw new Error('The deletion dialog does not name exactly the authorized file.');
|
|
461
809
|
const latest = (await readServerFiles()).find(file => file.path === target);
|
|
810
|
+
const latestMatches = await matchesExpected(latest);
|
|
462
811
|
assertCurrent();
|
|
463
|
-
if (!
|
|
812
|
+
if (!latestMatches || canDelete() !== true
|
|
464
813
|
|| rowIdentity(resolveRow()) !== identity || !verifyDialog()) {
|
|
465
814
|
throw new Error('The target changed before confirmation; deletion was stopped.');
|
|
466
815
|
}
|
|
467
816
|
const confirm = choose(ownedDialog, 'button', /^(?:Delete|删除)$/i);
|
|
817
|
+
assertCurrent();
|
|
468
818
|
stage = 'server deletion receipt';
|
|
469
819
|
mutationAttempted = true;
|
|
470
820
|
confirm.click();
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
821
|
+
for (let observation = 0; ; observation++) {
|
|
822
|
+
const files = await readServerFiles();
|
|
823
|
+
if (!files.some(file => file.path === target)) {
|
|
824
|
+
return finishCreatedDeletion({ ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
|
|
825
|
+
verified: true, verification: 'overleaf-zip' });
|
|
826
|
+
}
|
|
827
|
+
await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
|
|
828
|
+
}
|
|
474
829
|
} catch (error) {
|
|
475
|
-
|
|
476
|
-
|
|
830
|
+
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
831
|
+
const code = interrupted ? error.code : mutationAttempted ? 'file_tree_operation_unverified'
|
|
832
|
+
: error?.code || 'file_tree_controls_unavailable';
|
|
833
|
+
const retryable = canRetryRead(error), technicalMessage = error?.technicalMessage || error?.message || '';
|
|
834
|
+
const reason = interrupted ? error.message : mutationAttempted ? 'Deletion could not be confirmed.'
|
|
835
|
+
: code === 'source_zip_unavailable' ? 'The server copy could not be checked. No file was deleted.'
|
|
836
|
+
: code === 'delete_confirmation_budget_exhausted' ? 'Not enough time remains to verify deletion. No file was deleted.' : error.message;
|
|
837
|
+
return { ok: false, code, reason, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted,
|
|
838
|
+
technicalMessage, deadlineAt, remainingMs: remainingMs(), diagnostics: {
|
|
839
|
+
...(error?.diagnostics || {}), verificationPhase: 'delete-' + stage.replace(/ /g, '-'), zipReadCount, zipFailures,
|
|
840
|
+
reason: technicalMessage, originalCode: error?.originalCode || error?.code || '', status: error?.status,
|
|
841
|
+
deadlineAt, remainingMs: remainingMs(), mutationAttempted
|
|
842
|
+
}, failure: { code, stage: mutationAttempted ? 'verify' : 'preflight', severity: mutationAttempted ? 'warning' : 'blocked',
|
|
843
|
+
retryable, terminalState: interrupted ? 'cancelled' : mutationAttempted ? 'needs_review' : 'blocked',
|
|
844
|
+
changedDocument: mutationAttempted, file: target, operationType: 'delete', userMessage: reason,
|
|
845
|
+
nextAction: retryable ? 'Retry Undo to check the file.' : '', technicalMessage } };
|
|
477
846
|
} finally {
|
|
478
847
|
// Close only the dialog opened by this operation; never another modal.
|
|
479
848
|
if (ownedDialog && dialogs().includes(ownedDialog) && getProjectId() === projectId) {
|
|
@@ -487,7 +856,9 @@
|
|
|
487
856
|
async function createFile(operation, options = {}) {
|
|
488
857
|
return createTextFileWithDom(operation, options);
|
|
489
858
|
}
|
|
490
|
-
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder
|
|
859
|
+
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, isUploadParentCurrent, cleanupCreatedDirectories, findFolderNode: findFolder,
|
|
860
|
+
beginCreateBatch, finishCreateBatch,
|
|
861
|
+
cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
|
|
491
862
|
}
|
|
492
863
|
return { create };
|
|
493
864
|
});
|