codex-overleaf-link 2.5.1 → 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 +149 -398
- package/README.zh-CN.md +153 -404
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +78 -3
- package/extension/src/backgroundSharedSessions.js +55 -2
- package/extension/src/backgroundUpdateCoordinator.js +128 -12
- package/extension/src/content/activeTurnControl.js +164 -7
- package/extension/src/content/composerAttachments.js +11 -2
- package/extension/src/content/composerPanel.js +92 -12
- package/extension/src/content/contentRuntime.js +920 -276
- package/extension/src/content/contextTray.js +8 -2
- package/extension/src/content/contextUsageView.js +267 -0
- package/extension/src/content/diagnosticsPanel.js +18 -0
- package/extension/src/content/generated/content.bundle.js +4496 -924
- package/extension/src/content/generated/content.bundle.meta.json +6 -3
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/localSkillsPanel.js +2 -2
- package/extension/src/content/markdownDomRenderer.js +5 -3
- package/extension/src/content/markdownText.js +42 -5
- package/extension/src/content/modelPicker.js +1 -0
- package/extension/src/content/nativeCompatibilityController.js +29 -10
- package/extension/src/content/otWarmMirror.js +1 -1
- package/extension/src/content/pageBridgeClient.js +2 -2
- package/extension/src/content/panelMaintenance.js +6 -2
- package/extension/src/content/panelRenderer.js +51 -27
- package/extension/src/content/pendingInputView.js +1 -1
- package/extension/src/content/projectSettingsCoordinator.js +30 -6
- package/extension/src/content/providerSettingsDialog.js +2 -1
- package/extension/src/content/recentProjects.js +4 -3
- package/extension/src/content/runActivitySummary.js +122 -14
- package/extension/src/content/runCompletionMeta.js +194 -6
- package/extension/src/content/runController.js +2 -1
- package/extension/src/content/runFailureNotice.js +8 -1
- package/extension/src/content/runPresence.js +18 -8
- package/extension/src/content/runQueueScheduler.js +9 -5
- package/extension/src/content/runRecoveryActions.js +2 -1
- package/extension/src/content/runResultActions.js +22 -20
- package/extension/src/content/runReviewController.js +94 -35
- package/extension/src/content/runTimelineView.js +224 -25
- package/extension/src/content/scopedPersistencePanelState.js +92 -23
- package/extension/src/content/sensitivePreflight.js +14 -1
- package/extension/src/content/sessionManager.js +49 -3
- package/extension/src/content/sessionMenuView.js +54 -18
- package/extension/src/content/sessionPersistence.js +25 -5
- package/extension/src/content/settingsPanel.js +15 -2
- package/extension/src/content/settingsWorkbench.js +127 -89
- package/extension/src/content/sharedSessionViewSync.js +4 -3
- package/extension/src/content/subagentActivityView.js +23 -7
- package/extension/src/content/updateNotice.js +60 -6
- package/extension/src/content/writebackOrchestrator.js +35 -22
- package/extension/src/page/compileBridge.js +193 -180
- package/extension/src/page/overleafCapabilities.js +4 -1
- package/extension/src/page/textCoordinates.js +17 -1
- package/extension/src/page/textFileCreator.js +9 -1
- package/extension/src/page/writebackRouter.js +24 -77
- package/extension/src/pageBridge.js +107 -66
- package/extension/src/popup.js +1 -1
- package/extension/src/shared/agentTranscript.js +350 -17
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/compileAdapter.js +58 -17
- package/extension/src/shared/composerAttachmentDraft.js +6 -1
- package/extension/src/shared/contextUsage.js +125 -0
- package/extension/src/shared/failureReasons.js +42 -3
- package/extension/src/shared/i18n.js +299 -29
- package/extension/src/shared/lineReferences.js +38 -31
- package/extension/src/shared/pageRpcContract.js +1 -1
- package/extension/src/shared/panelIcons.js +39 -0
- 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/sessionState.js +63 -4
- package/extension/src/shared/sessionTitle.js +14 -1
- package/extension/src/shared/sharedSessionBridge.js +81 -13
- package/extension/src/shared/staleGuard.js +72 -63
- package/extension/src/shared/storageDb.js +35 -3
- package/extension/src/shared/storageMigration.js +7 -1
- package/extension/src/shared/storageRunActions.js +62 -8
- package/extension/src/shared/undoOperations.js +126 -28
- package/extension/src/shared/updateRuntimeIdentity.js +16 -2
- package/extension/src/shared/writebackSettlement.js +21 -3
- package/extension/styles/panel.css +1106 -490
- package/native-host/src/bootstrapUpdate.js +52 -16
- package/native-host/src/chatBridgeRequest.js +60 -6
- package/native-host/src/chatBridgeResponse.js +10 -1
- package/native-host/src/chatCompletionsBridge.js +29 -2
- package/native-host/src/codexPromptAssembly.js +86 -118
- package/native-host/src/codexSessionRunner.js +126 -17
- package/native-host/src/providerContextError.js +20 -0
- package/native-host/src/taskRunnerRuntime.js +18 -6
- package/native-host/src/textPatch.js +473 -377
- package/native-host/src/updateManager.js +120 -15
- package/package.json +5 -8
|
@@ -367,12 +367,12 @@
|
|
|
367
367
|
const trackReviewingChanges = options.requireReviewing === true && hasOperations;
|
|
368
368
|
let reviewingPolicy = null;
|
|
369
369
|
if (options.requireReviewing === true && hasOperations) {
|
|
370
|
-
const reviewing = await ensureReviewing({ waitMs: 1800 });
|
|
370
|
+
const reviewing = await ensureReviewing({ waitMs: 1800, runProjectId: options.runProjectId });
|
|
371
371
|
if (!reviewing.ok) {
|
|
372
372
|
return buildReviewingRequiredBlockedResult(operations, reviewing);
|
|
373
373
|
}
|
|
374
374
|
} else if (options.requireEditing === true && hasOperations) {
|
|
375
|
-
const editing = await ensureEditing({ waitMs: 1800 });
|
|
375
|
+
const editing = await ensureEditing({ waitMs: 1800, runProjectId: options.runProjectId });
|
|
376
376
|
if (!editing.ok) {
|
|
377
377
|
return buildEditingRequiredBlockedResult(operations, editing);
|
|
378
378
|
}
|
|
@@ -676,13 +676,13 @@
|
|
|
676
676
|
return decorated;
|
|
677
677
|
}
|
|
678
678
|
current = ready.text;
|
|
679
|
-
freshness =
|
|
679
|
+
freshness = ready.freshness;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
let nextContent = null;
|
|
683
683
|
let preparedPatches;
|
|
684
684
|
if (Array.isArray(operation.patches) && operation.patches.length) {
|
|
685
|
-
preparedPatches = window.CodexOverleafStaleGuard?.prepareOperationPatches(operation, current, options.baseFileLookup)
|
|
685
|
+
preparedPatches = freshness.preparedPatches || window.CodexOverleafStaleGuard?.prepareOperationPatches(operation, current, options.baseFileLookup)
|
|
686
686
|
|| { ok: true, patches: operation.patches };
|
|
687
687
|
const patched = preparedPatches.ok ? applyTextPatches(current, preparedPatches.patches) : preparedPatches;
|
|
688
688
|
if (!patched.ok) {
|
|
@@ -736,12 +736,6 @@
|
|
|
736
736
|
operationType: 'undo', changedDocument: false,
|
|
737
737
|
userMessage: 'The project identity guard is unavailable; the undo write was not applied.' }) };
|
|
738
738
|
if (blocked) return blocked.skipped?.[0]?.result || blocked;
|
|
739
|
-
if (getActiveFilePath() !== operation.path || readActiveEditorText() !== current) {
|
|
740
|
-
const reason = 'The editor changed while confirming Editing mode; the undo write was not applied.';
|
|
741
|
-
return { ok: false, code: 'stale_source_changed', reason,
|
|
742
|
-
failure: buildPageFailure('stale_source_changed', { file: operation.path,
|
|
743
|
-
operationType: 'undo', changedDocument: false, userMessage: reason }) };
|
|
744
|
-
}
|
|
745
739
|
if (!isEditingConfirmedForNoTraceUndo(getReviewingState({})))
|
|
746
740
|
return buildNoTraceUndoBlockedResult([operation], { ok: false, code: 'editing_not_confirmed',
|
|
747
741
|
reason: 'Overleaf left Editing mode before the undo write.' }).skipped[0].result;
|
|
@@ -749,6 +743,14 @@
|
|
|
749
743
|
|
|
750
744
|
const finalReviewBlock = options.reviewGuard?.();
|
|
751
745
|
if (finalReviewBlock) return finalReviewBlock;
|
|
746
|
+
// Read and dispatch synchronously against the same document. Navigation,
|
|
747
|
+
// freshness polling and Undo mode/project checks can all have awaited.
|
|
748
|
+
if (getActiveFilePath() !== operation.path || readActiveEditorText() !== current) {
|
|
749
|
+
const reason = 'The editor changed before the edit could be applied; the document was not overwritten.';
|
|
750
|
+
return { ok: false, code: 'stale_source_changed', reason,
|
|
751
|
+
failure: buildPageFailure('stale_source_changed', { file: operation.path,
|
|
752
|
+
operationType: options.noTraceUndo ? 'undo' : 'edit', changedDocument: false, userMessage: reason }) };
|
|
753
|
+
}
|
|
752
754
|
const result = Array.isArray(operation.patches) && operation.patches.length
|
|
753
755
|
? replaceActiveEditorPatches(preparedPatches.patches, nextContent)
|
|
754
756
|
: replaceActiveEditorText(nextContent);
|
|
@@ -827,6 +829,7 @@
|
|
|
827
829
|
}),
|
|
828
830
|
verified: true,
|
|
829
831
|
verifiedContent: nextContent,
|
|
832
|
+
...(preparedPatches ? { appliedPatches: preparedPatches.patches } : {}),
|
|
830
833
|
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
831
834
|
};
|
|
832
835
|
}
|
|
@@ -840,6 +843,7 @@
|
|
|
840
843
|
...result,
|
|
841
844
|
verified: true,
|
|
842
845
|
verifiedContent: nextContent,
|
|
846
|
+
...(preparedPatches ? { appliedPatches: preparedPatches.patches } : {}),
|
|
843
847
|
trackedChangeCapture,
|
|
844
848
|
trackedChanges: mergeTrackedChangeRefs(trackedChanges)
|
|
845
849
|
};
|
|
@@ -970,35 +974,17 @@
|
|
|
970
974
|
lastText = readActiveEditorText();
|
|
971
975
|
const activeFileMatches = lastActivePath === filePath;
|
|
972
976
|
const identityChanged = Boolean(previousEditorIdentity) && activeEditorIdentityChanged(previousEditorIdentity);
|
|
973
|
-
const baseComparison = compareEditorTextToBase(filePath, lastText, baseFileLookup);
|
|
974
|
-
const baseMatches = baseComparison.matches;
|
|
975
977
|
const settledLongEnough = Date.now() - startedAt >= minSettleMs;
|
|
976
978
|
const contentChangedFromPrevious = !options.previousSignature
|
|
977
979
|
|| contentSignature(lastText) !== options.previousSignature;
|
|
978
|
-
//
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
//
|
|
982
|
-
// 1. editor identity visibly changed (fast path when it works);
|
|
983
|
-
// 2. the editor shows EXACTLY the target file's expected base —
|
|
984
|
-
// the strongest possible proof the right document is loaded;
|
|
985
|
-
// 3. same-file reopen settled with unchanged content;
|
|
986
|
-
// 4. base UNKNOWN only: the content moved off the previous document
|
|
987
|
-
// and the settle window elapsed. When the base IS known, content
|
|
988
|
-
// must match it (acceptor 2) — a weakly-anchored patch (e.g. an
|
|
989
|
-
// insert into an empty file, expected:'') would otherwise anchor
|
|
990
|
-
// trivially into a wrong-but-different document.
|
|
980
|
+
// Confirm navigation independently of source freshness. A stale target
|
|
981
|
+
// can be correctly open; identical baseline bytes alone cannot prove a
|
|
982
|
+
// switch. Content movement remains the fallback for editor lifecycles
|
|
983
|
+
// whose document identity cannot be observed reliably.
|
|
991
984
|
const switchConfirmed = identityChanged
|
|
992
|
-
||
|
|
993
|
-
|| (
|
|
994
|
-
|
|
995
|
-
&& contentSignature(lastText) === options.previousSignature
|
|
996
|
-
&& settledLongEnough)
|
|
997
|
-
|| (!baseComparison.known && contentChangedFromPrevious && settledLongEnough);
|
|
998
|
-
const contentNoLongerLooksLikePreviousDocument = contentChangedFromPrevious
|
|
999
|
-
|| baseMatches
|
|
1000
|
-
|| options.initialActivePath === filePath;
|
|
1001
|
-
if (activeFileMatches && switchConfirmed && settledLongEnough && contentNoLongerLooksLikePreviousDocument) {
|
|
985
|
+
|| options.initialActivePath === filePath
|
|
986
|
+
|| (contentChangedFromPrevious && settledLongEnough);
|
|
987
|
+
if (activeFileMatches && switchConfirmed && settledLongEnough) {
|
|
1002
988
|
return {
|
|
1003
989
|
ok: true,
|
|
1004
990
|
text: lastText
|
|
@@ -1074,47 +1060,6 @@
|
|
|
1074
1060
|
};
|
|
1075
1061
|
}
|
|
1076
1062
|
|
|
1077
|
-
function editorContentMatchesBase(filePath, content, baseFileLookup) {
|
|
1078
|
-
return compareEditorTextToBase(filePath, content, baseFileLookup).matches;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
function compareEditorTextToBase(filePath, content, baseFileLookup) {
|
|
1082
|
-
const normalizedPath = normalizeSafeProjectPath(filePath);
|
|
1083
|
-
const current = normalizeEditorTextForComparison(content);
|
|
1084
|
-
if (!baseFileLookup || !normalizedPath || !baseFileLookup.has(normalizedPath)) {
|
|
1085
|
-
return {
|
|
1086
|
-
known: false,
|
|
1087
|
-
hasContent: false,
|
|
1088
|
-
matches: false,
|
|
1089
|
-
baseLength: null,
|
|
1090
|
-
currentLength: current.length
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
const base = normalizeEditorTextForComparison(baseFileLookup.get(normalizedPath));
|
|
1094
|
-
const matches = current === base;
|
|
1095
|
-
if (!matches && base.trim().length > 0) {
|
|
1096
|
-
console.warn('[codex-overleaf] editorContentMatchesBase MISMATCH', filePath,
|
|
1097
|
-
'base.length:', base.length, 'current.length:', current.length,
|
|
1098
|
-
'base[0..80]:', JSON.stringify(base.slice(0, 80)),
|
|
1099
|
-
'current[0..80]:', JSON.stringify(current.slice(0, 80)));
|
|
1100
|
-
}
|
|
1101
|
-
return {
|
|
1102
|
-
known: true,
|
|
1103
|
-
hasContent: base.trim().length > 0,
|
|
1104
|
-
matches,
|
|
1105
|
-
baseLength: base.length,
|
|
1106
|
-
currentLength: current.length
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
function hasBaseFileContent(filePath, baseFileLookup) {
|
|
1111
|
-
if (!baseFileLookup || !filePath || !baseFileLookup.has(filePath)) {
|
|
1112
|
-
return false;
|
|
1113
|
-
}
|
|
1114
|
-
const content = baseFileLookup.get(filePath);
|
|
1115
|
-
return typeof content === 'string' && content.trim().length > 0;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
1063
|
function normalizeEditorTextForComparison(value) {
|
|
1119
1064
|
const normalized = String(value ?? '').replace(/\r\n/g, '\n');
|
|
1120
1065
|
return normalized.trim().length === 0 ? '' : normalized;
|
|
@@ -1136,7 +1081,8 @@
|
|
|
1136
1081
|
if (freshness.ok) {
|
|
1137
1082
|
return {
|
|
1138
1083
|
ok: true,
|
|
1139
|
-
text
|
|
1084
|
+
text,
|
|
1085
|
+
freshness
|
|
1140
1086
|
};
|
|
1141
1087
|
}
|
|
1142
1088
|
await delay(60);
|
|
@@ -1895,6 +1841,7 @@
|
|
|
1895
1841
|
ok: false,
|
|
1896
1842
|
code: reviewing?.code || 'reviewing_not_enabled',
|
|
1897
1843
|
reason: reviewing?.reason || 'Overleaf Reviewing/Track Changes was not confirmed before writing. Codex did not change this file.',
|
|
1844
|
+
evidence: reviewing?.evidence, failure: reviewing?.failure,
|
|
1898
1845
|
reviewing: reviewing?.reviewing || null
|
|
1899
1846
|
}
|
|
1900
1847
|
})),
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
const textFileCreator = requirePageModule('CodexOverleafTextFileCreator').create({
|
|
74
74
|
window, document, treeOperations, snapshotRouter,
|
|
75
75
|
collectElements, ensureEditing, ensureReviewing, readActiveEditorText, readWriteCancellationSequence,
|
|
76
|
+
findUpgradeModal: () => window.CodexOverleafReviewing.findUpgradeModal(document),
|
|
76
77
|
uploadHelpers: requirePageModule('CodexOverleafBinaryAssetUploader')
|
|
77
78
|
});
|
|
78
79
|
binaryAssetUploader = requirePageModule('CodexOverleafBinaryAssetUploader').create({
|
|
@@ -399,6 +400,11 @@
|
|
|
399
400
|
fallbackUserMessage: 'Target file is active but the editor was not ready before timeout.',
|
|
400
401
|
fallbackNextAction: 'Wait for Overleaf to finish loading, then retry.'
|
|
401
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
|
+
},
|
|
402
408
|
reviewing_state_unknown: {
|
|
403
409
|
stage: 'reviewing', severity: 'blocked', defaultRetryable: true,
|
|
404
410
|
fallbackUserMessage: 'Codex could not determine whether Reviewing/Track Changes is enabled.',
|
|
@@ -488,6 +494,8 @@
|
|
|
488
494
|
findFileTreeManager,
|
|
489
495
|
findHistoryObject,
|
|
490
496
|
findReviewingActivationControl,
|
|
497
|
+
findUpgradeModal: () => window.CodexOverleafReviewing.findUpgradeModal(document),
|
|
498
|
+
reviewingConfirmed: isReviewingConfirmedForWrite(getReviewingState()),
|
|
491
499
|
getCodeMirrorEditorView,
|
|
492
500
|
getDeepActiveElement,
|
|
493
501
|
isInsideCodexPanel,
|
|
@@ -497,7 +505,7 @@
|
|
|
497
505
|
|
|
498
506
|
async function ensureReviewing(params = {}) {
|
|
499
507
|
const initial = getReviewingState(params);
|
|
500
|
-
if (isReviewingConfirmedForWrite(initial)) {
|
|
508
|
+
if (isReviewingConfirmedForWrite(initial) && !window.CodexOverleafReviewing.findUpgradeModal(document)) {
|
|
501
509
|
return {
|
|
502
510
|
ok: true,
|
|
503
511
|
activated: false,
|
|
@@ -506,6 +514,7 @@
|
|
|
506
514
|
}
|
|
507
515
|
|
|
508
516
|
const switched = await toggleReviewingMode(true, params);
|
|
517
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
509
518
|
if (switched.ok) {
|
|
510
519
|
return {
|
|
511
520
|
ok: true,
|
|
@@ -553,6 +562,7 @@
|
|
|
553
562
|
}
|
|
554
563
|
|
|
555
564
|
const switched = await toggleReviewingMode(false, params);
|
|
565
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
556
566
|
if (switched.ok) {
|
|
557
567
|
return {
|
|
558
568
|
ok: true,
|
|
@@ -743,9 +753,16 @@
|
|
|
743
753
|
const activeFileMatches = getActiveFilePath() === filePath;
|
|
744
754
|
const text = readActiveEditorText();
|
|
745
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);
|
|
746
763
|
if (activeFileMatches
|
|
747
764
|
&& window.CodexOverleafProjectFiles.isUsableProjectFileContent(text)
|
|
748
|
-
&&
|
|
765
|
+
&& documentChanged) {
|
|
749
766
|
return {
|
|
750
767
|
ok: true,
|
|
751
768
|
text
|
|
@@ -871,6 +888,7 @@
|
|
|
871
888
|
}
|
|
872
889
|
|
|
873
890
|
const switched = await toggleReviewingMode(enabled, params);
|
|
891
|
+
if (switched.code === 'reviewing_unavailable') return { ...switched, reason: switched.failure.userMessage };
|
|
874
892
|
if (switched.ok) {
|
|
875
893
|
return {
|
|
876
894
|
ok: true,
|
|
@@ -917,67 +935,23 @@
|
|
|
917
935
|
}
|
|
918
936
|
|
|
919
937
|
async function toggleReviewingMode(enabled, params = {}) {
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
for (const control of attemptedNodes) {
|
|
926
|
-
clickNode(control);
|
|
927
|
-
let after = await waitForReviewingEnabled(enabled, {
|
|
928
|
-
...params,
|
|
929
|
-
waitMs: Math.min(resolveWaitMs(params), 180)
|
|
930
|
-
});
|
|
931
|
-
if (isReviewingTargetStateConfirmed(enabled, after)) {
|
|
932
|
-
return {
|
|
933
|
-
ok: true,
|
|
934
|
-
attempted: true,
|
|
935
|
-
reviewing: after.reviewing
|
|
936
|
-
};
|
|
938
|
+
const sequence = readWriteCancellationSequence();
|
|
939
|
+
const assertCurrent = () => {
|
|
940
|
+
if (sequence !== readWriteCancellationSequence()) {
|
|
941
|
+
throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
|
|
937
942
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (modeOption) {
|
|
941
|
-
clickNode(modeOption);
|
|
942
|
-
after = await waitForReviewingEnabled(enabled, params);
|
|
943
|
-
if (isReviewingTargetStateConfirmed(enabled, after)) {
|
|
944
|
-
return {
|
|
945
|
-
ok: true,
|
|
946
|
-
attempted: true,
|
|
947
|
-
reviewing: after.reviewing
|
|
948
|
-
};
|
|
949
|
-
}
|
|
943
|
+
if (params.runProjectId && writeGuard.getEditorProjectIdPageSide(params.runProjectId) !== params.runProjectId) {
|
|
944
|
+
throw Object.assign(new Error('The active project changed.'), { code: 'aborted_project_changed' });
|
|
950
945
|
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const visibleModeOption = findReviewingModeOption(enabled, { requireMenuOption: true });
|
|
954
|
-
if (visibleModeOption) {
|
|
955
|
-
clickNode(visibleModeOption);
|
|
956
|
-
const after = await waitForReviewingEnabled(enabled, params);
|
|
957
|
-
return {
|
|
958
|
-
ok: isReviewingTargetStateConfirmed(enabled, after),
|
|
959
|
-
attempted: true,
|
|
960
|
-
reviewing: after.reviewing
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
const after = getReviewingState(params);
|
|
965
|
-
return {
|
|
966
|
-
ok: false,
|
|
967
|
-
attempted: attemptedNodes.length > 0,
|
|
968
|
-
reviewing: after.reviewing
|
|
969
946
|
};
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
current = getReviewingState(params);
|
|
979
|
-
}
|
|
980
|
-
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
|
+
});
|
|
981
955
|
}
|
|
982
956
|
|
|
983
957
|
function resolveWaitMs(params = {}) {
|
|
@@ -1109,7 +1083,72 @@
|
|
|
1109
1083
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
1110
1084
|
}
|
|
1111
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
|
+
|
|
1112
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
|
+
}
|
|
1113
1152
|
const candidates = collectReviewingModeOptionNodes()
|
|
1114
1153
|
.filter(node => isReviewingModeOptionCandidate(node, enabled, options))
|
|
1115
1154
|
.sort((left, right) => scoreReviewingModeOption(right, enabled) - scoreReviewingModeOption(left, enabled));
|
|
@@ -1145,7 +1184,7 @@
|
|
|
1145
1184
|
}
|
|
1146
1185
|
|
|
1147
1186
|
function isReviewingModeMenuControlCandidate(node) {
|
|
1148
|
-
if (!node || node
|
|
1187
|
+
if (!isReviewingModeNodeAvailable(node) || isMenuLikeModeOption(node)) {
|
|
1149
1188
|
return false;
|
|
1150
1189
|
}
|
|
1151
1190
|
const text = readNodeSignalText(node);
|
|
@@ -1198,7 +1237,7 @@
|
|
|
1198
1237
|
}
|
|
1199
1238
|
|
|
1200
1239
|
function isReviewingModeOptionCandidate(node, enabled, options = {}) {
|
|
1201
|
-
if (!node
|
|
1240
|
+
if (!isReviewingModeNodeAvailable(node)) {
|
|
1202
1241
|
return false;
|
|
1203
1242
|
}
|
|
1204
1243
|
if (options.requireMenuOption && !isMenuLikeModeOption(node)) {
|
|
@@ -1244,10 +1283,10 @@
|
|
|
1244
1283
|
&& !/\bsyntax highlighting\b/i.test(text);
|
|
1245
1284
|
}
|
|
1246
1285
|
|
|
1247
|
-
function findReviewingActivationControl() {
|
|
1286
|
+
function findReviewingActivationControl(enabled = true) {
|
|
1248
1287
|
const nodes = collectReviewingControlNodes();
|
|
1249
1288
|
const candidates = nodes
|
|
1250
|
-
.filter(node => isReviewingActivationCandidate(node))
|
|
1289
|
+
.filter(node => isReviewingActivationCandidate(node, enabled))
|
|
1251
1290
|
.sort((left, right) => scoreReviewingControl(right) - scoreReviewingControl(left));
|
|
1252
1291
|
return candidates[0] || null;
|
|
1253
1292
|
}
|
|
@@ -1271,10 +1310,12 @@
|
|
|
1271
1310
|
]).slice(0, 1500);
|
|
1272
1311
|
}
|
|
1273
1312
|
|
|
1274
|
-
function isReviewingActivationCandidate(node) {
|
|
1275
|
-
if (!node || node
|
|
1313
|
+
function isReviewingActivationCandidate(node, enabled) {
|
|
1314
|
+
if (!isReviewingModeNodeAvailable(node) || isMenuLikeModeOption(node)) {
|
|
1276
1315
|
return false;
|
|
1277
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;
|
|
1278
1319
|
const text = readNodeSignalText(node);
|
|
1279
1320
|
return /\breviewing\b|\btrack(?:ed)? changes?\b|\bsuggest(?:ing|ions?)\b/i.test(text)
|
|
1280
1321
|
&& !/\breview panel\b/i.test(text);
|
package/extension/src/popup.js
CHANGED
|
@@ -708,7 +708,7 @@ async function sendConsentAction(action) {
|
|
|
708
708
|
const code = String(state.code || '');
|
|
709
709
|
if (code === 'update_recovery_pending') return utx('The update result is being checked. Recovery has not been confirmed.', '正在确认更新结果,恢复状态尚未确认。');
|
|
710
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.
|
|
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
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
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
714
|
if (code === 'update_bootstrap_monitor_unavailable') return utx('The update recovery monitor could not start. Check recovery before retrying.', '更新恢复监护程序未能启动,请先检查恢复状态。');
|