codex-overleaf-link 2.3.4 → 2.3.6
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 +325 -211
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/background.js +9 -0
- package/extension/src/backgroundUpdateCoordinator.js +88 -4
- package/extension/src/content/composerPanel.js +1 -1
- package/extension/src/content/contentRuntime.js +51 -35
- package/extension/src/content/diagnosticsController.js +13 -3
- package/extension/src/content/diagnosticsPanel.js +1 -1
- package/extension/src/content/generated/content.bundle.js +1233 -298
- package/extension/src/content/generated/content.bundle.meta.json +8 -3
- package/extension/src/content/globalPreferencesController.js +125 -0
- package/extension/src/content/legacyGlobalRegistry.js +4 -0
- package/extension/src/content/otWarmMirror.js +95 -65
- package/extension/src/content/otWarmMirrorController.js +102 -1
- package/extension/src/content/panelMaintenance.js +26 -15
- package/extension/src/content/panelRenderer.js +6 -1
- package/extension/src/content/projectSettingsCoordinator.js +1 -0
- package/extension/src/content/providerSettingsCoordinator.js +16 -0
- package/extension/src/content/providerSettingsDialog.js +24 -15
- package/extension/src/content/runFailureNotice.js +104 -0
- package/extension/src/content/runScrollLayout.js +124 -0
- package/extension/src/content/runTimelineView.js +36 -10
- package/extension/src/content/settingsPanel.js +32 -13
- package/extension/src/content/themeController.js +3 -0
- package/extension/src/content/updateNotice.js +24 -4
- package/extension/src/page/overleafRealtimeObserver.js +116 -24
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/globalPreferences.js +131 -0
- package/extension/src/shared/i18n.js +79 -39
- package/extension/src/shared/managedUpdateProjection.js +1 -1
- package/extension/src/shared/otText.js +0 -1
- package/extension/src/shared/sessionState.js +17 -21
- package/extension/src/shared/sessionTitle.js +120 -0
- package/extension/src/shared/updateRuntimeIdentity.js +32 -0
- package/extension/styles/panel.css +90 -16
- 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/updateManager.js +40 -39
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
(function initRunScrollLayout(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafModuleRegistry.define('RunScrollLayout', [], factory);
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function runScrollLayoutFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const READING_ANCHORS = '.run-final-answer p, .run-final-answer li, .run-final-answer pre, .run-final-answer h1, .run-final-answer h2, .run-final-answer h3, .run-stream p, .run-stream li, .run-stream pre, [data-run-task], [data-run-status]';
|
|
11
|
+
|
|
12
|
+
function create({ getScroller, onLayoutChange, isFollowing = () => true, window: host = globalThis } = {}) {
|
|
13
|
+
let current = null;
|
|
14
|
+
let resizeObserver = null;
|
|
15
|
+
let mutationObserver = null;
|
|
16
|
+
let anchor = null;
|
|
17
|
+
let extent = '';
|
|
18
|
+
|
|
19
|
+
function measure() { return current ? `${current.scrollHeight}:${current.clientHeight}` : ''; }
|
|
20
|
+
|
|
21
|
+
function captureAnchor(force = false) {
|
|
22
|
+
if (!current || isFollowing()) { anchor = null; return; }
|
|
23
|
+
// A layout-induced scroll can arrive before ResizeObserver. Keep the
|
|
24
|
+
// pre-layout anchor until notify has compensated its displacement.
|
|
25
|
+
if (!force && extent !== measure()) return;
|
|
26
|
+
const bounds = current.getBoundingClientRect?.();
|
|
27
|
+
if (!bounds) return;
|
|
28
|
+
const nodes = current.querySelectorAll?.(READING_ANCHORS) || [];
|
|
29
|
+
anchor = null;
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
const rect = node.getBoundingClientRect();
|
|
32
|
+
if (rect.height > 0 && rect.bottom > bounds.top && rect.top < bounds.bottom) {
|
|
33
|
+
anchor = { node, offset: rect.top - bounds.top };
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function restoreAnchor() {
|
|
40
|
+
if (isFollowing() || !anchor || !current.contains?.(anchor.node)) return;
|
|
41
|
+
const rect = anchor.node.getBoundingClientRect();
|
|
42
|
+
if (!rect.height) return;
|
|
43
|
+
const delta = rect.top - current.getBoundingClientRect().top - anchor.offset;
|
|
44
|
+
if (Math.abs(delta) > 0.5) current.scrollTop += delta;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function snapshot() {
|
|
48
|
+
captureAnchor();
|
|
49
|
+
const run = anchor?.node.closest?.('[data-run-id]');
|
|
50
|
+
return { scrollTop: current?.scrollTop || 0, runId: run?.dataset?.runId || '',
|
|
51
|
+
index: run ? Array.from(run.querySelectorAll(READING_ANCHORS)).indexOf(anchor.node) : -1,
|
|
52
|
+
offset: anchor?.offset || 0 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function restore(reading) {
|
|
56
|
+
if (!current || !reading) return;
|
|
57
|
+
current.scrollTop = reading.scrollTop;
|
|
58
|
+
// History repaint replaces nodes. Rebind by run identity and paragraph
|
|
59
|
+
// position; a missing anchor falls back to the saved scroll offset.
|
|
60
|
+
const run = Array.from(current.querySelectorAll?.('[data-run-id]') || [])
|
|
61
|
+
.find(node => node.dataset.runId === reading.runId);
|
|
62
|
+
const node = run?.querySelectorAll(READING_ANCHORS)[reading.index];
|
|
63
|
+
anchor = node ? { node, offset: reading.offset } : null;
|
|
64
|
+
restoreAnchor();
|
|
65
|
+
extent = measure();
|
|
66
|
+
captureAnchor(true);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function dispose() {
|
|
70
|
+
resizeObserver?.disconnect();
|
|
71
|
+
mutationObserver?.disconnect();
|
|
72
|
+
resizeObserver = null;
|
|
73
|
+
mutationObserver = null;
|
|
74
|
+
current = null;
|
|
75
|
+
anchor = null;
|
|
76
|
+
extent = '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function notify() {
|
|
80
|
+
if (!current) return;
|
|
81
|
+
const active = getScroller();
|
|
82
|
+
if (active !== current) {
|
|
83
|
+
bind(active);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
restoreAnchor();
|
|
87
|
+
extent = measure();
|
|
88
|
+
onLayoutChange(current);
|
|
89
|
+
captureAnchor(true);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bind(scroller) {
|
|
93
|
+
if (scroller === current) return;
|
|
94
|
+
dispose();
|
|
95
|
+
if (!scroller || typeof host.ResizeObserver !== 'function') return;
|
|
96
|
+
current = scroller;
|
|
97
|
+
extent = measure();
|
|
98
|
+
captureAnchor(true);
|
|
99
|
+
resizeObserver = new host.ResizeObserver(notify);
|
|
100
|
+
resizeObserver.observe(scroller);
|
|
101
|
+
// Observe run-sized children, rather than every streaming text node.
|
|
102
|
+
for (const child of scroller.children || []) resizeObserver.observe(child);
|
|
103
|
+
if (typeof host.MutationObserver === 'function') {
|
|
104
|
+
mutationObserver = new host.MutationObserver(records => {
|
|
105
|
+
if (current !== scroller) return;
|
|
106
|
+
for (const record of records) {
|
|
107
|
+
for (const node of record.removedNodes) {
|
|
108
|
+
if (node.nodeType === 1) resizeObserver.unobserve(node);
|
|
109
|
+
}
|
|
110
|
+
for (const node of record.addedNodes) {
|
|
111
|
+
if (node.nodeType === 1) resizeObserver.observe(node);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
notify();
|
|
115
|
+
});
|
|
116
|
+
mutationObserver.observe(scroller, { childList: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Object.freeze({ bind, dispose, captureAnchor, snapshot, restore });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Object.freeze({ create });
|
|
124
|
+
});
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
const {
|
|
15
15
|
RunGuidanceView,
|
|
16
16
|
RunResultActions,
|
|
17
|
+
RunScrollLayout,
|
|
18
|
+
RunFailureNotice,
|
|
17
19
|
tr,
|
|
18
20
|
tx,
|
|
19
21
|
getLocale,
|
|
@@ -48,6 +50,7 @@
|
|
|
48
50
|
|
|
49
51
|
let logAutoFollow = true;
|
|
50
52
|
let userScrollIntentUntil = 0;
|
|
53
|
+
let userScrollExtent = '';
|
|
51
54
|
// Scroll engine: a single rAF coalesces a burst of scroll requests into one
|
|
52
55
|
// write per frame (streaming can fire ~25/sec); `scrollLogPendingForce`
|
|
53
56
|
// survives that coalesce so a forced scroll is never lost. `unreadSinceDetach`
|
|
@@ -60,6 +63,12 @@
|
|
|
60
63
|
// reads a static "Processing…" and the user can't tell a working run from a
|
|
61
64
|
// hung one — the single highest-value streaming signal per competitor UX.
|
|
62
65
|
let runElapsedTimer = null;
|
|
66
|
+
const scrollLayout = RunScrollLayout?.create({
|
|
67
|
+
getScroller: getLogScrollContainer,
|
|
68
|
+
isFollowing: () => logAutoFollow,
|
|
69
|
+
onLayoutChange: scroller => logAutoFollow ? scrollLogToBottom() : updateJumpToLatestButton(scroller)
|
|
70
|
+
});
|
|
71
|
+
const failureNotice = RunFailureNotice?.create({ tr, projectRunSettlement, sanitizeText: sanitizeAssistantVisibleText });
|
|
63
72
|
|
|
64
73
|
// Re-arm auto-follow (called by the runtime when a new run starts, so the
|
|
65
74
|
// log snaps back to following the live stream).
|
|
@@ -70,6 +79,7 @@
|
|
|
70
79
|
|
|
71
80
|
function bindLogAutoFollow() {
|
|
72
81
|
const scroller = getLogScrollContainer();
|
|
82
|
+
scrollLayout?.bind(scroller);
|
|
73
83
|
if (!scroller || scroller.dataset.autoFollowBound === 'true') {
|
|
74
84
|
return;
|
|
75
85
|
}
|
|
@@ -79,15 +89,14 @@
|
|
|
79
89
|
scroller.addEventListener('pointerdown', markUserScrollIntent, { passive: true });
|
|
80
90
|
scroller.addEventListener('keydown', event => {
|
|
81
91
|
if (['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End', ' '].includes(event.key)) {
|
|
82
|
-
markUserScrollIntent();
|
|
92
|
+
markUserScrollIntent(event);
|
|
83
93
|
}
|
|
84
94
|
});
|
|
85
95
|
scroller.addEventListener('scroll', () => {
|
|
86
|
-
if (Date.now() <= userScrollIntentUntil) {
|
|
96
|
+
if (Date.now() <= userScrollIntentUntil && userScrollExtent === `${scroller.scrollHeight}:${scroller.clientHeight}`) {
|
|
87
97
|
logAutoFollow = isLogNearBottom(scroller);
|
|
88
|
-
} else if (isLogNearBottom(scroller)) {
|
|
89
|
-
logAutoFollow = true;
|
|
90
98
|
}
|
|
99
|
+
scrollLayout?.captureAnchor();
|
|
91
100
|
// Reveal / hide the floating "jump to latest" button the instant the
|
|
92
101
|
// user detaches from or re-reaches the bottom.
|
|
93
102
|
updateJumpToLatestButton(scroller);
|
|
@@ -125,6 +134,7 @@
|
|
|
125
134
|
if (!el) {
|
|
126
135
|
return;
|
|
127
136
|
}
|
|
137
|
+
if (el.dataset) el.dataset.logAutoFollow = logAutoFollow ? 'true' : 'false';
|
|
128
138
|
const button = ensureJumpToLatestButton();
|
|
129
139
|
if (!button) {
|
|
130
140
|
return;
|
|
@@ -157,8 +167,16 @@
|
|
|
157
167
|
return getPanel()?.querySelector('[data-log]') || getPanel()?.querySelector('[data-main]');
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
function markUserScrollIntent() {
|
|
170
|
+
function markUserScrollIntent(event) {
|
|
171
|
+
const scroller = getLogScrollContainer();
|
|
172
|
+
if (!scroller || (event?.type === 'pointerdown' && event.target !== scroller)) return;
|
|
173
|
+
if (event?.target?.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
|
161
174
|
userScrollIntentUntil = Date.now() + 1200;
|
|
175
|
+
userScrollExtent = `${scroller.scrollHeight}:${scroller.clientHeight}`;
|
|
176
|
+
if ((event?.type === 'wheel' && event.deltaY < 0) || ['ArrowUp', 'PageUp', 'Home'].includes(event?.key)) {
|
|
177
|
+
logAutoFollow = false;
|
|
178
|
+
scrollLogPendingForce = false;
|
|
179
|
+
}
|
|
162
180
|
}
|
|
163
181
|
|
|
164
182
|
function isLogNearBottom(log) {
|
|
@@ -181,7 +199,7 @@
|
|
|
181
199
|
userScrollIntentUntil = 0;
|
|
182
200
|
unreadSinceDetach = 0;
|
|
183
201
|
scrollLogPendingForce = true;
|
|
184
|
-
} else if (!
|
|
202
|
+
} else if (!logAutoFollow) {
|
|
185
203
|
// The user has scrolled up: do not yank them. Just keep the jump button
|
|
186
204
|
// state current.
|
|
187
205
|
updateJumpToLatestButton(scroller);
|
|
@@ -195,7 +213,7 @@
|
|
|
195
213
|
}
|
|
196
214
|
// Re-check intent AT PAINT TIME: a user who flicked up between schedule
|
|
197
215
|
// and paint must not be snapped back down (closes the one-frame fight).
|
|
198
|
-
if (scrollLogPendingForce || logAutoFollow
|
|
216
|
+
if (scrollLogPendingForce || logAutoFollow) {
|
|
199
217
|
setLogScrollPosition(el);
|
|
200
218
|
}
|
|
201
219
|
scrollLogPendingForce = false;
|
|
@@ -284,11 +302,13 @@
|
|
|
284
302
|
}
|
|
285
303
|
return tr('processed', { elapsed });
|
|
286
304
|
}
|
|
287
|
-
function renderRunHistory() {
|
|
305
|
+
function renderRunHistory(options = {}) {
|
|
288
306
|
const log = getPanel()?.querySelector('[data-log]');
|
|
289
307
|
if (!log) {
|
|
290
308
|
return;
|
|
291
309
|
}
|
|
310
|
+
const reading = options.preserveScroll === true && !logAutoFollow
|
|
311
|
+
? scrollLayout?.snapshot() || { scrollTop: log.scrollTop } : null;
|
|
292
312
|
log.replaceChildren();
|
|
293
313
|
if (!getState().runs?.length) {
|
|
294
314
|
const empty = document.createElement('div');
|
|
@@ -317,7 +337,8 @@
|
|
|
317
337
|
for (const run of runs) {
|
|
318
338
|
log.append(renderRunCard(run));
|
|
319
339
|
}
|
|
320
|
-
|
|
340
|
+
if (reading) { log.scrollTop = reading.scrollTop; scrollLayout?.restore(reading); updateJumpToLatestButton(log); }
|
|
341
|
+
else scrollLogToBottom({ force: true });
|
|
321
342
|
}
|
|
322
343
|
|
|
323
344
|
function renderRunCard(run) {
|
|
@@ -637,7 +658,7 @@
|
|
|
637
658
|
if (row.key) dt.dataset.metaKey = row.key;
|
|
638
659
|
const dd = document.createElement('dd');
|
|
639
660
|
dd.className = 'run-final-answer__meta-value';
|
|
640
|
-
dd.textContent = row.value;
|
|
661
|
+
dd.textContent = failureNotice?.formatMetaValue(report, row) ?? row.value;
|
|
641
662
|
metaBlock.append(dt, dd);
|
|
642
663
|
}
|
|
643
664
|
if (metaBlock.children.length) {
|
|
@@ -714,6 +735,7 @@
|
|
|
714
735
|
report.append(main);
|
|
715
736
|
}
|
|
716
737
|
|
|
738
|
+
failureNotice?.append(report, event, run);
|
|
717
739
|
appendCompletionMetaBlock(report, structured.meta);
|
|
718
740
|
appendCompileFix(report);
|
|
719
741
|
appendRejectedRedo(report);
|
|
@@ -735,6 +757,7 @@
|
|
|
735
757
|
report.append(body);
|
|
736
758
|
appendCompileFix(report);
|
|
737
759
|
appendRejectedRedo(report);
|
|
760
|
+
failureNotice?.append(report, event, run);
|
|
738
761
|
appendCompletionMetaBlock(report, split.meta);
|
|
739
762
|
appendRecoveryActionForFailure(report, event, run);
|
|
740
763
|
return report;
|
|
@@ -793,6 +816,9 @@
|
|
|
793
816
|
}
|
|
794
817
|
|
|
795
818
|
function appendRecoveryActionForFailure(report, event, run) {
|
|
819
|
+
const recovery = failureNotice?.prepareRecovery(report, { openFile: openProjectFileForFailure });
|
|
820
|
+
if (recovery?.handled) return;
|
|
821
|
+
report = recovery?.target || report;
|
|
796
822
|
const failureCode = event?.failure?.code || '';
|
|
797
823
|
if (RETRYABLE_FAILURE_CODES.has(failureCode) && typeof refillComposerForRetry === 'function') {
|
|
798
824
|
const retry = buildRecoveryButton(failureCode,
|
|
@@ -95,18 +95,18 @@
|
|
|
95
95
|
</details>
|
|
96
96
|
<details class="codex-set-group" data-set-group="experimental">
|
|
97
97
|
<summary class="codex-set-group-head">
|
|
98
|
-
<span class="codex-set-group-title">${codexSetIcon('
|
|
98
|
+
<span class="codex-set-group-title">${codexSetIcon('database')}<span data-i18n="otSettingsTitle">Project cache</span></span>
|
|
99
99
|
<span class="codex-set-saved" data-set-saved hidden>✓ <span data-i18n="settingsSaved">Saved</span></span>
|
|
100
100
|
</summary>
|
|
101
101
|
<div class="codex-set-card">
|
|
102
102
|
<label class="codex-project-settings-row codex-project-settings-row--switch">
|
|
103
103
|
<span class="codex-project-settings-row-label">
|
|
104
|
-
<span data-i18n="experimentalOtMenuTitle">
|
|
105
|
-
<small class="codex-set-row-sub" data-i18n="experimentalOtMenuSubtitle">
|
|
104
|
+
<span data-i18n="experimentalOtMenuTitle">OT Mirror</span>
|
|
105
|
+
<small class="codex-set-row-sub" data-i18n="experimentalOtMenuSubtitle">Warms recent edits to the active text file locally. File scope and freshness are checked before reuse.</small>
|
|
106
106
|
</span>
|
|
107
107
|
<input type="checkbox" class="codex-switch" data-experimental-ot>
|
|
108
108
|
</label>
|
|
109
|
-
<p class="codex-set-row-help" data-experimental-ot-menu-status></p>
|
|
109
|
+
<p class="codex-set-row-help" data-experimental-ot-menu-status aria-live="polite"></p>
|
|
110
110
|
</div>
|
|
111
111
|
</details>
|
|
112
112
|
</div>
|
|
@@ -159,17 +159,25 @@
|
|
|
159
159
|
<button type="button" class="codex-set-btn" data-provider-settings-open data-i18n="providerSettingsConfigure">Configure</button>
|
|
160
160
|
</div>
|
|
161
161
|
</details>
|
|
162
|
+
<details class="codex-set-group" data-set-group="context-loading" open>
|
|
163
|
+
<summary class="codex-set-group-head">
|
|
164
|
+
<span class="codex-set-group-title">${codexSetIcon('database')}<span data-i18n="contextLoadingTitle">Context loading</span></span>
|
|
165
|
+
<span class="codex-set-saved" data-set-saved hidden>✓ <span data-i18n="settingsSaved">Saved</span></span>
|
|
166
|
+
</summary>
|
|
167
|
+
<div class="codex-set-card">
|
|
168
|
+
<label class="codex-project-settings-row codex-project-settings-row--switch">
|
|
169
|
+
<span class="codex-project-settings-row-label" data-i18n="preloadProjectContextTitle">Preload project context</span>
|
|
170
|
+
<input type="checkbox" class="codex-switch" data-preload-project-context>
|
|
171
|
+
</label>
|
|
172
|
+
<p class="codex-set-row-help" data-i18n="preloadProjectContextDescription">Fetch the exact project file list in the background after an Overleaf project becomes idle.</p>
|
|
173
|
+
</div>
|
|
174
|
+
</details>
|
|
162
175
|
<details class="codex-set-group" data-set-group="updates" open>
|
|
163
176
|
<summary class="codex-set-group-head">
|
|
164
177
|
<span class="codex-set-group-title">${codexSetIcon('software')}<span data-i18n="softwareUpdatesTitle">Software updates</span></span>
|
|
165
178
|
</summary>
|
|
166
179
|
<div class="codex-set-card" data-update-settings>
|
|
167
180
|
<div class="codex-set-row">
|
|
168
|
-
<label class="codex-project-settings-row codex-project-settings-row--switch">
|
|
169
|
-
<span class="codex-project-settings-row-label" data-i18n="preloadProjectContextTitle">Preload project context</span>
|
|
170
|
-
<input type="checkbox" class="codex-switch" data-preload-project-context>
|
|
171
|
-
</label>
|
|
172
|
-
<p class="codex-set-row-help" data-i18n="preloadProjectContextDescription">Fetch the exact project file list in the background after an Overleaf project becomes idle.</p>
|
|
173
181
|
<p class="codex-set-row-help" data-update-settings-summary data-i18n="softwareUpdatesHelp">Automatically checks signed stable releases for the Extension and Native Host.</p>
|
|
174
182
|
<p class="codex-set-note" data-update-settings-state aria-live="polite" hidden></p>
|
|
175
183
|
<button type="button" class="codex-set-btn" data-check-updates data-i18n="checkForUpdates">Check for updates</button>
|
|
@@ -235,8 +243,11 @@
|
|
|
235
243
|
instance.callbacks.onHistoryOpen?.();
|
|
236
244
|
}
|
|
237
245
|
});
|
|
246
|
+
container.querySelector('[data-storage-card]')?.addEventListener('toggle', event => {
|
|
247
|
+
if (event.target.open) instance.callbacks.onStorageOpen?.();
|
|
248
|
+
});
|
|
238
249
|
container.querySelector('[data-history-filter]')?.addEventListener('input', () => instance.callbacks.onHistoryFilter?.());
|
|
239
|
-
//
|
|
250
|
+
// OT mirror: a single visible switch; legacy storage/DOM keys are retained. The click is
|
|
240
251
|
// intercepted (the runtime's handler preventDefaults, runs the enable
|
|
241
252
|
// confirmation, then sets checked + drives the change flow itself) so the
|
|
242
253
|
// confirm-before-enable contract survives the control unification.
|
|
@@ -251,10 +262,17 @@
|
|
|
251
262
|
for (const selector of ['[data-governance-readonly-patterns]', '[data-governance-writable-patterns]', '[data-sensitive-check-enabled]', '[data-sensitive-confirm-allowed]', '[data-preload-project-context]', '[data-load-codex-local-skills]', '[data-load-codex-overleaf-skills]', '[data-theme-select]', '[data-language-select]']) {
|
|
252
263
|
const element = container.querySelector(selector);
|
|
253
264
|
element?.addEventListener?.('change', event => {
|
|
254
|
-
instance.callbacks.onInputChange?.(event)
|
|
255
|
-
|
|
265
|
+
Promise.resolve(instance.callbacks.onInputChange?.(event))
|
|
266
|
+
.then(() => {
|
|
267
|
+
flashSaved(instance, event);
|
|
268
|
+
if (selector === '[data-language-select]' && container.querySelector('[data-storage-card]')?.open) {
|
|
269
|
+
instance.callbacks.onStorageOpen?.();
|
|
270
|
+
}
|
|
271
|
+
}).catch(() => {});
|
|
272
|
+
});
|
|
273
|
+
element?.addEventListener?.('input', event => {
|
|
274
|
+
Promise.resolve(instance.callbacks.onInputChange?.(event)).catch(() => {});
|
|
256
275
|
});
|
|
257
|
-
element?.addEventListener?.('input', event => instance.callbacks.onInputChange?.(event));
|
|
258
276
|
}
|
|
259
277
|
// Guardrails: live, non-blocking analysis of the protection globs.
|
|
260
278
|
for (const selector of ['[data-governance-readonly-patterns]', '[data-governance-writable-patterns]']) {
|
|
@@ -265,6 +283,7 @@
|
|
|
265
283
|
setupGroupPersistence(instance);
|
|
266
284
|
|
|
267
285
|
return {
|
|
286
|
+
get container() { return instance.container; },
|
|
268
287
|
show: () => show(instance),
|
|
269
288
|
hide: () => hide(instance),
|
|
270
289
|
loadState: state => loadState(instance, state),
|
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
const resolved = resolveTheme(preference, mediaQueryList);
|
|
52
52
|
if (panelRoot && typeof panelRoot.setAttribute === 'function') {
|
|
53
53
|
panelRoot.setAttribute('data-theme', resolved);
|
|
54
|
+
for (const target of panelRoot.ownerDocument?.querySelectorAll?.('.codex-provider-dialog-root, #codex-overleaf-launcher') || []) {
|
|
55
|
+
target.setAttribute('data-theme', resolved);
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
return resolved;
|
|
56
59
|
}
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
install: 'codex-overleaf/consent-update-install',
|
|
91
91
|
retry: 'codex-overleaf/consent-update-check',
|
|
92
92
|
later: 'codex-overleaf/consent-update-later',
|
|
93
|
-
dismiss: 'codex-overleaf/consent-update-dismiss'
|
|
93
|
+
dismiss: 'codex-overleaf/consent-update-dismiss',
|
|
94
|
+
reload: 'codex-overleaf/consent-update-reload'
|
|
94
95
|
}[action]);
|
|
95
96
|
} catch (error) {
|
|
96
97
|
currentView = await reconcileAfterActionError(error, {
|
|
@@ -212,7 +213,10 @@
|
|
|
212
213
|
const stateName = currentView?.state?.state || 'idle';
|
|
213
214
|
let requestedAction = 'check';
|
|
214
215
|
try {
|
|
215
|
-
if (
|
|
216
|
+
if (['reload_required', 'reload_tabs_required'].includes(stateName)) {
|
|
217
|
+
requestedAction = 'reload';
|
|
218
|
+
currentView = await request('codex-overleaf/consent-update-reload');
|
|
219
|
+
} else if (stateName === 'update_available') {
|
|
216
220
|
requestedAction = 'install';
|
|
217
221
|
currentView = await request('codex-overleaf/consent-update-install');
|
|
218
222
|
} else if (['failed', 'rolled_back'].includes(stateName)) {
|
|
@@ -276,7 +280,7 @@
|
|
|
276
280
|
body.append(recovery, command);
|
|
277
281
|
}
|
|
278
282
|
|
|
279
|
-
if (!['update_available', 'failed', 'rolled_back'].includes(state.state)) {
|
|
283
|
+
if (!['update_available', 'failed', 'rolled_back', 'reload_required', 'reload_tabs_required'].includes(state.state)) {
|
|
280
284
|
const bar = document.createElement('div');
|
|
281
285
|
bar.className = 'codex-update-notice-progress' + (progress.determinate ? '' : ' is-indeterminate');
|
|
282
286
|
bar.setAttribute('role', 'progressbar');
|
|
@@ -328,7 +332,11 @@
|
|
|
328
332
|
let statusText = '';
|
|
329
333
|
let buttonText = tx('Check for updates', '检查更新');
|
|
330
334
|
|
|
331
|
-
if (
|
|
335
|
+
if (['reload_required', 'reload_tabs_required'].includes(stateName)) {
|
|
336
|
+
summaryText = tx(`Installed v${latestVersion}; running extension v${currentVersion}.`, `已安装 v${latestVersion};扩展当前运行 v${currentVersion}。`);
|
|
337
|
+
statusText = getCopy(state).detail;
|
|
338
|
+
buttonText = stateName === 'reload_required' ? tx('Reload extension', '重新加载扩展') : tx('Refresh Overleaf tabs', '刷新 Overleaf 标签页');
|
|
339
|
+
} else if (stateName === 'checking') {
|
|
332
340
|
statusText = tx('Checking the latest stable release…', '正在检查最新稳定版本…');
|
|
333
341
|
buttonText = tx('Checking…', '检查中…');
|
|
334
342
|
} else if (stateName === 'update_available') {
|
|
@@ -355,6 +363,10 @@
|
|
|
355
363
|
|
|
356
364
|
function visibleActions(state) {
|
|
357
365
|
if (actionInFlight) return [];
|
|
366
|
+
if (['reload_required', 'reload_tabs_required'].includes(state)) {
|
|
367
|
+
return [{ id: 'reload', label: state === 'reload_required'
|
|
368
|
+
? tx('Reload extension', '重新加载扩展') : tx('Refresh Overleaf tabs', '刷新 Overleaf 标签页'), primary: true }];
|
|
369
|
+
}
|
|
358
370
|
if (state === 'update_available') {
|
|
359
371
|
return [
|
|
360
372
|
{ id: 'later', label: tx('Later', '稍后'), primary: false },
|
|
@@ -380,6 +392,14 @@
|
|
|
380
392
|
const target = state.latestVersion ? 'v' + state.latestVersion : '';
|
|
381
393
|
const blocker = blockersCopy(state.blockers?.length ? state.blockers : [state.blocker]);
|
|
382
394
|
return {
|
|
395
|
+
reload_required: {
|
|
396
|
+
eyebrow: tx('Installed, awaiting reload', '已安装,等待加载'), title: target,
|
|
397
|
+
detail: state.message || tx('The new files are installed. Reload the extension when Overleaf is saved and idle.', '新版文件已安装,请在 Overleaf 保存并空闲后重新加载扩展。')
|
|
398
|
+
},
|
|
399
|
+
reload_tabs_required: {
|
|
400
|
+
eyebrow: tx('Updated runtime is ready', '新版运行组件已就绪'), title: target,
|
|
401
|
+
detail: state.message || tx('Refresh the saved, idle Overleaf tabs to load the updated panel.', '刷新已保存且空闲的 Overleaf 标签页以加载新版面板。')
|
|
402
|
+
},
|
|
383
403
|
update_available: {
|
|
384
404
|
eyebrow: tx('Update available', '发现新版本'),
|
|
385
405
|
title: target,
|