vg-coder-cli 2.0.30 → 2.0.32
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/ARCHITECTURE.md +255 -0
- package/README.md +0 -11
- package/change.sh +0 -0
- package/dist/vg-coder-bundle.js +42 -0
- package/gulpfile.js +111 -0
- package/package.json +19 -11
- package/scripts/postinstall.js +13 -3
- package/src/index.js +28 -220
- package/src/server/api-server.js +120 -428
- package/src/server/views/css/bubble.css +81 -0
- package/src/server/views/css/code-viewer.css +58 -0
- package/src/server/views/css/terminal.css +59 -155
- package/src/server/views/dashboard.css +78 -678
- package/src/server/views/dashboard.html +39 -278
- package/src/server/views/js/api.js +2 -22
- package/src/server/views/js/config.js +27 -15
- package/src/server/views/js/event-protocol.js +263 -0
- package/src/server/views/js/features/bubble-features/index.js +125 -0
- package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
- package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
- package/src/server/views/js/features/bubble.js +175 -0
- package/src/server/views/js/features/code-viewer.js +90 -0
- package/src/server/views/js/features/commands.js +34 -81
- package/src/server/views/js/features/editor-tabs.js +19 -46
- package/src/server/views/js/features/git-view.js +63 -81
- package/src/server/views/js/features/iframe-manager.js +3 -97
- package/src/server/views/js/features/monaco-manager.js +19 -39
- package/src/server/views/js/features/project-switcher.js +7 -63
- package/src/server/views/js/features/resize.js +5 -16
- package/src/server/views/js/features/structure.js +38 -106
- package/src/server/views/js/features/terminal.js +102 -418
- package/src/server/views/js/handlers.js +60 -43
- package/src/server/views/js/main.js +75 -179
- package/src/server/views/js/shadow-entry.js +21 -0
- package/src/server/views/js/utils.js +48 -28
- package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
- package/src/server/views/vg-coder/controller.js +33 -258
- package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
- package/vetgo-auto/vg-coder.zip +0 -0
- package/src/server/views/dashboard.js +0 -457
|
@@ -1,43 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Manages saved terminal commands that can be quickly executed
|
|
4
|
-
*/
|
|
1
|
+
import { API_BASE } from '../config.js';
|
|
2
|
+
import { getById, showToast } from '../utils.js';
|
|
5
3
|
|
|
6
4
|
let savedCommands = [];
|
|
7
5
|
let editingCommandId = null;
|
|
8
6
|
|
|
9
|
-
/**
|
|
10
|
-
* Initialize saved commands on page load
|
|
11
|
-
*/
|
|
12
7
|
export async function initSavedCommands() {
|
|
13
8
|
await loadSavedCommands();
|
|
14
9
|
renderCommands();
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* Load saved commands from backend
|
|
19
|
-
*/
|
|
20
12
|
async function loadSavedCommands() {
|
|
21
13
|
try {
|
|
22
|
-
const response = await fetch(
|
|
14
|
+
const response = await fetch(`${API_BASE}/api/commands/load`);
|
|
23
15
|
const data = await response.json();
|
|
24
16
|
savedCommands = data.commands || [];
|
|
25
|
-
renderCommands();
|
|
17
|
+
renderCommands();
|
|
26
18
|
} catch (error) {
|
|
27
19
|
console.error('Failed to load commands:', error);
|
|
28
20
|
savedCommands = [];
|
|
29
21
|
}
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
/**
|
|
33
|
-
* Save commands to backend (debounced)
|
|
34
|
-
*/
|
|
35
24
|
let saveTimeout = null;
|
|
36
25
|
async function saveCommands() {
|
|
37
26
|
clearTimeout(saveTimeout);
|
|
38
27
|
saveTimeout = setTimeout(async () => {
|
|
39
28
|
try {
|
|
40
|
-
const response = await fetch(
|
|
29
|
+
const response = await fetch(`${API_BASE}/api/commands/save`, {
|
|
41
30
|
method: 'POST',
|
|
42
31
|
headers: { 'Content-Type': 'application/json' },
|
|
43
32
|
body: JSON.stringify({ commands: savedCommands })
|
|
@@ -52,12 +41,9 @@ async function saveCommands() {
|
|
|
52
41
|
}, 500);
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
/**
|
|
56
|
-
* Render commands in UI
|
|
57
|
-
*/
|
|
58
44
|
function renderCommands() {
|
|
59
|
-
const container =
|
|
60
|
-
const emptyState =
|
|
45
|
+
const container = getById('commands-list');
|
|
46
|
+
const emptyState = getById('commands-empty-state');
|
|
61
47
|
|
|
62
48
|
if (!container) return;
|
|
63
49
|
|
|
@@ -90,63 +76,50 @@ function renderCommands() {
|
|
|
90
76
|
`).join('');
|
|
91
77
|
}
|
|
92
78
|
|
|
93
|
-
/**
|
|
94
|
-
* Open add command modal
|
|
95
|
-
*/
|
|
96
79
|
function openAddCommandModal() {
|
|
97
80
|
editingCommandId = null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
81
|
+
getById('modal-title').textContent = 'Add Command';
|
|
82
|
+
getById('command-icon').value = '🚀';
|
|
83
|
+
getById('command-name').value = '';
|
|
84
|
+
getById('command-text').value = '';
|
|
85
|
+
getById('command-modal').style.display = 'flex';
|
|
86
|
+
getById('command-name').focus();
|
|
104
87
|
}
|
|
105
88
|
|
|
106
|
-
/**
|
|
107
|
-
* Open edit command modal
|
|
108
|
-
*/
|
|
109
89
|
function editCommand(id) {
|
|
110
90
|
const command = savedCommands.find(c => c.id === id);
|
|
111
91
|
if (!command) return;
|
|
112
92
|
|
|
113
93
|
editingCommandId = id;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
getById('modal-title').textContent = 'Edit Command';
|
|
95
|
+
getById('command-icon').value = command.icon;
|
|
96
|
+
getById('command-name').value = command.name;
|
|
97
|
+
getById('command-text').value = command.command;
|
|
98
|
+
getById('command-modal').style.display = 'flex';
|
|
99
|
+
getById('command-name').focus();
|
|
120
100
|
}
|
|
121
101
|
|
|
122
|
-
/**
|
|
123
|
-
* Close command modal
|
|
124
|
-
*/
|
|
125
102
|
function closeCommandModal() {
|
|
126
|
-
|
|
103
|
+
const modal = getById('command-modal');
|
|
104
|
+
if (modal) modal.style.display = 'none';
|
|
127
105
|
editingCommandId = null;
|
|
128
106
|
}
|
|
129
107
|
|
|
130
|
-
/**
|
|
131
|
-
* Handle command form submit
|
|
132
|
-
*/
|
|
133
108
|
function handleCommandFormSubmit(event) {
|
|
134
109
|
event.preventDefault();
|
|
135
110
|
|
|
136
|
-
const icon =
|
|
137
|
-
const name =
|
|
138
|
-
const command =
|
|
111
|
+
const icon = getById('command-icon').value.trim();
|
|
112
|
+
const name = getById('command-name').value.trim();
|
|
113
|
+
const command = getById('command-text').value.trim();
|
|
139
114
|
|
|
140
115
|
if (!icon || !name || !command) return;
|
|
141
116
|
|
|
142
117
|
if (editingCommandId) {
|
|
143
|
-
// Edit existing
|
|
144
118
|
const index = savedCommands.findIndex(c => c.id === editingCommandId);
|
|
145
119
|
if (index !== -1) {
|
|
146
120
|
savedCommands[index] = { ...savedCommands[index], icon, name, command };
|
|
147
121
|
}
|
|
148
122
|
} else {
|
|
149
|
-
// Add new
|
|
150
123
|
savedCommands.push({
|
|
151
124
|
id: 'cmd_' + Date.now(),
|
|
152
125
|
icon,
|
|
@@ -160,9 +133,6 @@ function handleCommandFormSubmit(event) {
|
|
|
160
133
|
closeCommandModal();
|
|
161
134
|
}
|
|
162
135
|
|
|
163
|
-
/**
|
|
164
|
-
* Delete command with confirmation
|
|
165
|
-
*/
|
|
166
136
|
function deleteCommand(id) {
|
|
167
137
|
const command = savedCommands.find(c => c.id === id);
|
|
168
138
|
if (!command) return;
|
|
@@ -174,57 +144,40 @@ function deleteCommand(id) {
|
|
|
174
144
|
}
|
|
175
145
|
}
|
|
176
146
|
|
|
177
|
-
/**
|
|
178
|
-
* Run saved command - open terminal and copy command to clipboard
|
|
179
|
-
*/
|
|
180
147
|
function runSavedCommand(id) {
|
|
181
148
|
const command = savedCommands.find(c => c.id === id);
|
|
182
149
|
if (!command) return;
|
|
183
150
|
|
|
184
|
-
// Create new terminal
|
|
185
151
|
if (typeof window.createNewTerminal === 'function') {
|
|
186
152
|
window.createNewTerminal();
|
|
187
153
|
}
|
|
188
154
|
|
|
189
|
-
// Copy command to clipboard
|
|
190
155
|
navigator.clipboard.writeText(command.command).then(() => {
|
|
191
|
-
|
|
192
|
-
if (typeof window.showToast === 'function') {
|
|
193
|
-
window.showToast(`📋 Copied: ${command.command}`, 'success');
|
|
194
|
-
}
|
|
156
|
+
showToast(`📋 Copied: ${command.command}`, 'success');
|
|
195
157
|
}).catch(err => {
|
|
196
158
|
console.error('Failed to copy command:', err);
|
|
197
|
-
|
|
198
|
-
window.showToast('Failed to copy command', 'error');
|
|
199
|
-
}
|
|
159
|
+
showToast('Failed to copy command', 'error');
|
|
200
160
|
});
|
|
201
161
|
}
|
|
202
162
|
|
|
203
|
-
/**
|
|
204
|
-
* Escape HTML to prevent XSS
|
|
205
|
-
*/
|
|
206
163
|
function escapeHtml(text) {
|
|
207
164
|
const div = document.createElement('div');
|
|
208
165
|
div.textContent = text;
|
|
209
166
|
return div.innerHTML;
|
|
210
167
|
}
|
|
211
168
|
|
|
212
|
-
// Global exports for HTML onclick
|
|
213
169
|
window.openAddCommandModal = openAddCommandModal;
|
|
214
170
|
window.editCommand = editCommand;
|
|
215
171
|
window.closeCommandModal = closeCommandModal;
|
|
216
172
|
window.deleteCommand = deleteCommand;
|
|
217
173
|
window.runSavedCommand = runSavedCommand;
|
|
218
|
-
window.loadSavedCommands = loadSavedCommands;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
}
|
|
174
|
+
window.loadSavedCommands = loadSavedCommands;
|
|
175
|
+
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
const form = getById('command-form');
|
|
178
|
+
if (form) {
|
|
179
|
+
form.addEventListener('submit', handleCommandFormSubmit);
|
|
180
|
+
}
|
|
181
|
+
}, 500);
|
|
229
182
|
|
|
230
183
|
export { openAddCommandModal, editCommand, deleteCommand, runSavedCommand, loadSavedCommands };
|
|
@@ -1,50 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { openFileInViewer } from './code-viewer.js';
|
|
2
|
+
import { getById, qsa, qs } from '../utils.js';
|
|
2
3
|
|
|
3
|
-
let activeTabs = [];
|
|
4
|
-
let currentPath =
|
|
4
|
+
let activeTabs = [];
|
|
5
|
+
let currentPath = null;
|
|
5
6
|
|
|
6
7
|
export function initEditorTabs() {
|
|
7
8
|
renderTabs();
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export async function openFileTab(path, name, icon = '📄') {
|
|
11
|
-
//
|
|
12
|
+
// Switch to code mode
|
|
12
13
|
toggleViewMode('code');
|
|
13
14
|
|
|
14
|
-
// 2. Lưu state tab cũ
|
|
15
|
-
if (currentPath && currentPath !== 'ai-assistant' && currentPath !== path) {
|
|
16
|
-
saveViewState(currentPath);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// 3. Logic Tabs Array
|
|
20
15
|
const existingTab = activeTabs.find(t => t.path === path);
|
|
21
16
|
if (!existingTab) {
|
|
22
17
|
activeTabs.push({ path, name, icon });
|
|
23
18
|
renderTabs();
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
// 4. Update UI
|
|
27
21
|
currentPath = path;
|
|
28
22
|
updateTabUI();
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
await openFileInMonaco(path);
|
|
24
|
+
await openFileInViewer(path);
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
export function switchTab(path) {
|
|
35
|
-
if (currentPath && currentPath !== 'ai-assistant') {
|
|
36
|
-
saveViewState(currentPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
28
|
currentPath = path;
|
|
40
29
|
updateTabUI();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
toggleViewMode('ai');
|
|
44
|
-
} else {
|
|
45
|
-
toggleViewMode('code');
|
|
46
|
-
openFileInMonaco(path);
|
|
47
|
-
}
|
|
30
|
+
toggleViewMode('code');
|
|
31
|
+
openFileInViewer(path);
|
|
48
32
|
}
|
|
49
33
|
|
|
50
34
|
export function closeTab(event, path) {
|
|
@@ -54,22 +38,23 @@ export function closeTab(event, path) {
|
|
|
54
38
|
if (index === -1) return;
|
|
55
39
|
|
|
56
40
|
activeTabs.splice(index, 1);
|
|
57
|
-
disposeModel(path);
|
|
58
41
|
|
|
59
42
|
if (currentPath === path) {
|
|
60
43
|
if (activeTabs.length > 0) {
|
|
61
44
|
const nextTab = activeTabs[activeTabs.length - 1];
|
|
62
45
|
switchTab(nextTab.path);
|
|
63
46
|
} else {
|
|
64
|
-
|
|
47
|
+
currentPath = null;
|
|
48
|
+
updateTabUI();
|
|
49
|
+
const container = getById('code-viewer-container');
|
|
50
|
+
if (container) container.innerHTML = '<div class="cv-empty">No file open</div>';
|
|
65
51
|
}
|
|
66
52
|
}
|
|
67
53
|
renderTabs();
|
|
68
54
|
}
|
|
69
55
|
|
|
70
56
|
function renderTabs() {
|
|
71
|
-
|
|
72
|
-
const container = document.getElementById('file-tabs-container');
|
|
57
|
+
const container = getById('file-tabs-container');
|
|
73
58
|
if (!container) return;
|
|
74
59
|
|
|
75
60
|
let html = '';
|
|
@@ -91,13 +76,7 @@ function renderTabs() {
|
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
function updateTabUI() {
|
|
94
|
-
|
|
95
|
-
const aiTab = document.getElementById('ai-tab');
|
|
96
|
-
if (currentPath === 'ai-assistant') aiTab.classList.add('active');
|
|
97
|
-
else aiTab.classList.remove('active');
|
|
98
|
-
|
|
99
|
-
// 2. Update Dynamic Tabs
|
|
100
|
-
const fileTabs = document.querySelectorAll('#file-tabs-container .tab-item');
|
|
79
|
+
const fileTabs = qsa('#file-tabs-container .tab-item');
|
|
101
80
|
fileTabs.forEach(el => {
|
|
102
81
|
if (el.dataset.path === currentPath) el.classList.add('active');
|
|
103
82
|
else el.classList.remove('active');
|
|
@@ -105,19 +84,13 @@ function updateTabUI() {
|
|
|
105
84
|
}
|
|
106
85
|
|
|
107
86
|
function toggleViewMode(mode) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
aiContainer.classList.add('view-mode-hidden');
|
|
113
|
-
monacoContainer.classList.remove('view-mode-hidden');
|
|
114
|
-
} else {
|
|
115
|
-
aiContainer.classList.remove('view-mode-hidden');
|
|
116
|
-
monacoContainer.classList.add('view-mode-hidden');
|
|
117
|
-
}
|
|
87
|
+
// Placeholder for view switching logic if needed in future
|
|
88
|
+
// For now, it just ensures the container is visible if we were to hide it
|
|
89
|
+
const container = getById('code-viewer-container');
|
|
90
|
+
if (container) container.style.display = 'block';
|
|
118
91
|
}
|
|
119
92
|
|
|
120
|
-
//
|
|
93
|
+
// Expose to window for onclick handlers
|
|
121
94
|
window.switchTab = switchTab;
|
|
122
95
|
window.closeTab = closeTab;
|
|
123
96
|
window.openFileTab = openFileTab;
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { getGitStatus, getGitDiff, stageFile, unstageFile, commitChanges, discardChange } from '../api.js';
|
|
2
|
-
import { showToast } from '../utils.js';
|
|
2
|
+
import { showToast, getById, qsa } from '../utils.js';
|
|
3
|
+
// FIX: Import Diff2HtmlUI from npm package
|
|
4
|
+
import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui';
|
|
5
|
+
|
|
6
|
+
let isGitMode = false;
|
|
7
|
+
let currentStaged = [];
|
|
8
|
+
let currentChanges = [];
|
|
3
9
|
|
|
4
10
|
export function initGitView() {
|
|
5
|
-
const toggleBtn =
|
|
6
|
-
const refreshBtn =
|
|
11
|
+
const toggleBtn = getById('git-view-toggle');
|
|
12
|
+
const refreshBtn = getById('git-refresh-btn');
|
|
7
13
|
|
|
8
14
|
if (toggleBtn) toggleBtn.addEventListener('click', toggleGitMode);
|
|
9
15
|
if (refreshBtn) refreshBtn.addEventListener('click', loadGitData);
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
let isGitMode = false;
|
|
13
|
-
let currentStaged = [];
|
|
14
|
-
let currentChanges = [];
|
|
15
|
-
|
|
16
18
|
async function toggleGitMode() {
|
|
17
|
-
const gitContainer =
|
|
18
|
-
const toggleBtn =
|
|
19
|
-
const refreshBtn =
|
|
20
|
-
const toggleText =
|
|
19
|
+
const gitContainer = getById('git-view-container');
|
|
20
|
+
const toggleBtn = getById('git-view-toggle');
|
|
21
|
+
const refreshBtn = getById('git-refresh-btn');
|
|
22
|
+
const toggleText = getById('git-toggle-text');
|
|
21
23
|
|
|
22
24
|
isGitMode = !isGitMode;
|
|
23
25
|
|
|
@@ -36,9 +38,8 @@ async function toggleGitMode() {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
async function loadGitData() {
|
|
39
|
-
const container =
|
|
41
|
+
const container = getById('git-view-container');
|
|
40
42
|
|
|
41
|
-
// Initial Structure if empty
|
|
42
43
|
if (!container.querySelector('.git-sidebar')) {
|
|
43
44
|
container.innerHTML = `
|
|
44
45
|
<div class="git-sidebar">
|
|
@@ -77,34 +78,28 @@ async function loadGitData() {
|
|
|
77
78
|
</div>
|
|
78
79
|
`;
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
commitInput.addEventListener('keydown', (e) => {
|
|
100
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
handleCommit();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
const stageAll = getById('stage-all');
|
|
83
|
+
const unstageAll = getById('unstage-all');
|
|
84
|
+
const discardAll = getById('discard-all');
|
|
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);
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
const refreshBtn =
|
|
102
|
+
const refreshBtn = getById('git-refresh-btn');
|
|
108
103
|
if(refreshBtn) refreshBtn.disabled = true;
|
|
109
104
|
|
|
110
105
|
try {
|
|
@@ -120,9 +115,9 @@ async function loadGitData() {
|
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
async function handleCommit() {
|
|
123
|
-
const input =
|
|
118
|
+
const input = getById('git-commit-message');
|
|
124
119
|
const message = input.value.trim();
|
|
125
|
-
const btn =
|
|
120
|
+
const btn = getById('git-commit-btn');
|
|
126
121
|
|
|
127
122
|
if (!message) {
|
|
128
123
|
showToast('Please enter a commit message', 'error');
|
|
@@ -152,31 +147,35 @@ async function handleCommit() {
|
|
|
152
147
|
}
|
|
153
148
|
|
|
154
149
|
function renderTrees() {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
document.getElementById('unstage-all').style.display = currentStaged.length > 0 ? 'block' : 'none';
|
|
159
|
-
treeStaged.innerHTML = '';
|
|
150
|
+
const treeStaged = getById('tree-staged');
|
|
151
|
+
const badgeStaged = getById('badge-staged');
|
|
152
|
+
const unstageAll = getById('unstage-all');
|
|
160
153
|
|
|
161
|
-
if (currentStaged.length
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
if (badgeStaged) badgeStaged.textContent = currentStaged.length;
|
|
155
|
+
if (unstageAll) unstageAll.style.display = currentStaged.length > 0 ? 'block' : 'none';
|
|
156
|
+
if (treeStaged) {
|
|
157
|
+
treeStaged.innerHTML = '';
|
|
158
|
+
if (currentStaged.length > 0) {
|
|
159
|
+
const rootStaged = buildFileTree(currentStaged);
|
|
160
|
+
renderTreeNodes(rootStaged, treeStaged, 'staged', 0);
|
|
161
|
+
}
|
|
164
162
|
}
|
|
165
163
|
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
}
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
// --- TREE LOGIC ---
|
|
179
|
-
|
|
180
179
|
function buildFileTree(files) {
|
|
181
180
|
const root = {};
|
|
182
181
|
files.forEach(file => {
|
|
@@ -210,18 +209,15 @@ function renderTreeNodes(nodes, container, type, depth) {
|
|
|
210
209
|
const li = document.createElement('li');
|
|
211
210
|
li.className = 'git-tree-node';
|
|
212
211
|
|
|
213
|
-
// --- CONTENT ROW ---
|
|
214
212
|
const content = document.createElement('div');
|
|
215
213
|
content.className = 'git-tree-content';
|
|
216
214
|
content.style.paddingLeft = (depth * 16 + 8) + 'px';
|
|
217
215
|
|
|
218
|
-
// Arrow
|
|
219
216
|
const arrow = document.createElement('span');
|
|
220
217
|
arrow.className = 'git-arrow';
|
|
221
218
|
arrow.textContent = isFolder ? '▼' : '';
|
|
222
219
|
content.appendChild(arrow);
|
|
223
220
|
|
|
224
|
-
// Icon
|
|
225
221
|
const iconSpan = document.createElement('span');
|
|
226
222
|
iconSpan.className = 'git-icon';
|
|
227
223
|
if (isFolder) {
|
|
@@ -233,18 +229,15 @@ function renderTreeNodes(nodes, container, type, depth) {
|
|
|
233
229
|
}
|
|
234
230
|
content.appendChild(iconSpan);
|
|
235
231
|
|
|
236
|
-
// Label
|
|
237
232
|
const label = document.createElement('span');
|
|
238
233
|
label.className = isFolder ? 'git-label git-dir-label' : 'git-label git-file-label';
|
|
239
234
|
label.textContent = node.name;
|
|
240
235
|
content.appendChild(label);
|
|
241
236
|
|
|
242
|
-
// Actions
|
|
243
237
|
if (!isFolder && node.fileData) {
|
|
244
238
|
const actions = document.createElement('div');
|
|
245
239
|
actions.className = 'git-actions';
|
|
246
240
|
|
|
247
|
-
// Discard Button (Only for Changes)
|
|
248
241
|
if (type === 'changes') {
|
|
249
242
|
const discardBtn = document.createElement('button');
|
|
250
243
|
discardBtn.className = 'git-btn-action destructive';
|
|
@@ -254,7 +247,6 @@ function renderTreeNodes(nodes, container, type, depth) {
|
|
|
254
247
|
actions.appendChild(discardBtn);
|
|
255
248
|
}
|
|
256
249
|
|
|
257
|
-
// Stage/Unstage Button
|
|
258
250
|
const btn = document.createElement('button');
|
|
259
251
|
btn.className = 'git-btn-action';
|
|
260
252
|
if (type === 'staged') {
|
|
@@ -270,13 +262,12 @@ function renderTreeNodes(nodes, container, type, depth) {
|
|
|
270
262
|
content.appendChild(actions);
|
|
271
263
|
}
|
|
272
264
|
|
|
273
|
-
// Click Handler
|
|
274
265
|
content.addEventListener('click', (e) => {
|
|
275
266
|
e.stopPropagation();
|
|
276
267
|
if (isFolder) {
|
|
277
268
|
li.classList.toggle('collapsed');
|
|
278
269
|
} else {
|
|
279
|
-
|
|
270
|
+
qsa('.git-tree-content').forEach(el => el.classList.remove('selected'));
|
|
280
271
|
content.classList.add('selected');
|
|
281
272
|
loadDiffView(node.fileData.path, type);
|
|
282
273
|
}
|
|
@@ -284,7 +275,6 @@ function renderTreeNodes(nodes, container, type, depth) {
|
|
|
284
275
|
|
|
285
276
|
li.appendChild(content);
|
|
286
277
|
|
|
287
|
-
// --- CHILDREN ---
|
|
288
278
|
if (isFolder) {
|
|
289
279
|
const ul = document.createElement('ul');
|
|
290
280
|
renderTreeNodes(node.children, ul, type, depth + 1);
|
|
@@ -303,8 +293,6 @@ function getStatusIcon(status) {
|
|
|
303
293
|
return '?';
|
|
304
294
|
}
|
|
305
295
|
|
|
306
|
-
// --- ACTIONS ---
|
|
307
|
-
|
|
308
296
|
async function handleStage(path) {
|
|
309
297
|
try {
|
|
310
298
|
await stageFile(path);
|
|
@@ -341,18 +329,12 @@ async function handleDiscard(path) {
|
|
|
341
329
|
}
|
|
342
330
|
|
|
343
331
|
async function loadDiffView(filePath, type) {
|
|
344
|
-
const viewer =
|
|
332
|
+
const viewer = getById('git-diff-viewer');
|
|
345
333
|
viewer.innerHTML = '<div class="git-empty-state">Loading diff...</div>';
|
|
346
334
|
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
const UIConstructor = window.Diff2HtmlUI;
|
|
335
|
+
// FIX: Using imported class
|
|
336
|
+
const UIConstructor = Diff2HtmlUI;
|
|
350
337
|
|
|
351
|
-
if (!UIConstructor) {
|
|
352
|
-
viewer.innerHTML = '<div class="git-empty-state" style="color:#f85149">Error: Diff2HtmlUI library not loaded correctly.<br>Please check your internet connection or CDN availability.</div>';
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
338
|
try {
|
|
357
339
|
const diff = await getGitDiff(filePath, type === 'staged' ? 'staged' : 'working');
|
|
358
340
|
|