@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,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCode App Init -- bootstrap, WebSocket, keyboard shortcuts, module init.
|
|
3
|
+
* Last script loaded -- wires everything together.
|
|
4
|
+
*/
|
|
5
|
+
(function() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// ── CSS Token Reader ──
|
|
9
|
+
function getToken(name) {
|
|
10
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
11
|
+
}
|
|
12
|
+
window.getToken = getToken;
|
|
13
|
+
|
|
14
|
+
// ── Renderer type from query params ──
|
|
15
|
+
var params = new URLSearchParams(window.location.search);
|
|
16
|
+
var paramRenderer = params.get('renderer'); // null if not set
|
|
17
|
+
var effectiveRendererType = paramRenderer || 'mermaid'; // updated dynamically
|
|
18
|
+
|
|
19
|
+
// ── Auto-select renderer based on diagram type ──
|
|
20
|
+
function selectRendererType(diagramType) {
|
|
21
|
+
if (paramRenderer) return paramRenderer;
|
|
22
|
+
if (diagramType === 'flowchart' || diagramType === 'graph') return 'custom';
|
|
23
|
+
return 'mermaid';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Dynamic renderer indicator in status bar ──
|
|
27
|
+
function updateRendererIndicator() {
|
|
28
|
+
var existing = document.querySelector('.renderer-indicator');
|
|
29
|
+
if (existing) existing.remove();
|
|
30
|
+
if (effectiveRendererType === 'custom') {
|
|
31
|
+
var indicator = document.createElement('span');
|
|
32
|
+
indicator.className = 'renderer-indicator';
|
|
33
|
+
indicator.style.cssText = 'font-size:10px;color:#3b82f6;margin-left:8px;font-weight:600;';
|
|
34
|
+
indicator.textContent = 'CUSTOM';
|
|
35
|
+
var statusEl = document.querySelector('.topbar .status');
|
|
36
|
+
if (statusEl) statusEl.appendChild(indicator);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Detect diagram type from mermaid source ──
|
|
41
|
+
function detectDiagramType(text) {
|
|
42
|
+
if (!text) return null;
|
|
43
|
+
var first = text.trim().split(/\s/)[0].toLowerCase();
|
|
44
|
+
if (first === 'flowchart' || first === 'graph') return first;
|
|
45
|
+
return first;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Render with type (custom or mermaid) ──
|
|
49
|
+
async function renderWithType(text) {
|
|
50
|
+
var diagramType = detectDiagramType(text);
|
|
51
|
+
if (diagramType) {
|
|
52
|
+
effectiveRendererType = selectRendererType(diagramType);
|
|
53
|
+
updateRendererIndicator();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (effectiveRendererType === 'custom') {
|
|
57
|
+
try {
|
|
58
|
+
var currentFile = SmartCodeFileTree.getCurrentFile();
|
|
59
|
+
await SmartCodeCustomRenderer.fetchAndRender(currentFile);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.warn('Custom renderer failed, falling back to Mermaid:', e.message);
|
|
62
|
+
await SmartCodeRenderer.render(text);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
await SmartCodeRenderer.render(text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
window.render = renderWithType;
|
|
70
|
+
|
|
71
|
+
// ── Toast ──
|
|
72
|
+
function toast(msg) {
|
|
73
|
+
var el = document.getElementById('toast');
|
|
74
|
+
if (!el) return;
|
|
75
|
+
el.textContent = msg;
|
|
76
|
+
el.classList.add('show');
|
|
77
|
+
setTimeout(function() { el.classList.remove('show'); }, 2000);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Help ──
|
|
81
|
+
function showHelp() {
|
|
82
|
+
document.getElementById('helpOverlay').classList.toggle('show');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Typing context detection (QUAL-02) ──
|
|
86
|
+
// Returns true when the active element is an input field, textarea, select,
|
|
87
|
+
// or contenteditable -- single-key shortcuts must not fire in these contexts.
|
|
88
|
+
function isTypingContext(target) {
|
|
89
|
+
if (!target) return false;
|
|
90
|
+
var tag = target.tagName;
|
|
91
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
|
|
92
|
+
if (target.isContentEditable) return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Keyboard shortcuts ──
|
|
97
|
+
document.addEventListener('keydown', function(e) {
|
|
98
|
+
var target = e.target;
|
|
99
|
+
var editor = document.getElementById('editor');
|
|
100
|
+
if (target === editor) return;
|
|
101
|
+
|
|
102
|
+
// Modifier shortcuts (Ctrl/Cmd+key) always work, even in typing contexts
|
|
103
|
+
var hasModifier = e.ctrlKey || e.metaKey;
|
|
104
|
+
|
|
105
|
+
// Escape always works (close overlays, exit modes)
|
|
106
|
+
if (e.key === 'Escape') {
|
|
107
|
+
SmartCodeAnnotations.closePopover(); MmdEditor.closeEditorPopover(); MmdEditor.setMode(null); SmartCodeSearch.close();
|
|
108
|
+
if (window.SmartCodeSelection) SmartCodeSelection.deselectAll();
|
|
109
|
+
if (window.SmartCodeContextMenu) SmartCodeContextMenu.close();
|
|
110
|
+
if (window.SmartCodeInlineEdit) SmartCodeInlineEdit.cancel();
|
|
111
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.getState() !== 'idle') {
|
|
112
|
+
SmartCodeInteraction.forceState('idle');
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Block single-key shortcuts when typing in inputs/textareas/contenteditable
|
|
118
|
+
var typing = isTypingContext(target);
|
|
119
|
+
|
|
120
|
+
// Modifier shortcuts work regardless of typing context
|
|
121
|
+
if (e.key === 'f' && hasModifier) { e.preventDefault(); SmartCodeSearch.open(); return; }
|
|
122
|
+
if ((e.key === 'z' || e.key === 'Z') && hasModifier && e.shiftKey) { e.preventDefault(); MmdEditor.redo(); return; }
|
|
123
|
+
if (e.key === 'y' && hasModifier) { e.preventDefault(); MmdEditor.redo(); return; }
|
|
124
|
+
if (e.key === 'z' && hasModifier && !e.shiftKey) { e.preventDefault(); MmdEditor.undo(); return; }
|
|
125
|
+
if (e.key === 'e' && hasModifier) {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
document.getElementById('toggleEditor').click();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (e.key === 'b' && hasModifier) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
document.getElementById('toggleSidebar').click();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if ((e.key === '=' || e.key === '+') && hasModifier) { e.preventDefault(); zoomIn(); return; }
|
|
136
|
+
if (e.key === '-' && hasModifier) { e.preventDefault(); zoomOut(); return; }
|
|
137
|
+
if (e.key === '0' && hasModifier) { e.preventDefault(); zoomFit(); return; }
|
|
138
|
+
if (e.key === 'c' && hasModifier && !e.shiftKey) {
|
|
139
|
+
if (window.SmartCodeClipboard && SmartCodeClipboard.copy()) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
if (window.toast) toast('Node copied');
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (e.key === 'v' && hasModifier && !e.shiftKey) {
|
|
146
|
+
if (window.SmartCodeClipboard && SmartCodeClipboard.hasContent()) {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
SmartCodeClipboard.paste();
|
|
149
|
+
if (window.toast) toast('Node pasted');
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (e.key === 'd' && hasModifier) {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
if (window.SmartCodeClipboard && SmartCodeClipboard.duplicate()) {
|
|
156
|
+
if (window.toast) toast('Node duplicated');
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Everything below is single-key shortcuts -- block when typing
|
|
162
|
+
if (typing) return;
|
|
163
|
+
|
|
164
|
+
if (target.closest('.flag-popover')) return;
|
|
165
|
+
if (target.closest('.search-bar')) return;
|
|
166
|
+
|
|
167
|
+
if (e.key === 'f') {
|
|
168
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.isBlocking()) return;
|
|
169
|
+
SmartCodeAnnotations.toggleFlagMode();
|
|
170
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.forceState(SmartCodeAnnotations.getState().flagMode ? 'flagging' : 'idle');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (e.key === 'n') {
|
|
174
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.isBlocking()) return;
|
|
175
|
+
MmdEditor.toggleAddNode();
|
|
176
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.forceState(MmdEditor.getState().mode === 'addNode' ? 'add-node' : 'idle');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (e.key === 'a') {
|
|
180
|
+
if (window.SmartCodeInteraction && SmartCodeInteraction.isBlocking()) return;
|
|
181
|
+
MmdEditor.toggleAddEdge();
|
|
182
|
+
if (window.SmartCodeInteraction) SmartCodeInteraction.forceState(MmdEditor.getState().mode === 'addEdge' ? 'add-edge' : 'idle');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (e.key === '?') { showHelp(); return; }
|
|
186
|
+
if (e.key === ' ' && window.SmartCodeSessionPlayer && SmartCodeSessionPlayer.isVisible()) {
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
SmartCodeSessionPlayer.isPlaying() ? SmartCodeSessionPlayer.pause() : SmartCodeSessionPlayer.play();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (e.key === 'ArrowLeft' && window.SmartCodeSessionPlayer && SmartCodeSessionPlayer.isVisible()) {
|
|
192
|
+
e.preventDefault(); SmartCodeSessionPlayer.seekTo(SmartCodeSessionPlayer.getIndex() - 1); return;
|
|
193
|
+
}
|
|
194
|
+
if (e.key === 'ArrowRight' && window.SmartCodeSessionPlayer && SmartCodeSessionPlayer.isVisible()) {
|
|
195
|
+
e.preventDefault(); SmartCodeSessionPlayer.seekTo(SmartCodeSessionPlayer.getIndex() + 1); return;
|
|
196
|
+
}
|
|
197
|
+
if (e.key === 'g' && !e.altKey) {
|
|
198
|
+
if (window.SmartCodeGhostPaths) SmartCodeGhostPaths.toggle();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (e.key === 'h' && !e.altKey) {
|
|
202
|
+
if (window.SmartCodeHeatmap) SmartCodeHeatmap.toggle();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (e.key === 'b' && !e.altKey) {
|
|
206
|
+
if (window.SmartCodeBreakpoints && window.SmartCodeSelection) {
|
|
207
|
+
var sel = SmartCodeSelection.getSelected();
|
|
208
|
+
if (sel && sel.type === 'node') {
|
|
209
|
+
SmartCodeBreakpoints.toggleBreakpoint(sel.id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
document.addEventListener('keydown', function(e) {
|
|
217
|
+
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
saveCurrentFile();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ── Init Hooks for annotations, editor, search, collapse ──
|
|
224
|
+
var _initHooks = {
|
|
225
|
+
getEditor: function() { return document.getElementById('editor'); },
|
|
226
|
+
getCurrentFile: function() { return SmartCodeFileTree.getCurrentFile(); },
|
|
227
|
+
getLastContent: function() { return SmartCodeFileTree.getLastContent(); },
|
|
228
|
+
setLastContent: function(v) { SmartCodeFileTree.setLastContent(v); },
|
|
229
|
+
saveFile: function() { SmartCodeFileTree.saveCurrentFile(); },
|
|
230
|
+
renderDiagram: renderWithType,
|
|
231
|
+
getPan: function() { return SmartCodePanZoom.getPan(); },
|
|
232
|
+
setPan: function(px, py) { SmartCodePanZoom.setPan(px, py); },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// ── Set sidebar action button icons (safe: SmartCodeIcons are static SVG strings) ──
|
|
236
|
+
var _nf = document.getElementById('btnNewFolder');
|
|
237
|
+
if (_nf) _nf.innerHTML = SmartCodeIcons.folder;
|
|
238
|
+
var _nd = document.getElementById('btnNewFile');
|
|
239
|
+
if (_nd) _nd.innerHTML = SmartCodeIcons.file;
|
|
240
|
+
var _sv = document.getElementById('btnSaveFile');
|
|
241
|
+
if (_sv) _sv.innerHTML = SmartCodeIcons.save;
|
|
242
|
+
|
|
243
|
+
// ── Inject toolbar icons from data-icon attributes ──
|
|
244
|
+
// Safe: SmartCodeIcons contains only static SVG strings from icons.js (trusted source)
|
|
245
|
+
document.querySelectorAll('.toolbar-icon[data-icon]').forEach(function(span) {
|
|
246
|
+
var iconName = span.getAttribute('data-icon');
|
|
247
|
+
if (iconName && SmartCodeIcons[iconName]) {
|
|
248
|
+
span.innerHTML = SmartCodeIcons[iconName];
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
SmartCodeAnnotations.init(_initHooks);
|
|
253
|
+
MmdEditor.init(_initHooks);
|
|
254
|
+
SmartCodeSearch.init(_initHooks);
|
|
255
|
+
|
|
256
|
+
// ── Init Phase 13: Canvas Interaction Modules ──
|
|
257
|
+
if (window.SmartCodeSelection) SmartCodeSelection.init();
|
|
258
|
+
if (window.SmartCodeNodeDrag) SmartCodeNodeDrag.init();
|
|
259
|
+
if (window.SmartCodeContextMenu) SmartCodeContextMenu.init();
|
|
260
|
+
if (window.SmartCodeInlineEdit) SmartCodeInlineEdit.init();
|
|
261
|
+
|
|
262
|
+
// ── Init Phase 15: Breakpoints & Ghost Paths ──
|
|
263
|
+
if (window.SmartCodeBreakpoints) SmartCodeBreakpoints.init();
|
|
264
|
+
if (window.SmartCodeGhostPaths) SmartCodeGhostPaths.init();
|
|
265
|
+
|
|
266
|
+
// ── Init Phase 16: Heatmap & Session Player ──
|
|
267
|
+
if (window.SmartCodeHeatmap) SmartCodeHeatmap.init();
|
|
268
|
+
if (window.SmartCodeInteractionTracker) SmartCodeInteractionTracker.init();
|
|
269
|
+
if (window.SmartCodeSessionPlayer) SmartCodeSessionPlayer.init();
|
|
270
|
+
|
|
271
|
+
// ── Init MCP Sessions view ──
|
|
272
|
+
if (window.SmartCodeMcpSessions) SmartCodeMcpSessions.init();
|
|
273
|
+
|
|
274
|
+
// ── Init Collapse UI ──
|
|
275
|
+
if (window.SmartCodeCollapseUI) {
|
|
276
|
+
SmartCodeCollapseUI.init({
|
|
277
|
+
onToggle: async function(collapsedIds) {
|
|
278
|
+
try {
|
|
279
|
+
var toggleParams = new URLSearchParams();
|
|
280
|
+
if (collapsedIds.length > 0) {
|
|
281
|
+
toggleParams.set('collapsed', JSON.stringify(collapsedIds));
|
|
282
|
+
}
|
|
283
|
+
var currentFile = SmartCodeFileTree.getCurrentFile();
|
|
284
|
+
var url = baseUrl('/api/diagrams/' + encodeURIComponent(currentFile) + '?' + toggleParams.toString());
|
|
285
|
+
var resp = await fetch(url);
|
|
286
|
+
if (!resp.ok) return;
|
|
287
|
+
var data = await resp.json();
|
|
288
|
+
if (data.collapse) {
|
|
289
|
+
SmartCodeCollapseUI.setConfig(data.collapse.config);
|
|
290
|
+
SmartCodeCollapseUI.setAutoCollapsed(data.collapse.autoCollapsed || []);
|
|
291
|
+
}
|
|
292
|
+
if (data.mermaidContent) {
|
|
293
|
+
await renderWithType(data.mermaidContent);
|
|
294
|
+
}
|
|
295
|
+
} catch (e) { console.warn('[SmartCode] Collapse toggle error:', e); }
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
SmartCodeCollapseUI.initFocusMode({
|
|
300
|
+
onFocusChange: async function(event) {
|
|
301
|
+
try {
|
|
302
|
+
var currentFile = SmartCodeFileTree.getCurrentFile();
|
|
303
|
+
if (event.action === 'focus') {
|
|
304
|
+
var focusParams = new URLSearchParams({ focus: event.nodeId });
|
|
305
|
+
var collapsed = SmartCodeCollapseUI.getCollapsed();
|
|
306
|
+
if (collapsed.length > 0) {
|
|
307
|
+
focusParams.set('collapsed', JSON.stringify(collapsed));
|
|
308
|
+
}
|
|
309
|
+
var resp = await fetch(baseUrl('/api/diagrams/' + encodeURIComponent(currentFile) + '?' + focusParams.toString()));
|
|
310
|
+
if (!resp.ok) return;
|
|
311
|
+
var data = await resp.json();
|
|
312
|
+
if (data.collapse) {
|
|
313
|
+
SmartCodeCollapseUI.setBreadcrumbs(data.collapse.breadcrumbs, data.collapse.focusedSubgraph);
|
|
314
|
+
SmartCodeCollapseUI.setAutoCollapsed(data.collapse.autoCollapsed || []);
|
|
315
|
+
if (data.collapse.manualCollapsed) {
|
|
316
|
+
SmartCodeCollapseUI.setCollapsed(data.collapse.manualCollapsed);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (data.mermaidContent) {
|
|
320
|
+
await renderWithType(data.mermaidContent);
|
|
321
|
+
document.getElementById('preview').classList.add('diagram-focus-mode');
|
|
322
|
+
}
|
|
323
|
+
} else if (event.action === 'navigate') {
|
|
324
|
+
var navParams = new URLSearchParams({ breadcrumb: event.breadcrumbId });
|
|
325
|
+
var navResp = await fetch(baseUrl('/api/diagrams/' + encodeURIComponent(currentFile) + '?' + navParams.toString()));
|
|
326
|
+
if (!navResp.ok) return;
|
|
327
|
+
var navData = await navResp.json();
|
|
328
|
+
if (navData.collapse) {
|
|
329
|
+
SmartCodeCollapseUI.setBreadcrumbs(navData.collapse.breadcrumbs, navData.collapse.focusedSubgraph);
|
|
330
|
+
SmartCodeCollapseUI.setAutoCollapsed(navData.collapse.autoCollapsed || []);
|
|
331
|
+
}
|
|
332
|
+
if (navData.mermaidContent) {
|
|
333
|
+
await renderWithType(navData.mermaidContent);
|
|
334
|
+
}
|
|
335
|
+
} else if (event.action === 'exit') {
|
|
336
|
+
var exitResp = await fetch(baseUrl('/api/diagrams/' + encodeURIComponent(currentFile)));
|
|
337
|
+
if (!exitResp.ok) return;
|
|
338
|
+
var exitData = await exitResp.json();
|
|
339
|
+
SmartCodeCollapseUI.setBreadcrumbs([], null);
|
|
340
|
+
SmartCodeCollapseUI.setAutoCollapsed(exitData.collapse ? exitData.collapse.autoCollapsed || [] : []);
|
|
341
|
+
if (exitData.mermaidContent) {
|
|
342
|
+
await renderWithType(exitData.mermaidContent);
|
|
343
|
+
document.getElementById('preview').classList.remove('diagram-focus-mode');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch (e) { console.warn('[SmartCode] Focus mode error:', e); }
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Helper: resolve URL with workspace base ──
|
|
352
|
+
function baseUrl(path) {
|
|
353
|
+
return (window.SmartCodeBaseUrl || '') + path;
|
|
354
|
+
}
|
|
355
|
+
window.SmartCodeUrl = baseUrl;
|
|
356
|
+
|
|
357
|
+
// ── WebSocket context for ws-handler ──
|
|
358
|
+
var wsCtx = {
|
|
359
|
+
getRendererType: function() { return effectiveRendererType; },
|
|
360
|
+
setRendererType: function(type) { effectiveRendererType = type; },
|
|
361
|
+
selectRendererType: selectRendererType,
|
|
362
|
+
updateRendererIndicator: updateRendererIndicator,
|
|
363
|
+
renderWithType: renderWithType,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
function buildWsUrl(baseUrlStr) {
|
|
367
|
+
var wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
368
|
+
if (baseUrlStr) {
|
|
369
|
+
var host = baseUrlStr.replace(/^https?:\/\//, '');
|
|
370
|
+
return wsProtocol + '//' + host + '/ws';
|
|
371
|
+
}
|
|
372
|
+
return wsProtocol + '//' + location.host + '/ws';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Active WS connection ──
|
|
376
|
+
var activeWs = null;
|
|
377
|
+
|
|
378
|
+
function reconnectWebSocket(newBaseUrl) {
|
|
379
|
+
if (activeWs) activeWs.close();
|
|
380
|
+
var wsUrl = buildWsUrl(newBaseUrl);
|
|
381
|
+
activeWs = createReconnectingWebSocket(wsUrl,
|
|
382
|
+
function(msg) { SmartCodeWsHandler.handleMessage(msg, wsCtx); },
|
|
383
|
+
function(status) { SmartCodeWsHandler.handleStatus(status); }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
window.SmartCodeWsReconnect = reconnectWebSocket;
|
|
388
|
+
|
|
389
|
+
// ── Bootstrap: load initial file, connect WebSocket ──
|
|
390
|
+
(async function() {
|
|
391
|
+
var hint = document.getElementById('fitHint');
|
|
392
|
+
hint.classList.add('show');
|
|
393
|
+
setTimeout(function() { hint.classList.remove('show'); }, 4000);
|
|
394
|
+
|
|
395
|
+
var currentFile = SmartCodeFileTree.getCurrentFile();
|
|
396
|
+
var editor = document.getElementById('editor');
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
var resp = await fetch(baseUrl('/' + currentFile));
|
|
400
|
+
if (resp.ok) {
|
|
401
|
+
var text = await resp.text();
|
|
402
|
+
editor.value = text;
|
|
403
|
+
SmartCodeFileTree.setLastContent(text);
|
|
404
|
+
await renderWithType(text);
|
|
405
|
+
}
|
|
406
|
+
} catch (e) { console.warn('[SmartCode] Initial file load error:', e); }
|
|
407
|
+
|
|
408
|
+
if (!SmartCodeFileTree.getLastContent() && editor.value.trim()) {
|
|
409
|
+
await renderWithType(editor.value);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (currentFile) {
|
|
413
|
+
try {
|
|
414
|
+
var apiResp = await fetch(baseUrl('/api/diagrams/' + encodeURIComponent(currentFile)));
|
|
415
|
+
if (apiResp.ok) {
|
|
416
|
+
var data = await apiResp.json();
|
|
417
|
+
if (data.validation && data.validation.diagramType) {
|
|
418
|
+
effectiveRendererType = selectRendererType(data.validation.diagramType);
|
|
419
|
+
if (effectiveRendererType === 'custom') {
|
|
420
|
+
await SmartCodeCustomRenderer.fetchAndRender(currentFile);
|
|
421
|
+
}
|
|
422
|
+
updateRendererIndicator();
|
|
423
|
+
}
|
|
424
|
+
if (window.SmartCodeCollapseUI && data.collapse) {
|
|
425
|
+
SmartCodeCollapseUI.setConfig(data.collapse.config);
|
|
426
|
+
if (data.collapse.autoCollapsed && data.collapse.autoCollapsed.length > 0) {
|
|
427
|
+
SmartCodeCollapseUI.setAutoCollapsed(data.collapse.autoCollapsed);
|
|
428
|
+
if (data.mermaidContent) await renderWithType(data.mermaidContent);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} catch (e) { /* keep Mermaid as fallback */ }
|
|
433
|
+
|
|
434
|
+
if (window.SmartCodeGhostPaths) {
|
|
435
|
+
try {
|
|
436
|
+
var gpResp = await fetch(baseUrl('/api/ghost-paths/' + encodeURIComponent(currentFile)));
|
|
437
|
+
if (gpResp.ok) {
|
|
438
|
+
var gpData = await gpResp.json();
|
|
439
|
+
SmartCodeGhostPaths.updateGhostPaths(currentFile, gpData.ghostPaths || []);
|
|
440
|
+
}
|
|
441
|
+
} catch (e) {}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (window.SmartCodeHeatmap) {
|
|
445
|
+
fetch(baseUrl('/api/heatmap/' + encodeURIComponent(currentFile)))
|
|
446
|
+
.then(function(r) { return r.ok ? r.json() : null; })
|
|
447
|
+
.then(function(data) { if (data) SmartCodeHeatmap.updateVisitCounts(data); })
|
|
448
|
+
.catch(function() {});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (window.SmartCodeSessionPlayer) SmartCodeSessionPlayer.fetchSessionList(currentFile);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// WebSocket real-time sync
|
|
455
|
+
var wsUrl = buildWsUrl(window.SmartCodeBaseUrl);
|
|
456
|
+
activeWs = createReconnectingWebSocket(wsUrl,
|
|
457
|
+
function(msg) { SmartCodeWsHandler.handleMessage(msg, wsCtx); },
|
|
458
|
+
function(status) { SmartCodeWsHandler.handleStatus(status); }
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
updateRendererIndicator();
|
|
462
|
+
})();
|
|
463
|
+
|
|
464
|
+
SmartCodeFileTree.refreshFileList();
|
|
465
|
+
|
|
466
|
+
var resizeTimer = null;
|
|
467
|
+
window.addEventListener('resize', function() {
|
|
468
|
+
if (resizeTimer) clearTimeout(resizeTimer);
|
|
469
|
+
resizeTimer = setTimeout(function() { zoomFit(); }, 150);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// ── Drag & Drop .mmd files ──
|
|
473
|
+
document.addEventListener('dragover', function(e) { e.preventDefault(); });
|
|
474
|
+
document.addEventListener('drop', async function(e) {
|
|
475
|
+
e.preventDefault();
|
|
476
|
+
var file = e.dataTransfer.files[0];
|
|
477
|
+
if (!file || !file.name.endsWith('.mmd')) { toast('Only .mmd files'); return; }
|
|
478
|
+
var text = await file.text();
|
|
479
|
+
var editor = document.getElementById('editor');
|
|
480
|
+
editor.value = text;
|
|
481
|
+
SmartCodeFileTree.setLastContent(text);
|
|
482
|
+
SmartCodeFileTree.setCurrentFile(file.name);
|
|
483
|
+
document.getElementById('currentFileName').textContent = file.name;
|
|
484
|
+
SmartCodeFileTree.refreshFileList();
|
|
485
|
+
renderWithType(text);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// ── Public API ──
|
|
489
|
+
window.SmartCodeApp = {
|
|
490
|
+
toast: toast,
|
|
491
|
+
showHelp: showHelp,
|
|
492
|
+
get rendererType() { return effectiveRendererType; },
|
|
493
|
+
getRendererType: function() { return effectiveRendererType; },
|
|
494
|
+
};
|
|
495
|
+
window.toast = toast;
|
|
496
|
+
window.showHelp = showHelp;
|
|
497
|
+
})();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════
|
|
2
|
+
SmartCode — Breakpoint Indicators & Notification Bar
|
|
3
|
+
═══════════════════════════════════════════════ */
|
|
4
|
+
|
|
5
|
+
/* Breakpoint indicator — red circle on left edge of node */
|
|
6
|
+
.breakpoint-indicator {
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
filter: drop-shadow(0 0 3px rgba(239, 68, 68, 0.6));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Notification bar — fixed top of preview container */
|
|
12
|
+
.breakpoint-notification {
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 0; left: 0; right: 0;
|
|
15
|
+
z-index: 100;
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: 12px;
|
|
19
|
+
padding: 10px 16px;
|
|
20
|
+
background: var(--surface-1);
|
|
21
|
+
border-bottom: 2px solid var(--status-problem);
|
|
22
|
+
color: var(--text-primary);
|
|
23
|
+
font-family: 'Inter', sans-serif;
|
|
24
|
+
font-size: 13px;
|
|
25
|
+
animation: slideDown 0.2s ease-out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes slideDown {
|
|
29
|
+
from { transform: translateY(-100%); opacity: 0; }
|
|
30
|
+
to { transform: translateY(0); opacity: 1; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.breakpoint-notification .bp-node-name {
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
color: var(--status-problem);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.btn-breakpoint-action {
|
|
39
|
+
padding: 4px 12px;
|
|
40
|
+
border-radius: 4px;
|
|
41
|
+
border: 1px solid var(--border-default);
|
|
42
|
+
background: var(--surface-1);
|
|
43
|
+
color: var(--text-primary);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
font-size: 12px;
|
|
46
|
+
font-family: 'Inter', sans-serif;
|
|
47
|
+
transition: background 0.15s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.btn-breakpoint-action:hover {
|
|
51
|
+
background: var(--surface-3);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.btn-breakpoint-action.primary {
|
|
55
|
+
background: var(--status-ok);
|
|
56
|
+
color: #000;
|
|
57
|
+
border-color: var(--status-ok);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.btn-breakpoint-action.primary:hover {
|
|
61
|
+
background: var(--status-ok-stroke);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Ghost Paths Toggle Button ── */
|
|
65
|
+
#btnGhostPaths.active {
|
|
66
|
+
background: rgba(113, 113, 122, 0.2) !important;
|
|
67
|
+
color: var(--status-neutral) !important;
|
|
68
|
+
border-color: rgba(113, 113, 122, 0.3) !important;
|
|
69
|
+
}
|