@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,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCode MCP Sessions -- sidebar view showing AI sessions and their diagrams.
|
|
3
|
+
* Fetches data from GET /api/mcp-sessions and renders grouped by session.
|
|
4
|
+
* Supports renaming sessions via PATCH /api/mcp-sessions/:id.
|
|
5
|
+
*
|
|
6
|
+
* Dependencies: file-tree.js (SmartCodeFileTree), renderer.js (SmartCodeRenderer), modal.js (SmartCodeModal)
|
|
7
|
+
*
|
|
8
|
+
* Note: innerHTML usage is safe here -- all dynamic values pass through
|
|
9
|
+
* SmartCodeRenderer.escapeHtml() before interpolation, preventing XSS.
|
|
10
|
+
* This follows the same pattern established in file-tree.js.
|
|
11
|
+
*/
|
|
12
|
+
(function() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
var viewMode = 'files'; // 'files' | 'sessions'
|
|
16
|
+
var sessionsData = [];
|
|
17
|
+
|
|
18
|
+
function bUrl(path) { return (window.SmartCodeBaseUrl || '') + path; }
|
|
19
|
+
|
|
20
|
+
function escapeHtml(str) {
|
|
21
|
+
return SmartCodeRenderer.escapeHtml(str);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function prettyName(fname) {
|
|
25
|
+
var base = fname.includes('/') ? fname.split('/').pop() : fname;
|
|
26
|
+
return base.replace('.mmd', '').replace(/-/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function timeAgo(ts) {
|
|
30
|
+
var diff = Date.now() - ts;
|
|
31
|
+
var secs = Math.floor(diff / 1000);
|
|
32
|
+
if (secs < 60) return secs + 's ago';
|
|
33
|
+
var mins = Math.floor(secs / 60);
|
|
34
|
+
if (mins < 60) return mins + 'min ago';
|
|
35
|
+
var hours = Math.floor(mins / 60);
|
|
36
|
+
if (hours < 24) return hours + 'h ago';
|
|
37
|
+
return Math.floor(hours / 24) + 'd ago';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Fetch sessions from API ──
|
|
41
|
+
|
|
42
|
+
function fetchSessions() {
|
|
43
|
+
return fetch(bUrl('/api/mcp-sessions'))
|
|
44
|
+
.then(function(r) { return r.ok ? r.json() : { sessions: [] }; })
|
|
45
|
+
.then(function(data) {
|
|
46
|
+
sessionsData = data.sessions || [];
|
|
47
|
+
if (viewMode === 'sessions') renderSessionsView();
|
|
48
|
+
return sessionsData;
|
|
49
|
+
})
|
|
50
|
+
.catch(function() {
|
|
51
|
+
sessionsData = [];
|
|
52
|
+
if (viewMode === 'sessions') renderSessionsView();
|
|
53
|
+
return [];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Rename session via API ──
|
|
58
|
+
|
|
59
|
+
function renameSession(sessionId, newLabel) {
|
|
60
|
+
return fetch(bUrl('/api/mcp-sessions/' + encodeURIComponent(sessionId)), {
|
|
61
|
+
method: 'PATCH',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ label: newLabel }),
|
|
64
|
+
})
|
|
65
|
+
.then(function(r) { return r.json(); })
|
|
66
|
+
.then(function() { return fetchSessions(); });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Render sessions view ──
|
|
70
|
+
// Safe: all dynamic values are escaped via escapeHtml before interpolation (same as file-tree.js)
|
|
71
|
+
|
|
72
|
+
function renderSessionsView() {
|
|
73
|
+
var container = document.getElementById('fileTree');
|
|
74
|
+
if (!container) return;
|
|
75
|
+
|
|
76
|
+
if (sessionsData.length === 0) {
|
|
77
|
+
container.innerHTML =
|
|
78
|
+
'<div class="mcp-sessions-empty">' +
|
|
79
|
+
'<div class="mcp-sessions-empty-icon">' + (window.SmartCodeIcons ? SmartCodeIcons.eye : '') + '</div>' +
|
|
80
|
+
'<div>No active AI sessions</div>' +
|
|
81
|
+
'<div style="font-size:11px;margin-top:4px;color:var(--text-tertiary)">Start an MCP session with Claude to see diagrams grouped here</div>' +
|
|
82
|
+
'</div>';
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Sort sessions by startedAt desc (most recent first)
|
|
87
|
+
var sorted = sessionsData.slice().sort(function(a, b) { return b.startedAt - a.startedAt; });
|
|
88
|
+
|
|
89
|
+
var html = '';
|
|
90
|
+
sorted.forEach(function(session) {
|
|
91
|
+
var shortId = session.sessionId.substring(0, 8);
|
|
92
|
+
var ago = timeAgo(session.startedAt);
|
|
93
|
+
var diagrams = session.diagrams || [];
|
|
94
|
+
|
|
95
|
+
html += '<div class="mcp-session-card">';
|
|
96
|
+
html += '<div class="mcp-session-header">';
|
|
97
|
+
html += '<span class="mcp-session-dot active"></span>';
|
|
98
|
+
html += '<span class="mcp-session-label">' + escapeHtml(session.label || 'Session ' + shortId) + '</span>';
|
|
99
|
+
html += '<button class="mcp-session-rename-btn" data-action="rename-session" data-session-id="' + escapeHtml(session.sessionId) + '" data-current-label="' + escapeHtml(session.label || '') + '" title="Rename session">';
|
|
100
|
+
html += '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.828 2.828 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>';
|
|
101
|
+
html += '</button>';
|
|
102
|
+
html += '<span class="mcp-session-time">' + escapeHtml(ago) + '</span>';
|
|
103
|
+
html += '</div>';
|
|
104
|
+
|
|
105
|
+
if (diagrams.length === 0) {
|
|
106
|
+
html += '<div class="mcp-session-nofiles">No diagrams yet</div>';
|
|
107
|
+
} else {
|
|
108
|
+
diagrams.forEach(function(d) {
|
|
109
|
+
var isCurrentFile = d.filePath === (window.SmartCodeFileTree ? SmartCodeFileTree.getCurrentFile() : '');
|
|
110
|
+
html += '<div class="mcp-session-file ' + (isCurrentFile ? 'active' : '') + '" data-action="load-session-file" data-path="' + escapeHtml(d.filePath) + '">';
|
|
111
|
+
html += '<span class="mcp-session-file-icon">' + (window.SmartCodeIcons ? SmartCodeIcons.file : '') + '</span>';
|
|
112
|
+
html += '<span class="mcp-session-file-name">' + escapeHtml(prettyName(d.filePath)) + '</span>';
|
|
113
|
+
html += '</div>';
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
html += '</div>';
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Safe: all dynamic values pass through escapeHtml() above
|
|
120
|
+
container.innerHTML = html;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── View mode switching ──
|
|
124
|
+
|
|
125
|
+
function setViewMode(mode) {
|
|
126
|
+
viewMode = mode;
|
|
127
|
+
|
|
128
|
+
// Update tab styling
|
|
129
|
+
var tabFiles = document.getElementById('tabFiles');
|
|
130
|
+
var tabSessions = document.getElementById('tabSessions');
|
|
131
|
+
if (tabFiles) tabFiles.classList.toggle('active', mode === 'files');
|
|
132
|
+
if (tabSessions) tabSessions.classList.toggle('active', mode === 'sessions');
|
|
133
|
+
|
|
134
|
+
if (mode === 'sessions') {
|
|
135
|
+
fetchSessions();
|
|
136
|
+
} else {
|
|
137
|
+
SmartCodeFileTree.refreshFileList();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getViewMode() { return viewMode; }
|
|
142
|
+
|
|
143
|
+
// ── Event delegation ──
|
|
144
|
+
|
|
145
|
+
function handleClick(e) {
|
|
146
|
+
// Rename session
|
|
147
|
+
var renameBtn = e.target.closest('[data-action="rename-session"]');
|
|
148
|
+
if (renameBtn) {
|
|
149
|
+
e.stopPropagation();
|
|
150
|
+
var sessionId = renameBtn.getAttribute('data-session-id');
|
|
151
|
+
var currentLabel = renameBtn.getAttribute('data-current-label');
|
|
152
|
+
if (window.SmartCodeModal) {
|
|
153
|
+
SmartCodeModal.prompt({
|
|
154
|
+
title: 'Rename Session',
|
|
155
|
+
placeholder: 'Session name',
|
|
156
|
+
defaultValue: currentLabel || '',
|
|
157
|
+
onConfirm: function(val) {
|
|
158
|
+
renameSession(sessionId, val);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Load session file
|
|
166
|
+
var target = e.target.closest('[data-action="load-session-file"]');
|
|
167
|
+
if (!target) return;
|
|
168
|
+
var path = target.getAttribute('data-path');
|
|
169
|
+
if (path && window.SmartCodeFileTree) {
|
|
170
|
+
SmartCodeFileTree.loadFile(path);
|
|
171
|
+
// Re-render to update active state
|
|
172
|
+
if (viewMode === 'sessions') {
|
|
173
|
+
setTimeout(renderSessionsView, 50);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Init ──
|
|
179
|
+
|
|
180
|
+
function init() {
|
|
181
|
+
var container = document.getElementById('fileTree');
|
|
182
|
+
if (container) {
|
|
183
|
+
container.addEventListener('click', handleClick);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function refresh() {
|
|
188
|
+
if (viewMode === 'sessions') {
|
|
189
|
+
fetchSessions();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Public API ──
|
|
194
|
+
window.SmartCodeMcpSessions = {
|
|
195
|
+
init: init,
|
|
196
|
+
refresh: refresh,
|
|
197
|
+
fetchSessions: fetchSessions,
|
|
198
|
+
setViewMode: setViewMode,
|
|
199
|
+
getViewMode: getViewMode,
|
|
200
|
+
renderSessionsView: renderSessionsView,
|
|
201
|
+
};
|
|
202
|
+
})();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* SmartCode Modal -- reusable prompt/confirm replacement */
|
|
2
|
+
|
|
3
|
+
.smartcode-modal-backdrop {
|
|
4
|
+
position: fixed;
|
|
5
|
+
inset: 0;
|
|
6
|
+
background: rgba(0, 0, 0, 0.7);
|
|
7
|
+
z-index: 200;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
animation: smartcode-modal-fade 150ms ease;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.smartcode-modal-card {
|
|
15
|
+
background: var(--surface-2);
|
|
16
|
+
border: 1px solid var(--border-default);
|
|
17
|
+
border-radius: var(--radius-xl);
|
|
18
|
+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
19
|
+
max-width: 420px;
|
|
20
|
+
width: 90%;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
animation: smartcode-modal-pop 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.smartcode-modal-title {
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
color: var(--text-primary);
|
|
29
|
+
margin-bottom: 14px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.smartcode-modal-input {
|
|
33
|
+
width: 100%;
|
|
34
|
+
border: 1px solid var(--border-subtle);
|
|
35
|
+
border-radius: 8px;
|
|
36
|
+
background: var(--surface-2);
|
|
37
|
+
color: var(--text-primary);
|
|
38
|
+
font-family: inherit;
|
|
39
|
+
font-size: 12px;
|
|
40
|
+
padding: 7px 10px;
|
|
41
|
+
outline: none;
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.smartcode-modal-input:focus {
|
|
46
|
+
border-color: var(--accent);
|
|
47
|
+
box-shadow: 0 0 0 2px var(--accent-muted);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.smartcode-modal-message {
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
color: var(--text-secondary);
|
|
53
|
+
margin-bottom: 16px;
|
|
54
|
+
line-height: 1.5;
|
|
55
|
+
white-space: pre-line;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.smartcode-modal-actions {
|
|
59
|
+
display: flex;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
justify-content: flex-end;
|
|
62
|
+
margin-top: 16px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.smartcode-modal-btn {
|
|
66
|
+
padding: 6px 14px;
|
|
67
|
+
border-radius: var(--radius-lg);
|
|
68
|
+
border: none;
|
|
69
|
+
font-size: 12px;
|
|
70
|
+
font-weight: 500;
|
|
71
|
+
font-family: inherit;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
transition: background 120ms ease, opacity 120ms ease;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.smartcode-modal-btn--primary {
|
|
77
|
+
background: var(--accent);
|
|
78
|
+
color: #fff;
|
|
79
|
+
}
|
|
80
|
+
.smartcode-modal-btn--primary:hover {
|
|
81
|
+
background: var(--accent-hover);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.smartcode-modal-btn--danger {
|
|
85
|
+
background: var(--status-problem);
|
|
86
|
+
color: #fff;
|
|
87
|
+
}
|
|
88
|
+
.smartcode-modal-btn--danger:hover {
|
|
89
|
+
background: var(--status-problem-stroke);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.smartcode-modal-btn--secondary {
|
|
93
|
+
background: var(--surface-3);
|
|
94
|
+
color: var(--text-secondary);
|
|
95
|
+
}
|
|
96
|
+
.smartcode-modal-btn--secondary:hover {
|
|
97
|
+
background: var(--surface-4);
|
|
98
|
+
color: var(--text-primary);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes smartcode-modal-pop {
|
|
102
|
+
from { transform: scale(0.92); opacity: 0; }
|
|
103
|
+
to { transform: scale(1); opacity: 1; }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@keyframes smartcode-modal-fade {
|
|
107
|
+
from { opacity: 0; }
|
|
108
|
+
to { opacity: 1; }
|
|
109
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartCode Modal -- reusable prompt/confirm replacement.
|
|
3
|
+
* Replaces native prompt() and confirm() with styled modals.
|
|
4
|
+
*
|
|
5
|
+
* Dependencies: modal.css
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* SmartCodeModal.prompt({ title, placeholder, defaultValue, onConfirm });
|
|
9
|
+
* SmartCodeModal.confirm({ title, message, danger, onConfirm });
|
|
10
|
+
* SmartCodeModal.close();
|
|
11
|
+
*/
|
|
12
|
+
(function() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
var backdrop = null;
|
|
16
|
+
var outsideHandler = null;
|
|
17
|
+
|
|
18
|
+
function close() {
|
|
19
|
+
if (outsideHandler) {
|
|
20
|
+
document.removeEventListener('mousedown', outsideHandler);
|
|
21
|
+
outsideHandler = null;
|
|
22
|
+
}
|
|
23
|
+
if (backdrop) {
|
|
24
|
+
backdrop.remove();
|
|
25
|
+
backdrop = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createBackdrop() {
|
|
30
|
+
close();
|
|
31
|
+
var el = document.createElement('div');
|
|
32
|
+
el.className = 'smartcode-modal-backdrop';
|
|
33
|
+
document.body.appendChild(el);
|
|
34
|
+
backdrop = el;
|
|
35
|
+
|
|
36
|
+
// Outside-click dismiss (setTimeout pattern like context-menu.js)
|
|
37
|
+
setTimeout(function() {
|
|
38
|
+
outsideHandler = function(e) {
|
|
39
|
+
if (e.target === backdrop) close();
|
|
40
|
+
};
|
|
41
|
+
document.addEventListener('mousedown', outsideHandler);
|
|
42
|
+
}, 50);
|
|
43
|
+
|
|
44
|
+
return el;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createCard(titleText) {
|
|
48
|
+
var card = document.createElement('div');
|
|
49
|
+
card.className = 'smartcode-modal-card';
|
|
50
|
+
|
|
51
|
+
var title = document.createElement('div');
|
|
52
|
+
title.className = 'smartcode-modal-title';
|
|
53
|
+
title.textContent = titleText || '';
|
|
54
|
+
card.appendChild(title);
|
|
55
|
+
|
|
56
|
+
return card;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createActions() {
|
|
60
|
+
var actions = document.createElement('div');
|
|
61
|
+
actions.className = 'smartcode-modal-actions';
|
|
62
|
+
return actions;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createButton(text, variant) {
|
|
66
|
+
var btn = document.createElement('button');
|
|
67
|
+
btn.className = 'smartcode-modal-btn smartcode-modal-btn--' + variant;
|
|
68
|
+
btn.textContent = text;
|
|
69
|
+
return btn;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Prompt Modal ──
|
|
73
|
+
|
|
74
|
+
function showPrompt(opts) {
|
|
75
|
+
var title = opts.title || 'Input';
|
|
76
|
+
var placeholder = opts.placeholder || '';
|
|
77
|
+
var defaultValue = opts.defaultValue || '';
|
|
78
|
+
var onConfirm = opts.onConfirm;
|
|
79
|
+
|
|
80
|
+
var bg = createBackdrop();
|
|
81
|
+
var card = createCard(title);
|
|
82
|
+
|
|
83
|
+
var input = document.createElement('input');
|
|
84
|
+
input.className = 'smartcode-modal-input';
|
|
85
|
+
input.type = 'text';
|
|
86
|
+
input.placeholder = placeholder;
|
|
87
|
+
input.value = defaultValue;
|
|
88
|
+
card.appendChild(input);
|
|
89
|
+
|
|
90
|
+
var actions = createActions();
|
|
91
|
+
var btnCancel = createButton('Cancel', 'secondary');
|
|
92
|
+
var btnOk = createButton('OK', 'primary');
|
|
93
|
+
actions.appendChild(btnCancel);
|
|
94
|
+
actions.appendChild(btnOk);
|
|
95
|
+
card.appendChild(actions);
|
|
96
|
+
|
|
97
|
+
bg.appendChild(card);
|
|
98
|
+
|
|
99
|
+
// Focus and select input content
|
|
100
|
+
input.focus();
|
|
101
|
+
if (defaultValue) input.select();
|
|
102
|
+
|
|
103
|
+
function doConfirm() {
|
|
104
|
+
var val = input.value.trim();
|
|
105
|
+
if (!val && !opts.allowEmpty) return;
|
|
106
|
+
close();
|
|
107
|
+
if (onConfirm) onConfirm(val);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
btnOk.addEventListener('click', doConfirm);
|
|
111
|
+
btnCancel.addEventListener('click', close);
|
|
112
|
+
|
|
113
|
+
input.addEventListener('keydown', function(e) {
|
|
114
|
+
if (e.key === 'Enter') doConfirm();
|
|
115
|
+
if (e.key === 'Escape') close();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Confirm Modal ──
|
|
120
|
+
|
|
121
|
+
function showConfirm(opts) {
|
|
122
|
+
var title = opts.title || 'Confirm';
|
|
123
|
+
var message = opts.message || '';
|
|
124
|
+
var danger = opts.danger || false;
|
|
125
|
+
var onConfirm = opts.onConfirm;
|
|
126
|
+
|
|
127
|
+
var bg = createBackdrop();
|
|
128
|
+
var card = createCard(title);
|
|
129
|
+
|
|
130
|
+
if (message) {
|
|
131
|
+
var msg = document.createElement('div');
|
|
132
|
+
msg.className = 'smartcode-modal-message';
|
|
133
|
+
msg.textContent = message;
|
|
134
|
+
card.appendChild(msg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var actions = createActions();
|
|
138
|
+
var btnCancel = createButton('Cancel', 'secondary');
|
|
139
|
+
var btnOk = createButton(danger ? 'Delete' : 'OK', danger ? 'danger' : 'primary');
|
|
140
|
+
actions.appendChild(btnCancel);
|
|
141
|
+
actions.appendChild(btnOk);
|
|
142
|
+
card.appendChild(actions);
|
|
143
|
+
|
|
144
|
+
bg.appendChild(card);
|
|
145
|
+
|
|
146
|
+
btnOk.focus();
|
|
147
|
+
|
|
148
|
+
btnOk.addEventListener('click', function() {
|
|
149
|
+
close();
|
|
150
|
+
if (onConfirm) onConfirm();
|
|
151
|
+
});
|
|
152
|
+
btnCancel.addEventListener('click', close);
|
|
153
|
+
|
|
154
|
+
// Escape to close
|
|
155
|
+
function onKey(e) {
|
|
156
|
+
if (e.key === 'Escape') {
|
|
157
|
+
close();
|
|
158
|
+
document.removeEventListener('keydown', onKey);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
document.addEventListener('keydown', onKey);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Public API ──
|
|
165
|
+
|
|
166
|
+
window.SmartCodeModal = {
|
|
167
|
+
prompt: showPrompt,
|
|
168
|
+
confirm: showConfirm,
|
|
169
|
+
close: close,
|
|
170
|
+
};
|
|
171
|
+
})();
|