codex-overleaf-link 2.4.0 → 2.5.0
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 +272 -311
- package/README.zh-CN.md +287 -326
- package/extension/bootstrap/background.js +47 -10
- package/extension/bootstrap/manifest.template.json +9 -4
- package/extension/bootstrap/popup.html +10 -16
- package/extension/bootstrap/update.js +25 -2
- package/extension/runtime-manifest.json +3 -1
- package/extension/src/background.js +28 -3
- package/extension/src/backgroundSharedSessions.js +380 -0
- package/extension/src/backgroundUpdateCoordinator.js +442 -379
- package/extension/src/content/activeTurnControl.js +8 -0
- package/extension/src/content/assetTransferBroker.js +186 -12
- package/extension/src/content/changeHistoryView.js +175 -0
- package/extension/src/content/composerAttachments.js +24 -22
- package/extension/src/content/composerPanel.js +23 -8
- package/extension/src/content/contentRuntime.js +572 -571
- package/extension/src/content/diagnosticsController.js +1 -2
- package/extension/src/content/diagnosticsExport.js +222 -0
- package/extension/src/content/diagnosticsPanel.js +48 -19
- package/extension/src/content/diffReviewPanel.js +8 -5
- package/extension/src/content/generated/content.bundle.js +8974 -1781
- package/extension/src/content/generated/content.bundle.meta.json +22 -3
- package/extension/src/content/legacyGlobalRegistry.js +19 -1
- package/extension/src/content/localSkillsPanel.js +42 -7
- package/extension/src/content/markdownDomRenderer.js +9 -4
- package/extension/src/content/modelPicker.js +1 -1
- package/extension/src/content/pageBridgeClient.js +18 -3
- package/extension/src/content/panelMaintenance.js +51 -120
- package/extension/src/content/panelRenderer.js +4 -0
- package/extension/src/content/pendingInputView.js +109 -76
- package/extension/src/content/providerSettingsCoordinator.js +17 -6
- package/extension/src/content/providerSettingsDialog.js +661 -108
- package/extension/src/content/recentProjects.js +364 -356
- package/extension/src/content/runActivitySummary.js +631 -0
- package/extension/src/content/runController.js +3 -1
- package/extension/src/content/runFailureNotice.js +14 -1
- package/extension/src/content/runGuidanceController.js +4 -3
- package/extension/src/content/runPresence.js +306 -0
- package/extension/src/content/runQueueScheduler.js +2 -1
- package/extension/src/content/runResultActions.js +123 -3
- package/extension/src/content/runTimelineView.js +194 -164
- package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
- package/extension/src/content/scopedPersistencePanelState.js +45 -2
- package/extension/src/content/selectionContextView.js +300 -0
- package/extension/src/content/sessionPersistence.js +10 -2
- package/extension/src/content/settingsPanel.js +93 -54
- package/extension/src/content/settingsWorkbench.js +374 -0
- package/extension/src/content/sharedSessionViewSync.js +72 -0
- package/extension/src/content/subagentActivityView.js +195 -0
- package/extension/src/content/trackedChangeCaptureController.js +7 -3
- package/extension/src/content/updateNotice.js +121 -50
- package/extension/src/content/writebackController.js +257 -0
- package/extension/src/content/writebackOrchestrator.js +146 -141
- package/extension/src/content/writebackPlan.js +231 -0
- package/extension/src/content/writebackRecovery.js +122 -0
- package/extension/src/content/writingStyleSettings.js +576 -0
- package/extension/src/content/writtenChangesView.js +152 -0
- package/extension/src/page/binaryAssetUploader.js +97 -19
- package/extension/src/page/overleafProjectSnapshot.js +1 -0
- package/extension/src/page/referenceProjects.js +148 -0
- package/extension/src/page/saveState.js +58 -3
- package/extension/src/page/snapshotRouter.js +44 -13
- package/extension/src/page/textCoordinates.js +102 -0
- package/extension/src/page/textFileCreator.js +350 -59
- package/extension/src/page/trackedChangeCapture.js +42 -56
- package/extension/src/page/trackedChangeOwnership.js +198 -0
- package/extension/src/page/trackedChangeReplay.js +164 -0
- package/extension/src/page/trackedChangesLifecycle.js +139 -150
- package/extension/src/page/treeOperations.js +36 -29
- package/extension/src/page/writebackReceiptJournal.js +112 -0
- package/extension/src/page/writebackRouter.js +176 -136
- package/extension/src/pageBridge.js +59 -101
- package/extension/src/popup.js +203 -59
- package/extension/src/shared/agentTranscript.js +18 -4
- package/extension/src/shared/auditRecords.js +71 -0
- package/extension/src/shared/changeHistoryModel.js +94 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +42 -0
- package/extension/src/shared/failureReasons.js +20 -0
- package/extension/src/shared/globalPreferences.js +1 -1
- package/extension/src/shared/i18n.js +187 -65
- package/extension/src/shared/managedUpdateProjection.js +6 -3
- package/extension/src/shared/pageRpcContract.js +7 -1
- package/extension/src/shared/providerProfiles.js +109 -0
- 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/scopedPersistenceBrowserAdapter.js +6 -3
- package/extension/src/shared/selectionContext.js +28 -0
- package/extension/src/shared/sessionState.js +97 -6
- package/extension/src/shared/sharedSessionBridge.js +288 -0
- package/extension/src/shared/storageDb.js +23 -55
- package/extension/src/shared/storageMigration.js +3 -0
- package/extension/src/shared/storageRunActions.js +54 -4
- package/extension/src/shared/storageValuePruning.js +40 -0
- package/extension/src/shared/subagentActivityModel.js +174 -0
- package/extension/src/shared/undoOperations.js +87 -0
- package/extension/src/shared/updateConsent.js +37 -8
- package/extension/src/shared/writebackIntent.js +62 -0
- package/extension/src/shared/writebackSettlement.js +29 -10
- package/extension/src/shared/writingStyle.js +33 -0
- package/extension/styles/panel.css +2136 -744
- package/extension/styles/popup.css +110 -0
- package/native-host/src/anthropicBridgeRequest.js +2 -1
- package/native-host/src/codexModelRecovery.js +107 -0
- package/native-host/src/codexPromptAssembly.js +2 -0
- package/native-host/src/codexProviderLaunch.js +3 -3
- package/native-host/src/codexRuntimeIdentity.js +1 -0
- package/native-host/src/codexSessionRunner.js +146 -177
- package/native-host/src/codexSessionTiming.js +71 -0
- package/native-host/src/managedInstall.js +5 -4
- package/native-host/src/nativeEnvironment.js +70 -0
- package/native-host/src/nativeQuotas.js +15 -8
- package/native-host/src/overleafWorkspaceRules.js +16 -0
- package/native-host/src/processStopBarrier.js +80 -0
- package/native-host/src/providerModelCatalog.js +4 -3
- package/native-host/src/providerProfile.js +13 -1
- package/native-host/src/providerReasoning.js +9 -5
- package/native-host/src/providerStore.js +4 -0
- package/native-host/src/readProgressGuard.js +84 -17
- package/native-host/src/releaseTransport.js +14 -5
- package/native-host/src/selectionScope.js +58 -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/subagentBroker.js +102 -98
- package/native-host/src/subagentTelemetry.js +316 -0
- package/native-host/src/subagentWorkspace.js +103 -0
- package/native-host/src/subagentWorkspacePath.js +31 -0
- package/native-host/src/taskRunnerRuntime.js +55 -5
- package/native-host/src/turnAttachments.js +113 -0
- package/native-host/src/updateManager.js +122 -12
- 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 +447 -0
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
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,148 @@
|
|
|
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 verificationBudget = () => ({ remainingMs: 90000 });
|
|
22
|
+
async function pauseVerification(budget, durationMs) {
|
|
23
|
+
const started = now();
|
|
24
|
+
await delay(Math.max(0, Math.min(durationMs, budget.remainingMs)));
|
|
25
|
+
budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started));
|
|
26
|
+
}
|
|
27
|
+
function assertVerificationOwner(projectId, isCurrent) {
|
|
28
|
+
if (!projectId || getProjectId() !== projectId) {
|
|
29
|
+
throw Object.assign(new Error('The project changed during writeback.'), { code: 'aborted_project_changed' });
|
|
30
|
+
}
|
|
31
|
+
if (isCurrent?.() === false) {
|
|
32
|
+
throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function boundedSnapshotRead(params, controller, timeoutMs) {
|
|
36
|
+
const deadline = now() + timeoutMs;
|
|
37
|
+
const expired = diagnostics => Object.assign(new Error('The server snapshot request timed out.'), {
|
|
38
|
+
code: 'source_zip_unavailable', diagnostics,
|
|
39
|
+
technicalMessage: 'The server snapshot request reached its deletion verification deadline.'
|
|
40
|
+
});
|
|
41
|
+
let timer, onAbort;
|
|
42
|
+
try {
|
|
43
|
+
const interruption = new Promise((_, reject) => {
|
|
44
|
+
onAbort = () => reject(Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' }));
|
|
45
|
+
controller?.signal.addEventListener('abort', onAbort, { once: true });
|
|
46
|
+
timer = window.setTimeout(() => { reject(expired(null)); controller?.abort(); }, timeoutMs);
|
|
47
|
+
});
|
|
48
|
+
const result = await Promise.race([snapshotRouter.fetchProjectZipSnapshot(params), interruption]);
|
|
49
|
+
if (now() >= deadline) throw expired(result?.diagnostics);
|
|
50
|
+
return result;
|
|
51
|
+
} finally {
|
|
52
|
+
(window.clearTimeout || globalThis.clearTimeout)(timer);
|
|
53
|
+
controller?.signal.removeEventListener('abort', onAbort);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function readServerSnapshot({ projectId, isCurrent, budget = verificationBudget(), includeBinaryFiles = false,
|
|
57
|
+
singleAttempt = false, deadlineAt = Infinity }) {
|
|
58
|
+
const started = now(), deadline = Math.min(started + Math.max(0, budget.remainingMs), deadlineAt);
|
|
59
|
+
const attemptLimit = singleAttempt ? 1 : 3;
|
|
60
|
+
let last;
|
|
61
|
+
try {
|
|
62
|
+
for (let attempt = 0; attempt < attemptLimit && now() < deadline; attempt++) {
|
|
63
|
+
assertVerificationOwner(projectId, isCurrent);
|
|
64
|
+
const Controller = window.AbortController || globalThis.AbortController;
|
|
65
|
+
const controller = typeof Controller === 'function' ? new Controller() : null;
|
|
66
|
+
if (controller) verificationReads.add(controller);
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
const params = { force: true, maxAgeMs: 0,
|
|
70
|
+
serverOnly: true, writebackVerification: true, includeBinaryFiles, includeContent: true,
|
|
71
|
+
signal: controller?.signal, saveCheckId: String(now()) + '-' + attempt,
|
|
72
|
+
zipTimeoutMs: Math.min(30000, Math.max(1, deadline - now())) };
|
|
73
|
+
result = singleAttempt ? await boundedSnapshotRead(params, controller, params.zipTimeoutMs)
|
|
74
|
+
: await snapshotRouter.fetchProjectZipSnapshot(params);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (singleAttempt) assertVerificationOwner(projectId, isCurrent);
|
|
77
|
+
if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code)) throw error;
|
|
78
|
+
result = { ok: false, reason: error?.message || String(error), ...(singleAttempt ? {
|
|
79
|
+
reason: error?.technicalMessage || error?.message || String(error), diagnostics: error?.diagnostics,
|
|
80
|
+
status: error?.status || error?.statusCode || error?.response?.status, code: error?.code, retryable: error?.retryable
|
|
81
|
+
} : {}) };
|
|
82
|
+
} finally { if (controller) verificationReads.delete(controller); }
|
|
83
|
+
assertVerificationOwner(projectId, isCurrent);
|
|
84
|
+
if (result?.ok === true && Array.isArray(result.files)) return result;
|
|
85
|
+
last = Object.assign(new Error('The server project snapshot could not be read.'), {
|
|
86
|
+
code: 'source_zip_unavailable', diagnostics: result?.diagnostics || null,
|
|
87
|
+
technicalMessage: result?.reason || 'No complete server snapshot was returned.',
|
|
88
|
+
...(singleAttempt ? { status: result?.status || result?.statusCode, originalCode: result?.code, retryable: result?.retryable } : {})
|
|
89
|
+
});
|
|
90
|
+
const attempts = result?.diagnostics?.attempts || [];
|
|
91
|
+
if (attempts.length && attempts.every(item => [401, 403].includes(item.status))) break;
|
|
92
|
+
if (attempt < attemptLimit - 1 && now() < deadline) await delay(Math.min(1000 * (2 ** attempt), deadline - now()));
|
|
93
|
+
}
|
|
94
|
+
throw last || Object.assign(new Error('The project verification budget was exhausted.'), { code: 'source_zip_unavailable' });
|
|
95
|
+
} finally { budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started)); }
|
|
96
|
+
}
|
|
97
|
+
function verificationFailure(error, changedDocument, phase) {
|
|
98
|
+
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
99
|
+
const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed' : 'source_zip_unavailable';
|
|
100
|
+
const reason = interrupted ? error.message : changedDocument
|
|
101
|
+
? 'The file was submitted, but its saved content could not be confirmed.'
|
|
102
|
+
: 'The project could not be checked before uploading this file.';
|
|
103
|
+
return { ok: false, code, reason, changedDocument, diagnostics: {
|
|
104
|
+
verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
|
|
105
|
+
}, failure: { code, stage: changedDocument ? 'verify' : 'preflight', severity: changedDocument ? 'warning' : 'blocked',
|
|
106
|
+
retryable: !interrupted, terminalState: interrupted ? 'cancelled' : changedDocument ? 'needs_review' : 'blocked',
|
|
107
|
+
changedDocument, userMessage: reason, nextAction: interrupted ? '' : 'Retry sync to check the file and upload only if it is missing.',
|
|
108
|
+
technicalMessage: error?.technicalMessage || error?.message || '' } };
|
|
109
|
+
}
|
|
110
|
+
async function makeSaveReceipt(projectId, path, content) {
|
|
111
|
+
const cryptoApi = window.crypto || globalThis.crypto;
|
|
112
|
+
if (!cryptoApi?.subtle || typeof TextEncoder !== 'function') return undefined;
|
|
113
|
+
const digest = await cryptoApi.subtle.digest('SHA-256', new TextEncoder().encode(content));
|
|
114
|
+
return { v: 1, source: 'overleaf-zip', projectId, path,
|
|
115
|
+
sha256: Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join(''),
|
|
116
|
+
verifiedAt: new Date().toISOString() };
|
|
117
|
+
}
|
|
118
|
+
async function beginCreateBatch(operations, options = {}) {
|
|
119
|
+
const projectId = getProjectId(), budget = verificationBudget();
|
|
120
|
+
const batch = { projectId, budget, targets: new Set(operations.map(op => op.path)),
|
|
121
|
+
before: new Map(), pending: new Map(), blocked: new Set(), isCurrent: options.isCurrent, error: null };
|
|
122
|
+
try {
|
|
123
|
+
const snapshot = await readServerSnapshot({ projectId, budget, isCurrent: options.isCurrent });
|
|
124
|
+
batch.before = new Map(snapshot.files.map(file => [file.path, file]));
|
|
125
|
+
} catch (error) { batch.error = error; }
|
|
126
|
+
return batch;
|
|
127
|
+
}
|
|
128
|
+
async function finishCreateBatch(batch, entries, appliedEntries = []) {
|
|
129
|
+
let observed = new Map(), lastError = batch.error;
|
|
130
|
+
try {
|
|
131
|
+
for (let attempt = 0; entries.length && attempt < 3 && batch.budget.remainingMs > 0; attempt++) {
|
|
132
|
+
const snapshot = await readServerSnapshot(batch);
|
|
133
|
+
observed = new Map(snapshot.files.map(file => [file.path, file]));
|
|
134
|
+
if (entries.every(entry => observed.get(entry.operation.path)?.content === entry.operation.content)) break;
|
|
135
|
+
if (attempt < 2) await pauseVerification(batch.budget, 500 * (2 ** attempt));
|
|
136
|
+
}
|
|
137
|
+
assertVerificationOwner(batch.projectId, batch.isCurrent);
|
|
138
|
+
} catch (error) { lastError = error; }
|
|
139
|
+
const results = [];
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const operation = entry.operation;
|
|
142
|
+
if (!lastError && observed.get(operation.path)?.content === operation.content) {
|
|
143
|
+
results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
|
|
144
|
+
method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
|
|
145
|
+
saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
|
|
146
|
+
} else {
|
|
147
|
+
results.push({ operation, result: verificationFailure(lastError || new Error(
|
|
148
|
+
observed.has(operation.path) ? 'Server content differs from the submitted file.' : 'The file is not yet present in the server snapshot.'
|
|
149
|
+
), true, 'post-upload') });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// The final fresh snapshot can also prove unrelated edits in this batch.
|
|
153
|
+
if (!lastError) for (const entry of appliedEntries) {
|
|
154
|
+
const content = entry.result?.verifiedContent ?? (entry.operation?.type === 'create' ? entry.operation.content : undefined);
|
|
155
|
+
if (typeof content === 'string' && observed.get(entry.operation.path)?.content === content) {
|
|
156
|
+
entry.result.saveReceipt = await makeSaveReceipt(batch.projectId, entry.operation.path, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
}
|
|
17
161
|
const controlName = node => {
|
|
18
162
|
const labelledBy = (node.getAttribute('aria-labelledby') || '').split(/\s+/).filter(Boolean)
|
|
19
163
|
.map(id => document.getElementById?.(id)?.textContent || '').join(' ');
|
|
@@ -51,9 +195,8 @@
|
|
|
51
195
|
const target = normalizeSafeProjectPath(entryPath);
|
|
52
196
|
const projectId = getProjectId();
|
|
53
197
|
const assertCurrent = () => {
|
|
54
|
-
if (!target
|
|
55
|
-
|
|
56
|
-
}
|
|
198
|
+
if (!target) throw Object.assign(new Error('A valid folder path is required.'), { code: 'invalid_project_path' });
|
|
199
|
+
assertVerificationOwner(projectId, options.isCurrent);
|
|
57
200
|
};
|
|
58
201
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
59
202
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
@@ -139,15 +282,14 @@
|
|
|
139
282
|
return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
|
|
140
283
|
}
|
|
141
284
|
let mutationAttempted = false;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
};
|
|
285
|
+
let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
|
|
286
|
+
const readBudget = options.batch?.budget || verificationBudget();
|
|
287
|
+
let uploadDialog = null;
|
|
288
|
+
const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
|
|
147
289
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
148
290
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
149
|
-
const waitFor = async read => {
|
|
150
|
-
const until = Date.now() +
|
|
291
|
+
const waitFor = async (read, timeoutMs = 5000) => {
|
|
292
|
+
const until = Date.now() + timeoutMs;
|
|
151
293
|
do {
|
|
152
294
|
assertCurrent();
|
|
153
295
|
invalidateDomProjectPathCache();
|
|
@@ -174,13 +316,12 @@
|
|
|
174
316
|
await waitFor(() => findFolder(folderPath)?.getAttribute('aria-expanded') === 'true');
|
|
175
317
|
};
|
|
176
318
|
const readServerFiles = async () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return result.files;
|
|
319
|
+
zipReadCount += 1;
|
|
320
|
+
try {
|
|
321
|
+
const result = await readServerSnapshot({ projectId, budget: readBudget,
|
|
322
|
+
isCurrent: () => { assertCurrent(); return true; } });
|
|
323
|
+
return result.files;
|
|
324
|
+
} catch (error) { lastZipFailure = { reason: error.message, diagnostics: error.diagnostics }; throw error; }
|
|
184
325
|
};
|
|
185
326
|
const uploadText = async () => {
|
|
186
327
|
const parentPath = target.split('/').slice(0, -1).join('/');
|
|
@@ -197,6 +338,7 @@
|
|
|
197
338
|
const tree = document.querySelector('#ide-rail-tabs-tabpane-file-tree, #ide-redesign-file-tree, .ide-redesign-file-tree, .file-tree');
|
|
198
339
|
chooseButton(tree, /^(?:Upload|上传)$/i).click();
|
|
199
340
|
const dialog = await waitFor(() => dialogs().length === 1 && dialogs()[0]);
|
|
341
|
+
uploadDialog = dialog;
|
|
200
342
|
const input = await waitFor(() => uploadHelpers.chooseOverleafFileInput(dialog.querySelectorAll('input[type="file"]')));
|
|
201
343
|
if (projectPathExists(target)) throw new Error('The target appeared before upload; no replacement was attempted.');
|
|
202
344
|
const file = new window.File([operation.content], target.split('/').pop(), { type: 'text/plain' });
|
|
@@ -205,9 +347,22 @@
|
|
|
205
347
|
assertCurrent();
|
|
206
348
|
uploadHelpers.assignFilesToInput(input, data.files, window);
|
|
207
349
|
mutationAttempted = true;
|
|
350
|
+
options.batch?.pending.set(target, operation);
|
|
208
351
|
input.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
209
352
|
// Never click Replace/Overwrite. A concurrent name conflict must stop.
|
|
210
|
-
|
|
353
|
+
verificationPhase = 'post-upload';
|
|
354
|
+
const deadline = now() + readBudget.remainingMs;
|
|
355
|
+
let observations = 0;
|
|
356
|
+
if (options.batch) {
|
|
357
|
+
await waitFor(() => {
|
|
358
|
+
if (dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''))) {
|
|
359
|
+
options.batch.blocked.add(target);
|
|
360
|
+
throw new Error('Overleaf reported an upload conflict; replacement was not authorized.');
|
|
361
|
+
}
|
|
362
|
+
return projectPathExists(target);
|
|
363
|
+
}, 30000);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
211
366
|
do {
|
|
212
367
|
assertCurrent();
|
|
213
368
|
const conflict = dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''));
|
|
@@ -222,15 +377,24 @@
|
|
|
222
377
|
await waitFor(() => !dialogs().length);
|
|
223
378
|
return;
|
|
224
379
|
}
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
const remaining = deadline - now();
|
|
381
|
+
if (remaining <= 0) break;
|
|
382
|
+
await pauseVerification(readBudget, Math.min(250 * (2 ** Math.min(observations++, 3)), remaining));
|
|
383
|
+
} while (now() < deadline && observations < MAX_CONFIRMATION_OBSERVATIONS);
|
|
227
384
|
throw new Error('The new file content was not confirmed in the server source ZIP. No editor content was modified.');
|
|
228
385
|
};
|
|
229
386
|
try {
|
|
230
387
|
assertCurrent();
|
|
388
|
+
if (options.batch?.error) throw options.batch.error;
|
|
389
|
+
const existing = options.batch?.before.get(target);
|
|
390
|
+
if (existing && options.allowExistingMatches === true && existing.content === operation.content) {
|
|
391
|
+
return { ok: true, verified: true, changedDocument: false, idempotent: true,
|
|
392
|
+
method: 'overleaf.existing-text-match', verification: 'overleaf-zip',
|
|
393
|
+
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
394
|
+
}
|
|
231
395
|
if (projectPathExists(target)) return { ok: false, code: 'target_file_already_exists', reason: 'The target file already exists.' };
|
|
232
396
|
if (!uploadHelpers?.assignFilesToInput || !snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified text upload support is unavailable.');
|
|
233
|
-
if ((await readServerFiles()).some(file => file.path === target)) {
|
|
397
|
+
if (options.batch ? options.batch.before.has(target) : (await readServerFiles()).some(file => file.path === target)) {
|
|
234
398
|
return { ok: false, code: 'target_file_already_exists', reason: 'The server already contains the target file.' };
|
|
235
399
|
}
|
|
236
400
|
const parts = target.split('/');
|
|
@@ -243,15 +407,21 @@
|
|
|
243
407
|
await ensureFolderExpanded(folderPath);
|
|
244
408
|
}
|
|
245
409
|
await uploadText();
|
|
410
|
+
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true };
|
|
246
411
|
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true,
|
|
247
|
-
verified: true, verification: 'overleaf-zip'
|
|
412
|
+
verified: true, verification: 'overleaf-zip',
|
|
413
|
+
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
248
414
|
} catch (error) {
|
|
249
415
|
return {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
reason: error.message,
|
|
253
|
-
changedDocument: mutationAttempted
|
|
416
|
+
...verificationFailure(error, mutationAttempted, verificationPhase),
|
|
417
|
+
diagnostics: { verificationPhase, zipReadCount, lastZipFailure }
|
|
254
418
|
};
|
|
419
|
+
} finally {
|
|
420
|
+
if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
|
|
421
|
+
const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
|
|
422
|
+
&& /^(?:Close dialog|Close|Done|Cancel|关闭|完成|取消)$/i.test(controlName(node)));
|
|
423
|
+
close?.click();
|
|
424
|
+
}
|
|
255
425
|
}
|
|
256
426
|
}
|
|
257
427
|
// Binary uploads use the same folder hierarchy as verified text uploads.
|
|
@@ -259,12 +429,33 @@
|
|
|
259
429
|
async function prepareUploadParent(folderPath, options = {}) {
|
|
260
430
|
const target = normalizeSafeProjectPath(folderPath);
|
|
261
431
|
const projectId = getProjectId();
|
|
262
|
-
if (!
|
|
263
|
-
const assertCurrent = () =>
|
|
264
|
-
|
|
265
|
-
|
|
432
|
+
if (!projectId || (!target && folderPath !== '')) throw new Error('A valid upload parent and project are required.');
|
|
433
|
+
const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
|
|
434
|
+
if (!target) {
|
|
435
|
+
// Uppy inherits the selected entity's parent. A previous nested upload
|
|
436
|
+
// must not redirect a root asset into that folder. Use a direct root
|
|
437
|
+
// file row, never the tree adapter's basename fallback.
|
|
438
|
+
assertCurrent();
|
|
439
|
+
const readRoot = () => document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
440
|
+
const root = readRoot();
|
|
441
|
+
const anchor = Array.from(root?.querySelectorAll?.('[role="treeitem"]') || []).find(row =>
|
|
442
|
+
row.closest?.('[role="tree"]') === root
|
|
443
|
+
&& !row.querySelector('[data-file-type="folder"]')
|
|
444
|
+
&& row.querySelector('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]'));
|
|
445
|
+
if (!anchor) throw new Error('A root-level file could not be selected safely for asset upload.');
|
|
446
|
+
if (anchor.getAttribute('aria-selected') !== 'true') {
|
|
447
|
+
(anchor.querySelector('.file-tree-entity-button, .file-tree-entity-details') || anchor).click();
|
|
448
|
+
await delay(100);
|
|
266
449
|
}
|
|
267
|
-
|
|
450
|
+
assertCurrent();
|
|
451
|
+
const currentRoot = readRoot();
|
|
452
|
+
const selected = Array.from(currentRoot?.querySelectorAll?.('[role="treeitem"]') || [])
|
|
453
|
+
.filter(row => row.getAttribute('aria-selected') === 'true');
|
|
454
|
+
if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
|
|
455
|
+
throw new Error('Overleaf did not confirm the selected root upload parent.');
|
|
456
|
+
}
|
|
457
|
+
return anchor;
|
|
458
|
+
}
|
|
268
459
|
const expand = async path => {
|
|
269
460
|
assertCurrent();
|
|
270
461
|
const node = findFolder(path);
|
|
@@ -358,40 +549,88 @@
|
|
|
358
549
|
async function deleteFile(operation, options = {}) {
|
|
359
550
|
const target = normalizeSafeProjectPath(operation.path);
|
|
360
551
|
const projectId = getProjectId();
|
|
552
|
+
const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
|
|
553
|
+
const preflightDeadlineAt = deadlineAt - 15000;
|
|
361
554
|
const expected = options.expectedContent;
|
|
555
|
+
const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
|
|
556
|
+
&& options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
|
|
557
|
+
const matchesExpected = async file => {
|
|
558
|
+
if (!binary) return file && file.content === expected;
|
|
559
|
+
if (typeof file?.contentBase64 !== 'string') return false;
|
|
560
|
+
const cryptoApi = window.crypto || globalThis.crypto;
|
|
561
|
+
if (!cryptoApi?.subtle) return false;
|
|
562
|
+
const decode = window.atob || globalThis.atob;
|
|
563
|
+
if (typeof decode !== 'function') return false;
|
|
564
|
+
const bytes = Uint8Array.from(decode(file.contentBase64.replace(/\s/g, '')), value => value.charCodeAt(0));
|
|
565
|
+
const hash = await cryptoApi.subtle.digest('SHA-256', bytes);
|
|
566
|
+
return Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')
|
|
567
|
+
=== options.expectedSha256.toLowerCase();
|
|
568
|
+
};
|
|
362
569
|
const canDelete = options.canDelete || (() => typeof deps.readActiveEditorText === 'function'
|
|
363
570
|
&& treeOperations.getActiveFilePath?.() === target && deps.readActiveEditorText() === expected);
|
|
364
571
|
let mutationAttempted = false;
|
|
365
572
|
let stage = 'preflight';
|
|
366
573
|
let ownedDialog = null;
|
|
574
|
+
let zipReadCount = 0;
|
|
575
|
+
const zipFailures = [];
|
|
576
|
+
const remainingMs = () => Math.max(0, deadlineAt - now());
|
|
577
|
+
const budgetExpired = () => Object.assign(new Error('The deletion verification budget was exhausted.'), {
|
|
578
|
+
code: 'delete_confirmation_budget_exhausted',
|
|
579
|
+
technicalMessage: mutationAttempted ? 'No time remains to confirm the deletion in the server snapshot.'
|
|
580
|
+
: 'Deletion was not submitted because at least 15000ms must remain for server confirmation.'
|
|
581
|
+
});
|
|
367
582
|
const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
|
|
368
583
|
const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
|
|
369
584
|
const assertCurrent = () => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
585
|
+
assertVerificationOwner(projectId, options.isCurrent);
|
|
586
|
+
if (now() >= (mutationAttempted ? deadlineAt : preflightDeadlineAt)) throw budgetExpired();
|
|
373
587
|
};
|
|
374
588
|
const waitFor = async (read, timeoutMs = 5000) => {
|
|
375
|
-
const deadline =
|
|
376
|
-
|
|
589
|
+
const deadline = Math.min(now() + timeoutMs, preflightDeadlineAt);
|
|
590
|
+
while (now() < deadline) {
|
|
377
591
|
assertCurrent();
|
|
378
592
|
invalidateDomProjectPathCache();
|
|
379
593
|
const value = await read();
|
|
594
|
+
assertCurrent();
|
|
380
595
|
if (value) return value;
|
|
381
|
-
await delay(100);
|
|
382
|
-
}
|
|
596
|
+
await delay(Math.max(0, Math.min(100, deadline - now())));
|
|
597
|
+
}
|
|
598
|
+
assertCurrent();
|
|
383
599
|
throw new Error(`Overleaf did not confirm ${stage} before timeout.`);
|
|
384
600
|
};
|
|
601
|
+
const canRetryRead = error => {
|
|
602
|
+
if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code) || error?.retryable === false) return false;
|
|
603
|
+
const diagnostics = error?.diagnostics || {};
|
|
604
|
+
return ![error?.status, diagnostics.status, diagnostics.statusCode,
|
|
605
|
+
...(Array.isArray(diagnostics.attempts) ? diagnostics.attempts.map(item => item?.status) : [])]
|
|
606
|
+
.some(status => [401, 403].includes(Number(status)));
|
|
607
|
+
};
|
|
385
608
|
const readServerFiles = async () => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
609
|
+
const deadline = mutationAttempted ? deadlineAt : preflightDeadlineAt;
|
|
610
|
+
for (let attempt = 0; ; attempt++) {
|
|
611
|
+
assertCurrent();
|
|
612
|
+
snapshotRouter.invalidateCache?.();
|
|
613
|
+
zipReadCount++;
|
|
614
|
+
try {
|
|
615
|
+
const result = await readServerSnapshot({ projectId, includeBinaryFiles: binary, singleAttempt: true,
|
|
616
|
+
deadlineAt: deadline, budget: { remainingMs: Math.max(0, deadline - now()) }, isCurrent: options.isCurrent });
|
|
617
|
+
assertCurrent();
|
|
618
|
+
if ((result.skipped || []).some(file => file?.path === target)) {
|
|
619
|
+
throw Object.assign(new Error('The target was skipped in the server snapshot.'), {
|
|
620
|
+
code: 'source_zip_unavailable', retryable: false, diagnostics: result.diagnostics || null,
|
|
621
|
+
technicalMessage: 'The target was skipped in the server snapshot; absence cannot confirm deletion.'
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return result.files;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
zipFailures.push({ code: error?.originalCode || error?.code, status: error?.status,
|
|
627
|
+
technicalMessage: error?.technicalMessage || error?.message, diagnostics: error?.diagnostics || null,
|
|
628
|
+
remainingMs: remainingMs() });
|
|
629
|
+
if (zipFailures.length > MAX_CONFIRMATION_OBSERVATIONS) zipFailures.shift();
|
|
630
|
+
if (!canRetryRead(error) || now() >= deadline) throw error;
|
|
631
|
+
await delay(Math.min(1000 * (2 ** Math.min(attempt, 2)), deadline - now()));
|
|
632
|
+
}
|
|
393
633
|
}
|
|
394
|
-
return result.files;
|
|
395
634
|
};
|
|
396
635
|
const choose = (scope, selector, pattern) => {
|
|
397
636
|
const matches = Array.from(scope.querySelectorAll(selector)).filter(node => visible(node)
|
|
@@ -406,26 +645,55 @@
|
|
|
406
645
|
// Current Overleaf keeps the stable document ID on the draggable
|
|
407
646
|
// entity inside the row. The menu only mounts on hover/selection.
|
|
408
647
|
const rowIdentity = row => row?.querySelector('[data-file-type="doc"][data-file-id]')?.getAttribute('data-file-id')
|
|
648
|
+
|| row?.querySelector('[data-file-type="file"][data-file-id]')?.getAttribute('data-file-id')
|
|
409
649
|
|| row?.getAttribute('data-entity-id') || row?.getAttribute('data-doc-id')
|
|
410
650
|
|| row?.getAttribute('data-file-id') || row?.querySelector('[id^="menu-button-"]')?.id || '';
|
|
411
651
|
try {
|
|
412
|
-
if (!target || !projectId || typeof
|
|
413
|
-
|| !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
|
|
414
|
-
throw new Error('A checked
|
|
652
|
+
if (!target || !projectId || typeof canDelete !== 'function'
|
|
653
|
+
|| (!binary && (typeof expected !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)))) {
|
|
654
|
+
throw new Error('A checked file pre-image is required for deletion.');
|
|
415
655
|
}
|
|
416
656
|
assertCurrent();
|
|
417
657
|
if (dialogs().length) throw new Error('An existing Overleaf dialog must be closed before deleting files.');
|
|
418
658
|
if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
|
|
419
659
|
const original = (await readServerFiles()).find(file => file.path === target);
|
|
420
|
-
if (!original
|
|
660
|
+
if (!original && options.undoCreatedFile === true && !resolveRow()) {
|
|
661
|
+
return { ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
|
|
662
|
+
idempotent: true, verified: true, verification: 'overleaf-zip' };
|
|
663
|
+
}
|
|
664
|
+
const originalMatches = await matchesExpected(original);
|
|
665
|
+
assertCurrent();
|
|
666
|
+
if (!originalMatches) {
|
|
421
667
|
throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
422
668
|
}
|
|
423
669
|
const parentPath = target.split('/').slice(0, -1).join('/');
|
|
424
|
-
|
|
670
|
+
// Folder expansion must share the preflight deadline too. Uploads keep their existing timing.
|
|
671
|
+
const parents = parentPath ? parentPath.split('/') : [];
|
|
672
|
+
for (let depth = 1; depth <= parents.length; depth++) {
|
|
673
|
+
assertCurrent();
|
|
674
|
+
const path = parents.slice(0, depth).join('/'), parent = findFolder(path);
|
|
675
|
+
if (!parent) throw new Error('The target parent folder is unavailable.');
|
|
676
|
+
if (parent.getAttribute('aria-expanded') !== 'false') continue;
|
|
677
|
+
stage = 'parent folder expansion';
|
|
678
|
+
const toggle = parent.querySelector('.folder-expand-collapse-button');
|
|
679
|
+
if (!toggle) throw new Error('The target parent folder cannot be expanded safely.');
|
|
680
|
+
toggle.click();
|
|
681
|
+
await waitFor(() => findFolder(path)?.getAttribute('aria-expanded') === 'true');
|
|
682
|
+
}
|
|
683
|
+
// Collapsed folders can hide an existing file from the tree adapter.
|
|
684
|
+
// Server presence proves existence; prepare its editor only after expansion.
|
|
685
|
+
if (!binary && canDelete() !== true && typeof options.prepareTextPreimage === 'function') {
|
|
686
|
+
const current = await options.prepareTextPreimage();
|
|
687
|
+
assertCurrent();
|
|
688
|
+
if (!current?.ok || current.text !== expected) {
|
|
689
|
+
throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (canDelete() !== true) throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
|
|
425
693
|
const row = resolveRow();
|
|
426
694
|
const identity = rowIdentity(row);
|
|
427
695
|
if (!row || !identity || row.querySelector('[data-file-type="folder"]')) {
|
|
428
|
-
throw new Error('An exact Overleaf
|
|
696
|
+
throw new Error('An exact Overleaf file row could not be identified.');
|
|
429
697
|
}
|
|
430
698
|
stage = 'file selection';
|
|
431
699
|
(row.querySelector('.file-tree-entity-details, .file-tree-entity-button') || row).click();
|
|
@@ -459,21 +727,42 @@
|
|
|
459
727
|
};
|
|
460
728
|
if (!verifyDialog()) throw new Error('The deletion dialog does not name exactly the authorized file.');
|
|
461
729
|
const latest = (await readServerFiles()).find(file => file.path === target);
|
|
730
|
+
const latestMatches = await matchesExpected(latest);
|
|
462
731
|
assertCurrent();
|
|
463
|
-
if (!
|
|
732
|
+
if (!latestMatches || canDelete() !== true
|
|
464
733
|
|| rowIdentity(resolveRow()) !== identity || !verifyDialog()) {
|
|
465
734
|
throw new Error('The target changed before confirmation; deletion was stopped.');
|
|
466
735
|
}
|
|
467
736
|
const confirm = choose(ownedDialog, 'button', /^(?:Delete|删除)$/i);
|
|
737
|
+
assertCurrent();
|
|
468
738
|
stage = 'server deletion receipt';
|
|
469
739
|
mutationAttempted = true;
|
|
470
740
|
confirm.click();
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
741
|
+
for (let observation = 0; ; observation++) {
|
|
742
|
+
const files = await readServerFiles();
|
|
743
|
+
if (!files.some(file => file.path === target)) {
|
|
744
|
+
return { ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
|
|
745
|
+
verified: true, verification: 'overleaf-zip' };
|
|
746
|
+
}
|
|
747
|
+
await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
|
|
748
|
+
}
|
|
474
749
|
} catch (error) {
|
|
475
|
-
|
|
476
|
-
|
|
750
|
+
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
751
|
+
const code = interrupted ? error.code : mutationAttempted ? 'file_tree_operation_unverified'
|
|
752
|
+
: error?.code || 'file_tree_controls_unavailable';
|
|
753
|
+
const retryable = canRetryRead(error), technicalMessage = error?.technicalMessage || error?.message || '';
|
|
754
|
+
const reason = interrupted ? error.message : mutationAttempted ? 'Deletion could not be confirmed.'
|
|
755
|
+
: code === 'source_zip_unavailable' ? 'The server copy could not be checked. No file was deleted.'
|
|
756
|
+
: code === 'delete_confirmation_budget_exhausted' ? 'Not enough time remains to verify deletion. No file was deleted.' : error.message;
|
|
757
|
+
return { ok: false, code, reason, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted,
|
|
758
|
+
technicalMessage, deadlineAt, remainingMs: remainingMs(), diagnostics: {
|
|
759
|
+
...(error?.diagnostics || {}), verificationPhase: 'delete-' + stage.replace(/ /g, '-'), zipReadCount, zipFailures,
|
|
760
|
+
reason: technicalMessage, originalCode: error?.originalCode || error?.code || '', status: error?.status,
|
|
761
|
+
deadlineAt, remainingMs: remainingMs(), mutationAttempted
|
|
762
|
+
}, failure: { code, stage: mutationAttempted ? 'verify' : 'preflight', severity: mutationAttempted ? 'warning' : 'blocked',
|
|
763
|
+
retryable, terminalState: interrupted ? 'cancelled' : mutationAttempted ? 'needs_review' : 'blocked',
|
|
764
|
+
changedDocument: mutationAttempted, file: target, operationType: 'delete', userMessage: reason,
|
|
765
|
+
nextAction: retryable ? 'Retry Undo to check the file.' : '', technicalMessage } };
|
|
477
766
|
} finally {
|
|
478
767
|
// Close only the dialog opened by this operation; never another modal.
|
|
479
768
|
if (ownedDialog && dialogs().includes(ownedDialog) && getProjectId() === projectId) {
|
|
@@ -487,7 +776,9 @@
|
|
|
487
776
|
async function createFile(operation, options = {}) {
|
|
488
777
|
return createTextFileWithDom(operation, options);
|
|
489
778
|
}
|
|
490
|
-
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder
|
|
779
|
+
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder,
|
|
780
|
+
beginCreateBatch, finishCreateBatch,
|
|
781
|
+
cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
|
|
491
782
|
}
|
|
492
783
|
return { create };
|
|
493
784
|
});
|