codex-overleaf-link 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -26
- package/README.zh-CN.md +39 -27
- 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 +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- 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/textFileCreator.js +105 -25
- 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 +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- 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/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- 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 +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- 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/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 +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -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.',
|
|
@@ -142,11 +154,13 @@
|
|
|
142
154
|
if (!lastError && observed.get(operation.path)?.content === operation.content) {
|
|
143
155
|
results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
|
|
144
156
|
method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
|
|
157
|
+
createdDirectories: entry.result?.createdDirectories || [], fileUploadAttempted: entry.result?.fileUploadAttempted === true,
|
|
145
158
|
saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
|
|
146
159
|
} else {
|
|
147
|
-
results.push({ operation, result: verificationFailure(lastError || new Error(
|
|
160
|
+
results.push({ operation, result: { ...verificationFailure(lastError || new Error(
|
|
148
161
|
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')
|
|
162
|
+
), true, 'post-upload'), createdDirectories: entry.result?.createdDirectories || [],
|
|
163
|
+
fileUploadAttempted: entry.result?.fileUploadAttempted === true } });
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
166
|
// The final fresh snapshot can also prove unrelated edits in this batch.
|
|
@@ -191,6 +205,10 @@
|
|
|
191
205
|
return folder;
|
|
192
206
|
};
|
|
193
207
|
|
|
208
|
+
const directoryCleanup = OwnedDirectoryCleanup.create({ document, treeOperations, delay, now,
|
|
209
|
+
readServerSnapshot, assertVerificationOwner, normalizeDomText, normalizeSafeProjectPath });
|
|
210
|
+
const { folderIdentity, cleanupCreatedDirectories } = directoryCleanup;
|
|
211
|
+
|
|
194
212
|
async function createFolderWithDom(entryPath, options = {}) {
|
|
195
213
|
const target = normalizeSafeProjectPath(entryPath);
|
|
196
214
|
const projectId = getProjectId();
|
|
@@ -230,7 +248,15 @@
|
|
|
230
248
|
toggle.click();
|
|
231
249
|
await waitFor(() => findFolder(parentPath)?.getAttribute('aria-expanded') === 'true');
|
|
232
250
|
};
|
|
233
|
-
let ownedDialog = null;
|
|
251
|
+
let ownedDialog = null, createdProof = null;
|
|
252
|
+
const publishProof = () => {
|
|
253
|
+
if (!createdProof) return;
|
|
254
|
+
if (getProjectId() === projectId) {
|
|
255
|
+
const row = findFolder(target);
|
|
256
|
+
if (row) { createdProof.nativeFolderId = folderIdentity(row); createdProof.observedAfterCreate = true; }
|
|
257
|
+
}
|
|
258
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
259
|
+
};
|
|
234
260
|
try {
|
|
235
261
|
assertCurrent();
|
|
236
262
|
if (findFolder(target)) return;
|
|
@@ -259,12 +285,18 @@
|
|
|
259
285
|
await delay(0);
|
|
260
286
|
assertCurrent();
|
|
261
287
|
if (findFolder(target) || projectPathExists(target)) throw new Error('The target path appeared during creation; no replacement was attempted.');
|
|
288
|
+
const confirm = choose(ownedDialog, /^(?:Create|创建)$/i);
|
|
289
|
+
createdProof = { v: 1, projectId, path: target, nativeFolderId: '', ownershipConfirmed: false, observedAfterCreate: false };
|
|
290
|
+
options.onCreatedDirectory?.({ ...createdProof });
|
|
262
291
|
options.onMutation?.();
|
|
263
|
-
|
|
292
|
+
confirm.click();
|
|
293
|
+
publishProof();
|
|
264
294
|
await waitFor(() => !dialogs().length);
|
|
265
295
|
await expandParent();
|
|
266
296
|
await waitFor(() => findFolder(target));
|
|
297
|
+
publishProof();
|
|
267
298
|
} finally {
|
|
299
|
+
publishProof();
|
|
268
300
|
if (ownedDialog && getProjectId() === projectId && dialogs().includes(ownedDialog)) {
|
|
269
301
|
const cancel = Array.from(ownedDialog.querySelectorAll('button')).filter(node => visible(node) && /^(?:Cancel|取消)$/i.test(controlName(node)));
|
|
270
302
|
if (cancel.length === 1) cancel[0].click();
|
|
@@ -281,7 +313,12 @@
|
|
|
281
313
|
if (!target || !projectId || typeof operation.content !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
|
|
282
314
|
return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
|
|
283
315
|
}
|
|
284
|
-
let mutationAttempted = false;
|
|
316
|
+
let mutationAttempted = false, fileUploadAttempted = false;
|
|
317
|
+
const createdDirectories = [];
|
|
318
|
+
const recordDirectory = proof => {
|
|
319
|
+
const index = createdDirectories.findIndex(item => item.path === proof.path);
|
|
320
|
+
if (index < 0) createdDirectories.push(proof); else createdDirectories[index] = proof;
|
|
321
|
+
};
|
|
285
322
|
let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
|
|
286
323
|
const readBudget = options.batch?.budget || verificationBudget();
|
|
287
324
|
let uploadDialog = null;
|
|
@@ -346,7 +383,7 @@
|
|
|
346
383
|
data.items.add(file);
|
|
347
384
|
assertCurrent();
|
|
348
385
|
uploadHelpers.assignFilesToInput(input, data.files, window);
|
|
349
|
-
mutationAttempted = true;
|
|
386
|
+
mutationAttempted = true; fileUploadAttempted = true;
|
|
350
387
|
options.batch?.pending.set(target, operation);
|
|
351
388
|
input.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
352
389
|
// Never click Replace/Overwrite. A concurrent name conflict must stop.
|
|
@@ -402,20 +439,28 @@
|
|
|
402
439
|
const folderPath = parts.slice(0, depth).join('/');
|
|
403
440
|
if (!findFolder(folderPath)) await createFolderWithDom(folderPath, {
|
|
404
441
|
isCurrent: () => { assertCurrent(); return true; },
|
|
405
|
-
onMutation: () => { mutationAttempted = true; }
|
|
442
|
+
onMutation: () => { mutationAttempted = true; }, onCreatedDirectory: recordDirectory
|
|
406
443
|
});
|
|
407
444
|
await ensureFolderExpanded(folderPath);
|
|
408
445
|
}
|
|
409
446
|
await uploadText();
|
|
410
|
-
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true };
|
|
411
|
-
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true,
|
|
447
|
+
if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true, createdDirectories, fileUploadAttempted };
|
|
448
|
+
return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true, createdDirectories, fileUploadAttempted,
|
|
412
449
|
verified: true, verification: 'overleaf-zip',
|
|
413
450
|
saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
|
|
414
451
|
} catch (error) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
452
|
+
const directoryCleanup = createdDirectories.length
|
|
453
|
+
? await cleanupCreatedDirectories(createdDirectories, { projectId, isCurrent: options.isCurrent }) : undefined;
|
|
454
|
+
const result = verificationFailure(error, mutationAttempted, verificationPhase);
|
|
455
|
+
if (!fileUploadAttempted && mutationAttempted) {
|
|
456
|
+
const reason = directoryCleanup?.ok ? 'File upload did not start. The new empty folders were removed.'
|
|
457
|
+
: 'File upload did not start. Some new folders remain and need review.';
|
|
458
|
+
result.reason = reason; result.code = 'file_tree_operation_unverified';
|
|
459
|
+
result.failure = { ...result.failure, code: result.code, stage: 'directory-preparation', userMessage: reason,
|
|
460
|
+
nextAction: directoryCleanup?.ok ? 'Retry sync to create the file.' : 'Review the remaining folders before retrying.' };
|
|
461
|
+
}
|
|
462
|
+
return { ...result, createdDirectories: directoryCleanup?.remaining || createdDirectories,
|
|
463
|
+
fileUploadAttempted, directoryCleanup, diagnostics: { verificationPhase, zipReadCount, lastZipFailure } };
|
|
419
464
|
} finally {
|
|
420
465
|
if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
|
|
421
466
|
const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
|
|
@@ -424,6 +469,31 @@
|
|
|
424
469
|
}
|
|
425
470
|
}
|
|
426
471
|
}
|
|
472
|
+
const rootUploadIdentity = row => row?.querySelector?.('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]')
|
|
473
|
+
?.getAttribute?.('data-file-id') || '';
|
|
474
|
+
function rememberUploadParent(path, node, projectId) {
|
|
475
|
+
if (node && typeof node === 'object') uploadParentProofs.set(node, Object.freeze({
|
|
476
|
+
path, projectId, nativeId: path ? folderIdentity(node) : rootUploadIdentity(node)
|
|
477
|
+
}));
|
|
478
|
+
return node;
|
|
479
|
+
}
|
|
480
|
+
// Synchronous last-moment guard after any uploader await. Snapshot the
|
|
481
|
+
// identity at preparation, since React can reuse a DOM row for a new ID.
|
|
482
|
+
function isUploadParentCurrent(folderPath, parentNode) {
|
|
483
|
+
const target = normalizeSafeProjectPath(folderPath);
|
|
484
|
+
const proof = parentNode && uploadParentProofs.get(parentNode);
|
|
485
|
+
if ((!target && folderPath !== '') || !proof?.projectId || !proof.nativeId
|
|
486
|
+
|| proof.path !== target || getProjectId() !== proof.projectId) return false;
|
|
487
|
+
const root = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
|
|
488
|
+
if (!root?.children) return false;
|
|
489
|
+
const selected = Array.from(root.querySelectorAll?.('[role="treeitem"]') || [])
|
|
490
|
+
.filter(row => row.getAttribute('aria-selected') === 'true');
|
|
491
|
+
if (selected.length !== 1 || selected[0] !== parentNode) return false;
|
|
492
|
+
if (!target) return parentNode.closest?.('[role="tree"]') === root
|
|
493
|
+
&& !parentNode.querySelector('[data-file-type="folder"]') && rootUploadIdentity(parentNode) === proof.nativeId;
|
|
494
|
+
return findFolder(target) === parentNode && folderIdentity(parentNode) === proof.nativeId;
|
|
495
|
+
}
|
|
496
|
+
|
|
427
497
|
// Binary uploads use the same folder hierarchy as verified text uploads.
|
|
428
498
|
// A folder is not a text document and must never use basename fallback.
|
|
429
499
|
async function prepareUploadParent(folderPath, options = {}) {
|
|
@@ -454,7 +524,7 @@
|
|
|
454
524
|
if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
|
|
455
525
|
throw new Error('Overleaf did not confirm the selected root upload parent.');
|
|
456
526
|
}
|
|
457
|
-
return anchor;
|
|
527
|
+
return rememberUploadParent('', anchor, projectId);
|
|
458
528
|
}
|
|
459
529
|
const expand = async path => {
|
|
460
530
|
assertCurrent();
|
|
@@ -479,7 +549,8 @@
|
|
|
479
549
|
const path = parts.slice(0, depth).join('/');
|
|
480
550
|
assertCurrent();
|
|
481
551
|
if (!findFolder(path) && options.createMissing === true) await createFolderWithDom(path, {
|
|
482
|
-
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation
|
|
552
|
+
isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation,
|
|
553
|
+
onCreatedDirectory: options.onCreatedDirectory
|
|
483
554
|
});
|
|
484
555
|
await expand(path);
|
|
485
556
|
}
|
|
@@ -493,7 +564,7 @@
|
|
|
493
564
|
if (selected.getAttribute('aria-selected') !== 'true') {
|
|
494
565
|
throw new Error('Overleaf did not confirm the selected upload parent.');
|
|
495
566
|
}
|
|
496
|
-
return selected;
|
|
567
|
+
return rememberUploadParent(target, selected, projectId);
|
|
497
568
|
}
|
|
498
569
|
|
|
499
570
|
async function prepareEditor(params = {}) {
|
|
@@ -552,6 +623,15 @@
|
|
|
552
623
|
const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
|
|
553
624
|
const preflightDeadlineAt = deadlineAt - 15000;
|
|
554
625
|
const expected = options.expectedContent;
|
|
626
|
+
const finishCreatedDeletion = result => {
|
|
627
|
+
const proofs = operation.undoCreatedFile?.createdDirectories;
|
|
628
|
+
if (options.undoCreatedFile !== true || !Array.isArray(proofs) || !proofs.length) return result;
|
|
629
|
+
// The ZIP-confirmed file deletion completes its Undo checkpoint.
|
|
630
|
+
// Directory observations are retained separately and grant no deletion authority.
|
|
631
|
+
return { ...result, fileDeleted: true, directoryCleanup: {
|
|
632
|
+
ok: false, removed: [], remaining: proofs, changedDocument: false
|
|
633
|
+
} };
|
|
634
|
+
};
|
|
555
635
|
const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
|
|
556
636
|
&& options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
|
|
557
637
|
const matchesExpected = async file => {
|
|
@@ -658,8 +738,8 @@
|
|
|
658
738
|
if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
|
|
659
739
|
const original = (await readServerFiles()).find(file => file.path === target);
|
|
660
740
|
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' };
|
|
741
|
+
return finishCreatedDeletion({ ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
|
|
742
|
+
idempotent: true, verified: true, verification: 'overleaf-zip' });
|
|
663
743
|
}
|
|
664
744
|
const originalMatches = await matchesExpected(original);
|
|
665
745
|
assertCurrent();
|
|
@@ -741,8 +821,8 @@
|
|
|
741
821
|
for (let observation = 0; ; observation++) {
|
|
742
822
|
const files = await readServerFiles();
|
|
743
823
|
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' };
|
|
824
|
+
return finishCreatedDeletion({ ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
|
|
825
|
+
verified: true, verification: 'overleaf-zip' });
|
|
746
826
|
}
|
|
747
827
|
await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
|
|
748
828
|
}
|
|
@@ -776,7 +856,7 @@
|
|
|
776
856
|
async function createFile(operation, options = {}) {
|
|
777
857
|
return createTextFileWithDom(operation, options);
|
|
778
858
|
}
|
|
779
|
-
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder,
|
|
859
|
+
return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, isUploadParentCurrent, cleanupCreatedDirectories, findFolderNode: findFolder,
|
|
780
860
|
beginCreateBatch, finishCreateBatch,
|
|
781
861
|
cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
|
|
782
862
|
}
|
|
@@ -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 };
|
|
@@ -33,26 +33,17 @@
|
|
|
33
33
|
|
|
34
34
|
// Preferred: re-apply the run's original forward patches verbatim.
|
|
35
35
|
const originalPatches = patchesByPath.get(path);
|
|
36
|
-
if (originalPatches && originalPatches.length && patchesApplyCleanly(preContent, originalPatches)) {
|
|
37
|
-
operations.push({
|
|
38
|
-
type: 'edit',
|
|
39
|
-
path,
|
|
40
|
-
patches: originalPatches,
|
|
41
|
-
reason: 'Accept tracked edit (replay untracked)'
|
|
42
|
-
});
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
36
|
// Fallback: minimal pre->post diff trimmed to a single targeted patch.
|
|
47
|
-
const
|
|
48
|
-
|
|
37
|
+
const useOriginal = originalPatches?.length > 0 && applyTextPatches(preContent, originalPatches).ok === true;
|
|
38
|
+
const patches = useOriginal ? originalPatches : [buildMinimalDiffPatch(preContent, postContent)].filter(Boolean);
|
|
39
|
+
if (!patches.length) {
|
|
49
40
|
// pre === post: nothing to replay for this file.
|
|
50
41
|
continue;
|
|
51
42
|
}
|
|
52
43
|
operations.push({
|
|
53
44
|
type: 'edit',
|
|
54
45
|
path,
|
|
55
|
-
patches
|
|
46
|
+
patches,
|
|
56
47
|
reason: 'Accept tracked edit (replay untracked)'
|
|
57
48
|
});
|
|
58
49
|
}
|
|
@@ -87,13 +78,6 @@
|
|
|
87
78
|
return patchesByPath;
|
|
88
79
|
}
|
|
89
80
|
|
|
90
|
-
// Confirms a patch set re-applies cleanly against the given (pre-write) text:
|
|
91
|
-
// every patch range is valid and its `expected` slice matches.
|
|
92
|
-
function patchesApplyCleanly(text, patches) {
|
|
93
|
-
const applied = applyTextPatches(text, patches);
|
|
94
|
-
return applied.ok === true;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
81
|
// Computes a single targeted {from,to,insert} patch describing the pre->post
|
|
98
82
|
// change by trimming the common prefix and suffix. Returns null when the two
|
|
99
83
|
// strings are identical.
|
|
@@ -157,7 +141,43 @@
|
|
|
157
141
|
};
|
|
158
142
|
}
|
|
159
143
|
|
|
160
|
-
|
|
144
|
+
function prepareSnapshotUndoCheckpoints(expectedFiles, postFiles) {
|
|
145
|
+
const contentByPath = files => new Map((files || [])
|
|
146
|
+
.filter(file => file?.path && typeof file.content === 'string')
|
|
147
|
+
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
148
|
+
.filter(([path]) => path));
|
|
149
|
+
const expectedByPath = contentByPath(expectedFiles);
|
|
150
|
+
const postByPath = contentByPath(postFiles);
|
|
151
|
+
return { expectedByPath, postByPath,
|
|
152
|
+
paths: Array.from(expectedByPath.keys()).filter(path => postByPath.has(path)) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Convert edit receipts back to the review checkpoint contract. Native
|
|
156
|
+
// identity checks and editor mutations remain owned by the lifecycle.
|
|
157
|
+
function buildSnapshotUndoResult(result, expectedByPath, postByPath, undoRebaseProofs) {
|
|
158
|
+
const toTrackedResult = item => ({
|
|
159
|
+
trackedChange: {
|
|
160
|
+
key: `snapshot-undo:${item.operation?.path || ''}`,
|
|
161
|
+
id: '',
|
|
162
|
+
path: item.operation?.path || '',
|
|
163
|
+
label: 'No-trace snapshot undo'
|
|
164
|
+
},
|
|
165
|
+
result: item.result?.ok !== false && item.result?.verified === true
|
|
166
|
+
&& undoRebaseProofs.has(item.operation?.path)
|
|
167
|
+
&& item.result.verifiedContent === expectedByPath.get(item.operation?.path)
|
|
168
|
+
? { ...item.result, undoRebaseProof: undoRebaseProofs.get(item.operation.path) }
|
|
169
|
+
: item.result
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
attempted: true,
|
|
173
|
+
applied: (result.applied || []).map(toTrackedResult),
|
|
174
|
+
skipped: (result.skipped || []).map(toTrackedResult),
|
|
175
|
+
rebasedExpectedFiles: Array.from(expectedByPath, ([path, content]) => ({ path, content })),
|
|
176
|
+
rebasedPostFiles: Array.from(postByPath, ([path, content]) => ({ path, content }))
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { buildAcceptReplayOperations, rebaseCheckpointPair, prepareSnapshotUndoCheckpoints, buildSnapshotUndoResult };
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
return { create };
|