codex-overleaf-link 2.3.5 → 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 +63 -31
- package/README.zh-CN.md +543 -0
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +7 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/activeTurnControl.js +1 -0
- package/extension/src/content/assetTransferBroker.js +2 -0
- package/extension/src/content/contentRuntime.js +77 -57
- package/extension/src/content/contextTray.js +4 -0
- package/extension/src/content/diagnosticsController.js +17 -3
- package/extension/src/content/diagnosticsPanel.js +129 -5
- package/extension/src/content/generated/content.bundle.js +1422 -348
- package/extension/src/content/generated/content.bundle.meta.json +7 -3
- package/extension/src/content/legacyGlobalRegistry.js +3 -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 +126 -92
- package/extension/src/content/otWarmMirrorController.js +122 -1
- package/extension/src/content/pageBridgeClient.js +2 -0
- package/extension/src/content/panelMaintenance.js +26 -15
- package/extension/src/content/providerSettingsCoordinator.js +1 -0
- package/extension/src/content/runFailureNotice.js +104 -0
- package/extension/src/content/runResultActions.js +16 -1
- package/extension/src/content/runScrollLayout.js +124 -0
- package/extension/src/content/runTimelineView.js +37 -11
- package/extension/src/content/sessionForkController.js +20 -11
- package/extension/src/content/settingsPanel.js +28 -11
- package/extension/src/content/trackedChangeCaptureController.js +153 -0
- package/extension/src/content/updateNotice.js +24 -4
- package/extension/src/content/writebackOrchestrator.js +31 -0
- package/extension/src/page/binaryAssetUploader.js +101 -29
- package/extension/src/page/overleafRealtimeObserver.js +120 -25
- 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/i18n.js +77 -37
- package/extension/src/shared/managedUpdateProjection.js +1 -1
- package/extension/src/shared/otText.js +0 -1
- package/extension/src/shared/pageRpcContract.js +2 -1
- package/extension/src/shared/sessionState.js +23 -23
- package/extension/src/shared/sessionTitle.js +120 -0
- package/extension/src/shared/storageDb.js +10 -12
- package/extension/src/shared/storageRunActions.js +29 -1
- package/extension/src/shared/updateRuntimeIdentity.js +32 -0
- package/extension/src/shared/writebackSettlement.js +19 -5
- package/extension/styles/panel.css +128 -24
- package/native-host/src/mirrorWorkspace.js +20 -1
- package/native-host/src/releaseProxy.js +177 -0
- package/native-host/src/releaseTransport.js +154 -0
- package/native-host/src/taskRunnerRuntime.js +25 -0
- package/native-host/src/updateManager.js +40 -39
- package/package.json +5 -1
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
3
|
module.exports = factory(root);
|
|
4
4
|
} else {
|
|
5
|
+
if (typeof root.CodexOverleafRealtimeObserver?.dispose === 'function') {
|
|
6
|
+
root.CodexOverleafRealtimeObserver.dispose();
|
|
7
|
+
}
|
|
5
8
|
root.CodexOverleafRealtimeObserver = factory(root);
|
|
6
9
|
}
|
|
7
10
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function overleafRealtimeObserverFactory(root) {
|
|
@@ -12,8 +15,34 @@
|
|
|
12
15
|
const BASELINE_REFRESH_EVENT_TYPES = ['click', 'focusin', 'change'];
|
|
13
16
|
const CHANNEL_KEY_PATTERN = /socket|websocket|channel|realtime|collab|share|ot|doc|editor|connection|event|broadcast|presence/i;
|
|
14
17
|
const SENSITIVE_KEY_PATTERN = /^(content|previousContent|nextContent|text|body|raw|rawContent|source|sourceText)$/i;
|
|
18
|
+
const LIMITS = Object.freeze({ maxFileBytes: 1024 * 1024, maxEvents: 64, maxQueuedBytes: 4 * 1024 * 1024 });
|
|
19
|
+
const encoder = typeof TextEncoder === 'function' ? new TextEncoder() : null;
|
|
20
|
+
const ownedObservers = new WeakMap();
|
|
15
21
|
|
|
16
22
|
function create(deps = {}) {
|
|
23
|
+
const documentRef = Object.prototype.hasOwnProperty.call(deps, 'document')
|
|
24
|
+
? deps.document
|
|
25
|
+
: getDefaultDocument();
|
|
26
|
+
dispose(documentRef);
|
|
27
|
+
const observer = createObserver(deps);
|
|
28
|
+
if (documentRef && ['object', 'function'].includes(typeof documentRef)) {
|
|
29
|
+
ownedObservers.set(documentRef, observer);
|
|
30
|
+
}
|
|
31
|
+
return observer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function dispose(documentRef = getDefaultDocument()) {
|
|
35
|
+
if (!documentRef || !['object', 'function'].includes(typeof documentRef)) return;
|
|
36
|
+
const previous = ownedObservers.get(documentRef);
|
|
37
|
+
if (!previous) return;
|
|
38
|
+
try {
|
|
39
|
+
previous.stop();
|
|
40
|
+
} finally {
|
|
41
|
+
ownedObservers.delete(documentRef);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createObserver(deps = {}) {
|
|
17
46
|
const pageWindow = deps.window || getDefaultWindow();
|
|
18
47
|
const pageDocument = Object.prototype.hasOwnProperty.call(deps, 'document')
|
|
19
48
|
? deps.document
|
|
@@ -21,6 +50,9 @@
|
|
|
21
50
|
const events = [];
|
|
22
51
|
let activePath = '';
|
|
23
52
|
let lastContent = '';
|
|
53
|
+
let baselineByteCount = 0;
|
|
54
|
+
let queuedTextBytes = 0;
|
|
55
|
+
let coalescedEventCount = 0;
|
|
24
56
|
let running = false;
|
|
25
57
|
let listenersAttached = false;
|
|
26
58
|
let statusName = 'off';
|
|
@@ -31,6 +63,7 @@
|
|
|
31
63
|
|
|
32
64
|
function start(_params = {}) {
|
|
33
65
|
clearQueuedEvents();
|
|
66
|
+
coalescedEventCount = 0;
|
|
34
67
|
channelCandidates = collectChannelCandidates(pageWindow);
|
|
35
68
|
if (!canAttachDocumentListeners()) {
|
|
36
69
|
running = false;
|
|
@@ -40,13 +73,18 @@
|
|
|
40
73
|
|
|
41
74
|
running = true;
|
|
42
75
|
refreshActiveBaseline();
|
|
43
|
-
|
|
76
|
+
// The editor may mount after the page bridge. Keep the read-only
|
|
77
|
+
// observer alive until its first file identity is available.
|
|
78
|
+
if (statusName === 'unavailable' && lastErrorCode === 'missing_active_path') statusName = 'starting';
|
|
79
|
+
if (running) attachDocumentListeners();
|
|
44
80
|
return getStatus();
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
function stop() {
|
|
48
84
|
detachDocumentListeners();
|
|
49
85
|
clearQueuedEvents();
|
|
86
|
+
lastContent = '';
|
|
87
|
+
baselineByteCount = 0;
|
|
50
88
|
running = false;
|
|
51
89
|
statusName = 'off';
|
|
52
90
|
statusReason = '';
|
|
@@ -61,24 +99,74 @@
|
|
|
61
99
|
strategy: STRATEGY_ACTIVE_EDITOR,
|
|
62
100
|
activePath,
|
|
63
101
|
queuedEventCount: events.length,
|
|
102
|
+
queuedTextBytes,
|
|
103
|
+
baselineByteCount,
|
|
104
|
+
coalescedEventCount,
|
|
64
105
|
lastEventAt,
|
|
65
106
|
lastErrorCode,
|
|
66
107
|
channelCandidates: cloneChannelCandidates(channelCandidates)
|
|
67
108
|
};
|
|
68
|
-
if (statusName === 'unavailable' && statusReason) {
|
|
109
|
+
if ((statusName === 'unavailable' || statusName === 'starting') && statusReason) {
|
|
69
110
|
status.reason = statusReason;
|
|
70
111
|
}
|
|
71
112
|
return status;
|
|
72
113
|
}
|
|
73
114
|
|
|
74
115
|
function drainEvents() {
|
|
116
|
+
// Capture-phase input can precede the editor transaction; paste, undo
|
|
117
|
+
// and collaboration updates may dispatch no DOM input at all. The
|
|
118
|
+
// existing bounded poll samples committed text before draining.
|
|
119
|
+
if (running) handleEditorInput();
|
|
75
120
|
const drained = events.slice();
|
|
76
121
|
clearQueuedEvents();
|
|
77
|
-
return
|
|
122
|
+
if (!drained.length) return [];
|
|
123
|
+
const otText = resolveOtText(deps, pageWindow, root);
|
|
124
|
+
if (typeof otText?.normalizeObservedTextEvent !== 'function') {
|
|
125
|
+
stopObservation('missing_ot_text');
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const normalized = [];
|
|
129
|
+
for (const pending of drained) {
|
|
130
|
+
let event;
|
|
131
|
+
const { previousBytes, nextBytes, ...input } = pending;
|
|
132
|
+
try { event = otText.normalizeObservedTextEvent(input); } catch (_error) { /* fail closed below */ }
|
|
133
|
+
if (!event || event.ok !== true) {
|
|
134
|
+
stopObservation(event?.reason || 'normalize_observed_text_event_failed');
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
normalized.push(sanitizeObservedEvent(event));
|
|
138
|
+
}
|
|
139
|
+
return normalized;
|
|
78
140
|
}
|
|
79
141
|
|
|
80
142
|
function clearQueuedEvents() {
|
|
81
143
|
events.length = 0;
|
|
144
|
+
queuedTextBytes = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stopObservation(reason) {
|
|
148
|
+
stop();
|
|
149
|
+
markUnavailable(reason);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function queueObservedChange(event, previousBytes, nextBytes) {
|
|
153
|
+
const tail = events[events.length - 1];
|
|
154
|
+
const merge = tail?.path === event.path && tail.nextContent === event.previousContent;
|
|
155
|
+
const pending = { ...event, previousContent: merge ? tail.previousContent : event.previousContent,
|
|
156
|
+
previousBytes: merge ? tail.previousBytes : previousBytes, nextBytes };
|
|
157
|
+
const remainingBytes = queuedTextBytes - (merge ? tail.previousBytes + tail.nextBytes : 0);
|
|
158
|
+
const noChange = pending.previousContent === pending.nextContent;
|
|
159
|
+
const nextLength = events.length - (merge ? 1 : 0) + (noChange ? 0 : 1);
|
|
160
|
+
const nextTotal = remainingBytes + (noChange ? 0 : pending.previousBytes + pending.nextBytes);
|
|
161
|
+
if (nextLength > LIMITS.maxEvents || nextTotal > LIMITS.maxQueuedBytes) {
|
|
162
|
+
stopObservation('ot_queue_limit');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (merge) { events.pop(); coalescedEventCount += 1; }
|
|
166
|
+
if (!noChange) events.push(pending);
|
|
167
|
+
queuedTextBytes = nextTotal;
|
|
168
|
+
lastEventAt = event.observedAt;
|
|
169
|
+
return true;
|
|
82
170
|
}
|
|
83
171
|
|
|
84
172
|
function canAttachDocumentListeners() {
|
|
@@ -118,12 +206,14 @@
|
|
|
118
206
|
|
|
119
207
|
const textResult = readEditorText();
|
|
120
208
|
if (!textResult.ok) {
|
|
121
|
-
|
|
209
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
210
|
+
else markUnavailable(textResult.reason);
|
|
122
211
|
return false;
|
|
123
212
|
}
|
|
124
213
|
|
|
125
214
|
activePath = pathResult.path;
|
|
126
215
|
lastContent = textResult.text;
|
|
216
|
+
baselineByteCount = textResult.bytes;
|
|
127
217
|
markObserving();
|
|
128
218
|
return true;
|
|
129
219
|
}
|
|
@@ -144,16 +234,19 @@
|
|
|
144
234
|
|
|
145
235
|
const textResult = readEditorText();
|
|
146
236
|
if (!textResult.ok) {
|
|
147
|
-
|
|
237
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
238
|
+
else markUnavailable(textResult.reason);
|
|
148
239
|
return;
|
|
149
240
|
}
|
|
150
241
|
|
|
151
242
|
activePath = pathResult.path;
|
|
152
243
|
lastContent = textResult.text;
|
|
244
|
+
baselineByteCount = textResult.bytes;
|
|
153
245
|
markObserving();
|
|
154
246
|
}
|
|
155
247
|
|
|
156
|
-
function handleEditorInput() {
|
|
248
|
+
function handleEditorInput(inputEvent) {
|
|
249
|
+
if (inputEvent?.target?.closest?.('#codex-overleaf-panel')) return;
|
|
157
250
|
if (!running) {
|
|
158
251
|
return;
|
|
159
252
|
}
|
|
@@ -166,7 +259,8 @@
|
|
|
166
259
|
|
|
167
260
|
const textResult = readEditorText();
|
|
168
261
|
if (!textResult.ok) {
|
|
169
|
-
|
|
262
|
+
if (textResult.reason === 'ot_file_limit') stopObservation(textResult.reason);
|
|
263
|
+
else markUnavailable(textResult.reason);
|
|
170
264
|
return;
|
|
171
265
|
}
|
|
172
266
|
|
|
@@ -175,6 +269,7 @@
|
|
|
175
269
|
// Fallback for file switches that were not preceded by a selection/focus event.
|
|
176
270
|
activePath = pathResult.path;
|
|
177
271
|
lastContent = nextContent;
|
|
272
|
+
baselineByteCount = textResult.bytes;
|
|
178
273
|
markObserving();
|
|
179
274
|
return;
|
|
180
275
|
}
|
|
@@ -185,34 +280,25 @@
|
|
|
185
280
|
}
|
|
186
281
|
|
|
187
282
|
const observedAt = resolveObservedAt();
|
|
188
|
-
const
|
|
189
|
-
if (!otText || typeof otText.normalizeObservedTextEvent !== 'function') {
|
|
190
|
-
markUnavailable('missing_ot_text');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const event = otText.normalizeObservedTextEvent({
|
|
283
|
+
const queued = queueObservedChange({
|
|
195
284
|
path: activePath,
|
|
196
285
|
previousContent: lastContent,
|
|
197
286
|
nextContent,
|
|
198
287
|
observedAt,
|
|
199
288
|
source: STRATEGY_ACTIVE_EDITOR
|
|
200
|
-
});
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
lastEventAt = queuedEvent.observedAt || observedAt;
|
|
289
|
+
}, baselineByteCount, textResult.bytes);
|
|
290
|
+
if (queued) {
|
|
291
|
+
lastContent = nextContent;
|
|
292
|
+
baselineByteCount = textResult.bytes;
|
|
205
293
|
markObserving();
|
|
206
|
-
} else {
|
|
207
|
-
lastErrorCode = event?.reason || 'normalize_observed_text_event_failed';
|
|
208
294
|
}
|
|
209
|
-
lastContent = nextContent;
|
|
210
295
|
}
|
|
211
296
|
|
|
212
297
|
function handleActivePathUnavailable(reason) {
|
|
213
298
|
if (reason === 'missing_active_path') {
|
|
214
299
|
activePath = '';
|
|
215
300
|
lastContent = '';
|
|
301
|
+
baselineByteCount = 0;
|
|
216
302
|
}
|
|
217
303
|
markUnavailable(reason);
|
|
218
304
|
}
|
|
@@ -252,9 +338,17 @@
|
|
|
252
338
|
};
|
|
253
339
|
}
|
|
254
340
|
try {
|
|
341
|
+
const text = deps.readActiveEditorText();
|
|
342
|
+
if (typeof text !== 'string') {
|
|
343
|
+
return { ok: false, reason: 'missing_editor_content' };
|
|
344
|
+
}
|
|
345
|
+
const bytes = text.length > LIMITS.maxFileBytes ? LIMITS.maxFileBytes + 1
|
|
346
|
+
: encoder ? encoder.encode(text).byteLength : text.length * 3;
|
|
347
|
+
if (bytes > LIMITS.maxFileBytes) return { ok: false, reason: 'ot_file_limit' };
|
|
255
348
|
return {
|
|
256
349
|
ok: true,
|
|
257
|
-
text
|
|
350
|
+
text,
|
|
351
|
+
bytes
|
|
258
352
|
};
|
|
259
353
|
} catch (_error) {
|
|
260
354
|
return {
|
|
@@ -444,7 +538,6 @@
|
|
|
444
538
|
|
|
445
539
|
function normalizePath(value) {
|
|
446
540
|
return String(value || '')
|
|
447
|
-
.replace(/\s+/g, ' ')
|
|
448
541
|
.replace(/\\/g, '/')
|
|
449
542
|
.trim()
|
|
450
543
|
.replace(/^\/+/, '');
|
|
@@ -485,7 +578,9 @@
|
|
|
485
578
|
}
|
|
486
579
|
|
|
487
580
|
return {
|
|
581
|
+
LIMITS,
|
|
488
582
|
collectChannelCandidates,
|
|
489
|
-
create
|
|
583
|
+
create,
|
|
584
|
+
dispose
|
|
490
585
|
};
|
|
491
586
|
});
|