vg-coder-cli 2.0.33 → 2.0.35
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 +1 -0
- package/dist/vg-coder-bundle.js +3 -3
- package/package.json +2 -3
- package/src/server/api-server.js +111 -0
- package/src/server/views/css/commands-panel.css +75 -0
- package/src/server/views/css/editor.css +1 -1
- package/src/server/views/css/git-panel.css +361 -0
- package/src/server/views/css/git-view.css +1 -77
- package/src/server/views/css/project-panel.css +242 -0
- package/src/server/views/css/tool-window.css +267 -0
- package/src/server/views/dashboard.css +1 -1
- package/src/server/views/dashboard.html +59 -102
- package/src/server/views/js/api.js +13 -0
- package/src/server/views/js/event-protocol.js +1 -0
- package/src/server/views/js/features/bubble-features/copy-prompt-feature.js +14 -0
- package/src/server/views/js/features/bubble-features/index.js +6 -1
- package/src/server/views/js/features/commands-panel.js +63 -0
- package/src/server/views/js/features/git-panel.js +481 -0
- package/src/server/views/js/features/git-view.js +79 -307
- package/src/server/views/js/features/project-panel.js +452 -0
- package/src/server/views/js/features/resize.js +7 -10
- package/src/server/views/js/features/tool-window.js +148 -0
- package/src/server/views/js/handlers.js +11 -0
- package/src/server/views/js/main.js +13 -31
|
@@ -1,335 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
// FIX: Import Diff2HtmlUI from npm package
|
|
1
|
+
import { getById, qsa, showToast } from '../utils.js';
|
|
2
|
+
import { getGitDiff } from '../api.js';
|
|
4
3
|
import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui';
|
|
5
4
|
|
|
6
5
|
let isGitMode = false;
|
|
7
|
-
let currentStaged = [];
|
|
8
|
-
let currentChanges = [];
|
|
9
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Initialize Git View (Diff Viewer Only)
|
|
9
|
+
* This module only handles displaying git diffs in an overlay
|
|
10
|
+
* Commit/Stage/Unstage features are in git-panel.js
|
|
11
|
+
*/
|
|
10
12
|
export function initGitView() {
|
|
11
13
|
const toggleBtn = getById('git-view-toggle');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if (toggleBtn) {
|
|
15
|
+
toggleBtn.addEventListener('click', toggleGitMode);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Auto-close diff view when user switches to a tool panel
|
|
19
|
+
document.addEventListener('tool-panel-opened', (event) => {
|
|
20
|
+
console.log('[GitView] Tool panel opened:', event.detail.panelId);
|
|
21
|
+
if (isGitMode) {
|
|
22
|
+
console.log('[GitView] Auto-closing diff view due to panel switch');
|
|
23
|
+
closeGitView();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
16
26
|
}
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Toggle Git view overlay
|
|
30
|
+
*/
|
|
18
31
|
async function toggleGitMode() {
|
|
19
32
|
const gitContainer = getById('git-view-container');
|
|
20
33
|
const toggleBtn = getById('git-view-toggle');
|
|
21
|
-
const refreshBtn = getById('git-refresh-btn');
|
|
22
34
|
const toggleText = getById('git-toggle-text');
|
|
23
35
|
|
|
24
36
|
isGitMode = !isGitMode;
|
|
25
|
-
|
|
37
|
+
|
|
26
38
|
if (isGitMode) {
|
|
27
39
|
gitContainer.classList.add('active');
|
|
28
40
|
toggleBtn.classList.add('active');
|
|
29
|
-
if (toggleText) toggleText.textContent = 'Close
|
|
30
|
-
if (refreshBtn) refreshBtn.style.display = 'flex';
|
|
31
|
-
await loadGitData();
|
|
32
|
-
} else {
|
|
33
|
-
gitContainer.classList.remove('active');
|
|
34
|
-
toggleBtn.classList.remove('active');
|
|
35
|
-
if (toggleText) toggleText.textContent = 'View Changes';
|
|
36
|
-
if (refreshBtn) refreshBtn.style.display = 'none';
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function loadGitData() {
|
|
41
|
-
const container = getById('git-view-container');
|
|
42
|
-
|
|
43
|
-
if (!container.querySelector('.git-sidebar')) {
|
|
44
|
-
container.innerHTML = `
|
|
45
|
-
<div class="git-sidebar">
|
|
46
|
-
<div class="git-commit-section">
|
|
47
|
-
<textarea id="git-commit-message" class="git-commit-input" placeholder="Message (⌘Enter to commit)" rows="1"></textarea>
|
|
48
|
-
<button id="git-commit-btn" class="git-commit-btn">
|
|
49
|
-
<span>✓</span> Commit
|
|
50
|
-
</button>
|
|
51
|
-
</div>
|
|
52
|
-
<div class="git-section">
|
|
53
|
-
<div class="git-section-header" id="header-staged">
|
|
54
|
-
<div style="display:flex;align-items:center;gap:5px;">
|
|
55
|
-
<span>STAGED CHANGES</span>
|
|
56
|
-
<span class="git-badge" id="badge-staged">0</span>
|
|
57
|
-
</div>
|
|
58
|
-
<span class="git-btn-action" id="unstage-all" title="Unstage All" style="display:none;">-</span>
|
|
59
|
-
</div>
|
|
60
|
-
<ul class="git-tree-root" id="tree-staged"></ul>
|
|
61
|
-
</div>
|
|
62
|
-
<div class="git-section">
|
|
63
|
-
<div class="git-section-header" id="header-changes">
|
|
64
|
-
<div style="display:flex;align-items:center;gap:5px;">
|
|
65
|
-
<span>CHANGES</span>
|
|
66
|
-
<span class="git-badge" id="badge-changes">0</span>
|
|
67
|
-
</div>
|
|
68
|
-
<div style="display:flex;gap:5px;">
|
|
69
|
-
<span class="git-btn-action destructive" id="discard-all" title="Discard All Changes">↺</span>
|
|
70
|
-
<span class="git-btn-action" id="stage-all" title="Stage All">+</span>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
<ul class="git-tree-root" id="tree-changes"></ul>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
<div class="git-diff-area" id="git-diff-viewer">
|
|
77
|
-
<div class="git-empty-state">Select a file to view changes</div>
|
|
78
|
-
</div>
|
|
79
|
-
`;
|
|
41
|
+
if (toggleText) toggleText.textContent = 'Close Diff';
|
|
80
42
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const commitBtn = getById('git-commit-btn');
|
|
86
|
-
const commitInput = getById('git-commit-message');
|
|
87
|
-
|
|
88
|
-
if(stageAll) stageAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleStage('*'); });
|
|
89
|
-
if(unstageAll) unstageAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleUnstage('*'); });
|
|
90
|
-
if(discardAll) discardAll.addEventListener('click', async (e) => { e.stopPropagation(); await handleDiscard('*'); });
|
|
91
|
-
|
|
92
|
-
if(commitBtn) commitBtn.addEventListener('click', handleCommit);
|
|
93
|
-
if(commitInput) commitInput.addEventListener('keydown', (e) => {
|
|
94
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
95
|
-
e.preventDefault();
|
|
96
|
-
handleCommit();
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}, 0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const refreshBtn = getById('git-refresh-btn');
|
|
103
|
-
if(refreshBtn) refreshBtn.disabled = true;
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const { staged, changes } = await getGitStatus();
|
|
107
|
-
currentStaged = staged;
|
|
108
|
-
currentChanges = changes;
|
|
109
|
-
renderTrees();
|
|
110
|
-
} catch (err) {
|
|
111
|
-
showToast('Error loading git status: ' + err.message, 'error');
|
|
112
|
-
} finally {
|
|
113
|
-
if(refreshBtn) refreshBtn.disabled = false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function handleCommit() {
|
|
118
|
-
const input = getById('git-commit-message');
|
|
119
|
-
const message = input.value.trim();
|
|
120
|
-
const btn = getById('git-commit-btn');
|
|
121
|
-
|
|
122
|
-
if (!message) {
|
|
123
|
-
showToast('Please enter a commit message', 'error');
|
|
124
|
-
input.focus();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (currentStaged.length === 0) {
|
|
129
|
-
showToast('Nothing to commit. Stage changes first.', 'error');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
btn.disabled = true;
|
|
134
|
-
btn.innerHTML = '<span>...</span> Committing...';
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
await commitChanges(message);
|
|
138
|
-
showToast('Commit successful', 'success');
|
|
139
|
-
input.value = '';
|
|
140
|
-
await loadGitData();
|
|
141
|
-
} catch (err) {
|
|
142
|
-
showToast('Commit failed: ' + err.message, 'error');
|
|
143
|
-
} finally {
|
|
144
|
-
btn.disabled = false;
|
|
145
|
-
btn.innerHTML = '<span>✓</span> Commit';
|
|
43
|
+
// Render simple diff viewer
|
|
44
|
+
renderDiffViewer();
|
|
45
|
+
} else {
|
|
46
|
+
closeGitView();
|
|
146
47
|
}
|
|
147
48
|
}
|
|
148
49
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Close Git view overlay
|
|
52
|
+
*/
|
|
53
|
+
function closeGitView() {
|
|
54
|
+
const gitContainer = getById('git-view-container');
|
|
55
|
+
const toggleBtn = getById('git-view-toggle');
|
|
56
|
+
const toggleText = getById('git-toggle-text');
|
|
153
57
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (currentStaged.length > 0) {
|
|
159
|
-
const rootStaged = buildFileTree(currentStaged);
|
|
160
|
-
renderTreeNodes(rootStaged, treeStaged, 'staged', 0);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const treeChanges = getById('tree-changes');
|
|
165
|
-
const badgeChanges = getById('badge-changes');
|
|
166
|
-
const discardAll = getById('discard-all');
|
|
167
|
-
|
|
168
|
-
if (badgeChanges) badgeChanges.textContent = currentChanges.length;
|
|
169
|
-
if (discardAll) discardAll.style.display = currentChanges.length > 0 ? 'block' : 'none';
|
|
170
|
-
if (treeChanges) {
|
|
171
|
-
treeChanges.innerHTML = '';
|
|
172
|
-
if (currentChanges.length > 0) {
|
|
173
|
-
const rootChanges = buildFileTree(currentChanges);
|
|
174
|
-
renderTreeNodes(rootChanges, treeChanges, 'changes', 0);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
58
|
+
isGitMode = false;
|
|
59
|
+
gitContainer.classList.remove('active');
|
|
60
|
+
toggleBtn.classList.remove('active');
|
|
61
|
+
if (toggleText) toggleText.textContent = 'View Diff';
|
|
177
62
|
}
|
|
178
63
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
return root;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function renderTreeNodes(nodes, container, type, depth) {
|
|
198
|
-
const items = Object.values(nodes);
|
|
199
|
-
items.sort((a, b) => {
|
|
200
|
-
const aIsFolder = Object.keys(a.children).length > 0;
|
|
201
|
-
const bIsFolder = Object.keys(b.children).length > 0;
|
|
202
|
-
if (aIsFolder && !bIsFolder) return -1;
|
|
203
|
-
if (!aIsFolder && bIsFolder) return 1;
|
|
204
|
-
return a.name.localeCompare(b.name);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
items.forEach(node => {
|
|
208
|
-
const isFolder = Object.keys(node.children).length > 0;
|
|
209
|
-
const li = document.createElement('li');
|
|
210
|
-
li.className = 'git-tree-node';
|
|
211
|
-
|
|
212
|
-
const content = document.createElement('div');
|
|
213
|
-
content.className = 'git-tree-content';
|
|
214
|
-
content.style.paddingLeft = (depth * 16 + 8) + 'px';
|
|
215
|
-
|
|
216
|
-
const arrow = document.createElement('span');
|
|
217
|
-
arrow.className = 'git-arrow';
|
|
218
|
-
arrow.textContent = isFolder ? '▼' : '';
|
|
219
|
-
content.appendChild(arrow);
|
|
220
|
-
|
|
221
|
-
const iconSpan = document.createElement('span');
|
|
222
|
-
iconSpan.className = 'git-icon';
|
|
223
|
-
if (isFolder) {
|
|
224
|
-
iconSpan.textContent = '📂';
|
|
225
|
-
} else {
|
|
226
|
-
const status = node.fileData.status;
|
|
227
|
-
iconSpan.textContent = getStatusIcon(status);
|
|
228
|
-
iconSpan.className += ' git-status-' + status;
|
|
229
|
-
}
|
|
230
|
-
content.appendChild(iconSpan);
|
|
231
|
-
|
|
232
|
-
const label = document.createElement('span');
|
|
233
|
-
label.className = isFolder ? 'git-label git-dir-label' : 'git-label git-file-label';
|
|
234
|
-
label.textContent = node.name;
|
|
235
|
-
content.appendChild(label);
|
|
236
|
-
|
|
237
|
-
if (!isFolder && node.fileData) {
|
|
238
|
-
const actions = document.createElement('div');
|
|
239
|
-
actions.className = 'git-actions';
|
|
240
|
-
|
|
241
|
-
if (type === 'changes') {
|
|
242
|
-
const discardBtn = document.createElement('button');
|
|
243
|
-
discardBtn.className = 'git-btn-action destructive';
|
|
244
|
-
discardBtn.textContent = '↺';
|
|
245
|
-
discardBtn.title = 'Discard Changes';
|
|
246
|
-
discardBtn.onclick = (e) => { e.stopPropagation(); handleDiscard(node.fileData.path); };
|
|
247
|
-
actions.appendChild(discardBtn);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const btn = document.createElement('button');
|
|
251
|
-
btn.className = 'git-btn-action';
|
|
252
|
-
if (type === 'staged') {
|
|
253
|
-
btn.textContent = '-';
|
|
254
|
-
btn.title = 'Unstage';
|
|
255
|
-
btn.onclick = (e) => { e.stopPropagation(); handleUnstage(node.fileData.path); };
|
|
256
|
-
} else {
|
|
257
|
-
btn.textContent = '+';
|
|
258
|
-
btn.title = 'Stage';
|
|
259
|
-
btn.onclick = (e) => { e.stopPropagation(); handleStage(node.fileData.path); };
|
|
260
|
-
}
|
|
261
|
-
actions.appendChild(btn);
|
|
262
|
-
content.appendChild(actions);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
content.addEventListener('click', (e) => {
|
|
266
|
-
e.stopPropagation();
|
|
267
|
-
if (isFolder) {
|
|
268
|
-
li.classList.toggle('collapsed');
|
|
269
|
-
} else {
|
|
270
|
-
qsa('.git-tree-content').forEach(el => el.classList.remove('selected'));
|
|
271
|
-
content.classList.add('selected');
|
|
272
|
-
loadDiffView(node.fileData.path, type);
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
li.appendChild(content);
|
|
277
|
-
|
|
278
|
-
if (isFolder) {
|
|
279
|
-
const ul = document.createElement('ul');
|
|
280
|
-
renderTreeNodes(node.children, ul, type, depth + 1);
|
|
281
|
-
li.appendChild(ul);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
container.appendChild(li);
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function getStatusIcon(status) {
|
|
289
|
-
if(status === 'M') return 'M';
|
|
290
|
-
if(status === 'A' || status === 'U') return 'U';
|
|
291
|
-
if(status === 'D') return 'D';
|
|
292
|
-
if(status === 'R') return 'R';
|
|
293
|
-
return '?';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function handleStage(path) {
|
|
297
|
-
try {
|
|
298
|
-
await stageFile(path);
|
|
299
|
-
await loadGitData();
|
|
300
|
-
} catch (err) {
|
|
301
|
-
showToast('Stage failed: ' + err.message, 'error');
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function handleUnstage(path) {
|
|
306
|
-
try {
|
|
307
|
-
await unstageFile(path);
|
|
308
|
-
await loadGitData();
|
|
309
|
-
} catch (err) {
|
|
310
|
-
showToast('Unstage failed: ' + err.message, 'error');
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async function handleDiscard(path) {
|
|
315
|
-
const isAll = path === '*';
|
|
316
|
-
const msg = isAll
|
|
317
|
-
? 'Are you sure you want to discard ALL changes? This is irreversible!'
|
|
318
|
-
: `Discard changes to ${path}? This is irreversible!`;
|
|
319
|
-
|
|
320
|
-
if (confirm(msg)) {
|
|
321
|
-
try {
|
|
322
|
-
await discardChange(path);
|
|
323
|
-
await loadGitData();
|
|
324
|
-
showToast('Changes discarded', 'success');
|
|
325
|
-
} catch (err) {
|
|
326
|
-
showToast('Discard failed: ' + err.message, 'error');
|
|
327
|
-
}
|
|
328
|
-
}
|
|
64
|
+
/**
|
|
65
|
+
* Render diff viewer container
|
|
66
|
+
*/
|
|
67
|
+
function renderDiffViewer() {
|
|
68
|
+
const container = getById('git-view-container');
|
|
69
|
+
container.innerHTML = `
|
|
70
|
+
<div class="git-diff-area" id="git-diff-viewer">
|
|
71
|
+
<div class="git-empty-state">
|
|
72
|
+
<div style="font-size: 48px; margin-bottom: 16px;">📄</div>
|
|
73
|
+
<div style="font-size: 14px; color: #8b949e;">Select a file from Git panel to view changes</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
`;
|
|
329
77
|
}
|
|
330
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Load and display diff for a specific file
|
|
81
|
+
* Called by git-panel.js when user clicks on a file
|
|
82
|
+
*/
|
|
331
83
|
async function loadDiffView(filePath, type) {
|
|
332
84
|
const viewer = getById('git-diff-viewer');
|
|
85
|
+
if (!viewer) {
|
|
86
|
+
console.error('[GitView] Diff viewer not found');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
333
90
|
viewer.innerHTML = '<div class="git-empty-state">Loading diff...</div>';
|
|
334
91
|
|
|
335
92
|
// FIX: Using imported class
|
|
@@ -339,8 +96,14 @@ async function loadDiffView(filePath, type) {
|
|
|
339
96
|
const diff = await getGitDiff(filePath, type === 'staged' ? 'staged' : 'working');
|
|
340
97
|
|
|
341
98
|
if (!diff || !diff.trim()) {
|
|
342
|
-
|
|
343
|
-
|
|
99
|
+
viewer.innerHTML = `
|
|
100
|
+
<div class="git-empty-state">
|
|
101
|
+
<div style="font-size: 48px; margin-bottom: 16px;">📝</div>
|
|
102
|
+
<div style="font-size: 14px; color: #8b949e;">No text changes detected</div>
|
|
103
|
+
<div style="font-size: 12px; color: #6e7681; margin-top: 8px;">This might be a binary file or have no changes</div>
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
return;
|
|
344
107
|
}
|
|
345
108
|
|
|
346
109
|
viewer.innerHTML = '';
|
|
@@ -357,6 +120,15 @@ async function loadDiffView(filePath, type) {
|
|
|
357
120
|
ui.highlightCode();
|
|
358
121
|
|
|
359
122
|
} catch (err) {
|
|
360
|
-
viewer.innerHTML =
|
|
123
|
+
viewer.innerHTML = `
|
|
124
|
+
<div class="git-empty-state" style="color:#f85149">
|
|
125
|
+
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
126
|
+
<div style="font-size: 14px;">Error loading diff</div>
|
|
127
|
+
<div style="font-size: 12px; margin-top: 8px;">${err.message}</div>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
361
130
|
}
|
|
362
131
|
}
|
|
132
|
+
|
|
133
|
+
// Export loadDiffView as global function so git-panel can call it
|
|
134
|
+
window.loadGitDiffView = loadDiffView;
|