codex-overleaf-link 1.8.6 → 1.9.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 +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 +8895 -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 +1126 -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/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,174 @@
|
|
|
1
|
+
(function initCodexOverleafOtWarmMirrorController(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafOtWarmMirrorController = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function otWarmMirrorControllerFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const OT_POLL_INTERVAL_MS = 1000;
|
|
11
|
+
const OT_PATCH_DEBOUNCE_MS = 500;
|
|
12
|
+
const OT_MAX_PATCH_BATCH = 25;
|
|
13
|
+
const PAUSE_STATES = new Set(['running', 'writing', 'undoing', 'compiling']);
|
|
14
|
+
|
|
15
|
+
function buildPatchFilesRequest({ projectId, events, nativeCompatibility } = {}) {
|
|
16
|
+
const files = [];
|
|
17
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
18
|
+
if (files.length >= OT_MAX_PATCH_BATCH) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const filePath = normalizePath(event?.path);
|
|
22
|
+
if (!isSafeRelativePath(filePath) || typeof event?.baseHash !== 'string') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const baseHash = event.baseHash.trim();
|
|
26
|
+
if (!baseHash) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (typeof event.nextContent !== 'string') {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const file = {
|
|
34
|
+
path: filePath,
|
|
35
|
+
baseHash,
|
|
36
|
+
nextContent: event.nextContent
|
|
37
|
+
};
|
|
38
|
+
if (isPrimitiveMetadata(event.observedVersion)) {
|
|
39
|
+
file.observedVersion = event.observedVersion;
|
|
40
|
+
}
|
|
41
|
+
if (isPrimitiveMetadata(event.observedAt)) {
|
|
42
|
+
file.observedAt = event.observedAt;
|
|
43
|
+
}
|
|
44
|
+
files.push(file);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const params = {
|
|
48
|
+
projectId,
|
|
49
|
+
source: 'ot',
|
|
50
|
+
files
|
|
51
|
+
};
|
|
52
|
+
if (isNativeCompatibilityEvidence(nativeCompatibility)) {
|
|
53
|
+
params.nativeCompatibility = nativeCompatibility;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
method: 'mirror.patchFiles',
|
|
58
|
+
params
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function shouldPauseOtWarmMirror(state) {
|
|
63
|
+
const reason = getPauseStateName(state);
|
|
64
|
+
if (PAUSE_STATES.has(reason)) {
|
|
65
|
+
return { pause: true, reason };
|
|
66
|
+
}
|
|
67
|
+
return { pause: false };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function canUseOtWarmStart({ enabled, focusFiles, mirrorStatus } = {}) {
|
|
71
|
+
if (!enabled) {
|
|
72
|
+
return { ok: false, reason: 'disabled' };
|
|
73
|
+
}
|
|
74
|
+
if (!mirrorStatus || mirrorStatus.exists !== true) {
|
|
75
|
+
return { ok: false, reason: 'mirror_missing' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const normalizedFocusFiles = normalizePaths(focusFiles);
|
|
79
|
+
if (!normalizedFocusFiles.length) {
|
|
80
|
+
return { ok: false, reason: 'no_focus_files' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const freshFiles = new Set();
|
|
84
|
+
for (const file of Array.isArray(mirrorStatus.otFreshFiles) ? mirrorStatus.otFreshFiles : []) {
|
|
85
|
+
if (file?.state !== 'fresh') {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const filePath = normalizePath(file.path);
|
|
89
|
+
if (filePath) {
|
|
90
|
+
freshFiles.add(filePath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (normalizedFocusFiles.every(filePath => freshFiles.has(filePath))) {
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
reason: 'ot_focus_fresh',
|
|
98
|
+
focusFiles: normalizedFocusFiles,
|
|
99
|
+
restrictToFocusFiles: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ok: false, reason: 'missing_ot_fresh_focus' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizePath(value) {
|
|
106
|
+
return String(value ?? '')
|
|
107
|
+
.trim()
|
|
108
|
+
.replace(/^@file:/i, '')
|
|
109
|
+
.trim()
|
|
110
|
+
.replace(/\\/g, '/')
|
|
111
|
+
.replace(/\/+/g, '/')
|
|
112
|
+
.replace(/^\/+/, '');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizePaths(value) {
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
const paths = [];
|
|
118
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
119
|
+
const filePath = normalizePath(item);
|
|
120
|
+
if (!filePath || seen.has(filePath)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
seen.add(filePath);
|
|
124
|
+
paths.push(filePath);
|
|
125
|
+
}
|
|
126
|
+
return paths;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isSafeRelativePath(filePath) {
|
|
130
|
+
if (!filePath) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return !filePath.split('/').some(part => part === '.' || part === '..');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isPrimitiveMetadata(value) {
|
|
137
|
+
return ['string', 'number', 'boolean'].includes(typeof value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isNativeCompatibilityEvidence(value) {
|
|
141
|
+
return value && typeof value === 'object' && typeof value.status === 'string';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getPauseStateName(state) {
|
|
145
|
+
if (typeof state === 'string') {
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
148
|
+
if (!state || typeof state !== 'object') {
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
for (const key of ['status', 'state', 'phase', 'mode']) {
|
|
152
|
+
if (typeof state[key] === 'string') {
|
|
153
|
+
return state[key];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const pauseState of PAUSE_STATES) {
|
|
157
|
+
if (state[pauseState] === true) {
|
|
158
|
+
return pauseState;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
OT_POLL_INTERVAL_MS,
|
|
166
|
+
OT_PATCH_DEBOUNCE_MS,
|
|
167
|
+
OT_MAX_PATCH_BATCH,
|
|
168
|
+
buildPatchFilesRequest,
|
|
169
|
+
canUseOtWarmStart,
|
|
170
|
+
normalizePath,
|
|
171
|
+
normalizePaths,
|
|
172
|
+
shouldPauseOtWarmMirror
|
|
173
|
+
};
|
|
174
|
+
});
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
(function initCodexOverleafPanelMaintenance() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Panel maintenance — carved out of contentRuntime.js in v1.8.0
|
|
5
|
+
// (structural-debt phase 8): the recovery-action handlers injected into the
|
|
6
|
+
// run timeline (retry refill / open file / open storage settings), the
|
|
7
|
+
// change-history read path, the history & storage settings card, and the
|
|
8
|
+
// aggressive-compaction notice. Code moved with mutable runtime state
|
|
9
|
+
// (panel, state, currentRunView, settingsPanelInstance) rewritten to the
|
|
10
|
+
// injected getter/setter accessors, matching the earlier carves.
|
|
11
|
+
function create(deps = {}) {
|
|
12
|
+
const {
|
|
13
|
+
tx,
|
|
14
|
+
showPluginToast,
|
|
15
|
+
showPluginConfirm,
|
|
16
|
+
callPageBridge,
|
|
17
|
+
getCurrentProjectId,
|
|
18
|
+
saveState,
|
|
19
|
+
saveStateSoon,
|
|
20
|
+
autosizeTaskTextarea,
|
|
21
|
+
applyStateToPanel,
|
|
22
|
+
normalizePanelState,
|
|
23
|
+
openCustomInstructionsSettings,
|
|
24
|
+
onHistoryRowJump,
|
|
25
|
+
resetProjectNameCache,
|
|
26
|
+
getPanel,
|
|
27
|
+
getState,
|
|
28
|
+
setState,
|
|
29
|
+
getCurrentRunView,
|
|
30
|
+
getSettingsPanelInstance,
|
|
31
|
+
} = deps;
|
|
32
|
+
|
|
33
|
+
// --- Change history (v1.7.5): the audit log finally gets a read path. ---
|
|
34
|
+
// Loaded lazily when the Settings card is expanded; newest 50 project runs.
|
|
35
|
+
let auditHistoryRecords = [];
|
|
36
|
+
|
|
37
|
+
async function renderAuditHistoryPanel() {
|
|
38
|
+
const container = getSettingsPanelInstance()?.container;
|
|
39
|
+
const list = container?.querySelector('[data-history-list]');
|
|
40
|
+
if (!list) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const filterInput = container.querySelector('[data-history-filter]');
|
|
44
|
+
if (filterInput && !filterInput.placeholder) {
|
|
45
|
+
filterInput.placeholder = tx('Filter by file or task\u2026', '按文件名或任务过滤…');
|
|
46
|
+
}
|
|
47
|
+
list.textContent = tx('Loading\u2026', '正在加载…');
|
|
48
|
+
let records = [];
|
|
49
|
+
try {
|
|
50
|
+
const StorageDb = window.CodexOverleafStorageDb;
|
|
51
|
+
records = StorageDb?.getAllByIndex
|
|
52
|
+
? await StorageDb.getAllByIndex('auditLogs', 'projectId', getCurrentProjectId())
|
|
53
|
+
: [];
|
|
54
|
+
} catch (error) {
|
|
55
|
+
list.textContent = tx(`Could not load change history: ${error.message}`, `无法加载变更历史:${error.message}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
auditHistoryRecords = (records || [])
|
|
59
|
+
.sort((a, b) => String(b?.createdAt || '').localeCompare(String(a?.createdAt || '')))
|
|
60
|
+
.slice(0, 50);
|
|
61
|
+
applyAuditHistoryFilter();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function applyAuditHistoryFilter() {
|
|
65
|
+
const container = getSettingsPanelInstance()?.container;
|
|
66
|
+
const list = container?.querySelector('[data-history-list]');
|
|
67
|
+
if (!list) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const query = String(container.querySelector('[data-history-filter]')?.value || '').trim().toLowerCase();
|
|
71
|
+
const filePaths = record => []
|
|
72
|
+
.concat(record?.appliedFiles || [], record?.changedFiles || [])
|
|
73
|
+
.map(file => (file && typeof file === 'object') ? file.path : file)
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
const rows = auditHistoryRecords.filter(record => {
|
|
76
|
+
if (!query) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return [record?.promptSummary || '', ...filePaths(record)].join(' ').toLowerCase().includes(query);
|
|
80
|
+
});
|
|
81
|
+
list.replaceChildren();
|
|
82
|
+
if (!rows.length) {
|
|
83
|
+
const empty = document.createElement('div');
|
|
84
|
+
empty.className = 'codex-history-empty';
|
|
85
|
+
empty.textContent = query
|
|
86
|
+
? tx('No recorded changes match this filter.', '没有匹配该过滤条件的变更记录。')
|
|
87
|
+
: tx('No recorded changes for this project yet.', '本项目还没有已记录的变更。');
|
|
88
|
+
list.append(empty);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const record of rows) {
|
|
92
|
+
const row = document.createElement('div');
|
|
93
|
+
row.className = 'codex-history-row';
|
|
94
|
+
row.dataset.resultStatus = record.resultStatus || '';
|
|
95
|
+
const when = document.createElement('div');
|
|
96
|
+
when.className = 'codex-history-when';
|
|
97
|
+
when.textContent = record.createdAt ? new Date(record.createdAt).toLocaleString() : '';
|
|
98
|
+
const task = document.createElement('div');
|
|
99
|
+
task.className = 'codex-history-task';
|
|
100
|
+
task.textContent = record.promptSummary || '';
|
|
101
|
+
task.title = record.promptSummary || '';
|
|
102
|
+
const files = document.createElement('div');
|
|
103
|
+
files.className = 'codex-history-files';
|
|
104
|
+
const applied = (record.appliedFiles || [])
|
|
105
|
+
.map(file => (file && typeof file === 'object') ? file.path : file)
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
files.textContent = applied.length
|
|
108
|
+
? applied.join(' \u00b7 ')
|
|
109
|
+
: tx('No files were written.', '未写入文件。');
|
|
110
|
+
row.append(when, task, files);
|
|
111
|
+
// v1.8.0: rows jump to the run they describe (turnId === run.id).
|
|
112
|
+
if (record.sessionId && record.turnId && typeof onHistoryRowJump === 'function') {
|
|
113
|
+
row.classList.add('codex-history-row--linked');
|
|
114
|
+
row.setAttribute('role', 'button');
|
|
115
|
+
row.tabIndex = 0;
|
|
116
|
+
const jump = () => onHistoryRowJump(record);
|
|
117
|
+
row.addEventListener('click', jump);
|
|
118
|
+
row.addEventListener('keydown', keyEvent => {
|
|
119
|
+
if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
120
|
+
keyEvent.preventDefault();
|
|
121
|
+
jump();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
list.append(row);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- History & storage (v1.7.5): usage summary + clear-all in Settings ---
|
|
130
|
+
function refreshStorageUsageSummary() {
|
|
131
|
+
const node = getSettingsPanelInstance()?.container?.querySelector('[data-storage-usage]');
|
|
132
|
+
if (!node) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const sessionCount = Array.isArray(getState()?.sessions) ? getState().sessions.length : 0;
|
|
136
|
+
const runCount = Array.isArray(getState()?.runs) ? getState().runs.length : 0;
|
|
137
|
+
const counts = tx(
|
|
138
|
+
`${sessionCount} session(s) · ${runCount} recent run(s) kept`,
|
|
139
|
+
`${sessionCount} 个会话 · 保留最近 ${runCount} 轮运行`
|
|
140
|
+
);
|
|
141
|
+
node.textContent = counts;
|
|
142
|
+
const estimate = navigator.storage?.estimate?.();
|
|
143
|
+
if (estimate?.then) {
|
|
144
|
+
estimate.then(info => {
|
|
145
|
+
const usage = Number(info?.usage);
|
|
146
|
+
if (Number.isFinite(usage) && usage >= 0) {
|
|
147
|
+
const mb = usage / (1024 * 1024);
|
|
148
|
+
const size = mb >= 1 ? `${mb.toFixed(1)} MB` : `${Math.max(1, Math.round(usage / 1024))} KB`;
|
|
149
|
+
node.textContent = tx(`${counts} · ~${size} used on this site`, `${counts} · 本站点约占用 ${size}`);
|
|
150
|
+
}
|
|
151
|
+
}).catch(() => { /* estimate unsupported — counts alone are fine */ });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function clearAllHistoryWithConfirm() {
|
|
156
|
+
if (getCurrentRunView()) {
|
|
157
|
+
showPluginToast(tx('A run is in progress — wait for it to finish (or cancel it) before clearing history.', '有任务正在运行——请先等它结束(或取消)再清空历史。'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const confirmed = await showPluginConfirm({
|
|
161
|
+
title: tx('Clear all history?', '清空全部历史?'),
|
|
162
|
+
message: tx(
|
|
163
|
+
'This permanently deletes every stored session, run and audit record for ALL projects — including the dashboard project list and cached project names. Project settings and rules are kept. This cannot be undone.',
|
|
164
|
+
'将永久删除所有项目的全部会话、运行与审计记录——包括仪表盘的项目列表与缓存的项目名。项目设置与规则会保留。此操作不可撤销。'
|
|
165
|
+
),
|
|
166
|
+
confirmLabel: tx('Delete everything', '全部删除'),
|
|
167
|
+
destructive: true
|
|
168
|
+
});
|
|
169
|
+
if (!confirmed) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await window.CodexOverleafStorageDb?.clearAllStores?.();
|
|
174
|
+
// v1.8.1: the dashboard's project-name cache lives in
|
|
175
|
+
// chrome.storage.local, outside the IndexedDB stores — clear it too so
|
|
176
|
+
// no (potentially sensitive) project titles outlive the wipe.
|
|
177
|
+
await resetProjectNameCache?.();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
showPluginToast(tx(`Could not clear history: ${error.message}`, `清空历史失败:${error.message}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Reset the in-getPanel() getState() to match the emptied store and re-render
|
|
183
|
+
// EXPLICITLY. startNewSession() is unusable here: its empty-session reuse
|
|
184
|
+
// guard matches the fresh session that normalization mints and returns
|
|
185
|
+
// before applyStateToPanel(), leaving the deleted cards on screen.
|
|
186
|
+
setState(normalizePanelState({ ...getState(), sessions: [], runs: [], activeSessionId: '' }));
|
|
187
|
+
await saveState();
|
|
188
|
+
applyStateToPanel();
|
|
189
|
+
refreshStorageUsageSummary();
|
|
190
|
+
showPluginToast(tx('All history cleared.', '已清空全部历史。'));
|
|
191
|
+
getPanel()?.querySelector('[data-task]')?.focus();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// One toast per page load: aggressive compaction silently halves history
|
|
195
|
+
// limits; the user deserves to know trimming is happening and where to act.
|
|
196
|
+
let aggressiveCompactionNoticeShown = false;
|
|
197
|
+
function notifyAggressiveCompactionOnce() {
|
|
198
|
+
if (aggressiveCompactionNoticeShown) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
aggressiveCompactionNoticeShown = true;
|
|
202
|
+
showPluginToast(tx(
|
|
203
|
+
'Storage is tight: older history is being trimmed harder so saving keeps working. You can clear old history in Settings.',
|
|
204
|
+
'存储空间紧张:正在更积极地精简较旧历史以保证保存成功。可在设置的「历史与存储」中清理。'
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- Recovery-action handlers (v1.7.5) — injected into runTimelineView ---
|
|
209
|
+
// Retry deliberately refills the composer instead of re-running directly:
|
|
210
|
+
// the failed task usually needs a tweak before it is worth resending.
|
|
211
|
+
function refillComposerForRetry(run, textOverride) {
|
|
212
|
+
const input = getPanel()?.querySelector('[data-task]');
|
|
213
|
+
const task = typeof textOverride === 'string' && textOverride.trim()
|
|
214
|
+
? textOverride
|
|
215
|
+
: String(run?.task || '');
|
|
216
|
+
if (!input || !task.trim()) {
|
|
217
|
+
showPluginToast(tx('Nothing to refill: the original task text is unavailable.', '无法回填:原任务文本不可用。'));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
input.value = task;
|
|
221
|
+
getState().task = task;
|
|
222
|
+
autosizeTaskTextarea();
|
|
223
|
+
saveStateSoon();
|
|
224
|
+
input.focus();
|
|
225
|
+
try {
|
|
226
|
+
input.setSelectionRange(task.length, task.length);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
// Non-text inputs throw; cursor placement is best-effort.
|
|
229
|
+
}
|
|
230
|
+
if (Array.isArray(run?.attachments) && run.attachments.length) {
|
|
231
|
+
showPluginToast(tx('Task refilled. Attachments are not restored — re-add them via +.', '任务已回填。附件不会自动恢复,请在 + 中重新添加。'));
|
|
232
|
+
} else {
|
|
233
|
+
showPluginToast(tx('Task refilled — edit and resend.', '任务已回填,修改后可直接重发。'));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// jumpToPosition opens the file via the page-side editor bridge; 0..0 lands
|
|
238
|
+
// at the top without selecting anything.
|
|
239
|
+
async function openProjectFileForFailure(path) {
|
|
240
|
+
const target = String(path || '').trim();
|
|
241
|
+
if (!target) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
await callPageBridge('jumpToPosition', { path: target, from: 0, to: 0 });
|
|
246
|
+
} catch (error) {
|
|
247
|
+
showPluginToast(tx(`Could not open ${target}: ${error.message}`, `无法打开 ${target}:${error.message}`));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Per-file undo selection (v1.8.0 C3). A destructive-confirm variant with
|
|
252
|
+
// one checkbox per written file, all checked by default. Resolves with the
|
|
253
|
+
// selected paths, or null on cancel. Re-undoing an already-undone file is
|
|
254
|
+
// safe: the page-side base-content check skips it, so the modal never has
|
|
255
|
+
// to track which files a previous partial undo already restored.
|
|
256
|
+
function showUndoFileSelection({ title, task, confirmLabel, cancelLabel, selectAllLabel, paths }) {
|
|
257
|
+
const host = getPanel();
|
|
258
|
+
if (!host || !Array.isArray(paths) || !paths.length) {
|
|
259
|
+
return Promise.resolve(null);
|
|
260
|
+
}
|
|
261
|
+
return new Promise(resolve => {
|
|
262
|
+
host.querySelector('[data-undo-file-select]')?.remove();
|
|
263
|
+
const overlay = document.createElement('div');
|
|
264
|
+
overlay.className = 'codex-plugin-confirm codex-undo-file-select';
|
|
265
|
+
overlay.setAttribute('data-undo-file-select', '');
|
|
266
|
+
const box = document.createElement('div');
|
|
267
|
+
box.className = 'codex-plugin-confirm-card';
|
|
268
|
+
const heading = document.createElement('div');
|
|
269
|
+
heading.className = 'codex-plugin-confirm-title';
|
|
270
|
+
heading.textContent = title;
|
|
271
|
+
const taskLine = document.createElement('div');
|
|
272
|
+
taskLine.className = 'codex-plugin-confirm-body';
|
|
273
|
+
taskLine.textContent = task || '';
|
|
274
|
+
const list = document.createElement('div');
|
|
275
|
+
list.className = 'codex-undo-file-list';
|
|
276
|
+
const boxes = [];
|
|
277
|
+
const allToggle = document.createElement('label');
|
|
278
|
+
const allInput = document.createElement('input');
|
|
279
|
+
allInput.type = 'checkbox';
|
|
280
|
+
allInput.checked = true;
|
|
281
|
+
allToggle.append(allInput, document.createTextNode(` ${selectAllLabel}`));
|
|
282
|
+
allToggle.className = 'codex-undo-file-item codex-undo-file-item--all';
|
|
283
|
+
allInput.addEventListener('change', () => {
|
|
284
|
+
for (const input of boxes) {
|
|
285
|
+
input.checked = allInput.checked;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
list.append(allToggle);
|
|
289
|
+
for (const path of paths) {
|
|
290
|
+
const item = document.createElement('label');
|
|
291
|
+
item.className = 'codex-undo-file-item';
|
|
292
|
+
const input = document.createElement('input');
|
|
293
|
+
input.type = 'checkbox';
|
|
294
|
+
input.checked = true;
|
|
295
|
+
input.value = path;
|
|
296
|
+
input.addEventListener('change', () => {
|
|
297
|
+
allInput.checked = boxes.every(box => box.checked);
|
|
298
|
+
});
|
|
299
|
+
boxes.push(input);
|
|
300
|
+
item.append(input, document.createTextNode(` ${path}`));
|
|
301
|
+
list.append(item);
|
|
302
|
+
}
|
|
303
|
+
const actions = document.createElement('div');
|
|
304
|
+
actions.className = 'codex-plugin-confirm-actions';
|
|
305
|
+
const cancel = document.createElement('button');
|
|
306
|
+
cancel.type = 'button';
|
|
307
|
+
cancel.textContent = cancelLabel;
|
|
308
|
+
const confirm = document.createElement('button');
|
|
309
|
+
confirm.type = 'button';
|
|
310
|
+
confirm.className = 'codex-plugin-confirm-confirm';
|
|
311
|
+
confirm.dataset.destructive = 'true';
|
|
312
|
+
confirm.textContent = confirmLabel;
|
|
313
|
+
const finish = value => {
|
|
314
|
+
overlay.remove();
|
|
315
|
+
resolve(value);
|
|
316
|
+
};
|
|
317
|
+
cancel.addEventListener('click', () => finish(null));
|
|
318
|
+
confirm.addEventListener('click', () => {
|
|
319
|
+
const selected = boxes.filter(input => input.checked).map(input => input.value);
|
|
320
|
+
finish(selected.length ? selected : null);
|
|
321
|
+
});
|
|
322
|
+
actions.append(cancel, confirm);
|
|
323
|
+
box.append(heading, taskLine, list, actions);
|
|
324
|
+
overlay.append(box);
|
|
325
|
+
host.append(overlay);
|
|
326
|
+
cancel.focus();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Change history first-class entry (v1.8.0): callers jump straight to the
|
|
331
|
+
// Settings history card, expanded and loaded.
|
|
332
|
+
function openChangeHistory() {
|
|
333
|
+
openCustomInstructionsSettings();
|
|
334
|
+
const card = getSettingsPanelInstance()?.container?.querySelector('[data-history-card]');
|
|
335
|
+
if (card) {
|
|
336
|
+
if (!card.open) {
|
|
337
|
+
card.open = true;
|
|
338
|
+
}
|
|
339
|
+
renderAuditHistoryPanel();
|
|
340
|
+
card.scrollIntoView({ block: 'start' });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function openStorageSettings() {
|
|
345
|
+
openCustomInstructionsSettings();
|
|
346
|
+
const card = getPanel()?.querySelector('[data-storage-card]');
|
|
347
|
+
if (card) {
|
|
348
|
+
card.open = true;
|
|
349
|
+
card.scrollIntoView({ block: 'center' });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
showUndoFileSelection,
|
|
354
|
+
openChangeHistory,
|
|
355
|
+
renderAuditHistoryPanel,
|
|
356
|
+
applyAuditHistoryFilter,
|
|
357
|
+
refreshStorageUsageSummary,
|
|
358
|
+
clearAllHistoryWithConfirm,
|
|
359
|
+
notifyAggressiveCompactionOnce,
|
|
360
|
+
refillComposerForRetry,
|
|
361
|
+
openProjectFileForFailure,
|
|
362
|
+
openStorageSettings
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
window.CodexOverleafPanelMaintenance = { create };
|
|
367
|
+
})();
|