codex-overleaf-link 1.8.6 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -21
- package/bin/codex-overleaf-link.mjs +58 -0
- package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
- package/extension/assets/icons/codex-overleaf-icon.png +0 -0
- package/extension/assets/icons/icon128.png +0 -0
- package/extension/assets/icons/icon16.png +0 -0
- package/extension/assets/icons/icon32.png +0 -0
- package/extension/assets/icons/icon48.png +0 -0
- package/extension/bootstrap/background.js +404 -0
- package/extension/bootstrap/manifest.template.json +51 -0
- package/extension/bootstrap/popup.css +26 -0
- package/extension/bootstrap/popup.html +38 -0
- package/extension/bootstrap/popup.js +44 -0
- package/extension/bootstrap/runtimeContext.js +2 -0
- package/extension/runtime-manifest.json +60 -0
- package/extension/src/background.js +532 -0
- package/extension/src/content/applyResultFormatters.js +387 -0
- package/extension/src/content/composerAttachments.js +522 -0
- package/extension/src/content/composerPanel.js +254 -0
- package/extension/src/content/contentRuntime.js +8897 -0
- package/extension/src/content/contextTray.js +686 -0
- package/extension/src/content/diagnosticsController.js +659 -0
- package/extension/src/content/diagnosticsPanel.js +342 -0
- package/extension/src/content/diffReviewPanel.js +797 -0
- package/extension/src/content/localSkillsPanel.js +280 -0
- package/extension/src/content/markdownText.js +606 -0
- package/extension/src/content/mirrorHealth.js +92 -0
- package/extension/src/content/modelPicker.js +489 -0
- package/extension/src/content/nativeChannel.js +62 -0
- package/extension/src/content/otWarmMirror.js +964 -0
- package/extension/src/content/otWarmMirrorController.js +174 -0
- package/extension/src/content/panelMaintenance.js +367 -0
- package/extension/src/content/panelRenderer.js +253 -0
- package/extension/src/content/recentProjects.js +1063 -0
- package/extension/src/content/reviewHunks.js +247 -0
- package/extension/src/content/runController.js +242 -0
- package/extension/src/content/runTimelineView.js +1131 -0
- package/extension/src/content/sessionManager.js +487 -0
- package/extension/src/content/sessionPanel.js +263 -0
- package/extension/src/content/settingsPanel.js +527 -0
- package/extension/src/content/themeController.js +91 -0
- package/extension/src/content/updateIdle.js +67 -0
- package/extension/src/content/writebackController.js +300 -0
- package/extension/src/content/writebackOrchestrator.js +850 -0
- package/extension/src/contentScript.js +8 -0
- package/extension/src/page/compileBridge.js +460 -0
- package/extension/src/page/overleafCapabilities.js +100 -0
- package/extension/src/page/overleafEditor.js +282 -0
- package/extension/src/page/overleafProjectSnapshot.js +234 -0
- package/extension/src/page/overleafRealtimeObserver.js +491 -0
- package/extension/src/page/pageBridgeCapability.js +71 -0
- package/extension/src/page/snapshotRouter.js +948 -0
- package/extension/src/page/trackedChangesLifecycle.js +1506 -0
- package/extension/src/page/treeOperations.js +1584 -0
- package/extension/src/page/writeGuard.js +142 -0
- package/extension/src/page/writebackRouter.js +1934 -0
- package/extension/src/pageBridge.js +2224 -0
- package/extension/src/popup.js +247 -0
- package/extension/src/shared/agentTranscript.js +18 -4
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/styles/panel.css +4048 -0
- package/native-host/src/index.js +9 -2
- package/native-host/src/managedInstall.js +393 -0
- package/native-host/src/managedLauncherRuntime.js +71 -0
- package/native-host/src/nativeHostPlatform.js +16 -0
- package/native-host/src/runtimeInstaller.js +6 -1
- package/native-host/src/taskRunnerRuntime.js +8 -0
- package/native-host/src/updateArchive.js +253 -0
- package/native-host/src/updateManager.js +548 -0
- package/native-host/src/updateTrust.js +156 -0
- package/package.json +11 -2
- package/scripts/install-managed.mjs +64 -0
- package/scripts/uninstall-managed.mjs +48 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
(function initContentScript() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
if (!window.CodexOverleafContentRuntime || typeof window.CodexOverleafContentRuntime.init !== 'function') {
|
|
5
|
+
throw new Error('Codex Overleaf content runtime did not load.');
|
|
6
|
+
}
|
|
7
|
+
window.CodexOverleafContentRuntime.init();
|
|
8
|
+
})();
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
(function initCompileBridge(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafCompileBridge = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function compileBridgeFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const COMPILE_TEMPLATE_TTL_MS = 5 * 60 * 1000;
|
|
11
|
+
const GLOBAL_STATE_KEY = '__codexOverleafCompileBridgeState';
|
|
12
|
+
|
|
13
|
+
function create(deps = {}) {
|
|
14
|
+
const pageWindow = deps.window || window;
|
|
15
|
+
const pageDocument = deps.document || document;
|
|
16
|
+
let installed = false;
|
|
17
|
+
const state = pageWindow[GLOBAL_STATE_KEY] || {
|
|
18
|
+
capturedRequestTemplate: null,
|
|
19
|
+
lastCompileResponse: null,
|
|
20
|
+
lastCompileAt: 0,
|
|
21
|
+
lastCompileSourceChangeTimestamp: 0,
|
|
22
|
+
lastKnownSourceEditTimestamp: 0,
|
|
23
|
+
originalFetch: null,
|
|
24
|
+
wrappedFetch: null,
|
|
25
|
+
inputListenerInstalled: false
|
|
26
|
+
};
|
|
27
|
+
pageWindow[GLOBAL_STATE_KEY] = state;
|
|
28
|
+
|
|
29
|
+
function install() {
|
|
30
|
+
if (installed) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
installed = true;
|
|
34
|
+
interceptCompileRequests();
|
|
35
|
+
if (typeof pageDocument.addEventListener === 'function' && state.inputListenerInstalled !== true) {
|
|
36
|
+
pageDocument.addEventListener('input', markSourceEditFromInput, true);
|
|
37
|
+
state.inputListenerInstalled = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function interceptCompileRequests() {
|
|
42
|
+
if (state.wrappedFetch && pageWindow.fetch === state.wrappedFetch) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!state.originalFetch || pageWindow.fetch !== state.wrappedFetch) {
|
|
46
|
+
state.originalFetch = pageWindow.fetch;
|
|
47
|
+
}
|
|
48
|
+
const originalFetch = state.originalFetch;
|
|
49
|
+
state.wrappedFetch = async function codexOverleafCompileFetchWrapper(...args) {
|
|
50
|
+
const [resource, init] = args;
|
|
51
|
+
const url = typeof resource === 'string' ? resource : (resource?.url || '');
|
|
52
|
+
const method = (init?.method || 'GET').toUpperCase();
|
|
53
|
+
const isCompileRequest = /\/project\/[^/]+\/compile\b/.test(url) && method === 'POST';
|
|
54
|
+
const compileSourceChangeTimestamp = isCompileRequest ? state.lastKnownSourceEditTimestamp : 0;
|
|
55
|
+
|
|
56
|
+
if (isCompileRequest) {
|
|
57
|
+
state.capturedRequestTemplate = {
|
|
58
|
+
url,
|
|
59
|
+
method: 'POST',
|
|
60
|
+
capturedAt: Date.now(),
|
|
61
|
+
headers: init?.headers ? (
|
|
62
|
+
init.headers instanceof Headers
|
|
63
|
+
? Object.fromEntries(init.headers.entries())
|
|
64
|
+
: { ...init.headers }
|
|
65
|
+
) : {},
|
|
66
|
+
body: init?.body || null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const response = await originalFetch.apply(this, args);
|
|
71
|
+
|
|
72
|
+
if (isCompileRequest && response.ok) {
|
|
73
|
+
try {
|
|
74
|
+
const clone = response.clone();
|
|
75
|
+
const json = await clone.json();
|
|
76
|
+
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
77
|
+
if (CompileAdapter) {
|
|
78
|
+
state.lastCompileResponse = CompileAdapter.parseCompileResponse(json);
|
|
79
|
+
state.lastCompileAt = Date.now();
|
|
80
|
+
state.lastCompileSourceChangeTimestamp = compileSourceChangeTimestamp;
|
|
81
|
+
}
|
|
82
|
+
} catch (_e) { /* ignore parse errors */ }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return response;
|
|
86
|
+
};
|
|
87
|
+
pageWindow.fetch = state.wrappedFetch;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function triggerCompile(params = {}) {
|
|
91
|
+
const saveResult = await waitForCompileSaveState(params);
|
|
92
|
+
if (params.requireVerifiedSave !== false && saveResult.state !== 'verified_saved') {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
reason: saveResult.reason || 'Overleaf save state was not verified.',
|
|
96
|
+
saveStateVerified: false,
|
|
97
|
+
saveState: saveResult.state || 'unknown_timeout'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const saveMetadata = buildSaveStateMetadata(saveResult);
|
|
101
|
+
const sourceChangeTimestamp = state.lastKnownSourceEditTimestamp;
|
|
102
|
+
if (params.preferUiClick === true) {
|
|
103
|
+
const clicked = clickOverleafCompileButton();
|
|
104
|
+
if (clicked) {
|
|
105
|
+
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
106
|
+
if (captured.ok) {
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
compile: state.lastCompileResponse,
|
|
110
|
+
...saveMetadata,
|
|
111
|
+
triggeredBy: 'overleaf-button'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
compile: { ok: true, status: 'triggered' },
|
|
117
|
+
resultCaptured: false,
|
|
118
|
+
reason: captured.reason,
|
|
119
|
+
...saveMetadata,
|
|
120
|
+
triggeredBy: 'overleaf-button'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const template = state.capturedRequestTemplate;
|
|
125
|
+
if (!template) {
|
|
126
|
+
const clicked = clickOverleafCompileButton();
|
|
127
|
+
if (!clicked) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
reason: 'No Overleaf Recompile button or compile request template is available.',
|
|
131
|
+
...saveMetadata
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
135
|
+
if (!captured.ok) {
|
|
136
|
+
return {
|
|
137
|
+
...captured,
|
|
138
|
+
...saveMetadata
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
compile: state.lastCompileResponse,
|
|
144
|
+
...saveMetadata,
|
|
145
|
+
triggeredBy: 'overleaf-button'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (!isFreshCapturedRequestTemplate(template)) {
|
|
149
|
+
const clicked = clickOverleafCompileButton();
|
|
150
|
+
if (!clicked) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
reason: 'Stored compile request template expired. Please click Overleaf Recompile once, then retry.',
|
|
154
|
+
...saveMetadata
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const captured = await waitForCapturedCompile(params.captureTimeoutMs || 60000);
|
|
158
|
+
if (!captured.ok) {
|
|
159
|
+
return {
|
|
160
|
+
...captured,
|
|
161
|
+
...saveMetadata
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
compile: state.lastCompileResponse,
|
|
167
|
+
...saveMetadata,
|
|
168
|
+
triggeredBy: 'overleaf-button'
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const response = await pageWindow.fetch(template.url, {
|
|
174
|
+
method: template.method,
|
|
175
|
+
headers: template.headers,
|
|
176
|
+
body: template.body,
|
|
177
|
+
credentials: 'same-origin'
|
|
178
|
+
});
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
reason: `Compile request failed with status ${response.status}`,
|
|
183
|
+
...saveMetadata
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const json = await response.json();
|
|
187
|
+
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
188
|
+
const parsed = CompileAdapter
|
|
189
|
+
? CompileAdapter.parseCompileResponse(json)
|
|
190
|
+
: { ok: false, reason: 'CompileAdapter not available' };
|
|
191
|
+
state.lastCompileResponse = parsed;
|
|
192
|
+
state.lastCompileAt = Date.now();
|
|
193
|
+
state.lastCompileSourceChangeTimestamp = sourceChangeTimestamp;
|
|
194
|
+
return {
|
|
195
|
+
ok: true,
|
|
196
|
+
compile: parsed,
|
|
197
|
+
...saveMetadata
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
reason: `Compile request error: ${error.message}`,
|
|
203
|
+
...saveMetadata
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function waitForCompileSaveState(params = {}) {
|
|
209
|
+
if (typeof deps.waitForSaveState !== 'function') {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
state: 'unavailable',
|
|
213
|
+
reason: 'Overleaf save-state checker is unavailable.'
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return normalizeSaveStateResult(
|
|
218
|
+
await deps.waitForSaveState({ deadlineMs: resolveWaitForSaveMs(params.waitForSaveMs) })
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
state: 'unavailable',
|
|
224
|
+
reason: `Overleaf save-state checker failed: ${error.message}`
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function resolveWaitForSaveMs(value) {
|
|
230
|
+
if (value == null) {
|
|
231
|
+
return 5000;
|
|
232
|
+
}
|
|
233
|
+
const numeric = Number(value);
|
|
234
|
+
return Number.isFinite(numeric) ? numeric : 5000;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function normalizeSaveStateResult(result = {}) {
|
|
238
|
+
if (result.ok === true && !result.state) {
|
|
239
|
+
return {
|
|
240
|
+
...result,
|
|
241
|
+
state: 'verified_saved'
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (result.state === 'verified_saved') {
|
|
245
|
+
return {
|
|
246
|
+
...result,
|
|
247
|
+
ok: true,
|
|
248
|
+
state: 'verified_saved'
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
...result,
|
|
253
|
+
ok: false,
|
|
254
|
+
state: result.state || 'unknown_timeout'
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function buildSaveStateMetadata(saveResult = {}) {
|
|
259
|
+
const saveState = saveResult.state || 'unknown_timeout';
|
|
260
|
+
return {
|
|
261
|
+
saveStateVerified: saveState === 'verified_saved',
|
|
262
|
+
saveState
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function getCompileLog(params = {}) {
|
|
267
|
+
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
268
|
+
if (!CompileAdapter) {
|
|
269
|
+
return { ok: false, reason: 'CompileAdapter not loaded' };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const maxAgeMs = params.maxAgeMs || 30000;
|
|
273
|
+
const compileLogFresh = CompileAdapter.isCompileLogFresh(
|
|
274
|
+
{ sourceChangeTimestamp: state.lastCompileSourceChangeTimestamp },
|
|
275
|
+
state.lastKnownSourceEditTimestamp
|
|
276
|
+
);
|
|
277
|
+
const needsFreshCompile = !state.lastCompileResponse?.ok
|
|
278
|
+
|| (Date.now() - state.lastCompileAt > maxAgeMs)
|
|
279
|
+
|| !compileLogFresh;
|
|
280
|
+
|
|
281
|
+
if (needsFreshCompile && params.triggerIfStale !== false) {
|
|
282
|
+
const compileResult = await triggerCompile({ waitForSaveMs: resolveWaitForSaveMs(params.waitForSaveMs) });
|
|
283
|
+
if (!compileResult.ok) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
reason: `Could not get fresh compile: ${compileResult.reason}`,
|
|
287
|
+
saveStateVerified: compileResult.saveStateVerified,
|
|
288
|
+
saveState: compileResult.saveState
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const compiled = state.lastCompileResponse;
|
|
294
|
+
if (!compiled?.ok || !compiled.logUrl) {
|
|
295
|
+
return { ok: false, reason: 'No compile log URL available' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const logUrl = compiled.logUrl.startsWith('/')
|
|
300
|
+
? `${pageWindow.location.origin}${compiled.logUrl}`
|
|
301
|
+
: compiled.logUrl;
|
|
302
|
+
const response = await pageWindow.fetch(logUrl, { credentials: 'same-origin' });
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
return { ok: false, reason: `Failed to fetch compile log: ${response.status}` };
|
|
305
|
+
}
|
|
306
|
+
const logContent = await response.text();
|
|
307
|
+
const truncated = CompileAdapter.truncateLogForContext(logContent);
|
|
308
|
+
const parsed = CompileAdapter.parseLogErrors(logContent);
|
|
309
|
+
return {
|
|
310
|
+
ok: true,
|
|
311
|
+
log: truncated,
|
|
312
|
+
errors: parsed.errors,
|
|
313
|
+
warnings: parsed.warnings,
|
|
314
|
+
compiledAt: state.lastCompileAt,
|
|
315
|
+
fresh: CompileAdapter.isCompileLogFresh(
|
|
316
|
+
{ sourceChangeTimestamp: state.lastCompileSourceChangeTimestamp },
|
|
317
|
+
state.lastKnownSourceEditTimestamp
|
|
318
|
+
)
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return { ok: false, reason: `Log fetch error: ${error.message}` };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function getCompileState() {
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
hasCapturedTemplate: Boolean(state.capturedRequestTemplate),
|
|
329
|
+
capturedTemplateAgeMs: state.capturedRequestTemplate?.capturedAt
|
|
330
|
+
? Date.now() - state.capturedRequestTemplate.capturedAt
|
|
331
|
+
: null,
|
|
332
|
+
lastCompileAt: state.lastCompileAt,
|
|
333
|
+
lastCompileOk: state.lastCompileResponse?.ok || false,
|
|
334
|
+
lastCompileSourceChangeTimestamp: state.lastCompileSourceChangeTimestamp,
|
|
335
|
+
lastKnownSourceEditTimestamp: state.lastKnownSourceEditTimestamp
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isFreshCapturedRequestTemplate(template) {
|
|
340
|
+
return Boolean(template?.capturedAt && Date.now() - template.capturedAt <= COMPILE_TEMPLATE_TTL_MS);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function markSourceEdited(timestamp = Date.now()) {
|
|
344
|
+
state.lastKnownSourceEditTimestamp = timestamp;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function markSourceEditFromInput(event) {
|
|
348
|
+
const target = event.target;
|
|
349
|
+
if (!target || target.closest?.('#codex-overleaf-panel')) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!isLikelyEditorInputTarget(target)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const CompileAdapter = pageWindow.CodexOverleafCompileAdapter;
|
|
356
|
+
if (!CompileAdapter?.isCompilableFile(deps.getActiveFilePath?.())) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
markSourceEdited();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function clickOverleafCompileButton() {
|
|
363
|
+
const candidates = findOverleafCompileButtons();
|
|
364
|
+
for (const candidate of candidates) {
|
|
365
|
+
if (isDisabled(candidate)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (typeof candidate.click === 'function') {
|
|
369
|
+
candidate.click();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (typeof candidate.dispatchEvent === 'function') {
|
|
373
|
+
const EventCtor = pageWindow.MouseEvent || pageWindow.Event;
|
|
374
|
+
if (EventCtor) {
|
|
375
|
+
candidate.dispatchEvent(new EventCtor('click', { bubbles: true, cancelable: true }));
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function findOverleafCompileButtons() {
|
|
384
|
+
if (typeof pageDocument.querySelectorAll !== 'function') {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
const selectors = [
|
|
388
|
+
'button',
|
|
389
|
+
'[role="button"]',
|
|
390
|
+
'[aria-label*="compile" i]',
|
|
391
|
+
'[title*="compile" i]',
|
|
392
|
+
'[data-testid*="compile" i]'
|
|
393
|
+
];
|
|
394
|
+
const nodes = [];
|
|
395
|
+
for (const selector of selectors) {
|
|
396
|
+
try {
|
|
397
|
+
nodes.push(...Array.from(pageDocument.querySelectorAll(selector)));
|
|
398
|
+
} catch (_e) { /* ignore unsupported selectors in tests/older browsers */ }
|
|
399
|
+
}
|
|
400
|
+
return uniqueNodes(nodes).filter(isCompileButton);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function isCompileButton(node) {
|
|
404
|
+
const text = [
|
|
405
|
+
node.textContent,
|
|
406
|
+
node.innerText,
|
|
407
|
+
node.getAttribute?.('aria-label'),
|
|
408
|
+
node.getAttribute?.('title'),
|
|
409
|
+
node.getAttribute?.('data-testid')
|
|
410
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
411
|
+
return /\b(recompile|compile)\b/.test(text) || /重新编译|编译/.test(text);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function isDisabled(node) {
|
|
415
|
+
return Boolean(node.disabled
|
|
416
|
+
|| node.getAttribute?.('disabled') != null
|
|
417
|
+
|| node.getAttribute?.('aria-disabled') === 'true');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function waitForCapturedCompile(timeoutMs) {
|
|
421
|
+
const startCompileAt = state.lastCompileAt;
|
|
422
|
+
const deadline = Date.now() + timeoutMs;
|
|
423
|
+
while (Date.now() < deadline) {
|
|
424
|
+
if (state.lastCompileAt > startCompileAt && state.lastCompileResponse) {
|
|
425
|
+
return { ok: true };
|
|
426
|
+
}
|
|
427
|
+
await delay(100);
|
|
428
|
+
}
|
|
429
|
+
return { ok: false, reason: 'Clicked Overleaf Recompile, but no compile result was captured.' };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
getCompileLog,
|
|
434
|
+
getCompileState,
|
|
435
|
+
install,
|
|
436
|
+
markSourceEdited,
|
|
437
|
+
state,
|
|
438
|
+
triggerCompile
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function isLikelyEditorInputTarget(target) {
|
|
443
|
+
return Boolean(target.matches?.('textarea, [contenteditable="true"], .cm-content, .CodeMirror-code')
|
|
444
|
+
|| target.closest?.('.cm-content, .CodeMirror-code, .cm-editor, .CodeMirror'));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function delay(ms) {
|
|
448
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function uniqueNodes(nodes) {
|
|
452
|
+
return Array.from(new Set(nodes.filter(Boolean)));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
COMPILE_TEMPLATE_TTL_MS,
|
|
457
|
+
create,
|
|
458
|
+
isLikelyEditorInputTarget
|
|
459
|
+
};
|
|
460
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
(function initOverleafCapabilities(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafPageCapabilities = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function overleafCapabilitiesFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
function collectPageCapabilities(deps = {}) {
|
|
11
|
+
const editor = deps.editor || deps.detectEditor?.() || {};
|
|
12
|
+
const warnings = [];
|
|
13
|
+
const editorRead = Boolean(editor?.ok);
|
|
14
|
+
const editorWrite = canWriteActiveEditor(deps);
|
|
15
|
+
const fileTreeWrite = hasFileTreeWriteCapability(deps);
|
|
16
|
+
const checkpointWrite = hasCheckpointCapability(deps);
|
|
17
|
+
const compileCapture = Boolean(deps.compileState?.capturedRequestTemplate);
|
|
18
|
+
const reviewingControl = Boolean(deps.findReviewingActivationControl?.());
|
|
19
|
+
|
|
20
|
+
if (!editorRead) {
|
|
21
|
+
warnings.push('没有识别到当前 Overleaf 编辑器,Codex 不能读取当前文件。');
|
|
22
|
+
}
|
|
23
|
+
if (!editorWrite) {
|
|
24
|
+
warnings.push('没有识别到可写编辑器,Codex 不能自动写回 Overleaf。');
|
|
25
|
+
}
|
|
26
|
+
if (!fileTreeWrite) {
|
|
27
|
+
warnings.push('没有识别到可用文件树 API,新建、重命名、移动、删除可能不可用。');
|
|
28
|
+
}
|
|
29
|
+
if (!compileCapture) {
|
|
30
|
+
warnings.push('还没有捕获到 Overleaf 编译请求,@compile-log 需要先手动编译一次或由 Codex 触发。');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
editor: {
|
|
35
|
+
read: editorRead,
|
|
36
|
+
write: editorWrite,
|
|
37
|
+
type: editor?.type || 'unknown'
|
|
38
|
+
},
|
|
39
|
+
fileTree: {
|
|
40
|
+
write: fileTreeWrite
|
|
41
|
+
},
|
|
42
|
+
checkpoint: {
|
|
43
|
+
write: checkpointWrite
|
|
44
|
+
},
|
|
45
|
+
compile: {
|
|
46
|
+
capture: compileCapture,
|
|
47
|
+
trigger: compileCapture
|
|
48
|
+
},
|
|
49
|
+
reviewing: {
|
|
50
|
+
control: reviewingControl
|
|
51
|
+
},
|
|
52
|
+
warnings
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function canWriteActiveEditor(deps = {}) {
|
|
57
|
+
const editorView = deps.getCodeMirrorEditorView?.();
|
|
58
|
+
if (editorView && typeof editorView.dispatch === 'function') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const active = deps.getDeepActiveElement?.();
|
|
62
|
+
if (active?.tagName === 'TEXTAREA' && !deps.isInsideCodexPanel?.(active)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (deps.findEditorTextArea?.()) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return Boolean(deps.findEditorContentNode?.('.cm-content')
|
|
69
|
+
|| deps.findEditorContentNode?.('[contenteditable="true"]'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasFileTreeWriteCapability(deps = {}) {
|
|
73
|
+
const manager = deps.findFileTreeManager?.();
|
|
74
|
+
if (!manager) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return ['create', 'rename', 'move', 'delete']
|
|
78
|
+
.some(type => deps.fileTreeMethodNames?.(type).some(methodName => typeof manager[methodName] === 'function'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function hasCheckpointCapability(deps = {}) {
|
|
82
|
+
const pageWindow = deps.window || {};
|
|
83
|
+
const history = deps.findHistoryObject?.();
|
|
84
|
+
return [
|
|
85
|
+
history?.labelCurrentVersion,
|
|
86
|
+
history?.createLabel,
|
|
87
|
+
history?.addLabel,
|
|
88
|
+
pageWindow._ide?.projectHistoryManager?.labelCurrentVersion,
|
|
89
|
+
pageWindow._ide?.historyManager?.labelCurrentVersion,
|
|
90
|
+
pageWindow.Overleaf?.history?.labelCurrentVersion
|
|
91
|
+
].some(fn => typeof fn === 'function');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
canWriteActiveEditor,
|
|
96
|
+
collectPageCapabilities,
|
|
97
|
+
hasCheckpointCapability,
|
|
98
|
+
hasFileTreeWriteCapability
|
|
99
|
+
};
|
|
100
|
+
});
|