vg-coder-cli 2.0.22 → 2.0.23
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/.vg/tree-state.json +9 -0
- package/package.json +3 -1
- package/scripts/build.js +49 -6
- package/src/server/api-server.js +46 -0
- package/src/server/views/css/structure.css +4 -1
- package/src/server/views/dashboard.css +24 -1
- package/src/server/views/dashboard.html +2 -0
- package/src/server/views/js/api.js +24 -0
- package/src/server/views/js/features/resize.js +57 -0
- package/src/server/views/js/features/structure.js +109 -16
- package/src/server/views/js/main.js +5 -0
- package/src/server/views/vg-coder/background.js +48201 -2
- package/src/server/views/vg-coder/controller.js +496 -1
- package/src/server/views/vg-coder/manifest.json +13 -5
- package/src/server/views/vg-coder/{options.css → sidepanel.css} +34 -32
- package/src/server/views/vg-coder/{options.html → sidepanel.html} +2 -2
- package/src/server/views/vg-coder/sidepanel.js +347 -0
- package/vetgo-auto/README.md +3 -0
- package/vetgo-auto/chrome/CSP_IMPROVEMENTS.md +147 -0
- package/vetgo-auto/chrome/MANIFEST_V3_MIGRATION.md +123 -0
- package/vetgo-auto/chrome/assets/icon128.png +0 -0
- package/vetgo-auto/chrome/assets/icon16.png +0 -0
- package/vetgo-auto/chrome/assets/icon48.png +0 -0
- package/vetgo-auto/chrome/environments/environment.ts +13 -0
- package/vetgo-auto/chrome/manifest.json +66 -0
- package/vetgo-auto/chrome/rules.json +23 -0
- package/vetgo-auto/chrome/src/background.ts +200 -0
- package/vetgo-auto/chrome/src/controller.ts +98 -0
- package/vetgo-auto/chrome/src/controllers/common.firebase.ts +31 -0
- package/vetgo-auto/chrome/src/controllers/firebase-crud.ts +147 -0
- package/vetgo-auto/chrome/src/controllers/load-common-fuc.controller.ts +24 -0
- package/vetgo-auto/chrome/src/controllers/load-script.controller.ts +23 -0
- package/vetgo-auto/chrome/src/script-injector.ts +305 -0
- package/vetgo-auto/chrome/src/sidepanel.css +166 -0
- package/vetgo-auto/chrome/src/sidepanel.html +48 -0
- package/vetgo-auto/chrome/src/sidepanel.ts +127 -0
- package/vetgo-auto/chrome/src/utils/db-utils.ts +2 -0
- package/vetgo-auto/chrome/src/utils/environment-storage.service.ts +85 -0
- package/vetgo-auto/chrome/webpack.config.js +53 -0
- package/vetgo-auto/chrome/webpack.config.prod.js +54 -0
- package/vetgo-auto/package.json +30 -0
- package/vetgo-auto/tsconfig.json +27 -0
- package/vg-coder-cli-2.0.23.tgz +0 -0
- package/src/server/views/vg-coder/background.js.LICENSE.txt +0 -118
- package/src/server/views/vg-coder/options.js +0 -1
- package/vg-coder-cli-2.0.21.tgz +0 -0
- package/vg-coder-cli-2.0.22.tgz +0 -0
- package/vg-coder.zip +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vg-coder-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.23",
|
|
4
4
|
"description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"test": "jest",
|
|
14
14
|
"test:watch": "jest --watch",
|
|
15
15
|
"dev": "nodemon src/index.js",
|
|
16
|
+
"build:extension": "cd vetgo-auto && npm run build",
|
|
17
|
+
"build:copy": "node scripts/build.js",
|
|
16
18
|
"build": "node scripts/build.js",
|
|
17
19
|
"push": "npm run build && npm pack && npm publish"
|
|
18
20
|
},
|
package/scripts/build.js
CHANGED
|
@@ -1,29 +1,72 @@
|
|
|
1
1
|
const AdmZip = require('adm-zip');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
4
5
|
|
|
5
6
|
async function build() {
|
|
6
7
|
try {
|
|
7
8
|
const rootDir = path.resolve(__dirname, '..');
|
|
9
|
+
const extensionSourceDir = path.join(rootDir, 'vetgo-auto', 'chrome', 'dist');
|
|
10
|
+
const extensionWorkspaceDir = path.join(rootDir, 'vetgo-auto');
|
|
8
11
|
const zipPath = path.join(rootDir, 'vg-coder.zip');
|
|
9
12
|
const targetDir = path.join(rootDir, 'src', 'server', 'views', 'vg-coder');
|
|
10
13
|
|
|
11
14
|
console.log('🏗️ Starting build process...');
|
|
12
15
|
|
|
13
|
-
// 1
|
|
16
|
+
// Strategy 1: Try to build and copy from vetgo-auto/chrome/dist
|
|
17
|
+
if (fs.existsSync(extensionWorkspaceDir)) {
|
|
18
|
+
console.log('📦 Found vetgo-auto workspace. Building extension...');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Build the extension
|
|
22
|
+
console.log('🔨 Running: cd vetgo-auto && npm run build');
|
|
23
|
+
execSync('npm run build', {
|
|
24
|
+
cwd: extensionWorkspaceDir,
|
|
25
|
+
stdio: 'inherit'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Check if dist directory exists
|
|
29
|
+
if (fs.existsSync(extensionSourceDir)) {
|
|
30
|
+
console.log('✅ Extension built successfully at:', extensionSourceDir);
|
|
31
|
+
|
|
32
|
+
// Clean old directory
|
|
33
|
+
if (fs.existsSync(targetDir)) {
|
|
34
|
+
console.log('🧹 Cleaning old extension directory...');
|
|
35
|
+
fs.removeSync(targetDir);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Copy built extension
|
|
39
|
+
console.log('📋 Copying extension to:', targetDir);
|
|
40
|
+
fs.copySync(extensionSourceDir, targetDir);
|
|
41
|
+
|
|
42
|
+
console.log('✅ Extension copied successfully!');
|
|
43
|
+
console.log('🚀 Build completed successfully!');
|
|
44
|
+
return;
|
|
45
|
+
} else {
|
|
46
|
+
console.warn('⚠️ Extension dist directory not found. Falling back to zip extraction...');
|
|
47
|
+
}
|
|
48
|
+
} catch (buildError) {
|
|
49
|
+
console.warn('⚠️ Extension build failed:', buildError.message);
|
|
50
|
+
console.warn('⚠️ Falling back to zip extraction...');
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
console.log('ℹ️ vetgo-auto workspace not found. Using zip file...');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strategy 2: Fallback to zip extraction (for npm package users)
|
|
14
57
|
if (!fs.existsSync(zipPath)) {
|
|
15
|
-
console.error('
|
|
16
|
-
|
|
17
|
-
|
|
58
|
+
console.error('❌ vg-coder.zip not found and extension build failed.');
|
|
59
|
+
console.error('❌ Cannot proceed with build.');
|
|
60
|
+
process.exit(1);
|
|
18
61
|
}
|
|
19
62
|
|
|
20
|
-
//
|
|
63
|
+
// Clean old directory
|
|
21
64
|
if (fs.existsSync(targetDir)) {
|
|
22
65
|
console.log('🧹 Cleaning old extension directory...');
|
|
23
66
|
fs.removeSync(targetDir);
|
|
24
67
|
}
|
|
25
68
|
|
|
26
|
-
//
|
|
69
|
+
// Unzip
|
|
27
70
|
console.log('📦 Unzipping vg-coder.zip...');
|
|
28
71
|
const zip = new AdmZip(zipPath);
|
|
29
72
|
zip.extractAllTo(targetDir, true);
|
package/src/server/api-server.js
CHANGED
|
@@ -274,6 +274,52 @@ class ApiServer {
|
|
|
274
274
|
}
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
+
// --- TREE STATE API ---
|
|
278
|
+
|
|
279
|
+
// Save tree state (excluded paths)
|
|
280
|
+
this.app.post('/api/tree-state/save', async (req, res) => {
|
|
281
|
+
try {
|
|
282
|
+
const { excludedPaths } = req.body;
|
|
283
|
+
if (!Array.isArray(excludedPaths)) {
|
|
284
|
+
return res.status(400).json({ error: 'excludedPaths must be an array' });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Create .vg directory if it doesn't exist
|
|
288
|
+
const vgDir = path.join(this.workingDir, '.vg');
|
|
289
|
+
await fs.ensureDir(vgDir);
|
|
290
|
+
|
|
291
|
+
// Save state to .vg/tree-state.json
|
|
292
|
+
const stateFile = path.join(vgDir, 'tree-state.json');
|
|
293
|
+
await fs.writeJson(stateFile, { excludedPaths }, { spaces: 2 });
|
|
294
|
+
|
|
295
|
+
console.log(chalk.green(`✓ Saved tree state: ${excludedPaths.length} excluded items`));
|
|
296
|
+
res.json({ success: true, count: excludedPaths.length });
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(chalk.red('❌ [TREE STATE SAVE] Error:'), error.message);
|
|
299
|
+
res.status(500).json({ error: error.message });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Load tree state
|
|
304
|
+
this.app.get('/api/tree-state/load', async (req, res) => {
|
|
305
|
+
try {
|
|
306
|
+
const stateFile = path.join(this.workingDir, '.vg', 'tree-state.json');
|
|
307
|
+
|
|
308
|
+
// Check if state file exists
|
|
309
|
+
if (!await fs.pathExists(stateFile)) {
|
|
310
|
+
return res.json({ excludedPaths: [] });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Read and return state
|
|
314
|
+
const state = await fs.readJson(stateFile);
|
|
315
|
+
res.json({ excludedPaths: state.excludedPaths || [] });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(chalk.red('❌ [TREE STATE LOAD] Error:'), error.message);
|
|
318
|
+
// Return empty state on error instead of failing
|
|
319
|
+
res.json({ excludedPaths: [] });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
277
323
|
// --- GENERAL API ---
|
|
278
324
|
|
|
279
325
|
this.app.post('/api/analyze', async (req, res) => {
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
font-size: 11px;
|
|
26
26
|
/* Smaller tree font */
|
|
27
27
|
overflow-x: auto;
|
|
28
|
-
max-height:
|
|
28
|
+
max-height: 600px;
|
|
29
29
|
overflow-y: auto;
|
|
30
|
+
width: 100%; /* Take full width */
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
.tree-ul {
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
cursor: pointer;
|
|
59
60
|
transition: background 0.1s;
|
|
60
61
|
line-height: 1.2;
|
|
62
|
+
width: 100%; /* Ensure row takes full width */
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
.tree-item-row:hover {
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
font-weight: 600;
|
|
100
102
|
min-width: 30px;
|
|
101
103
|
text-align: center;
|
|
104
|
+
flex-shrink: 0; /* Always keep badge visible */
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
.token-low {
|
|
@@ -61,14 +61,37 @@ body {
|
|
|
61
61
|
height: 100%;
|
|
62
62
|
overflow-y: auto;
|
|
63
63
|
background: var(--ios-bg);
|
|
64
|
-
border-right: 1px solid var(--ios-separator);
|
|
65
64
|
position: relative;
|
|
66
65
|
z-index: 10;
|
|
66
|
+
/* allow resize override */
|
|
67
|
+
flex: 0 0 450px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Resize Handler */
|
|
71
|
+
.resize-handler {
|
|
72
|
+
width: 16px;
|
|
73
|
+
cursor: col-resize;
|
|
74
|
+
background: transparent;
|
|
75
|
+
z-index: 100;
|
|
76
|
+
margin-left: -3px; /* Overlap border */
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.resize-handler:hover,
|
|
81
|
+
body.resizing .resize-handler {
|
|
82
|
+
background: var(--ios-blue);
|
|
83
|
+
opacity: 0.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
body.resizing {
|
|
87
|
+
cursor: col-resize;
|
|
88
|
+
user-select: none;
|
|
67
89
|
}
|
|
68
90
|
|
|
69
91
|
.container {
|
|
70
92
|
padding: 15px;
|
|
71
93
|
padding-top: 15px;
|
|
94
|
+
padding-right: 0px;
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
/* --- COMPONENT STYLES --- */
|
|
@@ -100,6 +100,30 @@ export async function commitChanges(message) {
|
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// --- Tree State API ---
|
|
104
|
+
|
|
105
|
+
export async function saveTreeState(excludedPaths) {
|
|
106
|
+
const res = await fetch(`${API_BASE}/api/tree-state/save`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
body: JSON.stringify({ excludedPaths })
|
|
110
|
+
});
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
throw new Error(data.error || 'Failed to save tree state');
|
|
114
|
+
}
|
|
115
|
+
return await res.json();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function loadTreeState() {
|
|
119
|
+
const res = await fetch(`${API_BASE}/api/tree-state/load`);
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
throw new Error(data.error || 'Failed to load tree state');
|
|
123
|
+
}
|
|
124
|
+
return await res.json();
|
|
125
|
+
}
|
|
126
|
+
|
|
103
127
|
// ------------------------
|
|
104
128
|
|
|
105
129
|
export async function copyAsFile(filename, content) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature: Left Panel Resize
|
|
3
|
+
* Allows the user to drag the right edge of the left panel to resize it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function initResizeHandler() {
|
|
7
|
+
const leftPanel = document.querySelector('.left-panel');
|
|
8
|
+
const handle = document.getElementById('resize-handler'); // Match the ID we will add in HTML
|
|
9
|
+
const splitLayout = document.querySelector('.split-layout');
|
|
10
|
+
|
|
11
|
+
if (!leftPanel || !handle) {
|
|
12
|
+
console.warn('Resize elements not found');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let isResizing = false;
|
|
17
|
+
let startX = 0;
|
|
18
|
+
let startWidth = 0;
|
|
19
|
+
|
|
20
|
+
// Mouse Down
|
|
21
|
+
handle.addEventListener('mousedown', (e) => {
|
|
22
|
+
isResizing = true;
|
|
23
|
+
startX = e.clientX;
|
|
24
|
+
startWidth = leftPanel.getBoundingClientRect().width;
|
|
25
|
+
|
|
26
|
+
// Add resizing class for styling/cursor
|
|
27
|
+
document.body.classList.add('resizing');
|
|
28
|
+
|
|
29
|
+
// Disable text selection during drag
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Mouse Move
|
|
34
|
+
document.addEventListener('mousemove', (e) => {
|
|
35
|
+
if (!isResizing) return;
|
|
36
|
+
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
const currentX = e.clientX;
|
|
39
|
+
const diffX = currentX - startX;
|
|
40
|
+
const newWidth = Math.max(250, startWidth + diffX); // Min width 250px
|
|
41
|
+
const maxWidth = window.innerWidth - 300; // Leave space for right panel
|
|
42
|
+
|
|
43
|
+
if (newWidth < maxWidth) {
|
|
44
|
+
leftPanel.style.flex = `0 0 ${newWidth}px`;
|
|
45
|
+
leftPanel.style.width = `${newWidth}px`;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Mouse Up
|
|
51
|
+
document.addEventListener('mouseup', () => {
|
|
52
|
+
if (isResizing) {
|
|
53
|
+
isResizing = false;
|
|
54
|
+
document.body.classList.remove('resizing');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { getStructure, analyzeProject, copyToClipboard } from '../api.js';
|
|
1
|
+
import { getStructure, analyzeProject, copyToClipboard, saveTreeState as apiSaveTreeState, loadTreeState as apiLoadTreeState } from '../api.js';
|
|
2
2
|
import { showToast, showLoading, resetButton, showResponse, formatNumber, showCopiedState } from '../utils.js';
|
|
3
3
|
|
|
4
4
|
// Global variable to store current structure data
|
|
5
5
|
let currentStructureData = null;
|
|
6
6
|
|
|
7
|
+
// Tree state management
|
|
8
|
+
let excludedPaths = new Set(); // Paths that are unchecked
|
|
9
|
+
let saveStateTimeout = null; // Debounce timer for auto-save
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Handle Structure button click logic
|
|
9
13
|
*/
|
|
@@ -24,6 +28,9 @@ export async function handleStructureView(event) {
|
|
|
24
28
|
const data = await getStructure(path);
|
|
25
29
|
currentStructureData = data.structure;
|
|
26
30
|
|
|
31
|
+
// Load saved state before rendering
|
|
32
|
+
await loadTreeState();
|
|
33
|
+
|
|
27
34
|
// Render Tree HTML using recursive function
|
|
28
35
|
treeContent.innerHTML = generateTreeHtml(data.structure);
|
|
29
36
|
|
|
@@ -62,18 +69,35 @@ export function handleCheckboxChange(event) {
|
|
|
62
69
|
event.stopPropagation();
|
|
63
70
|
const checkbox = event.target;
|
|
64
71
|
const isChecked = checkbox.checked;
|
|
72
|
+
const path = checkbox.dataset.path;
|
|
73
|
+
|
|
74
|
+
// 1. Update excluded paths set
|
|
75
|
+
if (isChecked) {
|
|
76
|
+
excludedPaths.delete(path);
|
|
77
|
+
} else {
|
|
78
|
+
excludedPaths.add(path);
|
|
79
|
+
}
|
|
65
80
|
|
|
66
|
-
//
|
|
81
|
+
// 2. Sync Children: If this is a folder, update all children checkboxes
|
|
67
82
|
const li = checkbox.closest('.tree-li');
|
|
68
83
|
if (li) {
|
|
69
84
|
const childrenCheckboxes = li.querySelectorAll('.tree-checkbox');
|
|
70
85
|
childrenCheckboxes.forEach(child => {
|
|
71
86
|
child.checked = isChecked;
|
|
87
|
+
const childPath = child.dataset.path;
|
|
88
|
+
if (isChecked) {
|
|
89
|
+
excludedPaths.delete(childPath);
|
|
90
|
+
} else {
|
|
91
|
+
excludedPaths.add(childPath);
|
|
92
|
+
}
|
|
72
93
|
});
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
//
|
|
96
|
+
// 3. Recalculate Tokens
|
|
76
97
|
updateTotalTokens();
|
|
98
|
+
|
|
99
|
+
// 4. Auto-save state (debounced)
|
|
100
|
+
debouncedSaveState();
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
/**
|
|
@@ -169,11 +193,35 @@ export async function handleCopySelected(event) {
|
|
|
169
193
|
function generateTreeHtml(node) {
|
|
170
194
|
if (!node) return '';
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
|
|
196
|
+
// --- COMPACT FOLDER LOGIC ---
|
|
197
|
+
let currentNode = node;
|
|
198
|
+
let displayName = node.name;
|
|
199
|
+
let isCompact = false;
|
|
200
|
+
|
|
201
|
+
// Only compact if it's a directory
|
|
202
|
+
if (currentNode.type === 'directory') {
|
|
203
|
+
// While current node has EXACTLY one child and that child is a directory
|
|
204
|
+
while (
|
|
205
|
+
currentNode.children &&
|
|
206
|
+
currentNode.children.length === 1 &&
|
|
207
|
+
currentNode.children[0].type === 'directory'
|
|
208
|
+
) {
|
|
209
|
+
const child = currentNode.children[0];
|
|
210
|
+
displayName += '/' + child.name;
|
|
211
|
+
currentNode = child; // Advance to child
|
|
212
|
+
isCompact = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Now uses 'currentNode' for properties (it's the deepest folder in the chain)
|
|
217
|
+
// But we use 'displayName' for the UI
|
|
218
|
+
// 'tokens' should be from the root node of this chain (usually same as leaf for folders)
|
|
174
219
|
|
|
175
|
-
|
|
176
|
-
const
|
|
220
|
+
const isDir = currentNode.type === 'directory';
|
|
221
|
+
const hasChildren = isDir && currentNode.children && currentNode.children.length > 0;
|
|
222
|
+
|
|
223
|
+
// Determine Token Color (Use original node's tokens or deep node's tokens - they should be identical)
|
|
224
|
+
const tokens = currentNode.tokens || 0;
|
|
177
225
|
let tokenClass = 'token-low';
|
|
178
226
|
if (tokens > 5000) tokenClass = 'token-high';
|
|
179
227
|
else if (tokens > 2000) tokenClass = 'token-med';
|
|
@@ -184,7 +232,6 @@ function generateTreeHtml(node) {
|
|
|
184
232
|
const liClass = `tree-li ${hasChildren ? 'has-children' : ''}`;
|
|
185
233
|
|
|
186
234
|
// Actions
|
|
187
|
-
// On click: Toggle if folder, Open Tab if file
|
|
188
235
|
let clickAction = '';
|
|
189
236
|
let cursorStyle = '';
|
|
190
237
|
|
|
@@ -196,34 +243,38 @@ function generateTreeHtml(node) {
|
|
|
196
243
|
} else {
|
|
197
244
|
// File click -> Open in Editor
|
|
198
245
|
// Escape backslashes for Windows paths
|
|
199
|
-
const safePath = (
|
|
200
|
-
clickAction = `onclick="window.openFileTab('${safePath}', '${
|
|
246
|
+
const safePath = (currentNode.relativePath || currentNode.path).replace(/\\/g, '\\\\');
|
|
247
|
+
clickAction = `onclick="window.openFileTab('${safePath}', '${currentNode.name}')"`;
|
|
201
248
|
cursorStyle = 'cursor: pointer; color: var(--text-primary);';
|
|
202
249
|
}
|
|
203
250
|
|
|
204
251
|
// Build HTML
|
|
205
252
|
let html = `<li class="${liClass}">`;
|
|
206
253
|
|
|
254
|
+
// Check if this path should be excluded (unchecked)
|
|
255
|
+
const nodePath = currentNode.relativePath || currentNode.path;
|
|
256
|
+
const isExcluded = excludedPaths.has(nodePath);
|
|
257
|
+
|
|
207
258
|
html += `
|
|
208
259
|
<div class="tree-item-row" ${isDir ? clickAction : ''}>
|
|
209
260
|
<span class="arrow">${arrow}</span>
|
|
210
261
|
<input type="checkbox" class="tree-checkbox"
|
|
211
|
-
data-path="${
|
|
262
|
+
data-path="${nodePath}"
|
|
212
263
|
data-tokens="${tokens}"
|
|
213
|
-
data-type="${
|
|
214
|
-
checked
|
|
264
|
+
data-type="${currentNode.type}"
|
|
265
|
+
${isExcluded ? '' : 'checked'}
|
|
215
266
|
onclick="handleCheckboxChange(event)">
|
|
216
267
|
<span class="tree-icon">${icon}</span>
|
|
217
|
-
<span class="tree-name" style="${cursorStyle}" ${!isDir ? clickAction : ''}>${
|
|
268
|
+
<span class="tree-name" style="${cursorStyle}" ${!isDir ? clickAction : ''} title="${displayName}">${displayName}</span>
|
|
218
269
|
<span class="token-badge ${tokenClass}">${formatNumber(tokens)}</span>
|
|
219
270
|
</div>
|
|
220
271
|
`;
|
|
221
272
|
|
|
222
|
-
// Children recursion
|
|
273
|
+
// Children recursion (using deepest node's children)
|
|
223
274
|
if (hasChildren) {
|
|
224
275
|
html += '<ul class="tree-ul">';
|
|
225
276
|
// Sort: Folders first, then files
|
|
226
|
-
|
|
277
|
+
currentNode.children.forEach(child => {
|
|
227
278
|
html += generateTreeHtml(child);
|
|
228
279
|
});
|
|
229
280
|
html += '</ul>';
|
|
@@ -233,3 +284,45 @@ function generateTreeHtml(node) {
|
|
|
233
284
|
|
|
234
285
|
return isDir ? `<ul class="tree-ul">${html}</ul>` : html;
|
|
235
286
|
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Load tree state from backend
|
|
290
|
+
*/
|
|
291
|
+
async function loadTreeState() {
|
|
292
|
+
try {
|
|
293
|
+
const data = await apiLoadTreeState();
|
|
294
|
+
excludedPaths = new Set(data.excludedPaths || []);
|
|
295
|
+
console.log(`Loaded tree state: ${excludedPaths.size} excluded items`);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.error('Failed to load tree state:', err);
|
|
298
|
+
excludedPaths = new Set(); // Reset to empty on error
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Debounced save - waits 500ms after last change before saving
|
|
304
|
+
*/
|
|
305
|
+
function debouncedSaveState() {
|
|
306
|
+
// Clear existing timeout
|
|
307
|
+
if (saveStateTimeout) {
|
|
308
|
+
clearTimeout(saveStateTimeout);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Set new timeout
|
|
312
|
+
saveStateTimeout = setTimeout(() => {
|
|
313
|
+
saveTreeState();
|
|
314
|
+
}, 500);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Save current tree state to backend
|
|
319
|
+
*/
|
|
320
|
+
async function saveTreeState() {
|
|
321
|
+
try {
|
|
322
|
+
const excludedArray = Array.from(excludedPaths);
|
|
323
|
+
await apiSaveTreeState(excludedArray);
|
|
324
|
+
console.log(`Saved tree state: ${excludedArray.length} excluded items`);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error('Failed to save tree state:', err);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -8,6 +8,7 @@ import { initGitView } from './features/git-view.js';
|
|
|
8
8
|
import { initTerminal, createNewTerminal } from './features/terminal.js';
|
|
9
9
|
import { initEditorTabs } from './features/editor-tabs.js';
|
|
10
10
|
import { initMonaco, updateMonacoTheme } from './features/monaco-manager.js';
|
|
11
|
+
import { initResizeHandler } from './features/resize.js';
|
|
11
12
|
|
|
12
13
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
13
14
|
// Load system prompt text
|
|
@@ -34,9 +35,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
34
35
|
// Initialize Terminal System
|
|
35
36
|
initTerminal();
|
|
36
37
|
|
|
38
|
+
// Initialize Monaco & Tabs
|
|
37
39
|
// Initialize Monaco & Tabs
|
|
38
40
|
initMonaco();
|
|
39
41
|
initEditorTabs();
|
|
42
|
+
|
|
43
|
+
// Initialize Resize Handler
|
|
44
|
+
initResizeHandler();
|
|
40
45
|
|
|
41
46
|
// Set default tab to AI Assistant
|
|
42
47
|
if (window.switchTab) {
|