codex-overleaf-link 2.5.0 → 2.6.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 +155 -392
- package/README.zh-CN.md +154 -393
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +283 -55
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +129 -16
- package/extension/src/backgroundUpdateCoordinator.js +242 -29
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +171 -8
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +104 -47
- package/extension/src/content/composerPanel.js +110 -12
- package/extension/src/content/contentRuntime.js +1555 -926
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +48 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +11077 -3412
- package/extension/src/content/generated/content.bundle.meta.json +14 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +10 -0
- package/extension/src/content/localSkillsPanel.js +104 -14
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +47 -11
- package/extension/src/content/modelPicker.js +8 -4
- package/extension/src/content/nativeCompatibilityController.js +74 -30
- package/extension/src/content/otWarmMirror.js +5 -6
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +17 -4
- package/extension/src/content/panelMaintenance.js +46 -14
- package/extension/src/content/panelRenderer.js +57 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +67 -6
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +73 -14
- package/extension/src/content/recentProjects.js +15 -8
- package/extension/src/content/runActivitySummary.js +160 -18
- package/extension/src/content/runCompletionMeta.js +532 -0
- package/extension/src/content/runController.js +39 -15
- package/extension/src/content/runFailureNotice.js +29 -13
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +157 -47
- package/extension/src/content/runRecoveryActions.js +147 -0
- package/extension/src/content/runResultActions.js +28 -143
- package/extension/src/content/runReviewController.js +633 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +367 -175
- package/extension/src/content/scopedPersistencePanelState.js +134 -20
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +153 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +50 -4
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +22 -5
- package/extension/src/content/settingsWorkbench.js +132 -89
- package/extension/src/content/sharedSessionViewSync.js +49 -11
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +118 -12
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +116 -94
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +114 -26
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +103 -155
- package/extension/src/pageBridge.js +126 -69
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +373 -17
- package/extension/src/shared/auditRecords.js +4 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +129 -66
- package/extension/src/shared/composerAttachmentDraft.js +156 -0
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +349 -4
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +590 -24
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/panelIcons.js +39 -0
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/reviewing.js +130 -1
- package/extension/src/shared/runActivityModel.js +70 -4
- package/extension/src/shared/runExecutionSnapshot.js +13 -0
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +132 -28
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +118 -27
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +166 -52
- package/extension/src/shared/storageDb.js +90 -26
- package/extension/src/shared/storageMigration.js +78 -112
- package/extension/src/shared/storageRunActions.js +267 -30
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +253 -32
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +17 -3
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +58 -40
- package/extension/styles/panel.css +1123 -493
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- 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 +269 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +20 -1
- package/native-host/src/chatCompletionsBridge.js +48 -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/codexPromptAssembly.js +114 -127
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +234 -198
- package/native-host/src/codexSessionTiming.js +18 -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 +110 -35
- 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/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +92 -57
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +409 -46
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +5 -8
- package/scripts/install-managed.mjs +1 -0
|
@@ -97,9 +97,10 @@
|
|
|
97
97
|
};
|
|
98
98
|
}),
|
|
99
99
|
capabilities: {
|
|
100
|
-
fullProjectSnapshot:
|
|
100
|
+
fullProjectSnapshot: zipSnapshot.pathIdentityComplete !== false,
|
|
101
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
101
102
|
method: 'overleaf-zip-file-list',
|
|
102
|
-
skipped: [],
|
|
103
|
+
skipped: zipSnapshot.skipped || [],
|
|
103
104
|
diagnostics: {
|
|
104
105
|
zipEndpoint: zipSnapshot.endpoint,
|
|
105
106
|
zipTiming: zipSnapshot.diagnostics,
|
|
@@ -243,7 +244,7 @@
|
|
|
243
244
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
244
245
|
const mergedFiles = mergeSnapshotFiles(
|
|
245
246
|
zipSnapshot.files,
|
|
246
|
-
filterZipSnapshotOverlayFiles(zipSnapshot.files, lightweightSnapshot?.files || [])
|
|
247
|
+
filterZipSnapshotOverlayFiles(zipSnapshot.files, lightweightSnapshot?.files || [], zipSnapshot.skipped)
|
|
247
248
|
);
|
|
248
249
|
const files = filterRequestedSnapshotFiles(mergedFiles, params, activePath);
|
|
249
250
|
const requestedPathsComplete = params.restrictToRequestedPathsOnly === true
|
|
@@ -255,7 +256,9 @@
|
|
|
255
256
|
activePath,
|
|
256
257
|
files,
|
|
257
258
|
capabilities: {
|
|
258
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
259
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && zipSnapshot.pathIdentityComplete !== false,
|
|
260
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
261
|
+
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
259
262
|
requestedPathsComplete,
|
|
260
263
|
method: 'overleaf-zip',
|
|
261
264
|
skipped: zipSnapshot.skipped || [],
|
|
@@ -316,7 +319,8 @@
|
|
|
316
319
|
80
|
|
317
320
|
);
|
|
318
321
|
const files = [];
|
|
319
|
-
const skipped = [];
|
|
322
|
+
const skipped = [...(zipSnapshot?.skipped || [])];
|
|
323
|
+
const ambiguousPaths = new Set(skipped.filter(file => file.reason === 'zip_path_duplicate').map(file => file.path));
|
|
320
324
|
if (zipSnapshot && !zipSnapshot.ok) {
|
|
321
325
|
skipped.push({
|
|
322
326
|
path: 'project.zip',
|
|
@@ -326,6 +330,7 @@
|
|
|
326
330
|
let previousSignature = '';
|
|
327
331
|
|
|
328
332
|
for (const filePath of projectPaths) {
|
|
333
|
+
if (ambiguousPaths.has(filePath)) continue;
|
|
329
334
|
if (filePath === snapshotActivePath) {
|
|
330
335
|
const ready = await waitForActiveEditorText(filePath, 5000);
|
|
331
336
|
if (!ready.ok) {
|
|
@@ -404,7 +409,7 @@
|
|
|
404
409
|
await waitForActiveEditorText(activePath, 5000);
|
|
405
410
|
}
|
|
406
411
|
|
|
407
|
-
if (snapshotActivePath && !files.length && (!lightweightOnly || params.allowZipFallback === false)) {
|
|
412
|
+
if (snapshotActivePath && !ambiguousPaths.has(snapshotActivePath) && !files.length && (!lightweightOnly || params.allowZipFallback === false)) {
|
|
408
413
|
files.push({
|
|
409
414
|
path: snapshotActivePath,
|
|
410
415
|
content: readActiveEditorText(),
|
|
@@ -419,7 +424,8 @@
|
|
|
419
424
|
activePath: snapshotActivePath,
|
|
420
425
|
files,
|
|
421
426
|
capabilities: {
|
|
422
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
427
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && requestedPathsComplete && zipSnapshot?.pathIdentityComplete !== false,
|
|
428
|
+
pathIdentityComplete: zipSnapshot?.pathIdentityComplete,
|
|
423
429
|
requestedPathsComplete,
|
|
424
430
|
method: resolvePageSnapshotMethod(files, lightweightOnly),
|
|
425
431
|
skipped,
|
|
@@ -438,15 +444,14 @@
|
|
|
438
444
|
|
|
439
445
|
async function buildZipOnlyProjectSnapshot(params = {}) {
|
|
440
446
|
const activePath = getPassiveActiveFilePath();
|
|
441
|
-
|
|
447
|
+
// Capture read evidence together with the path/text before awaiting ZIP.
|
|
448
|
+
// A later editor identity cannot prove that an earlier empty read succeeded.
|
|
449
|
+
const activeOverlay = params.serverOnly === true ? [] : readActiveEditorOverlay(activePath);
|
|
442
450
|
|
|
443
451
|
const zipSnapshot = await fetchProjectZipSnapshot(params);
|
|
444
452
|
if (zipSnapshot.ok && zipSnapshot.files.length) {
|
|
445
|
-
const activeOverlay = params.serverOnly === true ? [] : buildActiveEditorOverlay(activePath, activeText, {
|
|
446
|
-
allowedPaths: zipSnapshot.files.map(file => file.path)
|
|
447
|
-
});
|
|
448
453
|
const files = filterRequestedSnapshotFiles(
|
|
449
|
-
mergeSnapshotFiles(zipSnapshot.files, activeOverlay),
|
|
454
|
+
mergeSnapshotFiles(zipSnapshot.files, filterZipSnapshotOverlayFiles(zipSnapshot.files, activeOverlay, zipSnapshot.skipped)),
|
|
450
455
|
params,
|
|
451
456
|
activePath
|
|
452
457
|
);
|
|
@@ -459,7 +464,9 @@
|
|
|
459
464
|
activePath,
|
|
460
465
|
files,
|
|
461
466
|
capabilities: {
|
|
462
|
-
fullProjectSnapshot: params.restrictToRequestedPathsOnly
|
|
467
|
+
fullProjectSnapshot: params.restrictToRequestedPathsOnly !== true && zipSnapshot.pathIdentityComplete !== false,
|
|
468
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
469
|
+
contentComplete: !(zipSnapshot.skipped || []).length,
|
|
463
470
|
requestedPathsComplete,
|
|
464
471
|
method: 'overleaf-zip',
|
|
465
472
|
skipped: zipSnapshot.skipped || [],
|
|
@@ -478,16 +485,18 @@
|
|
|
478
485
|
id: getProjectId(),
|
|
479
486
|
url: window.location.href,
|
|
480
487
|
activePath,
|
|
481
|
-
files:
|
|
488
|
+
files: filterZipSnapshotOverlayFiles([], activeOverlay, zipSnapshot.skipped, true),
|
|
482
489
|
capabilities: {
|
|
483
490
|
fullProjectSnapshot: false,
|
|
491
|
+
pathIdentityComplete: zipSnapshot.pathIdentityComplete,
|
|
484
492
|
method: params.serverOnly === true ? 'overleaf-zip-unavailable'
|
|
485
|
-
:
|
|
493
|
+
: activeOverlay.length
|
|
486
494
|
? 'active-editor-zip-only-fallback'
|
|
487
495
|
: 'zip-only-fallback-empty',
|
|
488
|
-
|
|
496
|
+
contentComplete: false,
|
|
497
|
+
skipped: [...(zipSnapshot.skipped || []), {
|
|
489
498
|
path: 'project.zip',
|
|
490
|
-
reason: zipSnapshot.reason
|
|
499
|
+
reason: zipSnapshot.reason || 'ZIP file identities were incomplete'
|
|
491
500
|
}],
|
|
492
501
|
diagnostics: {
|
|
493
502
|
docRecordCount: 0,
|
|
@@ -500,9 +509,11 @@
|
|
|
500
509
|
};
|
|
501
510
|
}
|
|
502
511
|
|
|
503
|
-
function filterZipSnapshotOverlayFiles(zipFiles = [], overlayFiles = []) {
|
|
512
|
+
function filterZipSnapshotOverlayFiles(zipFiles = [], overlayFiles = [], skipped = [], allowMissing = false) {
|
|
504
513
|
const zipPaths = new Set((zipFiles || []).map(file => file.path).filter(Boolean));
|
|
505
|
-
|
|
514
|
+
const ambiguousPaths = new Set((skipped || []).filter(file => file.reason === 'zip_path_duplicate').map(file => file.path));
|
|
515
|
+
return (overlayFiles || []).filter(file => !ambiguousPaths.has(file?.path)
|
|
516
|
+
&& (allowMissing || file?.source !== 'active-editor' || zipPaths.has(file.path)));
|
|
506
517
|
}
|
|
507
518
|
|
|
508
519
|
function getRequestedSnapshotPaths(params = {}, activePath = '') {
|
|
@@ -520,19 +531,32 @@
|
|
|
520
531
|
return (files || []).filter(file => requestedSet.has(file?.path));
|
|
521
532
|
}
|
|
522
533
|
|
|
523
|
-
function
|
|
524
|
-
if (!activePath
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
534
|
+
function readActiveEditorOverlay(activePath) {
|
|
535
|
+
if (!activePath) return [];
|
|
536
|
+
try {
|
|
537
|
+
const content = readActiveEditorText();
|
|
538
|
+
if (typeof content !== 'string') return [];
|
|
539
|
+
if (!content.trim()) {
|
|
540
|
+
// The legacy reader also returns '' when no surface exists. A readable
|
|
541
|
+
// identity must independently confirm blank text before it can erase ZIP.
|
|
542
|
+
const identity = deps.getActiveEditorIdentity?.();
|
|
543
|
+
let observed;
|
|
544
|
+
if (identity?.type === 'codemirror-view' && typeof identity.doc?.toString === 'function') {
|
|
545
|
+
observed = identity.doc.toString();
|
|
546
|
+
} else if (identity?.type === 'textarea') {
|
|
547
|
+
observed = identity.node?.value;
|
|
548
|
+
} else if (identity?.type === 'contenteditable') {
|
|
549
|
+
const node = identity.node;
|
|
550
|
+
if (typeof node?.innerText === 'string' || typeof node?.textContent === 'string') {
|
|
551
|
+
observed = node.innerText || node.textContent || '';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (typeof observed !== 'string' || observed !== content) return [];
|
|
555
|
+
} else if (!window.CodexOverleafProjectFiles.isUsableProjectFileContent(content)) return [];
|
|
556
|
+
return [{ path: activePath, content, source: 'active-editor', kind: 'text' }];
|
|
557
|
+
} catch (_error) {
|
|
528
558
|
return [];
|
|
529
559
|
}
|
|
530
|
-
return [{
|
|
531
|
-
path: activePath,
|
|
532
|
-
content: activeText,
|
|
533
|
-
source: 'active-editor',
|
|
534
|
-
kind: 'text'
|
|
535
|
-
}];
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
function mergeSnapshotFiles(primaryFiles = [], overrideFiles = []) {
|
|
@@ -541,7 +565,7 @@
|
|
|
541
565
|
if (!file?.path || file.kind === 'binary' || typeof file.content !== 'string') {
|
|
542
566
|
continue;
|
|
543
567
|
}
|
|
544
|
-
if (!window.CodexOverleafProjectFiles.isUsableProjectFileContent(file.content)) {
|
|
568
|
+
if (file.content.trim() && !window.CodexOverleafProjectFiles.isUsableProjectFileContent(file.content)) {
|
|
545
569
|
continue;
|
|
546
570
|
}
|
|
547
571
|
filesByPath.set(file.path, {
|
|
@@ -691,7 +715,7 @@
|
|
|
691
715
|
});
|
|
692
716
|
const extractMs = Date.now() - extractStartedAt;
|
|
693
717
|
const files = extracted.files || [];
|
|
694
|
-
if (!files.length) {
|
|
718
|
+
if (!files.length && extracted.pathIdentityComplete !== false) {
|
|
695
719
|
attempts.push({
|
|
696
720
|
endpoint,
|
|
697
721
|
status: response.status,
|
|
@@ -715,6 +739,7 @@
|
|
|
715
739
|
return {
|
|
716
740
|
ok: true,
|
|
717
741
|
endpoint,
|
|
742
|
+
pathIdentityComplete: extracted.pathIdentityComplete,
|
|
718
743
|
skipped: extracted.skipped || [],
|
|
719
744
|
files: files.map(file => ({
|
|
720
745
|
...file,
|
|
@@ -841,6 +866,9 @@
|
|
|
841
866
|
const entryCount = view.getUint16(eocdOffset + 10, true);
|
|
842
867
|
const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
|
|
843
868
|
const decoder = new TextDecoder('utf-8');
|
|
869
|
+
const nameDecoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
870
|
+
const seenPaths = new Set();
|
|
871
|
+
let pathIdentityComplete = true;
|
|
844
872
|
const files = [];
|
|
845
873
|
const skipped = [];
|
|
846
874
|
let binaryBytes = 0;
|
|
@@ -859,7 +887,25 @@
|
|
|
859
887
|
const commentLength = view.getUint16(offset + 32, true);
|
|
860
888
|
const localHeaderOffset = view.getUint32(offset + 42, true);
|
|
861
889
|
const rawName = bytes.slice(offset + 46, offset + 46 + fileNameLength);
|
|
862
|
-
|
|
890
|
+
let name = '';
|
|
891
|
+
try { name = nameDecoder.decode(rawName); } catch (_error) { /* unknown identity is incomplete */ }
|
|
892
|
+
const directory = name.endsWith('/');
|
|
893
|
+
const path = normalizeSafeProjectPath(directory ? name.slice(0, -1) : name);
|
|
894
|
+
if (!path || seenPaths.has(path)) {
|
|
895
|
+
if (path) {
|
|
896
|
+
const previousIndex = files.findIndex(file => file.path === path);
|
|
897
|
+
if (previousIndex >= 0) files.splice(previousIndex, 1);
|
|
898
|
+
}
|
|
899
|
+
pathIdentityComplete = false;
|
|
900
|
+
skipped.push({ path: name, reason: path ? 'zip_path_duplicate' : 'zip_path_invalid', unknownPath: !name });
|
|
901
|
+
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
seenPaths.add(path);
|
|
905
|
+
if (directory) {
|
|
906
|
+
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
863
909
|
const isText = window.CodexOverleafProjectFiles.isTextProjectPath(path);
|
|
864
910
|
const shouldInclude = path && !path.endsWith('/') && (isText || options.includeBinaryFiles);
|
|
865
911
|
|
|
@@ -904,7 +950,7 @@
|
|
|
904
950
|
offset += 46 + fileNameLength + extraLength + commentLength;
|
|
905
951
|
}
|
|
906
952
|
|
|
907
|
-
return { files, skipped };
|
|
953
|
+
return { files, skipped, pathIdentityComplete };
|
|
908
954
|
}
|
|
909
955
|
|
|
910
956
|
function uint8ArrayToBase64(bytes) {
|
|
@@ -1035,11 +1081,10 @@
|
|
|
1035
1081
|
}
|
|
1036
1082
|
|
|
1037
1083
|
function fallbackNormalizeSafeProjectPath(value) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
return '';
|
|
1041
|
-
|
|
1042
|
-
return text;
|
|
1084
|
+
if (typeof value !== 'string' || !value || value.length > 2048 || value.trim() !== value
|
|
1085
|
+
|| /[\u0000-\u001f\u007f]/.test(value) || value.startsWith('/')
|
|
1086
|
+
|| value.includes('\\') || /^[A-Za-z]:/.test(value)) return '';
|
|
1087
|
+
return value.split('/').some(part => !part || part === '.' || part === '..') ? '' : value;
|
|
1043
1088
|
}
|
|
1044
1089
|
|
|
1045
1090
|
return { create };
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
8
|
function resolveJumpToPositionRange(params, text, filePath) {
|
|
9
|
-
if (params.line === undefined && params.column === undefined) {
|
|
9
|
+
if (params.line === undefined && params.column === undefined && params.endLine === undefined) {
|
|
10
10
|
return {
|
|
11
11
|
ok: true,
|
|
12
12
|
from: params.from,
|
|
@@ -33,6 +33,22 @@
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
if (params.endLine !== undefined) {
|
|
37
|
+
const endLineNumber = Number(params.endLine);
|
|
38
|
+
if (!Number.isInteger(endLineNumber) || endLineNumber < lineNumber) {
|
|
39
|
+
return jumpPositionOutOfRange('line_out_of_range', filePath, {
|
|
40
|
+
line: lineNumber,
|
|
41
|
+
endLine: Number.isInteger(endLineNumber) ? endLineNumber : params.endLine,
|
|
42
|
+
lineCount
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
from: line.start,
|
|
48
|
+
to: lines[Math.min(endLineNumber, lineCount) - 1].end
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
const lineLength = line.end - line.start;
|
|
37
53
|
if (params.selectLine === true) {
|
|
38
54
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
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
7
|
const MAX_CONFIRMATION_OBSERVATIONS = 8;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));
|
|
19
19
|
const now = typeof deps.now === 'function' ? deps.now : Date.now;
|
|
20
20
|
const verificationReads = new Set();
|
|
21
|
+
const uploadParentProofs = new WeakMap();
|
|
21
22
|
const verificationBudget = () => ({ remainingMs: 90000 });
|
|
22
23
|
async function pauseVerification(budget, durationMs) {
|
|
23
24
|
const started = now();
|
|
@@ -81,7 +82,18 @@
|
|
|
81
82
|
} : {}) };
|
|
82
83
|
} finally { if (controller) verificationReads.delete(controller); }
|
|
83
84
|
assertVerificationOwner(projectId, isCurrent);
|
|
84
|
-
if (result?.ok === true && Array.isArray(result.files))
|
|
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
|
+
}
|
|
85
97
|
last = Object.assign(new Error('The server project snapshot could not be read.'), {
|
|
86
98
|
code: 'source_zip_unavailable', diagnostics: result?.diagnostics || null,
|
|
87
99
|
technicalMessage: result?.reason || 'No complete server snapshot was returned.',
|
|
@@ -96,9 +108,14 @@
|
|
|
96
108
|
}
|
|
97
109
|
function verificationFailure(error, changedDocument, phase) {
|
|
98
110
|
const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
|
|
99
|
-
|
|
111
|
+
// Only a failed server read means the project could not be checked; any
|
|
112
|
+
// other error before the upload came from the Overleaf file-tree controls.
|
|
113
|
+
const treeControls = !interrupted && !changedDocument && error?.code !== 'source_zip_unavailable';
|
|
114
|
+
const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed'
|
|
115
|
+
: treeControls ? 'file_tree_controls_unavailable' : 'source_zip_unavailable';
|
|
100
116
|
const reason = interrupted ? error.message : changedDocument
|
|
101
117
|
? 'The file was submitted, but its saved content could not be confirmed.'
|
|
118
|
+
: treeControls ? 'Codex could not safely perform this file operation with the available Overleaf controls.'
|
|
102
119
|
: 'The project could not be checked before uploading this file.';
|
|
103
120
|
return { ok: false, code, reason, changedDocument, diagnostics: {
|
|
104
121
|
verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
|
|
@@ -142,11 +159,13 @@
|
|
|
142
159
|
if (!lastError && observed.get(operation.path)?.content === operation.content) {
|
|
143
160
|
results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
|
|
144
161
|
method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
|
|
162
|
+
createdDirectories: entry.result?.createdDirectories || [], fileUploadAttempted: entry.result?.fileUploadAttempted === true,
|
|
145
163
|
saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
|
|
146
164
|
} else {
|
|
147
|
-
results.push({ operation, result: verificationFailure(lastError || new Error(
|
|
165
|
+
results.push({ operation, result: { ...verificationFailure(lastError || new Error(
|
|
148
166
|
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')
|
|
167
|
+
), true, 'post-upload'), createdDirectories: entry.result?.createdDirectories || [],
|
|
168
|
+
fileUploadAttempted: entry.result?.fileUploadAttempted === true } });
|
|
150
169
|
}
|
|
151
170
|
}
|
|
152
171
|
// The final fresh snapshot can also prove unrelated edits in this batch.
|
|
@@ -191,6 +210,10 @@
|
|
|
191
210
|
return folder;
|
|
192
211
|
};
|
|
193
212
|
|
|
213
|
+
const directoryCleanup = OwnedDirectoryCleanup.create({ document, treeOperations, delay, now,
|
|
214
|
+
readServerSnapshot, assertVerificationOwner, normalizeDomText, normalizeSafeProjectPath });
|
|
215
|
+
const { folderIdentity, cleanupCreatedDirectories } = directoryCleanup;
|
|
216
|
+
|
|
194
217
|
async function createFolderWithDom(entryPath, options = {}) {
|
|
195
218
|
const target = normalizeSafeProjectPath(entryPath);
|
|
196
219
|
const projectId = getProjectId();
|
|
@@ -230,7 +253,15 @@
|
|
|
230
253
|
toggle.click();
|
|
231
254
|
await waitFor(() => findFolder(parentPath)?.getAttribute('aria-expanded') === 'true');
|
|
232
255
|
};
|
|
233
|
-
let ownedDialog = null;
|
|
256
|
+
let ownedDialog = null, createdProof = null;
|
|
257
|
+
const publishProof = () => {
|
|
258
|
+
if (!createdProof) return;
|
|
259
|
+
if (getProjectId() === projectId) {
|
|
260
|
+
const row = findFolder(target);
|
|
261
|
+
if (row) { createdProof.nativeFolderId = folderIdentity(row); createdProof.observedAfterCreate = true; }
|
|
262
|
+
}
|
|
263
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
264
|
+
};
|
|
234
265
|
try {
|
|
235
266
|
assertCurrent();
|
|
236
267
|
if (findFolder(target)) return;
|
|
@@ -259,12 +290,18 @@
|
|
|
259
290
|
await delay(0);
|
|
260
291
|
assertCurrent();
|
|
261
292
|
if (findFolder(target) || projectPathExists(target)) throw new Error('The target path appeared during creation; no replacement was attempted.');
|
|
293
|
+
const confirm = choose(ownedDialog, /^(?:Create|创建)$/i);
|
|
294
|
+
createdProof = { v: 1, projectId, path: target, nativeFolderId: '', ownershipConfirmed: false, observedAfterCreate: false };
|
|
295
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
262
296
|
options.onMutation?.();
|
|
263
|
-
|
|
297
|
+
confirm.click();
|
|
298
|
+
publishProof();
|
|
264
299
|
await waitFor(() => !dialogs().length);
|
|
265
300
|
await expandParent();
|
|
266
301
|
await waitFor(() => findFolder(target));
|
|
302
|
+
publishProof();
|
|
267
303
|
} finally {
|
|
304
|
+
publishProof();
|
|
268
305
|
if (ownedDialog && getProjectId() === projectId && dialogs().includes(ownedDialog)) {
|
|
269
306
|
const cancel = Array.from(ownedDialog.querySelectorAll('button')).filter(node => visible(node) && /^(?:Cancel|取消)$/i.test(controlName(node)));
|
|
270
307
|
if (cancel.length === 1) cancel[0].click();
|
|
@@ -281,7 +318,12 @@
|
|
|
281
318
|
if (!target || !projectId || typeof operation.content !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
|
|
282
319
|
return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
|
|
283
320
|
}
|
|
284
|
-
let mutationAttempted = false;
|
|
321
|
+
let mutationAttempted = false, fileUploadAttempted = false;
|
|
322
|
+
const createdDirectories = [];
|
|
323
|
+
const recordDirectory = proof => {
|
|
324
|
+
const index = createdDirectories.findIndex(item => item.path === proof.path);
|
|
325
|
+
if (index < 0) createdDirectories.push(proof); else createdDirectories[index] = proof;
|
|
326
|
+
};
|
|
285
327
|
let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
|
|
286
328
|
const readBudget = options.batch?.budget || verificationBudget();
|
|
287
329
|
let uploadDialog = null;
|
|
@@ -346,7 +388,7 @@
|
|
|
346
388
|
data.items.add(file);
|
|
347
389
|
assertCurrent();
|
|
348
390
|
uploadHelpers.assignFilesToInput(input, data.files, window);
|
|
349
|
-
mutationAttempted = true;
|
|
391
|
+
mutationAttempted = true; fileUploadAttempted = true;
|
|
350
392
|
options.batch?.pending.set(target, operation);
|
|
351
393
|
input.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
352
394
|
// Never click Replace/Overwrite. A concurrent name conflict must stop.
|
|
@@ -402,20 +444,28 @@
|
|
|
402
444
|
const folderPath = parts.slice(0, depth).join('/');
|
|
403
445
|
if (!findFolder(folderPath)) await createFolderWithDom(folderPath, {
|
|
404
446
|
isCurrent: () => { assertCurrent(); return true; },
|
|
405
|
-
onMutation: () => { mutationAttempted = true; }
|
|
447
|
+
onMutation: () => { mutationAttempted = true; }, onCreatedDirectory: recordDirectory
|
|
406
448
|
});
|
|
407
449
|
await ensureFolderExpanded(folderPath);
|
|
408
450
|
}
|
|
409
451
|
await uploadText();
|
|
410
|
-
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true };
|
|
411
|
-
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true,
|
|
452
|
+
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true, createdDirectories, fileUploadAttempted };
|
|
453
|
+
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true, createdDirectories, fileUploadAttempted,
|
|
412
454
|
verified: true, verification: 'overleaf-zip',
|
|
413
455
|
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
414
456
|
} catch (error) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
457
|
+
const directoryCleanup = createdDirectories.length
|
|
458
|
+
? await cleanupCreatedDirectories(createdDirectories, { projectId, isCurrent: options.isCurrent }) : undefined;
|
|
459
|
+
const result = verificationFailure(error, mutationAttempted, verificationPhase);
|
|
460
|
+
if (!fileUploadAttempted && mutationAttempted) {
|
|
461
|
+
const reason = directoryCleanup?.ok ? 'File upload did not start. The new empty folders were removed.'
|
|
462
|
+
: 'File upload did not start. Some new folders remain and need review.';
|
|
463
|
+
result.reason = reason; result.code = 'file_tree_operation_unverified';
|
|
464
|
+
result.failure = { ...result.failure, code: result.code, stage: 'directory-preparation', userMessage: reason,
|
|
465
|
+
nextAction: directoryCleanup?.ok ? 'Retry sync to create the file.' : 'Review the remaining folders before retrying.' };
|
|
466
|
+
}
|
|
467
|
+
return { ...result, createdDirectories: directoryCleanup?.remaining || createdDirectories,
|
|
468
|
+
fileUploadAttempted, directoryCleanup, diagnostics: { verificationPhase, zipReadCount, lastZipFailure } };
|
|
419
469
|
} finally {
|
|
420
470
|
if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
|
|
421
471
|
const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
|
|
@@ -424,6 +474,31 @@
|
|
|
424
474
|
}
|
|
425
475
|
}
|
|
426
476
|
}
|
|
477
|
+
const rootUploadIdentity = row => row?.querySelector?.('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]')
|
|
478
|
+
?.getAttribute?.('data-file-id') || '';
|
|
479
|
+
function rememberUploadParent(path, node, projectId) {
|
|
480
|
+
if (node && typeof node === 'object') uploadParentProofs.set(node, Object.freeze({
|
|
481
|
+
path, projectId, nativeId: path ? folderIdentity(node) : rootUploadIdentity(node)
|
|
482
|
+
}));
|
|
483
|
+
return node;
|
|
484
|
+
}
|
|
485
|
+
// Synchronous last-moment guard after any uploader await. Snapshot the
|
|
486
|
+
// identity at preparation, since React can reuse a DOM row for a new ID.
|
|
487
|
+
function isUploadParentCurrent(folderPath, parentNode) {
|
|
488
|
+
const target = normalizeSafeProjectPath(folderPath);
|
|
489
|
+
const proof = parentNode && uploadParentProofs.get(parentNode);
|
|
490
|
+
if ((!target && folderPath !== '') || !proof?.projectId || !proof.nativeId
|
|
491
|
+
|| proof.path !== target || getProjectId() !== proof.projectId) return false;
|
|
492
|
+
const root = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
493
|
+
if (!root?.children) return false;
|
|
494
|
+
const selected = Array.from(root.querySelectorAll?.('[role="treeitem"]') || [])
|
|
495
|
+
.filter(row => row.getAttribute('aria-selected') === 'true');
|
|
496
|
+
if (selected.length !== 1 || selected[0] !== parentNode) return false;
|
|
497
|
+
if (!target) return parentNode.closest?.('[role="tree"]') === root
|
|
498
|
+
&& !parentNode.querySelector('[data-file-type="folder"]') && rootUploadIdentity(parentNode) === proof.nativeId;
|
|
499
|
+
return findFolder(target) === parentNode && folderIdentity(parentNode) === proof.nativeId;
|
|
500
|
+
}
|
|
501
|
+
|
|
427
502
|
// Binary uploads use the same folder hierarchy as verified text uploads.
|
|
428
503
|
// A folder is not a text document and must never use basename fallback.
|
|
429
504
|
async function prepareUploadParent(folderPath, options = {}) {
|
|
@@ -454,7 +529,7 @@
|
|
|
454
529
|
if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
|
|
455
530
|
throw new Error('Overleaf did not confirm the selected root upload parent.');
|
|
456
531
|
}
|
|
457
|
-
return anchor;
|
|
532
|
+
return rememberUploadParent('', anchor, projectId);
|
|
458
533
|
}
|
|
459
534
|
const expand = async path => {
|
|
460
535
|
assertCurrent();
|
|
@@ -479,7 +554,8 @@
|
|
|
479
554
|
const path = parts.slice(0, depth).join('/');
|
|
480
555
|
assertCurrent();
|
|
481
556
|
if (!findFolder(path) && options.createMissing === true) await createFolderWithDom(path, {
|
|
482
|
-
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation
|
|
557
|
+
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation,
|
|
558
|
+
onCreatedDirectory: options.onCreatedDirectory
|
|
483
559
|
});
|
|
484
560
|
await expand(path);
|
|
485
561
|
}
|
|
@@ -493,7 +569,7 @@
|
|
|
493
569
|
if (selected.getAttribute('aria-selected') !== 'true') {
|
|
494
570
|
throw new Error('Overleaf did not confirm the selected upload parent.');
|
|
495
571
|
}
|
|
496
|
-
return selected;
|
|
572
|
+
return rememberUploadParent(target, selected, projectId);
|
|
497
573
|
}
|
|
498
574
|
|
|
499
575
|
async function prepareEditor(params = {}) {
|
|
@@ -539,6 +615,9 @@
|
|
|
539
615
|
async function ensureWriteMode(reviewing, params = {}) {
|
|
540
616
|
const ensureMode = reviewing ? deps.ensureReviewing : deps.ensureEditing;
|
|
541
617
|
if (typeof ensureMode !== 'function') return { ok: false, reason: 'Editor mode controls are unavailable.' };
|
|
618
|
+
// An open paywall can make the underlying editor inert. Detect it before
|
|
619
|
+
// preparation attempts any file navigation behind that modal.
|
|
620
|
+
if (reviewing && deps.findUpgradeModal?.()) return ensureMode(params);
|
|
542
621
|
const prepared = await prepareEditor(params);
|
|
543
622
|
return prepared.ok ? ensureMode(params) : prepared;
|
|
544
623
|
}
|
|
@@ -552,6 +631,15 @@
|
|
|
552
631
|
const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
|
|
553
632
|
const preflightDeadlineAt = deadlineAt - 15000;
|
|
554
633
|
const expected = options.expectedContent;
|
|
634
|
+
const finishCreatedDeletion = result => {
|
|
635
|
+
const proofs = operation.undoCreatedFile?.createdDirectories;
|
|
636
|
+
if (options.undoCreatedFile !== true || !Array.isArray(proofs) || !proofs.length) return result;
|
|
637
|
+
// The ZIP-confirmed file deletion completes its Undo checkpoint.
|
|
638
|
+
// Directory observations are retained separately and grant no deletion authority.
|
|
639
|
+
return { ...result, fileDeleted: true, directoryCleanup: {
|
|
640
|
+
ok: false, removed: [], remaining: proofs, changedDocument: false
|
|
641
|
+
} };
|
|
642
|
+
};
|
|
555
643
|
const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
|
|
556
644
|
&& options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
|
|
557
645
|
const matchesExpected = async file => {
|
|
@@ -658,8 +746,8 @@
|
|
|
658
746
|
if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
|
|
659
747
|
const original = (await readServerFiles()).find(file => file.path === target);
|
|
660
748
|
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' };
|
|
749
|
+
return finishCreatedDeletion({ ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
|
|
750
|
+
idempotent: true, verified: true, verification: 'overleaf-zip' });
|
|
663
751
|
}
|
|
664
752
|
const originalMatches = await matchesExpected(original);
|
|
665
753
|
assertCurrent();
|
|
@@ -741,8 +829,8 @@
|
|
|
741
829
|
for (let observation = 0; ; observation++) {
|
|
742
830
|
const files = await readServerFiles();
|
|
743
831
|
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' };
|
|
832
|
+
return finishCreatedDeletion({ ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
|
|
833
|
+
verified: true, verification: 'overleaf-zip' });
|
|
746
834
|
}
|
|
747
835
|
await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
|
|
748
836
|
}
|
|
@@ -776,7 +864,7 @@
|
|
|
776
864
|
async function createFile(operation, options = {}) {
|
|
777
865
|
return createTextFileWithDom(operation, options);
|
|
778
866
|
}
|
|
779
|
-
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder,
|
|
867
|
+
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, isUploadParentCurrent, cleanupCreatedDirectories, findFolderNode: findFolder,
|
|
780
868
|
beginCreateBatch, finishCreateBatch,
|
|
781
869
|
cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
|
|
782
870
|
}
|
|
@@ -129,12 +129,22 @@
|
|
|
129
129
|
const mergedPrefix = 'native-merge:';
|
|
130
130
|
const nativeKey = tuple => 'native:' + JSON.stringify(tuple);
|
|
131
131
|
const ledgerKeys = refs => refs.map(ref => ref.key).sort();
|
|
132
|
-
const { mergedInsertionScope, prepareMergedInsertionReview, nativeOwned, rangesFor, shiftedBaseline, scopeCovered } = Ownership.create({
|
|
132
|
+
const { mergedInsertionScope, prepareMergedInsertionReview, nativeOwned, rangesFor, shiftedBaseline, scopeCovered, reviewNodeMatches } = Ownership.create({
|
|
133
133
|
fingerprint, readActiveEditorText, mergedPrefix, nativeKey, ledgerKeys
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
function isTrackedChangeNodeInNativeScope(node, path) {
|
|
139
|
+
if (typeof deps.getCodeMirrorEditorView !== 'function') return true;
|
|
140
|
+
return reviewNodeMatches(node, path, readNativeSnapshot(path), explicitId(node));
|
|
141
|
+
}
|
|
142
|
+
function rememberTrackedChangeNodeContext(path) {
|
|
143
|
+
if (typeof deps.getCodeMirrorEditorView !== 'function') return;
|
|
144
|
+
const snapshot = readNativeSnapshot(path);
|
|
145
|
+
if (snapshot.ready) for (const node of collectTrackedChangeNodes()) reviewNodeMatches(node, path, snapshot, explicitId(node));
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
function prepareTrackedChangeReview(path, refs = []) {
|
|
139
149
|
const snapshot = readNativeSnapshot(path);
|
|
140
150
|
const blocked = reason => ({ ...snapshot, ok: false, reason });
|
|
@@ -321,7 +331,7 @@
|
|
|
321
331
|
}
|
|
322
332
|
return observe(capture, params.expectedContent, 1200);
|
|
323
333
|
}
|
|
324
|
-
return { collectTrackedChangeNodes, trackedChangeRefFromNode, collectTrackedChangeRefsForPaths,
|
|
334
|
+
return { collectTrackedChangeNodes, trackedChangeRefFromNode, collectTrackedChangeRefsForPaths, isTrackedChangeNodeInNativeScope, rememberTrackedChangeNodeContext,
|
|
325
335
|
waitForTrackedChangeDiff, prepareTrackedChangeCapture, prepareTrackedChangeReview, prepareUntrackedUndo, getTrackedChangeCaptureStatus, captureTrackedWrite, reconcileTrackedChangeCapture };
|
|
326
336
|
}
|
|
327
337
|
return { create };
|
|
@@ -191,7 +191,25 @@
|
|
|
191
191
|
}));
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
const reviewNodeOwners = new WeakMap();
|
|
195
|
+
function reviewNodeMatches(node, path, snapshot, id = '') {
|
|
196
|
+
if (!node || snapshot?.ready !== true || !snapshot.nativeDocId) return false;
|
|
197
|
+
const explicitPath = node.getAttribute?.('data-path') || node.getAttribute?.('data-file-path') || node.getAttribute?.('data-doc-path');
|
|
198
|
+
if (explicitPath && explicitPath !== path) return false;
|
|
199
|
+
const rawPosition = node.getAttribute?.('data-pos'), from = Number(rawPosition);
|
|
200
|
+
const parts = Array.from(node.querySelectorAll?.('.review-panel-expandable-content') || []);
|
|
201
|
+
if (parts.length !== 1 || (!id && (rawPosition == null || rawPosition === '' || !Number.isSafeInteger(from) || from < 0))) return false;
|
|
202
|
+
const kind = /(?:delete|change-d)(?:\s|$)/.test(node.className || '') ? 'delete' : 'insert';
|
|
203
|
+
const textHash = fingerprint(parts[0].textContent || '');
|
|
204
|
+
if (!snapshot.refs.some(ref => (id ? ref.id === id : ref.from === from) && ref.kind === kind && ref.textHash === textHash)) return false;
|
|
205
|
+
const signature = JSON.stringify([id, from, kind, textHash]);
|
|
206
|
+
const prior = reviewNodeOwners.get(node);
|
|
207
|
+
if (prior && prior.docId !== snapshot.nativeDocId && prior.signature === signature) return false;
|
|
208
|
+
reviewNodeOwners.set(node, { docId: snapshot.nativeDocId, signature });
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { mergedInsertionScope, prepareMergedInsertionReview, nativeOwned, rangesFor, shiftedBaseline, scopeCovered, reviewNodeMatches };
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
return { create };
|