@hugobatist/smartcode 0.1.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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/cli.js +4324 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +374 -0
- package/dist/index.js +1167 -0
- package/dist/index.js.map +1 -0
- package/dist/static/annotations-panel.js +133 -0
- package/dist/static/annotations-svg.js +108 -0
- package/dist/static/annotations.css +367 -0
- package/dist/static/annotations.js +367 -0
- package/dist/static/app-init.js +497 -0
- package/dist/static/breakpoints.css +69 -0
- package/dist/static/breakpoints.js +197 -0
- package/dist/static/clipboard.js +94 -0
- package/dist/static/collapse-ui.js +325 -0
- package/dist/static/command-history.js +89 -0
- package/dist/static/context-menu.js +334 -0
- package/dist/static/custom-renderer.js +201 -0
- package/dist/static/dagre-layout.js +291 -0
- package/dist/static/diagram-dom.js +241 -0
- package/dist/static/diagram-editor.js +368 -0
- package/dist/static/editor-panel.js +107 -0
- package/dist/static/editor-popovers.js +187 -0
- package/dist/static/event-bus.js +57 -0
- package/dist/static/export.js +181 -0
- package/dist/static/file-tree.js +470 -0
- package/dist/static/ghost-paths.js +397 -0
- package/dist/static/heatmap.css +116 -0
- package/dist/static/heatmap.js +308 -0
- package/dist/static/icons.js +66 -0
- package/dist/static/inline-edit.js +294 -0
- package/dist/static/interaction-state.js +155 -0
- package/dist/static/interaction-tracker.js +93 -0
- package/dist/static/live.html +239 -0
- package/dist/static/main-layout.css +220 -0
- package/dist/static/main.css +334 -0
- package/dist/static/mcp-sessions.js +202 -0
- package/dist/static/modal.css +109 -0
- package/dist/static/modal.js +171 -0
- package/dist/static/node-drag.js +293 -0
- package/dist/static/pan-zoom.js +199 -0
- package/dist/static/renderer.js +280 -0
- package/dist/static/search.css +103 -0
- package/dist/static/search.js +304 -0
- package/dist/static/selection.js +353 -0
- package/dist/static/session-player.css +137 -0
- package/dist/static/session-player.js +411 -0
- package/dist/static/sidebar.css +248 -0
- package/dist/static/svg-renderer.js +313 -0
- package/dist/static/svg-shapes.js +218 -0
- package/dist/static/tokens.css +76 -0
- package/dist/static/vendor/dagre-bundle.js +43 -0
- package/dist/static/vendor/dagre.min.js +3 -0
- package/dist/static/vendor/graphlib.min.js +2 -0
- package/dist/static/viewport-transform.js +107 -0
- package/dist/static/workspace-switcher.js +202 -0
- package/dist/static/ws-client.js +71 -0
- package/dist/static/ws-handler.js +125 -0
- package/package.json +74 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCode File Tree -- file tree rendering, CRUD operations, file loading.
|
|
3
|
+
* Extracted from live.html (Phase 9 Plan 03).
|
|
4
|
+
*
|
|
5
|
+
* Dependencies: renderer.js (SmartCodeRenderer), event-bus.js (SmartCodeEventBus)
|
|
6
|
+
* Dependents: app-init.js, editor-panel.js, annotations.js
|
|
7
|
+
*
|
|
8
|
+
* Note: innerHTML usage is safe here -- all dynamic values pass through
|
|
9
|
+
* SmartCodeRenderer.escapeHtml() before interpolation, preventing XSS.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* SmartCodeFileTree.refreshFileList();
|
|
13
|
+
* SmartCodeFileTree.loadFile(path);
|
|
14
|
+
* SmartCodeFileTree.syncFile();
|
|
15
|
+
* SmartCodeFileTree.getCurrentFile();
|
|
16
|
+
* SmartCodeFileTree.setCurrentFile(path);
|
|
17
|
+
* SmartCodeFileTree.getLastContent();
|
|
18
|
+
* SmartCodeFileTree.setLastContent(v);
|
|
19
|
+
* SmartCodeFileTree.saveCurrentFile();
|
|
20
|
+
* SmartCodeFileTree.createNewFile();
|
|
21
|
+
* SmartCodeFileTree.createNewFolder();
|
|
22
|
+
* SmartCodeFileTree.deleteFile(path);
|
|
23
|
+
* SmartCodeFileTree.renameFile(path);
|
|
24
|
+
*/
|
|
25
|
+
(function() {
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
// ── State ──
|
|
29
|
+
var currentFile = '';
|
|
30
|
+
var lastContent = '';
|
|
31
|
+
var treeData = [];
|
|
32
|
+
var initialLoadDone = false;
|
|
33
|
+
var collapsedFolders = new Set(JSON.parse(localStorage.getItem('smartcode-collapsed') || '[]'));
|
|
34
|
+
|
|
35
|
+
// Centralized setter -- keeps window.currentFile in sync
|
|
36
|
+
function setFile(path) {
|
|
37
|
+
currentFile = path;
|
|
38
|
+
window.currentFile = path;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Initial sync
|
|
42
|
+
window.currentFile = currentFile;
|
|
43
|
+
|
|
44
|
+
/** Collect all file paths from nested tree data */
|
|
45
|
+
function collectFilePaths(nodes) {
|
|
46
|
+
var paths = [];
|
|
47
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
48
|
+
if (nodes[i].type === 'file') paths.push(nodes[i].path);
|
|
49
|
+
else if (nodes[i].children) paths = paths.concat(collectFilePaths(nodes[i].children));
|
|
50
|
+
}
|
|
51
|
+
return paths;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function saveCollapsed() {
|
|
55
|
+
localStorage.setItem('smartcode-collapsed', JSON.stringify([...collapsedFolders]));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Display helpers ──
|
|
59
|
+
function prettyName(fname) {
|
|
60
|
+
var base = fname.includes('/') ? fname.split('/').pop() : fname;
|
|
61
|
+
return base.replace('.mmd', '').replace(/-/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function prettyFolder(name) {
|
|
65
|
+
return name.replace(/-/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function countFiles(node) {
|
|
69
|
+
if (node.type === 'file') return 1;
|
|
70
|
+
return (node.children || []).reduce(function(s, c) { return s + countFiles(c); }, 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── URL helper ──
|
|
74
|
+
function bUrl(path) { return (window.SmartCodeBaseUrl || '') + path; }
|
|
75
|
+
|
|
76
|
+
// ── Tree rendering ──
|
|
77
|
+
function refreshFileList() {
|
|
78
|
+
fetch(bUrl('/tree.json?t=' + Date.now()))
|
|
79
|
+
.then(function(resp) {
|
|
80
|
+
if (resp.ok) return resp.json();
|
|
81
|
+
return null;
|
|
82
|
+
})
|
|
83
|
+
.then(function(data) {
|
|
84
|
+
if (data) treeData = data;
|
|
85
|
+
renderTree();
|
|
86
|
+
// On first load, auto-select first available file if current doesn't exist
|
|
87
|
+
if (!initialLoadDone && data) {
|
|
88
|
+
initialLoadDone = true;
|
|
89
|
+
var allFiles = collectFilePaths(data);
|
|
90
|
+
if (allFiles.length > 0 && (!currentFile || allFiles.indexOf(currentFile) === -1)) {
|
|
91
|
+
loadFile(allFiles[0]);
|
|
92
|
+
} else if (currentFile && allFiles.indexOf(currentFile) !== -1) {
|
|
93
|
+
syncFile();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.catch(function() {
|
|
98
|
+
renderTree();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderTree() {
|
|
103
|
+
// Skip rendering if MCP Sessions view is active
|
|
104
|
+
if (window.SmartCodeMcpSessions && SmartCodeMcpSessions.getViewMode() === 'sessions') return;
|
|
105
|
+
var container = document.getElementById('fileTree');
|
|
106
|
+
// Safe: all dynamic values pass through escapeHtml() — see renderNodes()
|
|
107
|
+
if (container) container.innerHTML = renderNodes(treeData, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function renderNodes(nodes, depth) {
|
|
111
|
+
return nodes.map(function(n) {
|
|
112
|
+
if (n.type === 'folder') {
|
|
113
|
+
var isOpen = !collapsedFolders.has(n.name);
|
|
114
|
+
var count = countFiles(n);
|
|
115
|
+
var pad = 8 + depth * 16;
|
|
116
|
+
return '<div class="tree-folder">' +
|
|
117
|
+
'<div class="tree-folder-header" style="padding-left:' + pad + 'px" data-action="toggle-folder" data-folder="' + escapeHtml(n.name) + '">' +
|
|
118
|
+
'<span class="tree-chevron ' + (isOpen ? 'open' : '') + '">' + SmartCodeIcons.chevronRight + '</span>' +
|
|
119
|
+
'<span class="tree-folder-icon">' + (isOpen ? SmartCodeIcons.folderOpen : SmartCodeIcons.folder) + '</span>' +
|
|
120
|
+
'<span class="tree-folder-name">' + escapeHtml(prettyFolder(n.name)) + '</span>' +
|
|
121
|
+
'<span class="tree-folder-count">' + count + '</span>' +
|
|
122
|
+
'<span class="tree-folder-actions">' +
|
|
123
|
+
'<button class="rename-btn" data-action="rename-folder" data-folder="' + escapeHtml(n.name) + '" title="Rename Folder">' + SmartCodeIcons.edit + '</button>' +
|
|
124
|
+
'<button class="delete-btn" data-action="delete-folder" data-folder="' + escapeHtml(n.name) + '" title="Delete Folder">' + SmartCodeIcons.trash + '</button>' +
|
|
125
|
+
'</span>' +
|
|
126
|
+
'</div>' +
|
|
127
|
+
'<div class="tree-children ' + (isOpen ? '' : 'collapsed') + '">' +
|
|
128
|
+
renderNodes(n.children || [], depth + 1) +
|
|
129
|
+
'</div>' +
|
|
130
|
+
'</div>';
|
|
131
|
+
} else {
|
|
132
|
+
var filePad = 8 + depth * 16;
|
|
133
|
+
var filePath = n.path;
|
|
134
|
+
var isActive = filePath === currentFile;
|
|
135
|
+
return '<div class="tree-file ' + (isActive ? 'active' : '') + '" style="padding-left:' + filePad + 'px" data-action="load-file" data-path="' + escapeHtml(filePath) + '">' +
|
|
136
|
+
'<span class="tree-file-icon">' + SmartCodeIcons.file + '</span>' +
|
|
137
|
+
'<span class="tree-file-name" title="' + escapeHtml(filePath) + '">' + escapeHtml(prettyName(n.name)) + '</span>' +
|
|
138
|
+
'<span class="tree-file-actions">' +
|
|
139
|
+
'<button class="rename-btn" data-action="rename-file" data-path="' + escapeHtml(filePath) + '" title="Rename">' + SmartCodeIcons.edit + '</button>' +
|
|
140
|
+
'<button class="delete-btn" data-action="delete-file" data-path="' + escapeHtml(filePath) + '" title="Delete">' + SmartCodeIcons.trash + '</button>' +
|
|
141
|
+
'</span>' +
|
|
142
|
+
'</div>';
|
|
143
|
+
}
|
|
144
|
+
}).join('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Event delegation for file tree ──
|
|
148
|
+
document.getElementById('fileTree').addEventListener('click', function(e) {
|
|
149
|
+
var target = e.target.closest('[data-action]');
|
|
150
|
+
if (!target) return;
|
|
151
|
+
var action = target.dataset.action;
|
|
152
|
+
var actionPath = target.dataset.path;
|
|
153
|
+
var folder = target.dataset.folder;
|
|
154
|
+
switch (action) {
|
|
155
|
+
case 'toggle-folder':
|
|
156
|
+
toggleFolder(folder);
|
|
157
|
+
break;
|
|
158
|
+
case 'load-file':
|
|
159
|
+
loadFile(actionPath);
|
|
160
|
+
break;
|
|
161
|
+
case 'rename-file':
|
|
162
|
+
e.stopPropagation();
|
|
163
|
+
renameFile(actionPath);
|
|
164
|
+
break;
|
|
165
|
+
case 'delete-file':
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
deleteFile(actionPath);
|
|
168
|
+
break;
|
|
169
|
+
case 'rename-folder':
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
renameFolder(folder);
|
|
172
|
+
break;
|
|
173
|
+
case 'delete-folder':
|
|
174
|
+
e.stopPropagation();
|
|
175
|
+
deleteFolder(folder);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
function toggleFolder(name) {
|
|
181
|
+
if (collapsedFolders.has(name)) collapsedFolders.delete(name);
|
|
182
|
+
else collapsedFolders.add(name);
|
|
183
|
+
saveCollapsed();
|
|
184
|
+
renderTree();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── File loading ──
|
|
188
|
+
function loadFile(path) {
|
|
189
|
+
// Clear undo/redo history when switching files
|
|
190
|
+
if (window.SmartCodeCommandHistory) SmartCodeCommandHistory.clear();
|
|
191
|
+
setFile(path);
|
|
192
|
+
document.getElementById('currentFileName').textContent = prettyName(path);
|
|
193
|
+
lastContent = '';
|
|
194
|
+
SmartCodeRenderer.setInitialRender(true);
|
|
195
|
+
syncFile();
|
|
196
|
+
renderTree();
|
|
197
|
+
|
|
198
|
+
// Reset ghost path user-hide tracking on file switch
|
|
199
|
+
if (window.SmartCodeGhostPaths) SmartCodeGhostPaths.resetUserHide();
|
|
200
|
+
|
|
201
|
+
// Flush and reset interaction tracker for new file
|
|
202
|
+
if (window.SmartCodeInteractionTracker) SmartCodeInteractionTracker.resetForFile();
|
|
203
|
+
|
|
204
|
+
// Fetch overlay data for the new file (ghost paths, heatmap, sessions)
|
|
205
|
+
var encoded = encodeURIComponent(path);
|
|
206
|
+
if (window.SmartCodeGhostPaths) {
|
|
207
|
+
fetch(bUrl('/api/ghost-paths/' + encoded))
|
|
208
|
+
.then(function(r) { return r.ok ? r.json() : null; })
|
|
209
|
+
.then(function(d) { if (d) SmartCodeGhostPaths.updateGhostPaths(path, d.ghostPaths || []); })
|
|
210
|
+
.catch(function() {});
|
|
211
|
+
}
|
|
212
|
+
if (window.SmartCodeHeatmap) {
|
|
213
|
+
fetch(bUrl('/api/heatmap/' + encoded))
|
|
214
|
+
.then(function(r) { return r.ok ? r.json() : null; })
|
|
215
|
+
.then(function(d) { if (d) SmartCodeHeatmap.updateVisitCounts(d); })
|
|
216
|
+
.catch(function() {});
|
|
217
|
+
}
|
|
218
|
+
if (window.SmartCodeSessionPlayer) {
|
|
219
|
+
SmartCodeSessionPlayer.fetchSessionList(path);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── File sync via fetch ──
|
|
224
|
+
function syncFile() {
|
|
225
|
+
var editor = document.getElementById('editor');
|
|
226
|
+
fetch(bUrl('/' + currentFile + '?t=' + Date.now()))
|
|
227
|
+
.then(function(resp) {
|
|
228
|
+
if (!resp.ok) throw new Error('not ok');
|
|
229
|
+
return resp.text();
|
|
230
|
+
})
|
|
231
|
+
.then(function(text) {
|
|
232
|
+
if (text !== lastContent) {
|
|
233
|
+
// Merge: preserve user flags when Claude edits arrive
|
|
234
|
+
if (window.SmartCodeAnnotations && SmartCodeAnnotations.getState().flags.size > 0) {
|
|
235
|
+
text = SmartCodeAnnotations.mergeIncomingContent(text);
|
|
236
|
+
} else if (window.SmartCodeAnnotations) {
|
|
237
|
+
var incoming = SmartCodeAnnotations.parseAnnotations(text);
|
|
238
|
+
SmartCodeAnnotations.getState().flags = incoming.flags;
|
|
239
|
+
SmartCodeAnnotations.getState().statuses = incoming.statuses;
|
|
240
|
+
SmartCodeAnnotations.getState().ghosts = incoming.ghosts || [];
|
|
241
|
+
if (window.SmartCodeGhostPaths) SmartCodeGhostPaths.updateGhostPaths(currentFile, incoming.ghosts || []);
|
|
242
|
+
SmartCodeAnnotations.renderPanel();
|
|
243
|
+
SmartCodeAnnotations.updateBadge();
|
|
244
|
+
}
|
|
245
|
+
lastContent = text;
|
|
246
|
+
editor.value = text;
|
|
247
|
+
render(text);
|
|
248
|
+
if (window.toast) toast('Atualizado');
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
.catch(function() {});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── File CRUD ──
|
|
255
|
+
function createNewFile() {
|
|
256
|
+
SmartCodeModal.prompt({
|
|
257
|
+
title: 'New Diagram',
|
|
258
|
+
placeholder: 'diagram-name (use folder/name for subfolders)',
|
|
259
|
+
onConfirm: function(name) {
|
|
260
|
+
var fname = name.replace(/[^a-z0-9-_/]/gi, '-').toLowerCase().replace(/^\/|\/$/g, '') + '.mmd';
|
|
261
|
+
var editor = document.getElementById('editor');
|
|
262
|
+
editor.value = 'flowchart LR\n A["Start"] --> B["End"]';
|
|
263
|
+
setFile(fname);
|
|
264
|
+
lastContent = editor.value;
|
|
265
|
+
saveCurrentFile();
|
|
266
|
+
render(editor.value);
|
|
267
|
+
document.getElementById('currentFileName').textContent = prettyName(fname);
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function createNewFolder() {
|
|
273
|
+
SmartCodeModal.prompt({
|
|
274
|
+
title: 'New Folder',
|
|
275
|
+
placeholder: 'folder-name',
|
|
276
|
+
onConfirm: function(name) {
|
|
277
|
+
var safe = name.replace(/[^a-z0-9-_/]/gi, '-').toLowerCase();
|
|
278
|
+
fetch(bUrl('/mkdir'), {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers: { 'Content-Type': 'application/json' },
|
|
281
|
+
body: JSON.stringify({ folder: safe }),
|
|
282
|
+
}).then(function() {
|
|
283
|
+
if (window.toast) toast('Folder created: ' + safe);
|
|
284
|
+
refreshFileList();
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function saveCurrentFile() {
|
|
291
|
+
var editor = document.getElementById('editor');
|
|
292
|
+
var content = editor.value;
|
|
293
|
+
if (!content.trim()) { if (window.toast) toast('Nothing to save'); return; }
|
|
294
|
+
fetch(bUrl('/save'), {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: { 'Content-Type': 'application/json' },
|
|
297
|
+
body: JSON.stringify({ filename: currentFile, content: content }),
|
|
298
|
+
}).then(function(resp) {
|
|
299
|
+
if (resp.ok) {
|
|
300
|
+
if (window.toast) toast('Saved: ' + currentFile);
|
|
301
|
+
lastContent = content;
|
|
302
|
+
refreshFileList();
|
|
303
|
+
if (window.SmartCodeEventBus) {
|
|
304
|
+
SmartCodeEventBus.emit('file:saved', { path: currentFile });
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
if (window.toast) toast('Error saving');
|
|
308
|
+
}
|
|
309
|
+
}).catch(function() {
|
|
310
|
+
if (window.toast) toast('Error: server offline?');
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function deleteFile(fpath) {
|
|
315
|
+
SmartCodeModal.confirm({
|
|
316
|
+
title: 'Delete Diagram',
|
|
317
|
+
message: 'Delete ' + prettyName(fpath) + '?',
|
|
318
|
+
danger: true,
|
|
319
|
+
onConfirm: function() {
|
|
320
|
+
fetch(bUrl('/delete'), {
|
|
321
|
+
method: 'POST',
|
|
322
|
+
headers: { 'Content-Type': 'application/json' },
|
|
323
|
+
body: JSON.stringify({ filename: fpath }),
|
|
324
|
+
}).then(function() {
|
|
325
|
+
if (window.toast) toast('Deleted');
|
|
326
|
+
if (currentFile === fpath) {
|
|
327
|
+
setFile('');
|
|
328
|
+
document.getElementById('currentFileName').textContent = '';
|
|
329
|
+
var editor = document.getElementById('editor');
|
|
330
|
+
editor.value = '';
|
|
331
|
+
lastContent = '';
|
|
332
|
+
}
|
|
333
|
+
refreshFileList();
|
|
334
|
+
}).catch(function() {
|
|
335
|
+
if (window.toast) toast('Error deleting file');
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renameFile(oldPath) {
|
|
342
|
+
var parts = oldPath.split('/');
|
|
343
|
+
var base = parts.pop().replace('.mmd', '');
|
|
344
|
+
var folder = parts.join('/');
|
|
345
|
+
SmartCodeModal.prompt({
|
|
346
|
+
title: 'Rename Diagram',
|
|
347
|
+
placeholder: 'New name',
|
|
348
|
+
defaultValue: base,
|
|
349
|
+
onConfirm: function(newBase) {
|
|
350
|
+
if (newBase === base) return;
|
|
351
|
+
var safeName = newBase.replace(/[^a-z0-9-_]/gi, '-').toLowerCase() + '.mmd';
|
|
352
|
+
var newPath = folder ? folder + '/' + safeName : safeName;
|
|
353
|
+
fetch(bUrl('/move'), {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
headers: { 'Content-Type': 'application/json' },
|
|
356
|
+
body: JSON.stringify({ from: oldPath, to: newPath }),
|
|
357
|
+
}).then(function() {
|
|
358
|
+
if (currentFile === oldPath) {
|
|
359
|
+
setFile(newPath);
|
|
360
|
+
document.getElementById('currentFileName').textContent = prettyName(newPath);
|
|
361
|
+
}
|
|
362
|
+
refreshFileList();
|
|
363
|
+
if (window.toast) toast('Renamed');
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function renameFolder(oldName) {
|
|
370
|
+
var displayName = prettyFolder(oldName);
|
|
371
|
+
SmartCodeModal.prompt({
|
|
372
|
+
title: 'Rename Folder',
|
|
373
|
+
placeholder: 'New folder name',
|
|
374
|
+
defaultValue: displayName,
|
|
375
|
+
onConfirm: function(newName) {
|
|
376
|
+
if (newName === displayName) return;
|
|
377
|
+
var safeName = newName.replace(/[^a-z0-9-_]/gi, '-').toLowerCase();
|
|
378
|
+
fetch(bUrl('/move'), {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: { 'Content-Type': 'application/json' },
|
|
381
|
+
body: JSON.stringify({ from: oldName, to: safeName }),
|
|
382
|
+
}).then(function() {
|
|
383
|
+
if (currentFile.startsWith(oldName + '/')) {
|
|
384
|
+
var newPath = currentFile.replace(oldName + '/', safeName + '/');
|
|
385
|
+
setFile(newPath);
|
|
386
|
+
document.getElementById('currentFileName').textContent = prettyName(newPath);
|
|
387
|
+
}
|
|
388
|
+
if (collapsedFolders.has(oldName)) {
|
|
389
|
+
collapsedFolders.delete(oldName);
|
|
390
|
+
collapsedFolders.add(safeName);
|
|
391
|
+
saveCollapsed();
|
|
392
|
+
}
|
|
393
|
+
refreshFileList();
|
|
394
|
+
if (window.toast) toast('Folder renamed');
|
|
395
|
+
}).catch(function() {
|
|
396
|
+
if (window.toast) toast('Error renaming folder');
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function deleteFolder(folderName) {
|
|
403
|
+
var count = 0;
|
|
404
|
+
// Count files in folder from tree data
|
|
405
|
+
function countInFolder(nodes) {
|
|
406
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
407
|
+
if (nodes[i].type === 'folder' && nodes[i].name === folderName) {
|
|
408
|
+
count = countFiles(nodes[i]);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (nodes[i].children) countInFolder(nodes[i].children);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
countInFolder(treeData);
|
|
415
|
+
var msg = 'Delete folder "' + prettyFolder(folderName) + '"';
|
|
416
|
+
if (count > 0) msg += ' with ' + count + ' file' + (count > 1 ? 's' : '') + '.';
|
|
417
|
+
else msg += ' (empty).';
|
|
418
|
+
msg += '\nThis action cannot be undone.';
|
|
419
|
+
SmartCodeModal.confirm({
|
|
420
|
+
title: 'Delete Folder',
|
|
421
|
+
message: msg,
|
|
422
|
+
danger: true,
|
|
423
|
+
onConfirm: function() {
|
|
424
|
+
fetch(bUrl('/rmdir'), {
|
|
425
|
+
method: 'POST',
|
|
426
|
+
headers: { 'Content-Type': 'application/json' },
|
|
427
|
+
body: JSON.stringify({ folder: folderName }),
|
|
428
|
+
}).then(function() {
|
|
429
|
+
if (currentFile.startsWith(folderName + '/')) {
|
|
430
|
+
setFile('');
|
|
431
|
+
document.getElementById('currentFileName').textContent = '';
|
|
432
|
+
document.getElementById('editor').value = '';
|
|
433
|
+
lastContent = '';
|
|
434
|
+
}
|
|
435
|
+
collapsedFolders.delete(folderName);
|
|
436
|
+
saveCollapsed();
|
|
437
|
+
refreshFileList();
|
|
438
|
+
if (window.toast) toast('Folder deleted');
|
|
439
|
+
}).catch(function() {
|
|
440
|
+
if (window.toast) toast('Error deleting folder');
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ── Public API ──
|
|
447
|
+
window.SmartCodeFileTree = {
|
|
448
|
+
refreshFileList: refreshFileList,
|
|
449
|
+
loadFile: loadFile,
|
|
450
|
+
syncFile: syncFile,
|
|
451
|
+
getCurrentFile: function() { return currentFile; },
|
|
452
|
+
setCurrentFile: function(p) { setFile(p); },
|
|
453
|
+
getLastContent: function() { return lastContent; },
|
|
454
|
+
setLastContent: function(v) { lastContent = v; },
|
|
455
|
+
createNewFile: createNewFile,
|
|
456
|
+
createNewFolder: createNewFolder,
|
|
457
|
+
saveCurrentFile: saveCurrentFile,
|
|
458
|
+
deleteFile: deleteFile,
|
|
459
|
+
renameFile: renameFile,
|
|
460
|
+
renameFolder: renameFolder,
|
|
461
|
+
deleteFolder: deleteFolder,
|
|
462
|
+
prettyName: prettyName,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Backward compat
|
|
466
|
+
window.refreshFileList = refreshFileList;
|
|
467
|
+
window.saveCurrentFile = saveCurrentFile;
|
|
468
|
+
window.createNewFile = createNewFile;
|
|
469
|
+
window.createNewFolder = createNewFolder;
|
|
470
|
+
})();
|