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
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
window
|
|
64
64
|
});
|
|
65
65
|
snapshotRouter = requirePageModule('CodexOverleafSnapshotRouter').create({
|
|
66
|
+
getActiveEditorIdentity,
|
|
66
67
|
normalizePath: normalizeSafeProjectPath,
|
|
67
68
|
readActiveEditorText,
|
|
68
69
|
treeOperations,
|
|
@@ -72,16 +73,22 @@
|
|
|
72
73
|
const textFileCreator = requirePageModule('CodexOverleafTextFileCreator').create({
|
|
73
74
|
window, document, treeOperations, snapshotRouter,
|
|
74
75
|
collectElements, ensureEditing, ensureReviewing, readActiveEditorText, readWriteCancellationSequence,
|
|
76
|
+
findUpgradeModal: () => window.CodexOverleafReviewing.findUpgradeModal(document),
|
|
75
77
|
uploadHelpers: requirePageModule('CodexOverleafBinaryAssetUploader')
|
|
76
78
|
});
|
|
77
79
|
binaryAssetUploader = requirePageModule('CodexOverleafBinaryAssetUploader').create({
|
|
78
80
|
prepareUploadParent: textFileCreator.prepareUploadParent,
|
|
81
|
+
cleanupCreatedDirectories: textFileCreator.cleanupCreatedDirectories,
|
|
82
|
+
isUploadParentCurrent: textFileCreator.isUploadParentCurrent,
|
|
79
83
|
findFolderNode: textFileCreator.findFolderNode,
|
|
80
84
|
document,
|
|
81
85
|
snapshotRouter,
|
|
82
86
|
treeOperations,
|
|
83
87
|
window
|
|
84
88
|
});
|
|
89
|
+
const fileMover = requirePageModule('CodexOverleafFileMover').create({
|
|
90
|
+
window, document, treeOperations, snapshotRouter, textFileCreator, normalizeSafeProjectPath
|
|
91
|
+
});
|
|
85
92
|
writeGuard = requirePageModule('CodexOverleafWriteGuard').create({
|
|
86
93
|
document,
|
|
87
94
|
treeOperations,
|
|
@@ -102,6 +109,7 @@
|
|
|
102
109
|
beginTextCreateBatch: textFileCreator.beginCreateBatch,
|
|
103
110
|
finishTextCreateBatch: textFileCreator.finishCreateBatch,
|
|
104
111
|
deleteTextFile: textFileCreator.deleteFile,
|
|
112
|
+
moveFile: fileMover.moveFile,
|
|
105
113
|
activeEditorIdentityChanged,
|
|
106
114
|
clickNode,
|
|
107
115
|
collectElements,
|
|
@@ -221,7 +229,8 @@
|
|
|
221
229
|
|
|
222
230
|
const pageRpcHandlers = {
|
|
223
231
|
probe,
|
|
224
|
-
cancelActiveWrite() {
|
|
232
|
+
cancelActiveWrite(params = {}) {
|
|
233
|
+
if (params.reviewOwnerToken) return { ok: true, cancelled: requirePageModule('CodexOverleafReviewContext').cancelOwner(params.reviewOwnerToken) };
|
|
225
234
|
const sequence = bumpWriteCancellationSequence();
|
|
226
235
|
textFileCreator.cancelVerifications();
|
|
227
236
|
snapshotRouter.cancelWritebackReads();
|
|
@@ -391,6 +400,11 @@
|
|
|
391
400
|
fallbackUserMessage: 'Target file is active but the editor was not ready before timeout.',
|
|
392
401
|
fallbackNextAction: 'Wait for Overleaf to finish loading, then retry.'
|
|
393
402
|
},
|
|
403
|
+
reviewing_unavailable: {
|
|
404
|
+
stage: 'reviewing', severity: 'blocked', defaultRetryable: false,
|
|
405
|
+
fallbackUserMessage: "Track Changes is unavailable for this project. It depends on the project owner's Overleaf plan.",
|
|
406
|
+
fallbackNextAction: 'Turn off Track in the composer to write without tracked changes. Codex still records each change so you can undo it.'
|
|
407
|
+
},
|
|
394
408
|
reviewing_state_unknown: {
|
|
395
409
|
stage: 'reviewing', severity: 'blocked', defaultRetryable: true,
|
|
396
410
|
fallbackUserMessage: 'Codex could not determine whether Reviewing/Track Changes is enabled.',
|
|
@@ -480,6 +494,8 @@
|
|
|
480
494
|
findFileTreeManager,
|
|
481
495
|
findHistoryObject,
|
|
482
496
|
findReviewingActivationControl,
|
|
497
|
+
findUpgradeModal: () => window.CodexOverleafReviewing.findUpgradeModal(document),
|
|
498
|
+
reviewingConfirmed: isReviewingConfirmedForWrite(getReviewingState()),
|
|
483
499
|
getCodeMirrorEditorView,
|
|
484
500
|
getDeepActiveElement,
|
|
485
501
|
isInsideCodexPanel,
|
|
@@ -489,7 +505,7 @@
|
|
|
489
505
|
|
|
490
506
|
async function ensureReviewing(params = {}) {
|
|
491
507
|
const initial = getReviewingState(params);
|
|
492
|
-
if (isReviewingConfirmedForWrite(initial)) {
|
|
508
|
+
if (isReviewingConfirmedForWrite(initial) && !window.CodexOverleafReviewing.findUpgradeModal(document)) {
|
|
493
509
|
return {
|
|
494
510
|
ok: true,
|
|
495
511
|
activated: false,
|
|
@@ -498,6 +514,7 @@
|
|
|
498
514
|
}
|
|
499
515
|
|
|
500
516
|
const switched = await toggleReviewingMode(true, params);
|
|
517
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
501
518
|
if (switched.ok) {
|
|
502
519
|
return {
|
|
503
520
|
ok: true,
|
|
@@ -545,6 +562,7 @@
|
|
|
545
562
|
}
|
|
546
563
|
|
|
547
564
|
const switched = await toggleReviewingMode(false, params);
|
|
565
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
548
566
|
if (switched.ok) {
|
|
549
567
|
return {
|
|
550
568
|
ok: true,
|
|
@@ -690,7 +708,14 @@
|
|
|
690
708
|
if (to - from > 20000) return { ok: false, code: 'selection_too_large' };
|
|
691
709
|
const source = editorState.doc.toString();
|
|
692
710
|
const text = source.slice(from, to);
|
|
693
|
-
|
|
711
|
+
// UTF-8 replaces isolated surrogates; exclude them so flank hashes prove exact UTF-16 text.
|
|
712
|
+
if (/[\uD800-\uDFFF]/u.test(source) || /[\uD800-\uDFFF]/u.test(text)) return { ok: false, code: 'selection_unavailable' };
|
|
713
|
+
const hashText = async value => Array.from(new Uint8Array(
|
|
714
|
+
await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))
|
|
715
|
+
), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
716
|
+
const [documentHash, prefixHash, suffixHash] = await Promise.all([
|
|
717
|
+
hashText(source), hashText(source.slice(0, from)), hashText(source.slice(to))
|
|
718
|
+
]);
|
|
694
719
|
if (getProjectId() !== projectId || getActiveFilePath() !== path || getCodeMirrorEditorView() !== view
|
|
695
720
|
|| view.state.doc !== editorState.doc || view.state.selection.main.from !== from || view.state.selection.main.to !== to) {
|
|
696
721
|
return { ok: false, code: 'selection_changed' };
|
|
@@ -698,7 +723,8 @@
|
|
|
698
723
|
const captured = { projectId, path, from, to, text,
|
|
699
724
|
lineStart: editorState.doc.lineAt(from).number,
|
|
700
725
|
lineEnd: editorState.doc.lineAt(Math.max(from, to - 1)).number,
|
|
701
|
-
documentHash
|
|
726
|
+
documentHash,
|
|
727
|
+
rebaseEvidence: { v: 1, documentLength: source.length, prefixHash, suffixHash },
|
|
702
728
|
capturedAt: new Date().toISOString() };
|
|
703
729
|
let anchor = null;
|
|
704
730
|
try {
|
|
@@ -727,9 +753,16 @@
|
|
|
727
753
|
const activeFileMatches = getActiveFilePath() === filePath;
|
|
728
754
|
const text = readActiveEditorText();
|
|
729
755
|
lastText = text;
|
|
756
|
+
const currentEditorIdentity = getActiveEditorIdentity();
|
|
757
|
+
// A file-tree click can reconfigure or blur the old editor before the
|
|
758
|
+
// target document loads. A new view/state alone does not prove a switch.
|
|
759
|
+
const documentChanged = currentEditorIdentity?.type === 'codemirror-view'
|
|
760
|
+
&& previousEditorIdentity?.type === 'codemirror-view'
|
|
761
|
+
? currentEditorIdentity.doc !== previousEditorIdentity.doc
|
|
762
|
+
: Boolean(currentEditorIdentity) && activeEditorIdentityChanged(previousEditorIdentity);
|
|
730
763
|
if (activeFileMatches
|
|
731
764
|
&& window.CodexOverleafProjectFiles.isUsableProjectFileContent(text)
|
|
732
|
-
&&
|
|
765
|
+
&& documentChanged) {
|
|
733
766
|
return {
|
|
734
767
|
ok: true,
|
|
735
768
|
text
|
|
@@ -855,6 +888,7 @@
|
|
|
855
888
|
}
|
|
856
889
|
|
|
857
890
|
const switched = await toggleReviewingMode(enabled, params);
|
|
891
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
858
892
|
if (switched.ok) {
|
|
859
893
|
return {
|
|
860
894
|
ok: true,
|
|
@@ -901,67 +935,23 @@
|
|
|
901
935
|
}
|
|
902
936
|
|
|
903
937
|
async function toggleReviewingMode(enabled, params = {}) {
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
for (const control of attemptedNodes) {
|
|
910
|
-
clickNode(control);
|
|
911
|
-
let after = await waitForReviewingEnabled(enabled, {
|
|
912
|
-
...params,
|
|
913
|
-
waitMs: Math.min(resolveWaitMs(params), 180)
|
|
914
|
-
});
|
|
915
|
-
if (isReviewingTargetStateConfirmed(enabled, after)) {
|
|
916
|
-
return {
|
|
917
|
-
ok: true,
|
|
918
|
-
attempted: true,
|
|
919
|
-
reviewing: after.reviewing
|
|
920
|
-
};
|
|
938
|
+
const sequence = readWriteCancellationSequence();
|
|
939
|
+
const assertCurrent = () => {
|
|
940
|
+
if (sequence !== readWriteCancellationSequence()) {
|
|
941
|
+
throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
|
|
921
942
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
if (modeOption) {
|
|
925
|
-
clickNode(modeOption);
|
|
926
|
-
after = await waitForReviewingEnabled(enabled, params);
|
|
927
|
-
if (isReviewingTargetStateConfirmed(enabled, after)) {
|
|
928
|
-
return {
|
|
929
|
-
ok: true,
|
|
930
|
-
attempted: true,
|
|
931
|
-
reviewing: after.reviewing
|
|
932
|
-
};
|
|
933
|
-
}
|
|
943
|
+
if (params.runProjectId && writeGuard.getEditorProjectIdPageSide(params.runProjectId) !== params.runProjectId) {
|
|
944
|
+
throw Object.assign(new Error('The active project changed.'), { code: 'aborted_project_changed' });
|
|
934
945
|
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const visibleModeOption = findReviewingModeOption(enabled, { requireMenuOption: true });
|
|
938
|
-
if (visibleModeOption) {
|
|
939
|
-
clickNode(visibleModeOption);
|
|
940
|
-
const after = await waitForReviewingEnabled(enabled, params);
|
|
941
|
-
return {
|
|
942
|
-
ok: isReviewingTargetStateConfirmed(enabled, after),
|
|
943
|
-
attempted: true,
|
|
944
|
-
reviewing: after.reviewing
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const after = getReviewingState(params);
|
|
949
|
-
return {
|
|
950
|
-
ok: false,
|
|
951
|
-
attempted: attemptedNodes.length > 0,
|
|
952
|
-
reviewing: after.reviewing
|
|
953
946
|
};
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
current = getReviewingState(params);
|
|
963
|
-
}
|
|
964
|
-
return current;
|
|
947
|
+
return window.CodexOverleafReviewing.toggleMode(enabled, params, {
|
|
948
|
+
document, assertCurrent, delay,
|
|
949
|
+
getState: () => getReviewingState(params), isConfirmed: isReviewingTargetStateConfirmed,
|
|
950
|
+
click: clickNode,
|
|
951
|
+
findControls: () => findReviewingModeControls(enabled),
|
|
952
|
+
findOption: value => findReviewingModeOption(value, { requireMenuOption: true }),
|
|
953
|
+
buildFailure: buildPageBridgeFailure
|
|
954
|
+
});
|
|
965
955
|
}
|
|
966
956
|
|
|
967
957
|
function resolveWaitMs(params = {}) {
|
|
@@ -1093,7 +1083,72 @@
|
|
|
1093
1083
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
1094
1084
|
}
|
|
1095
1085
|
|
|
1086
|
+
function isReviewingModeNodeAvailable(node) {
|
|
1087
|
+
if (!node || node.ownerDocument !== document || !node.isConnected || node.disabled
|
|
1088
|
+
|| node.getAttribute?.('aria-disabled') === 'true' || !node.getClientRects?.().length) return false;
|
|
1089
|
+
for (let ancestor = node; ancestor; ancestor = ancestor.parentElement || ancestor.getRootNode?.().host) {
|
|
1090
|
+
if (isInsideCodexPanel(ancestor) || ancestor.closest?.(
|
|
1091
|
+
'.review-panel, .review-panel-container, .review-panel-header-slot, .review-sidebar, #review-panel, [data-testid="review-panel"]'
|
|
1092
|
+
) || ancestor.hidden || ancestor.getAttribute?.('aria-hidden') === 'true' || ancestor.hasAttribute?.('inert')) return false;
|
|
1093
|
+
const style = window.getComputedStyle?.(ancestor);
|
|
1094
|
+
if (style?.display === 'none' || style?.visibility === 'hidden' || style?.visibility === 'collapse'
|
|
1095
|
+
|| style?.opacity === '0') return false;
|
|
1096
|
+
}
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function findReviewingToolbarControl() {
|
|
1101
|
+
return Array.from(document.querySelectorAll(
|
|
1102
|
+
'.toolbar-editor .review-mode-switcher > .review-mode-switcher-toggle-button'
|
|
1103
|
+
)).find(isReviewingModeNodeAvailable) || null;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function findReviewingModeControls(enabled) {
|
|
1107
|
+
const toolbarControl = findReviewingToolbarControl();
|
|
1108
|
+
return toolbarControl ? [toolbarControl]
|
|
1109
|
+
: [findReviewingActivationControl(enabled), findReviewingModeMenuControl()];
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function findReviewingToolbarMenu(control) {
|
|
1113
|
+
const controlledIds = (control.getAttribute('aria-controls') || '').split(/\s+/).filter(Boolean);
|
|
1114
|
+
for (const id of controlledIds) {
|
|
1115
|
+
const menu = document.getElementById(id);
|
|
1116
|
+
if (isReviewingModeNodeAvailable(menu)) return menu;
|
|
1117
|
+
}
|
|
1118
|
+
if (control.getAttribute('aria-expanded') !== 'true') return null;
|
|
1119
|
+
const selector = '[role="menu"], [role="listbox"], .dropdown-menu';
|
|
1120
|
+
// Current Overleaf keeps the menu beside the toggle, inside this switcher.
|
|
1121
|
+
const localMenu = Array.from(control.parentElement?.querySelectorAll(selector) || [])
|
|
1122
|
+
.find(node => node.closest('.review-mode-switcher') === control.parentElement && isReviewingModeNodeAvailable(node));
|
|
1123
|
+
if (localMenu) return localMenu;
|
|
1124
|
+
// A portalled menu must still name this switch, rather than an unrelated dropdown.
|
|
1125
|
+
return control.id ? Array.from(document.querySelectorAll(selector)).find(node =>
|
|
1126
|
+
(node.getAttribute('aria-labelledby') || '').split(/\s+/).includes(control.id)
|
|
1127
|
+
&& isReviewingModeNodeAvailable(node)) || null : null;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function isReviewingToolbarModeOption(node, enabled) {
|
|
1131
|
+
const mode = enabled ? 'reviewing' : 'editing';
|
|
1132
|
+
const values = [node.value, node.getAttribute('value'), node.getAttribute('data-value'),
|
|
1133
|
+
node.getAttribute('label'), node.getAttribute('aria-label'), ...String(node.className || '').split(/\s+/)];
|
|
1134
|
+
if (values.some(value => normalizeReviewingSignalText(value).toLowerCase() === mode)) return true;
|
|
1135
|
+
// Read a label separately from icon text and the option's explanatory subtitle.
|
|
1136
|
+
return [node, ...node.querySelectorAll('*')].some(label => {
|
|
1137
|
+
if (label.closest('[aria-hidden="true"]')) return false;
|
|
1138
|
+
const directText = Array.from(label.childNodes).filter(child => child.nodeType === 3)
|
|
1139
|
+
.map(child => child.textContent).join(' ');
|
|
1140
|
+
return [label.textContent, directText].some(value => normalizeReviewingSignalText(value).toLowerCase() === mode);
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1096
1144
|
function findReviewingModeOption(enabled, options = {}) {
|
|
1145
|
+
const toolbarControl = findReviewingToolbarControl();
|
|
1146
|
+
if (toolbarControl) {
|
|
1147
|
+
const menu = findReviewingToolbarMenu(toolbarControl);
|
|
1148
|
+
return Array.from(menu?.querySelectorAll(
|
|
1149
|
+
'[role="menuitem"], [role="menuitemradio"], [role="option"], .dropdown-item, button, a, option'
|
|
1150
|
+
) || []).find(node => isReviewingModeNodeAvailable(node) && isReviewingToolbarModeOption(node, enabled)) || null;
|
|
1151
|
+
}
|
|
1097
1152
|
const candidates = collectReviewingModeOptionNodes()
|
|
1098
1153
|
.filter(node => isReviewingModeOptionCandidate(node, enabled, options))
|
|
1099
1154
|
.sort((left, right) => scoreReviewingModeOption(right, enabled) - scoreReviewingModeOption(left, enabled));
|
|
@@ -1129,7 +1184,7 @@
|
|
|
1129
1184
|
}
|
|
1130
1185
|
|
|
1131
1186
|
function isReviewingModeMenuControlCandidate(node) {
|
|
1132
|
-
if (!node || node
|
|
1187
|
+
if (!isReviewingModeNodeAvailable(node) || isMenuLikeModeOption(node)) {
|
|
1133
1188
|
return false;
|
|
1134
1189
|
}
|
|
1135
1190
|
const text = readNodeSignalText(node);
|
|
@@ -1182,7 +1237,7 @@
|
|
|
1182
1237
|
}
|
|
1183
1238
|
|
|
1184
1239
|
function isReviewingModeOptionCandidate(node, enabled, options = {}) {
|
|
1185
|
-
if (!node
|
|
1240
|
+
if (!isReviewingModeNodeAvailable(node)) {
|
|
1186
1241
|
return false;
|
|
1187
1242
|
}
|
|
1188
1243
|
if (options.requireMenuOption && !isMenuLikeModeOption(node)) {
|
|
@@ -1228,10 +1283,10 @@
|
|
|
1228
1283
|
&& !/\bsyntax highlighting\b/i.test(text);
|
|
1229
1284
|
}
|
|
1230
1285
|
|
|
1231
|
-
function findReviewingActivationControl() {
|
|
1286
|
+
function findReviewingActivationControl(enabled = true) {
|
|
1232
1287
|
const nodes = collectReviewingControlNodes();
|
|
1233
1288
|
const candidates = nodes
|
|
1234
|
-
.filter(node => isReviewingActivationCandidate(node))
|
|
1289
|
+
.filter(node => isReviewingActivationCandidate(node, enabled))
|
|
1235
1290
|
.sort((left, right) => scoreReviewingControl(right) - scoreReviewingControl(left));
|
|
1236
1291
|
return candidates[0] || null;
|
|
1237
1292
|
}
|
|
@@ -1255,10 +1310,12 @@
|
|
|
1255
1310
|
]).slice(0, 1500);
|
|
1256
1311
|
}
|
|
1257
1312
|
|
|
1258
|
-
function isReviewingActivationCandidate(node) {
|
|
1259
|
-
if (!node || node
|
|
1313
|
+
function isReviewingActivationCandidate(node, enabled) {
|
|
1314
|
+
if (!isReviewingModeNodeAvailable(node) || isMenuLikeModeOption(node)) {
|
|
1260
1315
|
return false;
|
|
1261
1316
|
}
|
|
1317
|
+
// A standalone Reviewing command enables tracking; it cannot open Editing.
|
|
1318
|
+
if (!enabled && !node.getAttribute?.('aria-haspopup') && !node.hasAttribute?.('aria-expanded')) return false;
|
|
1262
1319
|
const text = readNodeSignalText(node);
|
|
1263
1320
|
return /\breviewing\b|\btrack(?:ed)? changes?\b|\bsuggest(?:ing|ions?)\b/i.test(text)
|
|
1264
1321
|
&& !/\breview panel\b/i.test(text);
|
package/extension/src/popup.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
(function initPopup() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// Keep only copy produced by this popup eligible for a language refresh.
|
|
5
|
+
const popupCopyPairs = new Map();
|
|
6
|
+
let newSiteCopyNodes = [];
|
|
7
|
+
|
|
4
8
|
const popupScriptUrl = document.currentScript?.src || chrome.runtime.getURL('src/popup.js');
|
|
5
9
|
ensureMinimalPopupShell();
|
|
6
10
|
const CodexOverleafCompatibility = window.CodexOverleafCompatibility;
|
|
7
|
-
const
|
|
11
|
+
const isManagedInstall = chrome.runtime.getManifest()?.background?.service_worker === 'bootstrap/background.js'
|
|
12
|
+
|| window.__CODEX_OVERLEAF_RUNTIME_PREFIX__ === 'runtime/';
|
|
13
|
+
let installIdentity = { managed: isManagedInstall };
|
|
14
|
+
const DEFAULT_INSTALL_COMMAND = isManagedInstall ? '' : CodexOverleafCompatibility?.buildInstallCommand?.(
|
|
8
15
|
undefined,
|
|
9
16
|
undefined,
|
|
10
17
|
getCurrentExtensionId()
|
|
@@ -71,6 +78,7 @@
|
|
|
71
78
|
});
|
|
72
79
|
|
|
73
80
|
copyInstallCommand.addEventListener('click', async () => {
|
|
81
|
+
if (!currentInstallCommand) return;
|
|
74
82
|
copyInstallCommand.disabled = true;
|
|
75
83
|
copyFeedback.hidden = true;
|
|
76
84
|
try {
|
|
@@ -81,7 +89,7 @@
|
|
|
81
89
|
copyFeedback.textContent = utx('Copy failed. Select the command above to copy it.', '复制失败,可选中上方命令复制。');
|
|
82
90
|
copyFeedback.hidden = false;
|
|
83
91
|
} finally {
|
|
84
|
-
copyInstallCommand.disabled =
|
|
92
|
+
copyInstallCommand.disabled = !currentInstallCommand;
|
|
85
93
|
}
|
|
86
94
|
});
|
|
87
95
|
|
|
@@ -157,6 +165,7 @@
|
|
|
157
165
|
const granted = await chrome.permissions.request({ origins: missing }).catch(() => false);
|
|
158
166
|
if (granted) section.remove();
|
|
159
167
|
});
|
|
168
|
+
newSiteCopyNodes = [title, detail, button];
|
|
160
169
|
section.append(title, detail, button);
|
|
161
170
|
document.getElementById('popup-shell')?.append(section);
|
|
162
171
|
}
|
|
@@ -172,10 +181,10 @@
|
|
|
172
181
|
connectionDetails.setAttribute('aria-label', utx('Version details', '版本信息'));
|
|
173
182
|
connectionToggle.title = utx('Show or hide version details', '展开或收起版本信息');
|
|
174
183
|
copyInstallCommand.textContent = utx('Copy install command', '复制安装命令');
|
|
184
|
+
[status, copyFeedback, ...newSiteCopyNodes].forEach(refreshPopupCopy);
|
|
175
185
|
if (lastCompatibility) {
|
|
176
186
|
setVersionStatus(lastCompatibility);
|
|
177
|
-
if (!nativeInstall.hidden)
|
|
178
|
-
lastCompatibility.status, getCompatibilityClassification(lastCompatibility));
|
|
187
|
+
if (!nativeInstall.hidden) showNativeInstallGuide(lastCompatibility);
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
190
|
|
|
@@ -217,6 +226,7 @@
|
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
async function checkNativeHost() {
|
|
229
|
+
await readInstallIdentity();
|
|
220
230
|
try {
|
|
221
231
|
const response = await chrome.runtime.sendMessage({
|
|
222
232
|
type: 'codex-overleaf/native-request',
|
|
@@ -244,11 +254,29 @@
|
|
|
244
254
|
}
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
async function readInstallIdentity() {
|
|
258
|
+
if (!isManagedInstall) return;
|
|
259
|
+
try {
|
|
260
|
+
const response = await fetch(chrome.runtime.getURL('.codex-overleaf-managed-extension.json'));
|
|
261
|
+
const marker = response.ok ? await response.json() : null;
|
|
262
|
+
if (marker?.managedBy === 'codex-overleaf-link' && marker.kind === 'extension'
|
|
263
|
+
&& marker.installIdentity?.extensionId === getCurrentExtensionId()) {
|
|
264
|
+
installIdentity = { ...marker.installIdentity, managed: true };
|
|
265
|
+
}
|
|
266
|
+
} catch (_) { /* Older markers cannot prove the original root or browser. */ }
|
|
267
|
+
}
|
|
268
|
+
|
|
247
269
|
function showNativeInstallGuide(compatibility = {}) {
|
|
248
270
|
nativeInstall.hidden = false;
|
|
249
271
|
currentInstallCommand = getCompatibilityUpdateCommand(compatibility);
|
|
250
272
|
installCommand.textContent = currentInstallCommand;
|
|
251
|
-
|
|
273
|
+
copyInstallCommand.disabled = !currentInstallCommand;
|
|
274
|
+
nativeStatusMessage.textContent = !currentInstallCommand && isManagedInstall
|
|
275
|
+
? utx('Repair this Managed installation using its original managed root, browser and extension ID. Rerun the original install-managed command with those parameters.',
|
|
276
|
+
'修复此托管安装需要原管理根目录、浏览器和扩展 ID。请使用这些原参数重新运行 install-managed 安装命令。')
|
|
277
|
+
: getNativeStatusMessage(compatibility.status, getCompatibilityClassification(compatibility))
|
|
278
|
+
+ ((compatibility.native?.platform || installIdentity.platform) === 'win32' && isManagedInstall
|
|
279
|
+
? utx(' Run the command in PowerShell.', ' 请在 PowerShell 中运行该命令。') : '');
|
|
252
280
|
}
|
|
253
281
|
|
|
254
282
|
function evaluateNativeCompatibility(response) {
|
|
@@ -303,7 +331,8 @@
|
|
|
303
331
|
version: CodexOverleafCompatibility?.BUILD_TARGET_VERSION ||
|
|
304
332
|
chrome.runtime.getManifest?.().version ||
|
|
305
333
|
'',
|
|
306
|
-
extensionId: getCurrentExtensionId()
|
|
334
|
+
extensionId: getCurrentExtensionId(),
|
|
335
|
+
...(isManagedInstall ? { installIdentity } : {})
|
|
307
336
|
};
|
|
308
337
|
}
|
|
309
338
|
|
|
@@ -331,8 +360,9 @@
|
|
|
331
360
|
return CodexOverleafCompatibility?.buildInstallCommand?.(
|
|
332
361
|
compatibility.recommendedVersion || CodexOverleafCompatibility.BUILD_TARGET_VERSION,
|
|
333
362
|
compatibility.native?.platform || compatibility.platform,
|
|
334
|
-
getCurrentExtensionId()
|
|
335
|
-
|
|
363
|
+
getCurrentExtensionId(),
|
|
364
|
+
installIdentity
|
|
365
|
+
) || (isManagedInstall ? '' : compatibility.updateCommand || compatibility.installCommand || DEFAULT_INSTALL_COMMAND);
|
|
336
366
|
}
|
|
337
367
|
|
|
338
368
|
function getStatusIconText(classification) {
|
|
@@ -558,7 +588,7 @@ async function sendConsentAction(action) {
|
|
|
558
588
|
const summary = document.createElement('summary');
|
|
559
589
|
summary.textContent = utx('Technical details', '技术详情');
|
|
560
590
|
const code = document.createElement('code');
|
|
561
|
-
code.textContent = [state.code, state
|
|
591
|
+
code.textContent = [state.code, formatUpdateFailure(state)].filter(Boolean).join(': ');
|
|
562
592
|
details.append(summary, code);
|
|
563
593
|
card.append(details);
|
|
564
594
|
}
|
|
@@ -668,12 +698,26 @@ async function sendConsentAction(action) {
|
|
|
668
698
|
awaiting_health: [utx('Checking updated components', '正在检查更新后的组件'), latest, utx('Restarting and checking both components', '正在重启并检查两个组件')],
|
|
669
699
|
committed: [utx('Update complete', '更新完成'), latest || current, utx('Extension and Native Host are healthy.', '扩展与 Native Host 均运行正常。')],
|
|
670
700
|
rolled_back: [utx('Previous version restored', '已恢复上一版本'), current, utx('Health check failed, so the previous coordinated pair was restored.', '健康检查失败,已恢复上一组协调版本。')],
|
|
671
|
-
failed: [utx('Update could not continue', '更新无法继续'), latest, state
|
|
701
|
+
failed: [utx('Update could not continue', '更新无法继续'), latest, formatUpdateFailure(state) || utx('Check the details and retry.', '请查看详情后重试。')]
|
|
672
702
|
};
|
|
673
703
|
const value = map[state.state] || map.idle;
|
|
674
704
|
return { title: value[0], version: value[1], detail: value[2], phase: value[2] };
|
|
675
705
|
}
|
|
676
706
|
|
|
707
|
+
function formatUpdateFailure(state = {}) {
|
|
708
|
+
const code = String(state.code || '');
|
|
709
|
+
if (code === 'update_recovery_pending') return utx('The update result is being checked. Recovery has not been confirmed.', '正在确认更新结果,恢复状态尚未确认。');
|
|
710
|
+
if (['update_bootstrap_health_timeout', 'update_bootstrap_reload_failed'].includes(code)) return utx('The updated startup components did not finish restarting and confirming. Check recovery before retrying.', '更新后的启动组件未能完成重启和确认,请先检查恢复状态。');
|
|
711
|
+
if (['update_bootstrap_source_mismatch', 'update_bootstrap_source_version_mismatch'].includes(code)) return utx('The installed startup files do not match a supported update source. Restore the previous version in Settings, or reinstall once.', '已安装的启动文件与支持的升级来源不一致。请在设置中恢复到上一版本,或重新安装一次。');
|
|
712
|
+
if (['update_bootstrap_record_missing', 'update_bootstrap_record_invalid', 'update_bootstrap_backup_mismatch', 'update_bootstrap_already_restoring'].includes(code)) return utx('The startup recovery material could not be confirmed. The update result remains unconfirmed.', '尚未确认启动组件的恢复材料有效,更新结果仍待确认。');
|
|
713
|
+
if (['update_bootstrap_confirmation_pending', 'update_bootstrap_native_restart_required'].includes(code)) return utx('Waiting for the updated startup components and Native Host to finish restarting and confirming.', '正在等待更新后的启动组件和本机服务完成重启及确认。');
|
|
714
|
+
if (code === 'update_bootstrap_monitor_unavailable') return utx('The update recovery monitor could not start. Check recovery before retrying.', '更新恢复监护程序未能启动,请先检查恢复状态。');
|
|
715
|
+
if (['update_bootstrap_transaction_mismatch', 'update_bootstrap_transaction_invalid'].includes(code)) return utx('The startup update transaction could not be matched. Check recovery before retrying.', '未能匹配启动组件的更新事务,请先检查恢复状态。');
|
|
716
|
+
if (code.startsWith('update_bootstrap_')) return utx('The startup update package or installed files could not be verified. Check recovery before retrying.', '无法验证启动组件更新包或已安装文件,请先检查恢复状态。');
|
|
717
|
+
if (['update_health_failed', 'update_request_failed', 'update_failed'].includes(code)) return utx('The updated components could not be confirmed. Check recovery before retrying.', '尚未确认更新后的组件正常,请先检查恢复状态。');
|
|
718
|
+
return state.message || '';
|
|
719
|
+
}
|
|
720
|
+
|
|
677
721
|
function formatBlocker(blocker) {
|
|
678
722
|
const labels = {
|
|
679
723
|
unsaved: utx('Overleaf has not confirmed that this document is saved.', 'Overleaf 尚未确认当前文档已保存。'),
|
|
@@ -688,7 +732,15 @@ async function sendConsentAction(action) {
|
|
|
688
732
|
return labels[blocker] || utx('Waiting until every Overleaf tab is saved and idle.', '正在等待所有 Overleaf 标签页完成保存并进入空闲状态。');
|
|
689
733
|
}
|
|
690
734
|
|
|
735
|
+
function refreshPopupCopy(element) {
|
|
736
|
+
const pair = popupCopyPairs.get(element?.textContent);
|
|
737
|
+
if (pair) element.textContent = utx(...pair);
|
|
738
|
+
}
|
|
739
|
+
|
|
691
740
|
function utx(english, chinese) {
|
|
741
|
+
const pair = [english, chinese];
|
|
742
|
+
popupCopyPairs.set(english, pair);
|
|
743
|
+
popupCopyPairs.set(chinese, pair);
|
|
692
744
|
const locale = document.documentElement?.lang || 'en';
|
|
693
745
|
return /^zh\b/i.test(locale) ? chinese : english;
|
|
694
746
|
}
|