codex-overleaf-link 2.3.6 → 2.4.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 +59 -34
- package/README.zh-CN.md +543 -0
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +3 -0
- package/extension/src/content/activeTurnControl.js +1 -0
- package/extension/src/content/assetTransferBroker.js +2 -0
- package/extension/src/content/contentRuntime.js +53 -50
- package/extension/src/content/contextTray.js +4 -0
- package/extension/src/content/diagnosticsController.js +4 -0
- package/extension/src/content/diagnosticsPanel.js +128 -4
- package/extension/src/content/generated/content.bundle.js +583 -112
- package/extension/src/content/generated/content.bundle.meta.json +4 -3
- package/extension/src/content/legacyGlobalRegistry.js +1 -0
- package/extension/src/content/modelPicker.js +21 -9
- package/extension/src/content/modelPickerSupport.js +38 -1
- package/extension/src/content/nativeCompatibilityController.js +4 -1
- package/extension/src/content/otWarmMirror.js +34 -30
- package/extension/src/content/otWarmMirrorController.js +20 -0
- package/extension/src/content/pageBridgeClient.js +2 -0
- package/extension/src/content/runResultActions.js +16 -1
- package/extension/src/content/runTimelineView.js +1 -1
- package/extension/src/content/sessionForkController.js +20 -11
- package/extension/src/content/trackedChangeCaptureController.js +153 -0
- package/extension/src/content/writebackOrchestrator.js +31 -0
- package/extension/src/page/binaryAssetUploader.js +101 -29
- package/extension/src/page/overleafRealtimeObserver.js +4 -1
- package/extension/src/page/textFileCreator.js +493 -0
- package/extension/src/page/trackedChangeCapture.js +342 -0
- package/extension/src/page/trackedChangesLifecycle.js +145 -149
- package/extension/src/page/treeOperations.js +30 -2
- package/extension/src/page/writebackRouter.js +94 -48
- package/extension/src/pageBridge.js +62 -25
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/pageRpcContract.js +2 -1
- package/extension/src/shared/sessionState.js +6 -2
- package/extension/src/shared/storageDb.js +10 -12
- package/extension/src/shared/storageRunActions.js +29 -1
- package/extension/src/shared/writebackSettlement.js +19 -5
- package/extension/styles/panel.css +55 -10
- package/native-host/src/taskRunnerRuntime.js +25 -0
- package/package.json +5 -1
|
@@ -51,8 +51,7 @@
|
|
|
51
51
|
'codex.providers.delete',
|
|
52
52
|
'task.run',
|
|
53
53
|
'mirror.sync',
|
|
54
|
-
'mirror.patchFiles',
|
|
55
|
-
'mirror.confirmWriteback',
|
|
54
|
+
'mirror.patchFiles', 'mirror.confirmWriteback', 'mirror.invalidate',
|
|
56
55
|
'mirror.scanSensitive',
|
|
57
56
|
'codex.history.clearPlugin',
|
|
58
57
|
'skills.list',
|
|
@@ -583,6 +582,7 @@
|
|
|
583
582
|
RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
|
|
584
583
|
getState: () => state,
|
|
585
584
|
getCurrentRunView: () => currentRunView,
|
|
585
|
+
appendRunRecordEvent,
|
|
586
586
|
onMirrorRefreshSettled: runSettlementPersistence.settleMirrorRefresh
|
|
587
587
|
});
|
|
588
588
|
const {
|
|
@@ -790,6 +790,7 @@
|
|
|
790
790
|
},
|
|
791
791
|
saveState,
|
|
792
792
|
saveStateSoon,
|
|
793
|
+
onProjectFilesReady: refreshHistoricalProjectReferences,
|
|
793
794
|
updateActiveSession,
|
|
794
795
|
callPageBridge,
|
|
795
796
|
getCurrentProjectId,
|
|
@@ -1776,9 +1777,7 @@
|
|
|
1776
1777
|
return contextTrayController.setContextStatus(text);
|
|
1777
1778
|
}
|
|
1778
1779
|
|
|
1779
|
-
function resetContextProject() {
|
|
1780
|
-
return contextTrayController.resetContextProject();
|
|
1781
|
-
}
|
|
1780
|
+
function resetContextProject() { return contextTrayController.resetContextProject(); }
|
|
1782
1781
|
|
|
1783
1782
|
async function runTask(options = {}) {
|
|
1784
1783
|
// Barrier on a still-flying background mirror refresh from the previous
|
|
@@ -2485,14 +2484,15 @@
|
|
|
2485
2484
|
setRunning(false);
|
|
2486
2485
|
nativeChannel.clearActiveRequest();
|
|
2487
2486
|
stopRunElapsedTick();
|
|
2488
|
-
if (isExperimentalOtEnabled()) {
|
|
2489
|
-
await resumeOtWarmMirror('run-settled');
|
|
2490
|
-
}
|
|
2491
2487
|
await flushQueuedSaveState().catch(() => {});
|
|
2492
2488
|
currentRunView = null;
|
|
2493
2489
|
runCancellationRequested = false;
|
|
2494
2490
|
runCancellationController = null;
|
|
2495
2491
|
activeTurnControl.release(settlement.requestId);
|
|
2492
|
+
if (isExperimentalOtEnabled()) {
|
|
2493
|
+
try { await resumeOtWarmMirror('run-settled'); }
|
|
2494
|
+
catch (_error) { updateOtStatusDisplay('unavailable'); }
|
|
2495
|
+
}
|
|
2496
2496
|
if (settlement.requestId) {
|
|
2497
2497
|
activeTurnControl.acknowledge(settlement.requestId).catch(() => {});
|
|
2498
2498
|
}
|
|
@@ -5570,6 +5570,7 @@
|
|
|
5570
5570
|
title: session.title || '',
|
|
5571
5571
|
titleSource: session.titleSource || 'auto',
|
|
5572
5572
|
focusFiles: session.focusFiles || [],
|
|
5573
|
+
projectReferenceFiles: session.projectReferenceFiles || [],
|
|
5573
5574
|
codexThreadId: session.codexThreadId || '',
|
|
5574
5575
|
createdAt: session.createdAt,
|
|
5575
5576
|
updatedAt: session.updatedAt,
|
|
@@ -5977,15 +5978,7 @@
|
|
|
5977
5978
|
}
|
|
5978
5979
|
|
|
5979
5980
|
function getRenderedModelEntries() {
|
|
5980
|
-
return
|
|
5981
|
-
id: option.value,
|
|
5982
|
-
label: option.textContent,
|
|
5983
|
-
reasoningEfforts: (option.dataset.reasoningEfforts || '').split(',').filter(Boolean),
|
|
5984
|
-
defaultReasoningEffort: option.dataset.defaultReasoningEffort || '',
|
|
5985
|
-
reasoningPresentation: option.dataset.reasoningPresentation || '',
|
|
5986
|
-
speedTiers: (option.dataset.speedTiers || 'standard').split(',').filter(Boolean),
|
|
5987
|
-
defaultSpeedTier: option.dataset.defaultSpeedTier || 'standard'
|
|
5988
|
-
}));
|
|
5981
|
+
return Modules.ModelPickerSupport.getRenderedModelEntries(panel);
|
|
5989
5982
|
}
|
|
5990
5983
|
|
|
5991
5984
|
async function selectMode(mode) {
|
|
@@ -6123,6 +6116,7 @@
|
|
|
6123
6116
|
applySessionLabel();
|
|
6124
6117
|
renderSessionList();
|
|
6125
6118
|
renderRunHistory(options);
|
|
6119
|
+
getTrackedChangeCaptureController().schedule();
|
|
6126
6120
|
renderContextSelection();
|
|
6127
6121
|
renderContextSummary();
|
|
6128
6122
|
// v1.8.0 C4: refresh recovery — small attachments persisted in state are
|
|
@@ -6212,7 +6206,7 @@
|
|
|
6212
6206
|
undoExpectedFiles: [],
|
|
6213
6207
|
undoStatus: '',
|
|
6214
6208
|
queueItemId,
|
|
6215
|
-
nativeRequestId: '',
|
|
6209
|
+
nativeRequestId: '', codexThreadId: '',
|
|
6216
6210
|
codexTurnId: '',
|
|
6217
6211
|
nativeEventSeq: 0,
|
|
6218
6212
|
executionSnapshot: executionSnapshot
|
|
@@ -6379,7 +6373,7 @@
|
|
|
6379
6373
|
currentRunView.activeTurn = { threadId, turnId };
|
|
6380
6374
|
const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
|
|
6381
6375
|
if (record) {
|
|
6382
|
-
record.codexTurnId = turnId;
|
|
6376
|
+
record.codexThreadId = threadId; record.codexTurnId = turnId;
|
|
6383
6377
|
record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
|
|
6384
6378
|
}
|
|
6385
6379
|
if (threadId) {
|
|
@@ -6753,6 +6747,13 @@
|
|
|
6753
6747
|
return files;
|
|
6754
6748
|
}
|
|
6755
6749
|
|
|
6750
|
+
function refreshHistoricalProjectReferences(project) {
|
|
6751
|
+
if (!project?.id || String(project.id) !== getCurrentProjectId()) return;
|
|
6752
|
+
state = updateActiveSession(state, { projectReferenceFiles: captureProjectReferenceFiles(project) });
|
|
6753
|
+
saveStateSoon();
|
|
6754
|
+
if (!currentRunView && !trackedChangeInFlight.size) renderRunHistory({ preserveScroll: true });
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6756
6757
|
function persistCurrentProjectReferenceFiles(project) {
|
|
6757
6758
|
const projectFiles = captureProjectReferenceFiles(project);
|
|
6758
6759
|
if (currentRunView) {
|
|
@@ -6884,6 +6885,7 @@
|
|
|
6884
6885
|
runProjectId: getRunProjectIdForWriteback(run)
|
|
6885
6886
|
});
|
|
6886
6887
|
const undoApplied = isUndoResultEffectivelyApplied(run, result);
|
|
6888
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6887
6889
|
appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
|
|
6888
6890
|
appendRunRecordEvent(runId, {
|
|
6889
6891
|
title: tr('undoResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
|
|
@@ -6946,11 +6948,16 @@
|
|
|
6946
6948
|
});
|
|
6947
6949
|
|
|
6948
6950
|
let result;
|
|
6951
|
+
let trackedUndoPostFilesAtDispatch;
|
|
6949
6952
|
try {
|
|
6953
|
+
trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
|
|
6950
6954
|
result = await callPageBridge('rejectTrackedChanges', {
|
|
6951
6955
|
trackedChanges: run.undoTrackedChanges || [],
|
|
6952
6956
|
expectedFiles: run.undoExpectedFiles || [],
|
|
6953
|
-
postFiles:
|
|
6957
|
+
postFiles: trackedUndoPostFilesAtDispatch,
|
|
6958
|
+
untrackedUndo: run.executionSnapshot
|
|
6959
|
+
? run.executionSnapshot.requireReviewing === false && ['submitted', 'legacy-captured'].includes(run.executionSnapshot.source)
|
|
6960
|
+
: getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
|
|
6954
6961
|
// Welcome-panel + write-guard:
|
|
6955
6962
|
// bind the reject to the run's original project. If the user has
|
|
6956
6963
|
// navigated away the page-side guard refuses with
|
|
@@ -6962,6 +6969,7 @@
|
|
|
6962
6969
|
trackedChangeInFlight.delete(runId);
|
|
6963
6970
|
}
|
|
6964
6971
|
}
|
|
6972
|
+
writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
|
|
6965
6973
|
appendRunRecordEvent(runId, {
|
|
6966
6974
|
title: trackedUndo
|
|
6967
6975
|
? tr('undoTrackedResult', { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 })
|
|
@@ -6981,7 +6989,8 @@
|
|
|
6981
6989
|
});
|
|
6982
6990
|
if (lifecycleReject) {
|
|
6983
6991
|
WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
|
|
6984
|
-
buildFailure: buildContentFailure
|
|
6992
|
+
buildFailure: buildContentFailure,
|
|
6993
|
+
postFiles: trackedUndoPostFilesAtDispatch
|
|
6985
6994
|
});
|
|
6986
6995
|
applyTrackedChangeSettlement(runId, 'reject', result);
|
|
6987
6996
|
return;
|
|
@@ -7195,6 +7204,25 @@
|
|
|
7195
7204
|
return WritebackSettlement.attachVerifiedContentToOperation(operation, result);
|
|
7196
7205
|
}
|
|
7197
7206
|
|
|
7207
|
+
let trackedChangeCaptureController = null;
|
|
7208
|
+
function getTrackedChangeCaptureController() {
|
|
7209
|
+
return trackedChangeCaptureController ||= Modules.TrackedChangeCaptureController.create({
|
|
7210
|
+
getRuns: () => (state.sessions || []).flatMap(session => session.runs || []),
|
|
7211
|
+
getProjectId: () => window.location.pathname.match(/^\/project\/([^/]+)/)?.[1] || '',
|
|
7212
|
+
buildExpectedFilesAfterOperations, normalizeTrackedChanges: normalizeApplyTrackedChanges, callPageBridge,
|
|
7213
|
+
persist: () => flushQueuedSaveState({ preserveRunActionPayload: true }),
|
|
7214
|
+
refresh: run => refreshRunCardControls(run.id),
|
|
7215
|
+
notify: (run, kind, detail) => appendRunRecordEvent(run.id, {
|
|
7216
|
+
title: {
|
|
7217
|
+
pending: tx('Files written; confirming tracked changes.', '文件已写入,正在确认留痕记录。'),
|
|
7218
|
+
observed: tx('Tracked changes confirmed; Accept is available.', '留痕记录已确认,可以接受改动。'),
|
|
7219
|
+
needs_review: tx('Tracked changes could not be safely attributed. Review them in Overleaf; Undo is preserved.', '留痕归属尚未确认,请在 Overleaf 审阅;原有撤销能力已保留。')
|
|
7220
|
+
}[kind], status: 'info', detail
|
|
7221
|
+
}),
|
|
7222
|
+
onError: error => appendPlainLog(tx('Tracked-change confirmation unavailable: ', '留痕确认暂不可用:') + error.message)
|
|
7223
|
+
});
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7198
7226
|
function recordUndoFromApply(project, applyResult) {
|
|
7199
7227
|
const appliedEntries = getAppliedEntries(applyResult);
|
|
7200
7228
|
if (!currentRunView?.recordId || !appliedEntries.length) {
|
|
@@ -7238,6 +7266,7 @@
|
|
|
7238
7266
|
if (combinedTrackedChanges.length) {
|
|
7239
7267
|
record.trackedChangeStatus = 'pending';
|
|
7240
7268
|
}
|
|
7269
|
+
getTrackedChangeCaptureController().record(record, applyResult?.trackedChangeCaptures);
|
|
7241
7270
|
refreshRunCardControls(record.id);
|
|
7242
7271
|
if (combinedTrackedChanges.length) {
|
|
7243
7272
|
appendRunEvent({
|
|
@@ -7302,38 +7331,12 @@
|
|
|
7302
7331
|
return WritebackSettlement.normalizeApplyTrackedChanges(changes);
|
|
7303
7332
|
}
|
|
7304
7333
|
|
|
7305
|
-
function selectExpectedFilesForTrackedUndo(
|
|
7306
|
-
project,
|
|
7307
|
-
operations = [],
|
|
7308
|
-
trackedChanges = [],
|
|
7309
|
-
previousExpectedFiles = []
|
|
7310
|
-
) {
|
|
7311
|
-
return WritebackSettlement.selectExpectedFilesForTrackedUndo(
|
|
7312
|
-
project,
|
|
7313
|
-
operations,
|
|
7314
|
-
trackedChanges,
|
|
7315
|
-
previousExpectedFiles
|
|
7316
|
-
);
|
|
7334
|
+
function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
|
|
7335
|
+
return WritebackSettlement.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
|
|
7317
7336
|
}
|
|
7318
7337
|
|
|
7319
7338
|
function buildTrackedUndoPostFiles(run) {
|
|
7320
|
-
|
|
7321
|
-
const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
|
|
7322
|
-
if (!expectedFiles.length || !appliedOperations.length) {
|
|
7323
|
-
return [];
|
|
7324
|
-
}
|
|
7325
|
-
|
|
7326
|
-
const postFilesByPath = buildExpectedFilesAfterOperations(
|
|
7327
|
-
{ files: expectedFiles },
|
|
7328
|
-
appliedOperations
|
|
7329
|
-
);
|
|
7330
|
-
const expectedPaths = new Set(expectedFiles.map(file => file?.path).filter(Boolean));
|
|
7331
|
-
return Array.from(postFilesByPath.entries())
|
|
7332
|
-
.filter(([path, content]) => expectedPaths.has(path) && typeof content === 'string')
|
|
7333
|
-
.map(([path, content]) => ({
|
|
7334
|
-
path,
|
|
7335
|
-
content
|
|
7336
|
-
}));
|
|
7339
|
+
return getTrackedChangeCaptureController().buildPostFiles(run);
|
|
7337
7340
|
}
|
|
7338
7341
|
|
|
7339
7342
|
function hasTrackedEditorUndo(run) {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
setState = () => {},
|
|
23
23
|
saveState = null,
|
|
24
24
|
saveStateSoon = () => {},
|
|
25
|
+
onProjectFilesReady = () => {},
|
|
25
26
|
updateActiveSession = defaultUpdateActiveSession,
|
|
26
27
|
callPageBridge = () => Promise.resolve({ ok: false }),
|
|
27
28
|
getCurrentProjectId = () => '',
|
|
@@ -201,6 +202,7 @@
|
|
|
201
202
|
renderContextFiles(contextProject);
|
|
202
203
|
if (isExactContextFileListProject(contextProject)) {
|
|
203
204
|
setContextSyncState('exact-ready');
|
|
205
|
+
onProjectFilesReady(contextProject);
|
|
204
206
|
} else if (contextSyncState !== 'partial') {
|
|
205
207
|
setContextSyncState('exact-loading');
|
|
206
208
|
void enhanceContextFilesFromExactSnapshot({ loadId: contextLoadId });
|
|
@@ -224,6 +226,7 @@
|
|
|
224
226
|
contextProject = project;
|
|
225
227
|
setContextSyncState(isExactContextFileListProject(project) ? 'exact-ready' : 'exact-loading');
|
|
226
228
|
renderContextFiles(project);
|
|
229
|
+
if (isExactContextFileListProject(project)) onProjectFilesReady(project);
|
|
227
230
|
if (!isExactContextFileListProject(project)) {
|
|
228
231
|
void enhanceContextFilesFromExactSnapshot({
|
|
229
232
|
force: Boolean(options.force),
|
|
@@ -292,6 +295,7 @@
|
|
|
292
295
|
contextProject = project;
|
|
293
296
|
setContextSyncState('exact-ready');
|
|
294
297
|
renderContextFiles(project);
|
|
298
|
+
onProjectFilesReady(project);
|
|
295
299
|
} catch (_error) {
|
|
296
300
|
// The page file tree remains usable when the optional exact ZIP list is unavailable.
|
|
297
301
|
if (loadId === contextLoadId) {
|
|
@@ -641,6 +641,10 @@
|
|
|
641
641
|
lines.push(`CodeMirror: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || 'unknown'}`);
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
|
+
const capture = probe?.trackedChangeCapture;
|
|
645
|
+
if (capture) lines.push(tx('Tracked ledger: ', '留痕记录:')
|
|
646
|
+
+ 'source=' + capture.source + '; ready=' + capture.ready + '; count=' + capture.count
|
|
647
|
+
+ '; doc=' + (capture.docId || 'none') + '; adapter=' + capture.adapter + '; reason=' + (capture.reason || 'none'));
|
|
644
648
|
const projectDiagnostics = probe?.projectDiagnostics;
|
|
645
649
|
if (projectDiagnostics) {
|
|
646
650
|
lines.push(`Project records: ${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(', ') || 'none'}`);
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
container,
|
|
11
11
|
callbacks: options.callbacks || {},
|
|
12
12
|
i18n: options.i18n || {},
|
|
13
|
-
dismissInstalled: false
|
|
13
|
+
dismissInstalled: false,
|
|
14
|
+
cleanup: []
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
container.innerHTML = `
|
|
@@ -99,6 +100,11 @@
|
|
|
99
100
|
instance.callbacks.onBeforeOpen?.();
|
|
100
101
|
}
|
|
101
102
|
popover.hidden = !open;
|
|
103
|
+
if (open) {
|
|
104
|
+
openFloating(instance, popover, 300);
|
|
105
|
+
} else {
|
|
106
|
+
hideFloating(popover);
|
|
107
|
+
}
|
|
102
108
|
button.dataset.active = open ? 'true' : 'false';
|
|
103
109
|
button.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
104
110
|
}
|
|
@@ -110,7 +116,7 @@
|
|
|
110
116
|
if (!popover || !button) {
|
|
111
117
|
return;
|
|
112
118
|
}
|
|
113
|
-
popover
|
|
119
|
+
hideFloating(popover);
|
|
114
120
|
button.dataset.active = 'false';
|
|
115
121
|
button.setAttribute('aria-expanded', 'false');
|
|
116
122
|
}
|
|
@@ -119,7 +125,7 @@
|
|
|
119
125
|
const instance = target?._instance || target;
|
|
120
126
|
const result = instance?.container?.querySelector('[data-diagnostics-result]');
|
|
121
127
|
if (result) {
|
|
122
|
-
result
|
|
128
|
+
hideFloating(result);
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -181,6 +187,73 @@
|
|
|
181
187
|
details.open = false;
|
|
182
188
|
details.hidden = !technicalText;
|
|
183
189
|
technical.textContent = technicalText;
|
|
190
|
+
closeMenu(instance);
|
|
191
|
+
openFloating(instance, root, 560);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Keep DOM ownership (theme, locale and callbacks), but render above clipping
|
|
195
|
+
// ancestors where the browser supports the top layer. Fixed positioning is
|
|
196
|
+
// the viewport-based fallback for browsers without the Popover API.
|
|
197
|
+
function openFloating(instance, element, preferredWidth) {
|
|
198
|
+
if (element.isConnected === false) return;
|
|
199
|
+
element.hidden = false;
|
|
200
|
+
if (typeof element.showPopover === 'function') {
|
|
201
|
+
element.setAttribute('popover', 'manual');
|
|
202
|
+
try {
|
|
203
|
+
if (!element.matches(':popover-open')) element.showPopover();
|
|
204
|
+
} catch (_error) {
|
|
205
|
+
element.removeAttribute('popover');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
positionFloating(instance, element, preferredWidth);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function hideFloating(element) {
|
|
212
|
+
if (!element) return;
|
|
213
|
+
if (typeof element.hidePopover === 'function' && element.getAttribute('popover') === 'manual') {
|
|
214
|
+
try {
|
|
215
|
+
if (element.matches(':popover-open')) element.hidePopover();
|
|
216
|
+
} catch (_error) {
|
|
217
|
+
element.removeAttribute('popover');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
element.hidden = true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function positionFloating(instance, element, preferredWidth) {
|
|
224
|
+
if (!element || element.hidden) return;
|
|
225
|
+
const button = instance.container.querySelector('[data-diagnostics-menu]');
|
|
226
|
+
if (!button?.getBoundingClientRect || !element.getBoundingClientRect) return;
|
|
227
|
+
if (button.getClientRects && !button.getClientRects().length) {
|
|
228
|
+
closeMenu(instance);
|
|
229
|
+
closeResult(instance);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const doc = instance.container.ownerDocument || document;
|
|
233
|
+
const view = doc.defaultView || window;
|
|
234
|
+
const viewport = view.visualViewport;
|
|
235
|
+
const width = viewport?.width || view.innerWidth || doc.documentElement?.clientWidth;
|
|
236
|
+
const height = viewport?.height || view.innerHeight || doc.documentElement?.clientHeight;
|
|
237
|
+
if (!(width > 0 && height > 0)) return;
|
|
238
|
+
const margin = 12;
|
|
239
|
+
const gap = 8;
|
|
240
|
+
const leftEdge = (viewport?.offsetLeft || 0) + margin;
|
|
241
|
+
const topEdge = (viewport?.offsetTop || 0) + margin;
|
|
242
|
+
const rightEdge = leftEdge + Math.max(0, width - margin * 2);
|
|
243
|
+
const bottomEdge = topEdge + Math.max(0, height - margin * 2);
|
|
244
|
+
element.style.width = `${Math.min(preferredWidth, Math.max(0, width - margin * 2))}px`;
|
|
245
|
+
element.style.maxHeight = `${Math.min(640, Math.max(0, height - margin * 2))}px`;
|
|
246
|
+
const anchor = button.getBoundingClientRect();
|
|
247
|
+
const bounds = element.getBoundingClientRect();
|
|
248
|
+
const below = Math.max(0, bottomEdge - anchor.bottom - gap);
|
|
249
|
+
const above = Math.max(0, anchor.top - topEdge - gap);
|
|
250
|
+
const desiredTop = bounds.height > below && above > below
|
|
251
|
+
? anchor.top - gap - bounds.height
|
|
252
|
+
: anchor.bottom + gap;
|
|
253
|
+
element.style.left = `${Math.max(leftEdge, Math.min(anchor.right - bounds.width, rightEdge - bounds.width))}px`;
|
|
254
|
+
element.style.top = `${Math.max(topEdge, Math.min(desiredTop, bottomEdge - bounds.height))}px`;
|
|
255
|
+
element.style.right = 'auto';
|
|
256
|
+
element.style.bottom = 'auto';
|
|
184
257
|
}
|
|
185
258
|
|
|
186
259
|
function renderInstallCommand(instance, container, command) {
|
|
@@ -272,13 +345,60 @@
|
|
|
272
345
|
return;
|
|
273
346
|
}
|
|
274
347
|
instance.dismissInstalled = true;
|
|
275
|
-
|
|
348
|
+
const doc = instance.container.ownerDocument || document;
|
|
349
|
+
const view = doc.defaultView || window;
|
|
350
|
+
const listen = (target, type, handler, options) => {
|
|
351
|
+
if (!target?.addEventListener) return;
|
|
352
|
+
target.addEventListener(type, handler, options);
|
|
353
|
+
instance.cleanup.push(() => target.removeEventListener(type, handler, options));
|
|
354
|
+
};
|
|
355
|
+
listen(doc, 'click', event => {
|
|
276
356
|
const wrap = instance.container?.querySelector('.codex-diagnostics-wrap');
|
|
277
357
|
if (!wrap || wrap.contains(event.target)) {
|
|
278
358
|
return;
|
|
279
359
|
}
|
|
280
360
|
closeMenu(instance);
|
|
281
361
|
}, true);
|
|
362
|
+
listen(doc, 'keydown', event => {
|
|
363
|
+
if (event.key !== 'Escape') return;
|
|
364
|
+
const menu = instance.container.querySelector('[data-diagnostics-popover]');
|
|
365
|
+
const result = instance.container.querySelector('[data-diagnostics-result]');
|
|
366
|
+
if (menu && !menu.hidden) closeMenu(instance);
|
|
367
|
+
else if (result && !result.hidden) closeResult(instance);
|
|
368
|
+
else return;
|
|
369
|
+
event.preventDefault();
|
|
370
|
+
event.stopPropagation();
|
|
371
|
+
instance.container.querySelector('[data-diagnostics-menu]')?.focus();
|
|
372
|
+
}, true);
|
|
373
|
+
let frame = null;
|
|
374
|
+
const reposition = () => {
|
|
375
|
+
frame = null;
|
|
376
|
+
positionFloating(instance, instance.container.querySelector('[data-diagnostics-popover]'), 300);
|
|
377
|
+
positionFloating(instance, instance.container.querySelector('[data-diagnostics-result]'), 560);
|
|
378
|
+
};
|
|
379
|
+
const schedule = () => {
|
|
380
|
+
if (frame !== null) return;
|
|
381
|
+
if (view.requestAnimationFrame) frame = view.requestAnimationFrame(reposition);
|
|
382
|
+
else reposition();
|
|
383
|
+
};
|
|
384
|
+
listen(view, 'resize', schedule);
|
|
385
|
+
listen(doc, 'scroll', schedule, true);
|
|
386
|
+
listen(view.visualViewport, 'resize', schedule);
|
|
387
|
+
listen(view.visualViewport, 'scroll', schedule);
|
|
388
|
+
if (typeof view.ResizeObserver === 'function') {
|
|
389
|
+
const observer = new view.ResizeObserver(schedule);
|
|
390
|
+
for (const node of [
|
|
391
|
+
instance.container.closest('#codex-overleaf-panel') || instance.container,
|
|
392
|
+
instance.container.querySelector('[data-diagnostics-popover]'),
|
|
393
|
+
instance.container.querySelector('[data-diagnostics-result]')
|
|
394
|
+
]) {
|
|
395
|
+
if (node) observer.observe(node);
|
|
396
|
+
}
|
|
397
|
+
instance.cleanup.push(() => observer.disconnect());
|
|
398
|
+
}
|
|
399
|
+
instance.cleanup.push(() => {
|
|
400
|
+
if (frame !== null) view.cancelAnimationFrame?.(frame);
|
|
401
|
+
});
|
|
282
402
|
}
|
|
283
403
|
|
|
284
404
|
// Drive the trigger's health dot. `health` is one of ok / warn / fail /
|
|
@@ -450,6 +570,10 @@
|
|
|
450
570
|
}
|
|
451
571
|
|
|
452
572
|
function destroy(instance) {
|
|
573
|
+
closeMenu(instance);
|
|
574
|
+
closeResult(instance);
|
|
575
|
+
for (const cleanup of instance.cleanup || []) cleanup();
|
|
576
|
+
instance.cleanup = [];
|
|
453
577
|
instance.container.textContent = '';
|
|
454
578
|
}
|
|
455
579
|
|