codex-lens 0.1.25 → 0.1.27
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/dist/aggregator.js +1 -1
- package/dist/public/assets/{main-CYNmzqDG.css → main-DNXrKVO-.css} +1 -1
- package/dist/public/assets/{main-DJ9sK-1n.js → main-d-2BqwRB.js} +36 -36
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/src/aggregator.js +1 -1
- package/src/components/App.jsx +201 -136
- package/src/components/CodeViewer.jsx +11 -19
- package/src/global.css +10 -6
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Lens</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-d-2BqwRB.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -121,7 +121,7 @@ class Aggregator {
|
|
|
121
121
|
logger.info(`File saved: ${filePath}`);
|
|
122
122
|
res.json({ success: true, path: filePath });
|
|
123
123
|
} catch (error) {
|
|
124
|
-
logger.error(`Failed to save file: ${
|
|
124
|
+
logger.error(`Failed to save file: ${error.message}`);
|
|
125
125
|
res.status(500).json({ error: error.message });
|
|
126
126
|
}
|
|
127
127
|
});
|
package/src/components/App.jsx
CHANGED
|
@@ -12,59 +12,23 @@ export function App() {
|
|
|
12
12
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
13
13
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
14
14
|
const [projectName, setProjectName] = useState('');
|
|
15
|
+
const [saving, setSaving] = useState(false);
|
|
15
16
|
const wsRef = useRef(null);
|
|
16
|
-
const activeTabIdRef = useRef(null);
|
|
17
|
-
const tabsRef = useRef([]);
|
|
18
|
-
const saveCurrentFileRef = useRef(null);
|
|
19
17
|
|
|
20
18
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
tabsRef.current = tabs;
|
|
26
|
-
}, [tabs]);
|
|
27
|
-
|
|
28
|
-
function saveCurrentFile() {
|
|
29
|
-
const currentTabId = activeTabIdRef.current;
|
|
30
|
-
const currentTabs = tabsRef.current;
|
|
31
|
-
const activeTab = currentTabs.find(t => t.id === currentTabId);
|
|
32
|
-
|
|
33
|
-
if (!activeTab || activeTab.isDiff) {
|
|
34
|
-
console.log('No active tab or is diff, skip save');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
39
|
-
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
40
|
-
|
|
41
|
-
console.log('Saving file:', activeTab.path);
|
|
42
|
-
|
|
43
|
-
fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
44
|
-
method: 'POST',
|
|
45
|
-
headers: { 'Content-Type': 'application/json' },
|
|
46
|
-
body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
|
|
47
|
-
})
|
|
48
|
-
.then(response => {
|
|
49
|
-
if (response.ok) {
|
|
50
|
-
console.log('File saved successfully');
|
|
51
|
-
setTabs(prevTabs => prevTabs.map(t =>
|
|
52
|
-
t.id === currentTabId ? { ...t, isModified: false } : t
|
|
53
|
-
));
|
|
54
|
-
} else {
|
|
55
|
-
response.json().then(error => {
|
|
56
|
-
console.error('Failed to save file:', error);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
.catch(error => {
|
|
61
|
-
console.error('Failed to save file:', error);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
19
|
+
fetchStatus();
|
|
20
|
+
connectWebSocket();
|
|
21
|
+
document.addEventListener('click', handleDocumentClick);
|
|
22
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
64
23
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
24
|
+
return () => {
|
|
25
|
+
document.removeEventListener('click', handleDocumentClick);
|
|
26
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
27
|
+
if (wsRef.current) {
|
|
28
|
+
wsRef.current.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
68
32
|
|
|
69
33
|
async function fetchStatus() {
|
|
70
34
|
try {
|
|
@@ -90,63 +54,26 @@ export function App() {
|
|
|
90
54
|
setContextMenu(null);
|
|
91
55
|
}
|
|
92
56
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
connectWebSocket();
|
|
57
|
+
function handleKeyDown(e) {
|
|
58
|
+
if (!activeTabId) return;
|
|
96
59
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
60
|
+
if (e.ctrlKey && e.key === 'w') {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
closeTab(activeTabId);
|
|
63
|
+
} else if (e.ctrlKey && e.key === 'Tab') {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (e.shiftKey) {
|
|
66
|
+
switchToPrevTab();
|
|
67
|
+
} else {
|
|
68
|
+
switchToNextTab();
|
|
105
69
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
closeTab(activeTabIdRef.current);
|
|
112
|
-
} else if (e.ctrlKey && e.key === 'Tab') {
|
|
113
|
-
e.preventDefault();
|
|
114
|
-
if (e.shiftKey) {
|
|
115
|
-
switchToPrevTab();
|
|
116
|
-
} else {
|
|
117
|
-
switchToNextTab();
|
|
118
|
-
}
|
|
70
|
+
} else if (e.ctrlKey && e.key === 's') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
73
|
+
if (activeTab && activeTab.modified) {
|
|
74
|
+
saveFile(activeTabId);
|
|
119
75
|
}
|
|
120
76
|
}
|
|
121
|
-
|
|
122
|
-
document.addEventListener('click', handleDocumentClick);
|
|
123
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
124
|
-
|
|
125
|
-
const preventBrowserSave = (e) => {
|
|
126
|
-
if (e.ctrlKey && e.key === 's') {
|
|
127
|
-
e.preventDefault();
|
|
128
|
-
e.stopPropagation();
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
window.addEventListener('keydown', preventBrowserSave, true);
|
|
132
|
-
|
|
133
|
-
return () => {
|
|
134
|
-
document.removeEventListener('click', handleDocumentClick);
|
|
135
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
136
|
-
window.removeEventListener('keydown', preventBrowserSave, true);
|
|
137
|
-
if (wsRef.current) {
|
|
138
|
-
wsRef.current.close();
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
}, []);
|
|
142
|
-
|
|
143
|
-
function handleContentChange(newContent) {
|
|
144
|
-
setTabs(prevTabs => prevTabs.map(t => {
|
|
145
|
-
if (t.id === activeTabId) {
|
|
146
|
-
return { ...t, content: newContent, isModified: true };
|
|
147
|
-
}
|
|
148
|
-
return t;
|
|
149
|
-
}));
|
|
150
77
|
}
|
|
151
78
|
|
|
152
79
|
function connectWebSocket() {
|
|
@@ -218,8 +145,10 @@ export function App() {
|
|
|
218
145
|
path: data.path,
|
|
219
146
|
name: fileName,
|
|
220
147
|
content: data.content,
|
|
148
|
+
originalContent: data.content,
|
|
221
149
|
diff: data.diff || null,
|
|
222
|
-
isDiff: !!data.diff
|
|
150
|
+
isDiff: !!data.diff,
|
|
151
|
+
modified: false
|
|
223
152
|
};
|
|
224
153
|
setActiveTabId(newTab.id);
|
|
225
154
|
return [...prevTabs, newTab];
|
|
@@ -234,7 +163,14 @@ export function App() {
|
|
|
234
163
|
const existingTab = prevTabs.find(t => t.path === data.path);
|
|
235
164
|
|
|
236
165
|
if (existingTab) {
|
|
237
|
-
const updatedTab = {
|
|
166
|
+
const updatedTab = {
|
|
167
|
+
...existingTab,
|
|
168
|
+
content: data.newContent,
|
|
169
|
+
originalContent: data.newContent,
|
|
170
|
+
diff: data.diff,
|
|
171
|
+
isDiff: true,
|
|
172
|
+
modified: false
|
|
173
|
+
};
|
|
238
174
|
setActiveTabId(existingTab.id);
|
|
239
175
|
return prevTabs.map(t =>
|
|
240
176
|
t.path === data.path ? updatedTab : t
|
|
@@ -245,8 +181,10 @@ export function App() {
|
|
|
245
181
|
path: data.path,
|
|
246
182
|
name: fileName,
|
|
247
183
|
content: data.newContent,
|
|
184
|
+
originalContent: data.newContent,
|
|
248
185
|
diff: data.diff,
|
|
249
|
-
isDiff: true
|
|
186
|
+
isDiff: true,
|
|
187
|
+
modified: false
|
|
250
188
|
};
|
|
251
189
|
setActiveTabId(newTab.id);
|
|
252
190
|
return [...prevTabs, newTab];
|
|
@@ -254,6 +192,99 @@ export function App() {
|
|
|
254
192
|
});
|
|
255
193
|
}
|
|
256
194
|
|
|
195
|
+
function handleContentChange(tabId, newContent) {
|
|
196
|
+
setTabs(prevTabs => prevTabs.map(tab => {
|
|
197
|
+
if (tab.id === tabId) {
|
|
198
|
+
const modified = newContent !== tab.originalContent;
|
|
199
|
+
return { ...tab, content: newContent, modified };
|
|
200
|
+
}
|
|
201
|
+
return tab;
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function saveFile(tabId) {
|
|
206
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
207
|
+
if (!tab || !tab.modified) return;
|
|
208
|
+
|
|
209
|
+
setSaving(true);
|
|
210
|
+
try {
|
|
211
|
+
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
212
|
+
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
213
|
+
const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify({ path: tab.path, content: tab.content })
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
setTabs(prevTabs => prevTabs.map(t => {
|
|
221
|
+
if (t.id === tabId) {
|
|
222
|
+
return { ...t, originalContent: t.content, modified: false };
|
|
223
|
+
}
|
|
224
|
+
return t;
|
|
225
|
+
}));
|
|
226
|
+
console.log('File saved:', tab.path);
|
|
227
|
+
} else {
|
|
228
|
+
const error = await response.json();
|
|
229
|
+
console.error('Failed to save file:', error.message);
|
|
230
|
+
alert('保存失败: ' + error.message);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to save file:', error);
|
|
234
|
+
alert('保存失败: ' + error.message);
|
|
235
|
+
} finally {
|
|
236
|
+
setSaving(false);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function saveAllFiles() {
|
|
241
|
+
const modifiedTabs = tabs.filter(t => t.modified);
|
|
242
|
+
if (modifiedTabs.length === 0) return;
|
|
243
|
+
|
|
244
|
+
setSaving(true);
|
|
245
|
+
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
246
|
+
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
247
|
+
|
|
248
|
+
let savedCount = 0;
|
|
249
|
+
let failedFiles = [];
|
|
250
|
+
|
|
251
|
+
for (const tab of modifiedTabs) {
|
|
252
|
+
try {
|
|
253
|
+
const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({ path: tab.path, content: tab.content })
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (response.ok) {
|
|
260
|
+
savedCount++;
|
|
261
|
+
} else {
|
|
262
|
+
const error = await response.json();
|
|
263
|
+
failedFiles.push(tab.name);
|
|
264
|
+
console.error('Failed to save file:', tab.name, error.message);
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
failedFiles.push(tab.name);
|
|
268
|
+
console.error('Failed to save file:', tab.name, error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (savedCount > 0) {
|
|
273
|
+
setTabs(prevTabs => prevTabs.map(t => {
|
|
274
|
+
if (t.modified && !failedFiles.includes(t.name)) {
|
|
275
|
+
return { ...t, originalContent: t.content, modified: false };
|
|
276
|
+
}
|
|
277
|
+
return t;
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (failedFiles.length > 0) {
|
|
282
|
+
alert(`以下文件保存失败: ${failedFiles.join(', ')}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
setSaving(false);
|
|
286
|
+
}
|
|
287
|
+
|
|
257
288
|
function handleFileClick(path) {
|
|
258
289
|
const existingTab = tabs.find(t => t.path === path);
|
|
259
290
|
if (existingTab) {
|
|
@@ -266,10 +297,17 @@ export function App() {
|
|
|
266
297
|
}
|
|
267
298
|
|
|
268
299
|
function closeTab(tabId) {
|
|
300
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
301
|
+
if (tab?.modified) {
|
|
302
|
+
const confirmed = window.confirm(`文件 "${tab.name}" 已修改,是否保存?`);
|
|
303
|
+
if (confirmed) {
|
|
304
|
+
saveFile(tabId);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
269
308
|
setTabs(prev => {
|
|
270
309
|
const newTabs = prev.filter(t => t.id !== tabId);
|
|
271
|
-
|
|
272
|
-
if (currentActiveId === tabId) {
|
|
310
|
+
if (activeTabId === tabId) {
|
|
273
311
|
const newActiveId = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
|
|
274
312
|
setActiveTabId(newActiveId);
|
|
275
313
|
}
|
|
@@ -278,11 +316,30 @@ export function App() {
|
|
|
278
316
|
}
|
|
279
317
|
|
|
280
318
|
function closeOtherTabs(tabId) {
|
|
281
|
-
setTabs(prev =>
|
|
319
|
+
setTabs(prev => {
|
|
320
|
+
const tabsToClose = prev.filter(t => t.id !== tabId);
|
|
321
|
+
tabsToClose.forEach(t => {
|
|
322
|
+
if (t.modified) {
|
|
323
|
+
const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
|
|
324
|
+
if (confirmed) {
|
|
325
|
+
saveFile(t.id);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
return prev.filter(t => t.id === tabId);
|
|
330
|
+
});
|
|
282
331
|
setActiveTabId(tabId);
|
|
283
332
|
}
|
|
284
333
|
|
|
285
334
|
function closeAllTabs() {
|
|
335
|
+
tabs.forEach(t => {
|
|
336
|
+
if (t.modified) {
|
|
337
|
+
const confirmed = window.confirm(`文件 "${t.name}" 已修改,是否保存?`);
|
|
338
|
+
if (confirmed) {
|
|
339
|
+
saveFile(t.id);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
286
343
|
setTabs([]);
|
|
287
344
|
setActiveTabId(null);
|
|
288
345
|
}
|
|
@@ -292,24 +349,20 @@ export function App() {
|
|
|
292
349
|
}
|
|
293
350
|
|
|
294
351
|
function switchToNextTab() {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
setActiveTabId(
|
|
300
|
-
} else if (currentTabs.length > 0) {
|
|
301
|
-
setActiveTabId(currentTabs[0].id);
|
|
352
|
+
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
353
|
+
if (currentIndex < tabs.length - 1) {
|
|
354
|
+
setActiveTabId(tabs[currentIndex + 1].id);
|
|
355
|
+
} else if (tabs.length > 0) {
|
|
356
|
+
setActiveTabId(tabs[0].id);
|
|
302
357
|
}
|
|
303
358
|
}
|
|
304
359
|
|
|
305
360
|
function switchToPrevTab() {
|
|
306
|
-
const
|
|
307
|
-
const currentActiveId = activeTabIdRef.current;
|
|
308
|
-
const currentIndex = currentTabs.findIndex(t => t.id === currentActiveId);
|
|
361
|
+
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
309
362
|
if (currentIndex > 0) {
|
|
310
|
-
setActiveTabId(
|
|
311
|
-
} else if (
|
|
312
|
-
setActiveTabId(
|
|
363
|
+
setActiveTabId(tabs[currentIndex - 1].id);
|
|
364
|
+
} else if (tabs.length > 0) {
|
|
365
|
+
setActiveTabId(tabs[tabs.length - 1].id);
|
|
313
366
|
}
|
|
314
367
|
}
|
|
315
368
|
|
|
@@ -367,8 +420,7 @@ export function App() {
|
|
|
367
420
|
diff={activeTab.diff}
|
|
368
421
|
isDiff={activeTab.isDiff}
|
|
369
422
|
filePath={activeTab.path}
|
|
370
|
-
onChange={handleContentChange}
|
|
371
|
-
isModified={activeTab.isModified}
|
|
423
|
+
onChange={(value) => handleContentChange(activeTabId, value)}
|
|
372
424
|
/>
|
|
373
425
|
)}
|
|
374
426
|
</div>
|
|
@@ -377,12 +429,10 @@ export function App() {
|
|
|
377
429
|
<ContextMenu
|
|
378
430
|
x={contextMenu.x}
|
|
379
431
|
y={contextMenu.y}
|
|
380
|
-
|
|
432
|
+
tab={tabs.find(t => t.id === contextMenu.tabId)}
|
|
433
|
+
tabs={tabs}
|
|
434
|
+
saving={saving}
|
|
381
435
|
onClose={() => setContextMenu(null)}
|
|
382
|
-
onSave={() => {
|
|
383
|
-
saveCurrentFile();
|
|
384
|
-
setContextMenu(null);
|
|
385
|
-
}}
|
|
386
436
|
onCloseTab={() => {
|
|
387
437
|
closeTab(contextMenu.tabId);
|
|
388
438
|
setContextMenu(null);
|
|
@@ -395,6 +445,14 @@ export function App() {
|
|
|
395
445
|
closeAllTabs();
|
|
396
446
|
setContextMenu(null);
|
|
397
447
|
}}
|
|
448
|
+
onSave={() => {
|
|
449
|
+
saveFile(contextMenu.tabId);
|
|
450
|
+
setContextMenu(null);
|
|
451
|
+
}}
|
|
452
|
+
onSaveAll={() => {
|
|
453
|
+
saveAllFiles();
|
|
454
|
+
setContextMenu(null);
|
|
455
|
+
}}
|
|
398
456
|
/>
|
|
399
457
|
)}
|
|
400
458
|
<div className="panel right-panel">
|
|
@@ -413,12 +471,12 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
413
471
|
{tabs.map(tab => (
|
|
414
472
|
<div
|
|
415
473
|
key={tab.id}
|
|
416
|
-
className={`tab ${activeTabId === tab.id ? 'active' : ''}`}
|
|
474
|
+
className={`tab ${activeTabId === tab.id ? 'active' : ''} ${tab.modified ? 'modified' : ''}`}
|
|
417
475
|
onClick={() => onTabClick(tab.id)}
|
|
418
476
|
onContextMenu={(e) => onContextMenu(e, tab.id)}
|
|
419
477
|
>
|
|
420
478
|
<span className="tab-name">
|
|
421
|
-
{tab.
|
|
479
|
+
{tab.modified && <span className="tab-modified-mark">●</span>}
|
|
422
480
|
{tab.name}
|
|
423
481
|
</span>
|
|
424
482
|
<button
|
|
@@ -436,14 +494,21 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
436
494
|
);
|
|
437
495
|
}
|
|
438
496
|
|
|
439
|
-
function ContextMenu({ x, y,
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
const canSave = tab && !tab.isDiff;
|
|
443
|
-
|
|
497
|
+
function ContextMenu({ x, y, tab, tabs, saving, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs, onSave, onSaveAll }) {
|
|
498
|
+
const hasModified = tabs?.some(t => t.modified);
|
|
499
|
+
|
|
444
500
|
return (
|
|
445
501
|
<div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
|
|
446
|
-
{
|
|
502
|
+
{tab?.modified && (
|
|
503
|
+
<div className="context-menu-item" onClick={onSave} style={{ color: '#4ade80' }}>
|
|
504
|
+
{saving ? '保存中...' : '保存'}
|
|
505
|
+
</div>
|
|
506
|
+
)}
|
|
507
|
+
{hasModified && (
|
|
508
|
+
<div className="context-menu-item" onClick={onSaveAll} style={{ color: '#4ade80' }}>
|
|
509
|
+
{saving ? '保存中...' : '全部保存'}
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
447
512
|
<div className="context-menu-item" onClick={onCloseTab}>关闭</div>
|
|
448
513
|
<div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
|
|
449
514
|
<div className="context-menu-item" onClick={onCloseAllTabs}>关闭所有</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useMemo, useRef
|
|
1
|
+
import React, { useMemo, useRef } from 'react';
|
|
2
2
|
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { EditorView, Decoration, ViewPlugin,
|
|
3
|
+
import { EditorView, Decoration, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
|
4
4
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
5
5
|
import { tags as t } from '@lezer/highlight';
|
|
6
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
|
@@ -215,11 +215,8 @@ const darkHighlightStyle = HighlightStyle.define([
|
|
|
215
215
|
|
|
216
216
|
const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
|
|
217
217
|
|
|
218
|
-
export function CodeViewer({ content, diff, isDiff, filePath, onChange
|
|
218
|
+
export function CodeViewer({ content, diff, isDiff, filePath, onChange }) {
|
|
219
219
|
const editorRef = useRef(null);
|
|
220
|
-
const [localContent, setLocalContent] = useState(content || '');
|
|
221
|
-
|
|
222
|
-
const isEditable = !isDiff;
|
|
223
220
|
|
|
224
221
|
const extensions = useMemo(() => {
|
|
225
222
|
const exts = [
|
|
@@ -257,27 +254,23 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
|
|
|
257
254
|
return content || '';
|
|
258
255
|
}, [content, diff, isDiff]);
|
|
259
256
|
|
|
260
|
-
const
|
|
261
|
-
setLocalContent(value);
|
|
262
|
-
if (onChange) {
|
|
263
|
-
onChange(value);
|
|
264
|
-
}
|
|
265
|
-
}, [onChange]);
|
|
257
|
+
const editable = !isDiff;
|
|
266
258
|
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
259
|
+
function handleChange(value) {
|
|
260
|
+
if (onChange && editable) {
|
|
261
|
+
onChange(value);
|
|
270
262
|
}
|
|
271
|
-
}
|
|
263
|
+
}
|
|
272
264
|
|
|
273
265
|
return (
|
|
274
266
|
<div className="code-viewer-codemirror">
|
|
275
267
|
<CodeMirror
|
|
276
|
-
value={
|
|
268
|
+
value={code}
|
|
277
269
|
height="100%"
|
|
278
270
|
theme={darkTheme}
|
|
279
271
|
extensions={extensions}
|
|
280
|
-
editable={
|
|
272
|
+
editable={editable}
|
|
273
|
+
onChange={handleChange}
|
|
281
274
|
basicSetup={{
|
|
282
275
|
lineNumbers: true,
|
|
283
276
|
foldGutter: false,
|
|
@@ -285,7 +278,6 @@ export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModifi
|
|
|
285
278
|
highlightSelectionMatches: false,
|
|
286
279
|
bracketMatching: true,
|
|
287
280
|
}}
|
|
288
|
-
onChange={isEditable ? handleChange : undefined}
|
|
289
281
|
onCreateEditor={(view) => {
|
|
290
282
|
editorRef.current = view;
|
|
291
283
|
}}
|
package/src/global.css
CHANGED
|
@@ -357,12 +357,6 @@ body {
|
|
|
357
357
|
white-space: nowrap;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
.tab-modified {
|
|
361
|
-
color: var(--accent-color);
|
|
362
|
-
font-weight: bold;
|
|
363
|
-
margin-right: 2px;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
360
|
.tab-close {
|
|
367
361
|
background: none;
|
|
368
362
|
border: none;
|
|
@@ -382,6 +376,16 @@ body {
|
|
|
382
376
|
opacity: 1;
|
|
383
377
|
}
|
|
384
378
|
|
|
379
|
+
.tab.modified .tab-name {
|
|
380
|
+
color: var(--accent-color);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.tab-modified-mark {
|
|
384
|
+
color: var(--accent-color);
|
|
385
|
+
margin-right: 4px;
|
|
386
|
+
font-size: 10px;
|
|
387
|
+
}
|
|
388
|
+
|
|
385
389
|
.context-menu {
|
|
386
390
|
position: fixed;
|
|
387
391
|
background: linear-gradient(180deg, var(--bg-tertiary) 0%, var(--bg-secondary) 100%);
|