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.
Files changed (73) hide show
  1. package/README.md +45 -21
  2. package/bin/codex-overleaf-link.mjs +58 -0
  3. package/extension/assets/icons/codex-overleaf-dialog-icon.png +0 -0
  4. package/extension/assets/icons/codex-overleaf-icon.png +0 -0
  5. package/extension/assets/icons/icon128.png +0 -0
  6. package/extension/assets/icons/icon16.png +0 -0
  7. package/extension/assets/icons/icon32.png +0 -0
  8. package/extension/assets/icons/icon48.png +0 -0
  9. package/extension/bootstrap/background.js +404 -0
  10. package/extension/bootstrap/manifest.template.json +51 -0
  11. package/extension/bootstrap/popup.css +26 -0
  12. package/extension/bootstrap/popup.html +38 -0
  13. package/extension/bootstrap/popup.js +44 -0
  14. package/extension/bootstrap/runtimeContext.js +2 -0
  15. package/extension/runtime-manifest.json +60 -0
  16. package/extension/src/background.js +532 -0
  17. package/extension/src/content/applyResultFormatters.js +387 -0
  18. package/extension/src/content/composerAttachments.js +522 -0
  19. package/extension/src/content/composerPanel.js +254 -0
  20. package/extension/src/content/contentRuntime.js +8897 -0
  21. package/extension/src/content/contextTray.js +686 -0
  22. package/extension/src/content/diagnosticsController.js +659 -0
  23. package/extension/src/content/diagnosticsPanel.js +342 -0
  24. package/extension/src/content/diffReviewPanel.js +797 -0
  25. package/extension/src/content/localSkillsPanel.js +280 -0
  26. package/extension/src/content/markdownText.js +606 -0
  27. package/extension/src/content/mirrorHealth.js +92 -0
  28. package/extension/src/content/modelPicker.js +489 -0
  29. package/extension/src/content/nativeChannel.js +62 -0
  30. package/extension/src/content/otWarmMirror.js +964 -0
  31. package/extension/src/content/otWarmMirrorController.js +174 -0
  32. package/extension/src/content/panelMaintenance.js +367 -0
  33. package/extension/src/content/panelRenderer.js +253 -0
  34. package/extension/src/content/recentProjects.js +1063 -0
  35. package/extension/src/content/reviewHunks.js +247 -0
  36. package/extension/src/content/runController.js +242 -0
  37. package/extension/src/content/runTimelineView.js +1131 -0
  38. package/extension/src/content/sessionManager.js +487 -0
  39. package/extension/src/content/sessionPanel.js +263 -0
  40. package/extension/src/content/settingsPanel.js +527 -0
  41. package/extension/src/content/themeController.js +91 -0
  42. package/extension/src/content/updateIdle.js +67 -0
  43. package/extension/src/content/writebackController.js +300 -0
  44. package/extension/src/content/writebackOrchestrator.js +850 -0
  45. package/extension/src/contentScript.js +8 -0
  46. package/extension/src/page/compileBridge.js +460 -0
  47. package/extension/src/page/overleafCapabilities.js +100 -0
  48. package/extension/src/page/overleafEditor.js +282 -0
  49. package/extension/src/page/overleafProjectSnapshot.js +234 -0
  50. package/extension/src/page/overleafRealtimeObserver.js +491 -0
  51. package/extension/src/page/pageBridgeCapability.js +71 -0
  52. package/extension/src/page/snapshotRouter.js +948 -0
  53. package/extension/src/page/trackedChangesLifecycle.js +1506 -0
  54. package/extension/src/page/treeOperations.js +1584 -0
  55. package/extension/src/page/writeGuard.js +142 -0
  56. package/extension/src/page/writebackRouter.js +1934 -0
  57. package/extension/src/pageBridge.js +2224 -0
  58. package/extension/src/popup.js +247 -0
  59. package/extension/src/shared/agentTranscript.js +18 -4
  60. package/extension/src/shared/compatibility.js +1 -1
  61. package/extension/styles/panel.css +4048 -0
  62. package/native-host/src/index.js +9 -2
  63. package/native-host/src/managedInstall.js +393 -0
  64. package/native-host/src/managedLauncherRuntime.js +71 -0
  65. package/native-host/src/nativeHostPlatform.js +16 -0
  66. package/native-host/src/runtimeInstaller.js +6 -1
  67. package/native-host/src/taskRunnerRuntime.js +8 -0
  68. package/native-host/src/updateArchive.js +253 -0
  69. package/native-host/src/updateManager.js +548 -0
  70. package/native-host/src/updateTrust.js +156 -0
  71. package/package.json +11 -2
  72. package/scripts/install-managed.mjs +64 -0
  73. 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
+ });